精华内容
下载资源
问答
  • AudioPolicy&AudioFlinger初始化总体框架初始化步骤简介初始化步骤详细流程分析1、loadConfig()2、initialize()2.1、初始音频路由引擎2.2、加载so 并且打开设备节点2.3、打开输出流 总体框架 AudioFlinger和Audio...
  • 【游戏开发创新】手把手教你使用Unity制作一个高仿酷狗音乐播放器(滨崎步 | MY ALL | 声音可视化 | 频谱 | Audio

    一、前言

    嗨,大家好,我是新发。
    事情是这样的,我在听歌的时候听到了滨崎步的MY ALL,热泪盈眶,旋律起,爷青回,

    【4K收藏级画质】滨崎步神曲《My All》唱哭全场!!!

    我点开酷狗,端详了一下,冒出个想法,我能不能用Unity做一个高仿酷狗音乐播放器,只播放滨崎步的这首歌。
    于是,我就做了一个,最终效果如下:
    请添加图片描述
    运行效果:
    请添加图片描述
    请添加图片描述
    发布成exe,运行效果:
    请添加图片描述
    下面,我来讲讲我的制作过程~

    二、获取UI素材

    下面是真酷狗界面,
    在这里插入图片描述
    根据界面,去阿里图标库搜索需要的素材,
    阿里图标库地址:https://www.iconfont.cn/
    比如我们要找这一块的图标素材,
    在这里插入图片描述
    我的电台为例,
    在这里插入图片描述
    你就搜电台,找到一个形状相似的图标,如下
    在这里插入图片描述
    点击图标,可以先设置图标的颜色为白色,然后再下载,
    在这里插入图片描述
    以此类推,我找的素材如下:
    在这里插入图片描述
    所有的UI素材图片格式都设置为Sprite (2D and UI)
    在这里插入图片描述
    有一部分UI需要设置一下九宫格,不然可能会变形,比如这个椭圆按钮,
    在这里插入图片描述

    三、使用UGUI制作界面

    最终界面效果如下,
    在这里插入图片描述
    下面我会挑一下重点讲一下。

    1、界面布局

    在开始做UI界面之前,我们要先分析一下界面布局,首先从大布局看,是上、中、下,
    在这里插入图片描述
    那我们做界面时,就按上、中、下三块来做,
    在这里插入图片描述
    如下:
    请添加图片描述
    中间布局,又可以继续拆分成顶部标签栏和底部详细面板,以我的音乐为例,底部面板可以分为左右布局,如下:
    在这里插入图片描述
    这样子,我们中间就可以分成tabsleftright三块,
    在这里插入图片描述
    如下:
    请添加图片描述
    以此类推,大布局里嵌小布局,小布局里继续嵌小小布局,根据界面进行合理设计。

    2、账号圆形头像

    头像图片是正正方方的,如下:
    请添加图片描述
    我们需要显示圆形头像,可以使用Mask组件,如下head_frame节点,
    在这里插入图片描述
    使用一张圆形的图片显示,并挂上Mask组件,
    在这里插入图片描述
    子节点head_img就是头像图片了,
    在这里插入图片描述
    使用RawImage组件来显示头像,
    在这里插入图片描述
    效果如下:
    在这里插入图片描述

    3、搜索框

    搜索框使用InputField组件,
    在这里插入图片描述
    替换框的图片,设置颜色,如下,
    在这里插入图片描述
    搜索框中再放一个Button来显示放大镜图片,
    在这里插入图片描述
    在这里插入图片描述
    效果如下:
    请添加图片描述

    4、调节UI层

    我的音乐这个按钮,
    在这里插入图片描述
    正确是要显示在最前面,
    在这里插入图片描述
    它之所以被挡住,是因为UGUI默认是按节点的顺序来渲染的,后面的按钮排在它下面,所以后面的按钮会盖住它,
    在这里插入图片描述
    如果我们不想调节节点的顺序,但又想让我的音乐按钮显示在前面,我们可以给它单独挂一个Canvas组件,并设置Sort Order为一个大于上一级CanvasSort Order的值,
    在这里插入图片描述
    这样它就可以正常显示在前面了,
    在这里插入图片描述

    5、黑色按钮悬浮高亮效果

    鼠标悬停在按钮上时,按钮呈高亮状态,如下,
    请添加图片描述
    如果你把按钮Image图片设置为黑色,那么是没有这个高亮效果的,因为一个按钮是由ImageButton配合工作的,Button组件可以设置各个状态的颜色,Button组件的颜色和Image的颜色相乘就是最终的按钮状态颜色,如果Image颜色你设置成黑色,那么相乘得出的结果都是黑色,你也就看不到高亮效果了,
    在这里插入图片描述
    正确做法是,Image为白色,ButtonNormal Color为黑色,其他状态的颜色根据具体需求而定,比如高亮Hightlighted Color为灰色,最终颜色设置如下:
    在这里插入图片描述

    6、纯文字按钮

    这排按钮是纯文字按钮,
    在这里插入图片描述
    UGUI创建的默认按钮是带图片的,做下小改动即可,我们通过菜单创建一个Button
    在这里插入图片描述
    Image组件移除掉,
    在这里插入图片描述
    然后把子节点的Text赋值给Button组件的Target Graphic即可,
    在这里插入图片描述
    设置一下按钮的各个状态颜色,
    在这里插入图片描述
    我们直接调节Button节点的宽高即可调节响应区域啦~
    在这里插入图片描述
    鼠标靠近文字时,按钮可以检测到,如下,

    注:对于有图片的按钮,如果也想扩大按钮的响应区域但又不想扩大图片本身,也可以利用这个技巧,把按钮背景图作为Button组件的子节点显示即可

    请添加图片描述

    7、滚动列表自适应

    滚动列表用的是ScrollView
    请添加图片描述
    需要处理的是Content的自适应,首先列表是竖向滑动的,我们给Content节点挂上VerticalLayoutGroup组件,
    在这里插入图片描述
    要让Content的高度随着列表item的数量增多而增大,我们可以使用ContentSizeFitter组件,把Vertical Fit设置为Preferred Size
    在这里插入图片描述
    然后给item设定一个高度,
    在这里插入图片描述
    这样子,当item增多的时候,Content节点就会自动扩大高度啦,
    请添加图片描述

    8、歌名与视频图标混排

    在单曲列表中,歌名和视频图标混排在一起,视频图标需要跟在歌名后面,歌名文字增多,视频图标需要自动往后移动,如下:
    请添加图片描述
    这个要用到锚点,视频图标作为歌名文字的子节点,然后视频图标的锚点设置为middle-right,如下,这样子,视频图标就会以文字框的右边为参考线计算相对位置了,
    在这里插入图片描述

    9、点击按钮切换图片

    点击按钮切换按钮图片,如下
    请添加图片描述
    这里要写点代码,在代码中替换Button组件的ImageSprite对象,例:

    playBtn.onClick.AddListener(() =>
    {
        if (!audioSource.isPlaying)
        {
            audioSource.Play();
            playBtn.image.sprite = pauseSprite;
        }
        else
        {
            audioSource.Pause();
            playBtn.image.sprite = playSprite;
        }
    });
    

    10、进度条

    进度条使用Slider组件,
    请添加图片描述

    需要解决的是进度条很细的情况下,扩大进度条的操作区域,解决办法是给Slider添加一个Image组件,并设置透明度为0
    在这里插入图片描述
    调整Slider的高为一个稍微大一点的值,
    在这里插入图片描述
    调整BackgroundTopBottom,使其变细,
    在这里插入图片描述
    同理,Fill Area也调整一下TopBottom,使其变细,
    在这里插入图片描述
    这样子做出来的进度条,就可以很细,但是操作区域又得到保证,
    在这里插入图片描述

    四、写代码

    1、歌曲的播放与暂停

    Unity中要播放一个声音并听到,需要两个组件配合,一个是AudioSource(相当于喇叭),另一个AudioListener(相当于耳朵),
    其中,AudioListener默认挂在摄像机上,如果你有多个摄像机,要确保场景中只有一个激活状态的AudioListener,否则会报错。
    AudioSource需要赋值AudioClip成员,即设置具体的声音文件,我们可以直接拖拽一个声音文件到AudioSourceAudioClip成员上,如下:
    在这里插入图片描述
    我们也可以在代码中动态设置:

    // AudioClip audioClip = TODO 动态加载AudioClip文件;
    audioSource.clip = audioClip;
    

    动态加载资源文件我之前的文章有讲过三种主要方式,大家感兴趣的可以看下,
    《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》
    在这里插入图片描述
    然后播放声音和暂停播放,调用AudioSourcePlayPause方法即可:

    // (继续)播放
    audioSource.Play();
    
    // 暂停
    audioSource.Pause();
    

    2、声音频谱特效

    声音播放过程中,可以实时显示频谱特效,
    请添加图片描述
    这里要用到AudioSourceGetSpectrumData接口,这个接口可以采样声谱数据块,

    public static void GetSpectrumData(float[] samples, 
    									int channel, 
    									FFTWindow window);
    

    参数说明:
    samples: 函数返回值。将音频样本数据传送至samples数组,数组大小必须为2的n次方,最小64,最大8192
    channel: 声道,一般设置为0
    window: 转换信号所用的窗函数,算法越复杂,声音越柔和,但速度更慢。
    用法 :
    先声明一个浮点数组:

    public float[] samples = new float[512];
    

    然后在Update方法里面使用方法:

    audiosource.GetSpectrumData(samples, 0, FFTWindow.BlackmanHarris);
    

    我这里封装了一个脚本,如下:

    using UnityEngine;
    
    /// <summary>
    /// 声音特效
    /// </summary>
    public class AudioEffect : MonoBehaviour
    {
        // 用于显示的方块
        public GameObject cubeObj;
        // 方块的其实点
        public Transform startPoint;
    
        // 采样数据长度
        private const int SPECTRUM_CNT = 512;
        // 采样数据
        private float[] spectrumData = new float[SPECTRUM_CNT];
        // 方块Transform数组
        private Transform[] cubeTransforms = new Transform[SPECTRUM_CNT];
    
        void Start()
        {
            //cube生成与排列
            Vector3 p = startPoint.position;
    
            for (int i = 0; i < SPECTRUM_CNT; i++)
            {
                p = new Vector3(p.x + 0.11f, p.y, p.z);
                GameObject cube = Instantiate(cubeObj, p, cubeObj.transform.rotation);
                cube.transform.parent = startPoint;
                cubeTransforms[i] = cube.transform;
            }
        }
    
        /// <summary>
        /// 当前帧频率波功率,传到对应cube的localScale
        /// </summary>
        public void UpdateEffect(AudioSource audioSource)
        {
            audioSource.GetSpectrumData(spectrumData, 0, FFTWindow.BlackmanHarris);
            float scaleY;
            for (int i = 0; i < SPECTRUM_CNT; i++)
            {
                scaleY = Mathf.Lerp(cubeTransforms[i].localScale.y, spectrumData[i] * 10000f, 0.5f);
                // 限制一下功率
                if (scaleY > 400)
                {
                    scaleY -= 400;
                }
                else if (scaleY > 300)
                {
                    scaleY -= 300;
                }
                else if (scaleY > 200)
                {
                    scaleY -= 200;
                }
                else if (scaleY > 100)
                {
                    scaleY -= 100;
                }
    
                cubeTransforms[i].localScale = new Vector3(0.15f, scaleY, 0.15f);
            }
        }
    }
    

    其中cubeObj是一个用于显示的小方块,我们做一下方块预设,如下:
    在这里插入图片描述
    材质球用的shaderUnlit/Texture,贴图是上面白下面黑的渐变色,
    在这里插入图片描述
    因为这个特效要显示在UI界面中,并且是穿插在UI层中,我这里使用RenderTexture来解决。
    先创建一张RenderTexture
    在这里插入图片描述
    然后创建一个独立的摄像机EffectCamera(记得把AudioListener组件去掉),Culling Mask只勾选Water层,
    在这里插入图片描述
    主摄像机记得把Water去掉,
    在这里插入图片描述
    把特效的Layer设置为Water
    在这里插入图片描述
    这样子,特效就只会在EffectCamera摄像机中渲染了,
    我们把刚刚的RenderTexture拖给EffectCamera摄像机的Target Texture,如下,
    在这里插入图片描述
    接着,给audioEffect节点挂AudioEffect脚本,并赋值成员对象,如下,
    在这里插入图片描述
    这样特效就会渲染到RenderTexture上了,如下:
    请添加图片描述
    我们再使用一张RawImage来显示它,
    在这里插入图片描述
    我们可以调节RawImage的颜色来调整特效的颜色,如下:
    请添加图片描述

    4、获取声音总时长

    var len = audioSource.clip.length;
    

    5、设置当前时间戳

    根据进度条进度设置当前时间戳

    slider.onValueChanged.AddListener((v) =>
        {
            if (v < 1)
                audioSource.time = v * audioSource.clip.length;
        });
    

    6、发布exe隐藏标题栏

    发布成exe,运行时自动隐藏默认的标题栏,
    请添加图片描述
    隐藏标题栏的方法,我之前有写过相关文章,《Unity发布PC平台exe的窗口花样(WindowsAPI、捕获关闭事件、隐藏窗口标题栏、隐藏最小化最大化关闭按钮等等)》
    用到了几个Windows API,如下:

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
    
    [DllImport("user32.dll")]
    public static extern long GetWindowLong(IntPtr hwd, int nIndex);
    
    [DllImport("user32.dll")]
    public static extern void SetWindowLong(IntPtr hwd, int nIndex, long dwNewLong);
    
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hwd, int cmdShow);
    

    隐藏标题栏:

    #if UNITY_STANDALONE && !UNITY_EDITOR
    /// <summary>
    /// 窗口风格
    /// </summary>
    const int GWL_STYLE = -16;
    /// <summary>
    /// 标题栏
    /// </summary>
    const int WS_CAPTION = 0x00c00000;
    
    
    // 隐藏标题栏
    var hwd = GetForegroundWindow();
    var wl = GetWindowLong(hwd, GWL_STYLE);
    wl &= ~WS_CAPTION;
    SetWindowLong(hwd, GWL_STYLE, wl);
    #endif
    

    另外,我们需要实现这三个按钮的逻辑,
    在这里插入图片描述
    最小化窗口:

    #if UNITY_STANDALONE && !UNITY_EDITOR
    /// <summary>
    /// 最小化
    /// </summary>
    const int SW_SHOWMINIMIZED = 2;
    
    // 获得窗口句柄
    var hwd = GetForegroundWindow();
    // 设置窗口最小化
    ShowWindow(hwd, SW_SHOWMINIMIZED);
    #endif
    

    最大化窗口:

    #if UNITY_STANDALONE && !UNITY_EDITOR
    /// <summary>
    /// 最大化
    /// </summary>
    const int SW_SHOWMAXIMIZED = 3;
    
    // 获得窗口句柄
    var hwd = GetForegroundWindow();
    // 设置窗口最小化
    ShowWindow(hwd, SW_SHOWMAXIMIZED);
    #endif
    

    关闭程序:

    Application.Quit();
    

    五、工程源码

    本文的工程源码我以上传到CODE CHINA,感兴趣的同学可自行下载学习,
    地址:https://codechina.csdn.net/linxinfa/UnityMusicPlayer
    注:我使用的Unity版本为Unity 2020.1.14f1c1 (64-bit)
    温馨提示:本工程仅供学习使用,禁止用于商业用途,否则后果自负
    在这里插入图片描述

    六、完毕

    最后,附上滨崎步MY ALL的歌词,初识不知曲中意,再听已是曲中人!

    多少时光
    我们一同经历
    多少路程
    我们一起走过
    至今我们所留下的
    虽然不够完美却也灿烂过
    如今在这里 那些结晶
    正闪耀著骄傲的光辉
    一直都那麽开心和快乐
    坦白说并不是那麽回事
    然而我们永远
    都不会是孤身一人
    想让你看见梦想的所在
    没有终结 没有消亡
    真的很想看见那样的梦想
    那正是我的愿望
    想要一直守护在你身旁
    不管即将发生什么
    我将用我的全部
    一直将你守护
    从不曾有丝毫后悔
    知道现在我都可以这样断言
    我们一直都在竭尽全力地
    奋战到底
    在那些铭心的夜晚
    事实上也会常常想起你
    然而我们永远
    都不是孤身一人
    看到你的笑颜
    令人爱恋 令人目眩
    多想再看到那样的笑颜
    所以我仍活到今天
    我能感觉到你的爱
    有力而温暖
    那样无偿的爱情
    我尽全力地感受著
    想让你看见梦想的所在
    没有终结 没有消亡
    真的很想让你看见那样的梦想
    那正是我的愿望
    我想守护在你的身旁
    不管即将发生什麽
    我将用我的全部
    一直将你守护

    请添加图片描述
    好了,就到这里吧,我要再去单曲循环听亿编了~
    我是林新发:https://blog.csdn.net/linxinfa
    原创不易,若转载请注明出处,感谢大家~
    喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信,我们下期见~

    展开全文
  • Audio AudioFocus流程

    千次阅读 2018-09-26 09:24:17
    AudioFocus是Android引入的一个Audio协调机制,当多方需要使用Audio资源时,可以通过AudioFocus机制来协调配合,提高用户的体验。 该机制需要开发者主动去遵守,比如A应用没遵守该机制,则其它遵守了该机制的应用是...

         AudioFocus是Android引入的一个Audio协调机制,当多方需要使用Audio资源时,可以通过AudioFocus机制来协调配合,提高用户的体验。    该机制需要开发者主动去遵守,比如A应用没遵守该机制,则其它遵守了该机制的应用是完全没办法影响A应用的。    试想下后台在播放着音乐的时候你点开了某个视频,使得后台的音乐和视频的声音一起播放,毫无关联的声音一同播放会给用户带来极差的体验,此时我们就可以通过AudioFocus机制来解决这样的问题。

           使用AudioFocus机制主要是通过android.media.AudioManager这个类来进行的 public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) 方法请求获取焦点,

    如果获取成功,会返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,

    失败则返回AudioManager.AUDIOFOCUS_REQUEST_FAILED。

    通过abandonAudioFocus(OnAudioFocusChangeListener l)方法放弃焦点。 由于音频焦点是唯一的,所以可以在需要播放音乐时去申请音频焦点,如果获取到了则播放,同时正在播放的音频在失去音频焦点时停止播放或者调低音量,从而达到音频播放间的相互协调。

           对requestAudioFocus方法的参数进行解析:  OnAudioFocusChangeListener是一个接口,在这个接口里面只有一个方法需要实现 public void onAudioFocusChange(int focusChange);

    该方法会在焦点状态变化的时候被调用 参数focusChange代表变化后当前的状态,一共有以下四个值:

    AUDIOFOCUS_GAIN 重新获取到音频焦点时触发的状态。

    AUDIOFOCUS_LOSS 失去音频焦点时触发的状态,且该状态应该会长期保持,此时应当暂停音频并释放音频相关的资源。

    AUDIOFOCUS_LOSS_TRANSIENT 失去音频焦点时触发的状态,但是该状态不会长时间保持,此时应该暂停音频,且当重新获取音频焦点的时候继续播放。

    AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 失去音频焦点时触发的状态,在该状态的时候不需要暂停音频,但是应该降低音频的声音。

    streamType(STREAM_RING,STREAM_MUSIC,STREAM_ALARM,STREAM_NOTIFICATION,STREAM_BLUETOOTH_SCO,STREAM_DTMF,STREAM_TTS)

    durationHint用来表示获取焦点的时长,同时通知其它获取了音频焦点的OnAudioFocusChangeListener该如何相互配合,有以下几个值: AUDIOFOCUS_GAIN 代表此次申请的音频焦点需要长时间持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS。

    AUDIOFOCUS_GAIN_TRANSIENT 代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:适用于短暂的音频,在接收到事件通知等情景时可使用该durationHint。

    AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在需要录音或语音识别等情景时可使用该durationHint。

    AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 代表此次申请不需要暂停其它申请的音频播放,应用跟其他应用共用焦点但播放的时候其他音频会降低音量。原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。

     

    在AudioManager的requestAudioFocus调用内部重载后的方法,根据传进来的streamType,构造了一个AudioAttributes对象向下传递,这个AudioAttributes主要是存储了一些音频流信息的属性. 这里面对falgs进行了与操作,由于之前传进来的是0,所以转换后的结果还是0.

    继续AudioManager.requestAudioFocus中 public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) 里面首先在registerAudioFocusRequest中注册所有的

    AudioFocusRequest private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap =         new ConcurrentHashMap<String, FocusRequestInfo>();        

     final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :new ServiceEventHandlerDelegate(h).getHandler());

    其中mAudioFocusIdListenerMap根据AudioFocusRequest中OnAudioFocusChangeListener对象的来生成一个key,value则为根据afr生成的FocusRequestInfo ,存储在了mAudioFocusIdListenerMap对象中。 而之前所述的abandonAudioFocus中有unregisterAudioFocusRequest做的操作就是remove掉mAudioFocusIdListenerMap中的OnAudioFocusChangeListener 

    在AudioManager中还有一个电话相关的调用:

    public void requestAudioFocusForCall(int streamType, int durationHint)

     这个调用的也是AudioService中的requestAudioFocus并且往其中设置了一个clientId为AudioSystem.IN_VOICE_COMM_FOCUS_ID的状态。 继续往下就是进入到AudioService.requestAudioFocus中首先会进行权限检查,这里面就用到了AudioSystem.IN_VOICE_COMM_FOCUS_ID 也就是说如果clientId等于AudioSystem.IN_VOICE_COMM_FOCUS_ID,且要申请到MODIFY_PHONE_STATE的权限,否则会申请焦点失败。

    AudioService也只是做了中转,并没有做实际的操作,具体实现都是在MediaFocusControl.requestAudioFocus中大致过程如下:

    private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); 最主要的是对mFocusStack栈的操作,用来维护各client的申请和释放。

    1.判断mFocusStack.size() 不能超过100

    2.检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。

    3. 压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。  

    如果mFocusStack不为空,并且栈顶的clientId与要申请焦点的clientId相同,得到栈顶元素即FocusRequester对象。如果申请的时长和flags都相同,则表示重复申请,直接返回成功,如果如果申请的时长和flags有一个不相同,则认为需要重新申请,此时如果focusGrantDelayed = false则需要将栈顶的元素出栈并将其释放

    4. // focus requester might already be somewhere below in the stack, remove it          

      removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);    

    移除可能在栈中(栈顶或者栈中)其他位置存在着相同clientId的元素

    a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;

    b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。

    5.创建FocusRequester实例将请求包含的各种信息传入

    AudioAttributes aa, int focusChangeHint, IBinder cb,  IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, int sdk

    6.如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false即不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功

    遍历mFocusStack,调用FocusRequester对象的handleExternalFocusGain方法 通知栈中其他元素丢失焦点流程. stackIterator.next()得到的是FocusRequester对象,因此查看FocusRequester中handleExternalFocusGain的代码

    主要关注两个变量gainRequest和mFocusLossReceived, mFocusLossReceived这个值在handleFocusLoss中进行赋值的,默认值是AudioManager.AUDIOFOCUS_NONE。

     * gainRequest这个是传进来的,例如

    AudioManager.AUDIOFOCUS_GAIN

     * return AudioManager.AUDIOFOCUS_LOSS

     * 若传进来的参数是 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT

     * 则return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

    在handleFocusLoss中

    fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);

    通过mFocusDispatcher对象调用了dispatchAudioFocusChange方法,将mFocusLossReceived和mClientId传了进去。

    3.1 FocusRequester构造方法的第四个参数IAudioFocusDispatcher afl

    3.2 MediaFocusControl的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd。

    3.3 AudioService的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd 最终在AudioManager中实现。IAudioFocusDispatcher 的回调dispatchAudioFocusChange方法。

    在dispatchAudioFocusChange中通过Handler发送MSSG_FOCUS_CHANGE

    在ServiceEventHandlerDelegate 的创建handleMessage在其中根据MSSG_FOCUS_CHANGE来回调,而msg.arg1为focusChange即mFocusLossReceived

    listener.onAudioFocusChange(msg.arg1);

    最终app中实现onAudioFocusChange根据焦点的切换做相应的控制。

    展开全文
  • 高通audio入门

    2018-01-09 10:11:01
    介绍了高通audio入门 1.Audio软硬件系统框架 2.Audio bringup 3.ACDB校准 4.how to debug
  • Android8.0 Audio系统之AudioFlinger

    千次阅读 2018-08-13 16:52:40
    继上一篇AudioTrack的分析,本篇我们来看AudioFlinger,AF主要承担音频混合输出,是Audio系统的核心,从AudioTrack来的数据最终都会在这里处理,并被写入到Audio的HAL。 frameworks\av\services\audioflinger\Audio...

    继上一篇AudioTrack的分析,本篇我们来看AudioFlinger,AF主要承担音频混合输出,是Audio系统的核心,从AudioTrack来的数据最终都会在这里处理,并被写入到Audio的HAL。

    1. AudioFlinger 创建

    frameworks\av\services\audioflinger\AudioFlinger.cpp

    AudioFlinger::AudioFlinger()
        : BnAudioFlinger(),
          mMediaLogNotifier(new AudioFlinger::MediaLogNotifier()),
          mPrimaryHardwareDev(NULL),
          mAudioHwDevs(NULL),
          mHardwareStatus(AUDIO_HW_IDLE),
          mMasterVolume(1.0f),
          mMasterMute(false),
          // mNextUniqueId(AUDIO_UNIQUE_ID_USE_MAX),
          mMode(AUDIO_MODE_INVALID),
          mBtNrecIsOff(false),
          mIsLowRamDevice(true),
          mIsDeviceTypeKnown(false),
          mGlobalEffectEnableTime(0),
          mSystemReady(false)
    {
        ......
        mDevicesFactoryHal = DevicesFactoryHalInterface::create();
        mEffectsFactoryHal = EffectsFactoryHalInterface::create();
        ......
    }
    

    frameworks\av\media\libaudiohal\include\DevicesFactoryHalInterface.h
    其中 DevicesFactoryHalHidl.cpp , DevicesFactoryHalHybrid.cpp , DevicesFactoryHalLocal.cpp分别都继承自DevicesFactoryHalInterface

    class DevicesFactoryHalInterface : public RefBase
    {
      public:
        // Opens a device with the specified name. To close the device, it is
        // necessary to release references to the returned object.
        virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device) = 0;
    
        static sp<DevicesFactoryHalInterface> create();
    
      protected:
        // Subclasses can not be constructed directly by clients.
        DevicesFactoryHalInterface() {}
    
        virtual ~DevicesFactoryHalInterface() {}
    };
    

    在DevicesFactoryHalHybrid.cpp文件中找到了create()函数的实现

    sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
        return new DevicesFactoryHalHybrid();
    }
    

    DevicesFactoryHalHybrid.h文件中定义了mLocalFactory,mHidlFactory

    class DevicesFactoryHalHybrid : public DevicesFactoryHalInterface
    {
      public:
        // Opens a device with the specified name. To close the device, it is
        // necessary to release references to the returned object.
        virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
    
      private:
        friend class DevicesFactoryHalInterface;
    
        // Can not be constructed directly by clients.
        DevicesFactoryHalHybrid();
    
        virtual ~DevicesFactoryHalHybrid();
    
        sp<DevicesFactoryHalInterface> mLocalFactory;
        sp<DevicesFactoryHalInterface> mHidlFactory;
    };
    

    DevicesFactoryHalHybrid.cpp中分别创建了不同的实现对象,通过 USE_LEGACY_LOCAL_AUDIO_HAL

    DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
            : mLocalFactory(new DevicesFactoryHalLocal()),
              mHidlFactory(
    #ifdef USE_LEGACY_LOCAL_AUDIO_HAL
                      nullptr
    #else
                      new DevicesFactoryHalHidl()
    #endif
                           ) {
    }
    
    status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
        if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
            return mHidlFactory->openDevice(name, device);
        }
        return mLocalFactory->openDevice(name, device);
    }
    

    如果是本地模式,DevicesFactoryHalLocal.cpp中使用8.0以前的HAL加载方式

    static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev)
    {
        const hw_module_t *mod;
        int rc;
    
        rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
        if (rc) {
            ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__,
                    AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
            goto out;
        }
        rc = audio_hw_device_open(mod, dev);
        if (rc) {
            ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__,
                    AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
            goto out;
        }
        if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) {
            ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version);
            rc = BAD_VALUE;
            audio_hw_device_close(*dev);
            goto out;
        }
        return OK;
    
    out:
        *dev = NULL;
        return rc;
    }
    
    status_t DevicesFactoryHalLocal::openDevice(const char *name, sp<DeviceHalInterface> *device) {
        audio_hw_device_t *dev;
        status_t rc = load_audio_interface(name, &dev);
        if (rc == OK) {
            *device = new DeviceHalLocal(dev);
        }
        return rc;
    }
    

    如果是HIDL的Treble模式,将采用8.0的新架构

    DevicesFactoryHalHidl::DevicesFactoryHalHidl() {
        mDevicesFactory = IDevicesFactory::getService(); //后续会讲到
        if (mDevicesFactory != 0) {
            // It is assumed that DevicesFactory is owned by AudioFlinger
            // and thus have the same lifespan.
            mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/);
        } else {
            ALOGE("Failed to obtain IDevicesFactory service, terminating process.");
            exit(1);
        }
    }
    
    DevicesFactoryHalHidl::~DevicesFactoryHalHidl() {
    }
    
    // static
    status_t DevicesFactoryHalHidl::nameFromHal(const char *name, IDevicesFactory::Device *device) {
        if (strcmp(name, AUDIO_HARDWARE_MODULE_ID_PRIMARY) == 0) {
            *device = IDevicesFactory::Device::PRIMARY;
            return OK;
        } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_A2DP) == 0) {
            *device = IDevicesFactory::Device::A2DP;
            return OK;
        } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_USB) == 0) {
            *device = IDevicesFactory::Device::USB;
            return OK;
        } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX) == 0) {
            *device = IDevicesFactory::Device::R_SUBMIX;
            return OK;
        } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_STUB) == 0) {
            *device = IDevicesFactory::Device::STUB;
            return OK;
        }
        ALOGE("Invalid device name %s", name);
        return BAD_VALUE;
    }
    
    status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) {
        if (mDevicesFactory == 0) return NO_INIT;
        IDevicesFactory::Device hidlDevice;
        status_t status = nameFromHal(name, &hidlDevice);
        if (status != OK) return status;
        Result retval = Result::NOT_INITIALIZED;
        Return<void> ret = mDevicesFactory->openDevice(
                hidlDevice,
                [&](Result r, const sp<IDevice>& result) {
                    retval = r;
                    if (retval == Result::OK) {
                        *device = new DeviceHalHidl(result); //后续讲解
                    }
                });
        if (ret.isOk()) {
            if (retval == Result::OK) return OK;
            else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE;
            else return NO_INIT;
        }
        return FAILED_TRANSACTION;
    }
    

    2. createTrack()

    我们暂且跳过Audio的硬件抽象层,回头看看AudioTrack调用AF创建Track的过程

    sp<IAudioTrack> AudioFlinger::createTrack(
            audio_stream_type_t streamType,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t *frameCount,
            audio_output_flags_t *flags,
            const sp<IMemory>& sharedBuffer,
            audio_io_handle_t output,
            pid_t pid,
            pid_t tid,
            audio_session_t *sessionId,
            int clientUid,
            status_t *status,
            audio_port_handle_t portId)
    {
        sp<PlaybackThread::Track> track;
        sp<TrackHandle> trackHandle;
        sp<Client> client;
        status_t lStatus;
        audio_session_t lSessionId;
    
        {
            Mutex::Autolock _l(mLock);
            PlaybackThread *thread = checkPlaybackThread_l(output); //创建回放线程
            if (thread == NULL) {
                ALOGE("no playback thread found for output handle %d", output);
                lStatus = BAD_VALUE;
                goto Exit;
            }
    
            client = registerPid(pid); //注册到Client
    
            PlaybackThread *effectThread = NULL;
            if (sessionId != NULL && *sessionId != AUDIO_SESSION_ALLOCATE) {
            ......
            track = thread->createTrack_l(client, streamType, sampleRate, format,
                    channelMask, frameCount, sharedBuffer, lSessionId, flags, tid,
                    clientUid, &lStatus, portId);
            setAudioHwSyncForSession_l(thread, lSessionId);
        }
    
        // return handle to client
        trackHandle = new TrackHandle(track); //返回Track的代理
    
    Exit:
        *status = lStatus;
        return trackHandle;
    }
    

    为Track取一个线程,并没有创建,奇怪

    // checkPlaybackThread_l() 取出线程
    AudioFlinger::PlaybackThread *AudioFlinger::checkPlaybackThread_l(audio_io_handle_t output) const
    {
        return mPlaybackThreads.valueFor(output).get(); //那么线程在哪里创建的
    }
    

    frameworks\av\services\audioflinger\Threads.cpp

    sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l(
            const sp<AudioFlinger::Client>& client,
            audio_stream_type_t streamType,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t *pFrameCount,
            const sp<IMemory>& sharedBuffer,
            audio_session_t sessionId,
            audio_output_flags_t *flags,
            pid_t tid,
            uid_t uid,
            status_t *status,
            audio_port_handle_t portId)
    {
           //创建Track
           track = new Track(this, client, streamType, sampleRate, format,
                              channelMask, frameCount, NULL, sharedBuffer,
                              sessionId, uid, *flags, TrackBase::TYPE_DEFAULT, portId);
                              
        return track;
    }
    

    那么上面的线程是如何创建出来的,我们得到APS里去看看

    void AudioPolicyService::onFirstRef()
    {
        {
            Mutex::Autolock _l(mLock);
    
            // start tone playback thread
            mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
            // start audio commands thread
            mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
            // start output activity command thread
            mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);
    
            mAudioPolicyClient = new AudioPolicyClient(this);
            mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient); //创建代理管家
        }
        // load audio processing modules
        sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
        {
            Mutex::Autolock _l(mLock);
            mAudioPolicyEffects = audioPolicyEffects;
        }
    }
    

    frameworks\av\services\audiopolicy\manager\AudioPolicyFactory.cpp

    extern "C" AudioPolicyInterface* createAudioPolicyManager(
            AudioPolicyClientInterface *clientInterface)
    {
        return new AudioPolicyManager(clientInterface);
    }
    
    extern "C" void destroyAudioPolicyManager(AudioPolicyInterface *interface)
    {
        delete interface;
    }
    

    frameworks\av\services\audiopolicy\managerdefault\AudioPolicyManager.cpp

    udioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
       ......
    {
         ......
        //调用APS的openOutput
        status_t status = mpClientInterface->openOutput(......);
    }
    
    status_t AudioPolicyService::AudioPolicyClient::openOutput(......)
    {
        sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
        if (af == 0) {
            ALOGW("%s: could not get AudioFlinger", __func__);
            return PERMISSION_DENIED;
        }
        return af->openOutput(module, output, config, devices, address, latencyMs, flags);
    }
    
    

    调用AF的openOutput

    status_t AudioFlinger::openOutput(......)
    {
        ......
        sp<ThreadBase> thread = openOutput_l(module, output, config, *devices, address, flags);
        ......
        return NO_INIT;
    }
    

    饶了一圈回来在此创建线程,并放入到mPlaybackThreads中

    sp<AudioFlinger::ThreadBase> AudioFlinger::openOutput_l(......)
    {
         sp<PlaybackThread> thread;
         if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
                thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
            } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
                        || !isValidPcmSinkFormat(config->format)
                        || !isValidPcmSinkChannelMask(config->channel_mask)) {
                thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
            } else {
                thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
            }
            mPlaybackThreads.add(*output, thread);
            return thread;
    }
    
    

    3. start()

    frameworks\av\services\audioflinger\Tracks.cpp
    由此可见APS控制着整个音频系统,而AF管理的是音频输入混合输出,AudioTrack创建成功以后,开始调用start()函数,start()函数将由TrackHandle代理并调用Track的start()函数

    status_t AudioFlinger::PlaybackThread::Track::start(......)
    {
        status_t status = NO_ERROR;
    
        sp<ThreadBase> thread = mThread.promote(); 
           if (state == PAUSED || state == PAUSING) {
                if (mResumeToStopping) {
                    // happened we need to resume to STOPPING_1
                    mState = TrackBase::STOPPING_1;
                    ALOGV("PAUSED => STOPPING_1 (%d) on thread %p", mName, this);
                } else {
                    mState = TrackBase::RESUMING;
                    ALOGV("PAUSED => RESUMING (%d) on thread %p", mName, this);
                }
            } else {
                mState = TrackBase::ACTIVE; //设置Track的状态
                ALOGV("? => ACTIVE (%d) on thread %p", mName, this);
            }
            status = playbackThread->addTrack_l(this);
        return status;
    }
    

    将创建好的Track加入到mActiveTracks中

    status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
    {
        status_t status = ALREADY_EXISTS;
    
        if (mActiveTracks.indexOf(track) < 0) {
            ......
            // set retry count for buffer fill
            if (track->isOffloaded()) {
                if (track->isStopping_1()) {
                    track->mRetryCount = kMaxTrackStopRetriesOffload;
                } else {
                    track->mRetryCount = kMaxTrackStartupRetriesOffload;
                }
                track->mFillingUpStatus = mStandby ? Track::FS_FILLING : Track::FS_FILLED;
            } else {
                track->mRetryCount = kMaxTrackStartupRetries; //重试次数
                track->mFillingUpStatus =
                        track->sharedBuffer() != 0 ? Track::FS_FILLED : Track::FS_FILLING;
            }
    
            track->mResetDone = false;
            track->mPresentationCompleteFrames = 0;
            mActiveTracks.add(track);
            sp<EffectChain> chain = getEffectChain_l(track->sessionId());
            if (chain != 0) {
                chain->incActiveTrackCnt();
            }
    
            char buffer[256];
            track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
    
            status = NO_ERROR;
        }
    
        onAddNewTrack_l(); //触发音频混合线程MixerThread的
        return status;
    }
    

    PlaybackThread的threadLoop()将调用MixerThread::prepareTracks_l()函数

    AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
            Vector< sp<Track> > *tracksToRemove)
    {
    
        mixer_state mixerStatus = MIXER_IDLE;
        // find out which tracks need to be processed
        size_t count = mActiveTracks.size(); //已激活的Track数量
        ......
        for (size_t i=0 ; i<count ; i++) { //遍历查询
            const sp<Track> t = mActiveTracks[i];
    
            // this const just means the local variable doesn't change
            Track* const track = t.get();
    
            {
            //取cblk
            audio_track_cblk_t* cblk = track->cblk();
    
            // The first time a track is added we wait
            // for all its buffers to be filled before processing it
            int name = track->name();
           ......
          // XXX: these things DON'T need to be done each time
                //设置数据来源为Track
                mAudioMixer->setBufferProvider(name, track);
                mAudioMixer->enable(name);
                //设置音量等参数
                mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, &vlf);
                mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, &vrf);
                mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, &vaf);
                mAudioMixer->setParameter(
                    name,
                    AudioMixer::TRACK,
                    AudioMixer::FORMAT, (void *)track->format());
                mAudioMixer->setParameter(
                    name,
                    AudioMixer::TRACK,
                    AudioMixer::CHANNEL_MASK, (void *)(uintptr_t)track->channelMask());
                mAudioMixer->setParameter(
                    name,
                    AudioMixer::TRACK,
                    AudioMixer::MIXER_CHANNEL_MASK, (void *)(uintptr_t)mChannelMask);
            
        ......
        mMixerStatusIgnoringFastTracks = mixerStatus;
        if (fastTracks > 0) {
            mixerStatus = MIXER_TRACKS_READY;
        }
        return mixerStatus;
    }
    

    混合线程的处理函数

    void AudioFlinger::MixerThread::threadLoop_mix()
    {
        // mix buffers...
        mAudioMixer->process();
        if ((mSleepTimeUs == 0) && (sleepTimeShift > 0)) {
            sleepTimeShift--;
        }
        mSleepTimeUs = 0;
        mStandbyTimeNs = systemTime() + mStandbyDelayNs;
        //TODO: delay standby when effects have a tail
    
    }
    

    调用混音器的处理函数

    void AudioMixer::process()
    {
        mState.hook(&mState); //hook指针根据Track的个数和它的音频格式使用不同的处理函数
    }
    

    使能混合器

    void AudioMixer::enable(int name)
    {
        name -= TRACK0;
        ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
        track_t& track = mState.tracks[name];
    
        if (!track.enabled) {
            track.enabled = true;
            ALOGV("enable(%d)", name);
            invalidateState(1 << name); //刷新
        }
    }
    
    

    刷新缓冲

    void AudioMixer::invalidateState(uint32_t mask)
    {
        if (mask != 0) {
            mState.needsChanged |= mask;
            mState.hook = process__validate; //赋给函数指针
        }
     }
    

    采样处理函数

    void AudioMixer::process__validate(state_t* state)
    {
        
        uint32_t enabled = 0;
        uint32_t disabled = 0;
        ......
        if (countActiveTracks > 0) {
            if (resampling) {
                .....
                state->hook = process__genericResampling; //采样
            } else {
                if (state->outputTemp) {
                    delete [] state->outputTemp;
                    state->outputTemp = NULL;
                }
                if (state->resampleTemp) {
                    delete [] state->resampleTemp;
                    state->resampleTemp = NULL;
                }
                state->hook = process_OneTrack16BitsStereoNoResampling; //采样
           }
    }
    
    

    从Track中取出音频数据,写出

    void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
    {
        t.bufferProvider->getNextBuffer(&b); //获得可读缓冲
        numFrames -= b.frameCount;
        t.bufferProvider->releaseBuffer(&b); //释放缓冲区
    }
    

    MixerThread获取Track的数据,进行混音后通过AudioStreamOutPut的 *output 写入音频输出设备

    AudioFlinger的分析就到这里,AudioFlinger通过APS创建混音线程,混音线程将Track中的数据取出,进入环形缓冲区处理,最终输出到音频硬件设备;这个过程较为复杂,特别audio_track_cblk在此并没有详细分析;AF的分析暂且到此结束,后续连贯分析音频流程的时候会更清晰。Bye,Bye !

    展开全文
  • AudioPolicy&AudioFlinger初始化初始化概览总体框架启动步骤AudioPolicy初始化分析1、loadConfig()2、initialize() 初始化概览 总体框架 AudioFlinger和AudioPolicy两者是Android Audio框架层最主要的两个服务,...

    总体框架

    AudioFlinger和AudioPolicy两者是Android Audio框架层最主要的两个服务,他们两个是Android框架层的本地服务,在init.rc中启动;
    AudioPolicyManager负责音频策略定制者,说白了就相当于Audio系统的司令。
    AudioFlinger负责与底层audio alsa进行交互的实现者,那么它就是Audio系统的军官,干苦力的;
    总体框架:
    两个服务都属于audioserver进程,严格意义上来说audioserver通过init进程fork出来的,所以它是Linux系统中的一个进程。
    AudioFlinger:media.audio_flinger
    AudioPolicyService:media.audio_policy
    在这里插入图片描述

    初始化步骤简介

    1、通过init进程fork出来,从而开始各自服务的初始化
    2、首先初始化audioflinger服务
    3、其次初始化audiopolicyservice服务
    4、进一步通过audiopolicyservice和audioflinger完成音频hal层的初始化,这部分将是本文的重点难点分析。

    1、通过init进程fork出来,从而开始各自服务的初始化
    来,看下它是怎么定义:

    //frameworks/av/media/audioserver/audioserver.rc
    service audioserver /system/bin/audioserver
        class core
        user audioserver
        onrestart restart audio-hal-2-0
        ioprio rt 4 //设置io优先级
        disabled
    

    可以看到audioserver属于core类型,优于一般的main类型,也就是说它的启动是更早的。
    audioflinger&audiopolicyserver启动:

    frameworks/av/media/audioserver/main_audioserver.cpp
    int main(int argc __unused, char **argv)
    {
    	---
         android::hardware::configureRpcThreadpool(4, false /*callerWillJoin*/);
         sp<ProcessState> proc(ProcessState::self());
         sp<IServiceManager> sm = defaultServiceManager();
         ALOGI("ServiceManager: %p", sm.get());
         AudioFlinger::instantiate();
         AudioPolicyService::instantiate(); 
    	---
    }
    

    2、首先初始化audioflinger服务
    AudioFlinger初始化比较简洁,就是创建服务并将自身注册到systemserver中去,其次就是初始化部分通信组件以便后续与audio hal层进行通讯。如下图所示:
    在这里插入图片描述
    3、其次初始化audiopolicyservice服务
    AudioPolicyService的初始化就比audioflinger服务初始化复杂了,下图仅仅是audiopolicyservice与audiopolicymanager的初始化。主要就是创建出几个线程(AudioCommandThread类型的线程),以便后续与上层进行交互使用,上层调用的比如播放暂停的操作指令会进入这个线程队列,实现上层异步调用也可以防止底层耗时操作导致阻塞上层应用。接着便是创建AudioPolicyManager实例以及客户端等。大概流程如下图所示:
    在这里插入图片描述
    4、进一步通过audiopolicyservice和audioflinger完成音频hal层的初始化,这部分将是本文的重点难点分析。

    audiopolicyservice启动后,开始创建audiopolicymanager,并通过audiopolicymanager初始化audiopolicy策略,然后再进行对audio路由引擎(EngineInstance)进行初始化,初始化完路由引擎后便对audio hal 的so进行加载初始化,进一步通过加载后的so针对音频设备进行open操作,并默认打开主通道的输出音频流,最后将成功初始化的音频设备进行保存到audiopolicymanager以及audioflinger中,最后完成初始化。
    详细的初始化流程如下图所示:
    在这里插入图片描述

    初始化步骤详细流程分析

    从上面的初始大概流程可以知道,audio框架的初始化重点在audiopolicy部分的初始化,它不仅需要初始音频策略,还需针对加载的音频策略针对hal层的音频设备进行初始化,这部分还涉及到audioflinger部分,但以audiopolicy作为主线进行分析,下面将一步步对其进行分析。

    //frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
    AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
            : AudioPolicyManager(clientInterface, false /*forTesting*/)
    {
    	//1、加载audiopolicy的策略文件
        loadConfig();
        //2、针对加载的策略进行真正的初始化
        initialize();
    }
    

    可以看到AudioPolicyManager构造函数很简单,就两个调用:
    第一步:loadConfig()
    第二步:initialize()

    下面进入详细分析:

    1、loadConfig()

    很简单,就通过配置文件USE_XML_AUDIO_POLICY_CONF来控制是使用XML配置的策略文件还是使用传统旧config配置文件。这个变量的初始化可以通过配置文件进行选择。

    //frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
    void AudioPolicyManager::loadConfig() {
    #ifdef USE_XML_AUDIO_POLICY_CONF
    	//getConfig()这个很重要,为了后续的加载so做准备
        if (deserializeAudioPolicyXmlConfig(getConfig()) != NO_ERROR) {
    #else
        if ((ConfigParsingUtils::loadConfig(AUDIO_POLICY_VENDOR_CONFIG_FILE, getConfig()) != NO_ERROR)
               && (ConfigParsingUtils::loadConfig(AUDIO_POLICY_CONFIG_FILE, getConfig()) != NO_ERROR)) {
    #endif
            ALOGE("could not load audio policy configuration file, setting defaults");
            getConfig().setDefault();
        }
    }
    

    1.1这个getConfig()得到的mConfig成员变量如下:

    mConfig(mHwModulesAll, mAvailableOutputDevices, mAvailableInputDevices, mDefaultOutputDevice, 
    static_cast<VolumeCurvesCollection*>(mVolumeCurves.get()))
    

    这些成员变量在解析配置文件(XML格式或者config格式)会得到初始化,这点很重要,后续的so加载会根据配置的module name来进行加载。其会通过Serializer.cpp进行XML文件的解析,这个是一个很繁重的任务,如需讲明其解析过程还需另起一个篇幅才能将其介绍,与初始化关系不大,一笔带过。
    XML的配置文件格式如下(简化版配置,这块涉及到音频路由,后续将会再写一篇详细介绍该配置文件):

    //frameworks/av/services/audiopolicy/config/audio_policy_configuration.xml 
    <audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
        <globalConfiguration speaker_drc_enabled="true"/>
        <modules>
        	//编译后生成的so命名会根据module name 以及soc名字生成e.g. audio.[module name].[soc name]
        	//如IMX8的:audio.primary.imx8.so
            <module name="primary" halVersion="2.0">
                <attachedDevices>
                    <item>Speaker</item>
                </attachedDevices>
                <defaultOutputDevice>Speaker</defaultOutputDevice>
                <mixPorts>//输出混音线程
                    <mixPort name="primary output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                        <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" samplingRates="48000"
                        channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    </mixPort>
                </mixPorts>
                <devicePorts>//输出设备节点
                    <devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="sink" >
                    </devicePort>
                </devicePorts>
                <routes>
                	//音频路由
                    <route type="mix" sink="Speaker" sources="esai output,primary output"/>
                </routes>
            </module>
        </modules>
    </audioPolicyConfiguration>
    
    

    2、initialize()

    好了,上面的都是开胃菜,这个才是硬菜。
    来,看下这个大概步骤,心中有谱,码海不慌。
    主要是三个步骤:
    2.1初始音频路由引擎
    audio_policy::EngineInstance *engineInstance = audio_policy::EngineInstance::getInstance();
    2.2、加载so 并且打开设备节点
    mpClientInterface->loadHwModule(hwModule->getName())
    2.3、打开输出流
    status_t status = outputDesc->open(nullptr, profileType, address, AUDIO_STREAM_DEFAULT, AUDIO_OUTPUT_FLAG_NONE,&output);
    怕你不信,所以贴了部分代码出来:

    //frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
    status_t AudioPolicyManager::initialize() {
    	//1、初始音频路由引擎
        // Once policy config has been parsed, retrieve an instance of the engine and initialize it.
        audio_policy::EngineInstance *engineInstance = audio_policy::EngineInstance::getInstance();
        if (!engineInstance) {
            ALOGE("%s:  Could not get an instance of policy engine", __FUNCTION__);
            return NO_INIT;
        }
        // Retrieve the Policy Manager Interface
        mEngine = engineInstance->queryInterface<AudioPolicyManagerInterface>();
        if (mEngine == NULL) {
            ALOGE("%s: Failed to get Policy Engine Interface", __FUNCTION__);
            return NO_INIT;
        }
        mEngine->setObserver(this);
        status_t status = mEngine->initCheck();
    
          for (const auto& hwModule : mHwModulesAll) {
          	//2、加载so 并且打开设备节点
            hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));
            mHwModules.push_back(hwModule);
            // open all output streams needed to access attached devices
            // except for direct output streams that are only opened when they are actually
            // required by an app.
            // This also validates mAvailableOutputDevices list
            for (const auto& outProfile : hwModule->getOutputProfiles()) {
                //经过一系列有效判断后 创建输出相关参数
                sp<SwAudioOutputDescriptor> outputDesc = new SwAudioOutputDescriptor(outProfile,
                                                                                     mpClientInterface);
                const DeviceVector &supportedDevices = outProfile->getSupportedDevices();
                const DeviceVector &devicesForType = supportedDevices.getDevicesFromType(profileType);
                String8 address = devicesForType.size() > 0 ? devicesForType.itemAt(0)->mAddress
                        : String8("");
                audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
                //3、打开输出流
                status_t status = outputDesc->open(nullptr, profileType, address,
                                               AUDIO_STREAM_DEFAULT, AUDIO_OUTPUT_FLAG_NONE, &output);
    
                if (status != NO_ERROR) {
                    ALOGW("Cannot open output stream for device %08x on hw module %s",
                          outputDesc->mDevice,
                          hwModule->getName());
                } else {
                    for (const auto& dev : supportedDevices) {
                        ssize_t index = mAvailableOutputDevices.indexOf(dev);
                        // give a valid ID to an attached device once confirmed it is reachable
                        if (index >= 0 && !mAvailableOutputDevices[index]->isAttached()) {
                        	//这个很重要的变量,保存了可用的输出设备,后续会进一步说明
                            mAvailableOutputDevices[index]->attach(hwModule);
                        }
                    }
                    if (mPrimaryOutput == 0 &&
                            outProfile->getFlags() & AUDIO_OUTPUT_FLAG_PRIMARY) {
                        mPrimaryOutput = outputDesc;
                    }
                    addOutput(output, outputDesc);
                    setOutputDevice(outputDesc, profileType, true, 0,  NULL, address);
                }
             }//end inner for
            }//end out for
        }
        // make sure all attached devices have been allocated a unique ID
    

    好了,是不是也挺简单的,就三步。

    2.1、初始音频路由引擎

    2.1.1 创建路由
    audio_policy::EngineInstance
    这块有可配置路由和默认路由之分,音频流是根据路由策略进行打开相应的音频路由通路的。这部分内容需要领开一篇进行分析。这是一块很重要的内容,这里暂不展开分析。

    2.2、加载so 并且打开设备节点

    loadHwModule(hwModule->getName());
    参数是由上面步骤初始配置文件得到,hwModule->getName():如IMX8的根据配置文件:audio.primary.imx8.so
    会在vendor/lib/hw/加载文件,如果找不到会依次在system/lib/hw/进行查找。
    详细步骤如下:

    //注意这个返回值是audio_module_handle_t,这是个线程,这个很重要
    //因为后续的播放录音Track都是挂到这个audio_module_handle_t上去的,这个是个线程
    //frameworks/av/services/audioflinger/AudioFlinger.cpp
    audio_module_handle_t AudioFlinger::loadHwModule(const char *name){
    1==>进一步调用loadHwModule_l(name);
    2====> 再进一步调用到DevicesFactoryHal的openDevice方法打开驱动设备
    //frameworks/av/services/audioflinger/AudioFlinger.cpp
    int rc = mDevicesFactoryHal->openDevice(name, &dev);
    3=======>调用本地通讯方式的DeviceFactroy实现
    //frameworks\av\media\libaudiohal\2.0\DevicesFactoryHalLocal.cpp
    status_t DevicesFactoryHalLocal::openDevice(const char *name, sp<DeviceHalInterface> *device) 
    3.1=======>继续调用到load_audio_interface
    //frameworks\av\media\libaudiohal\2.0\DevicesFactoryHalLocal.cpp
    static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev){
        const hw_module_t *mod;
        int rc;
        rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
        if (rc) {
            ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__,
                    AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
            goto out;
        }
        rc = audio_hw_device_open(mod, dev);
        return rc;
    }
    4=========>最终会调用到audio.h的方法open,audio_hw_device_open方法会调用设备的open方法
    //hardware/libhardware/include/hardware/audio.h
    static inline int audio_hw_device_open(const struct hw_module_t* module,
                                           struct audio_hw_device** device){
        return module->methods->open(module, AUDIO_HARDWARE_INTERFACE,
                                     TO_HW_DEVICE_T_OPEN(device));
    }
    5==========>最后会调用到
    //最终会调用到各自厂商实现的hal层的open方法,代码路径就不放了
    static int adev_open(const hw_module_t* module, const char* name,
                         hw_device_t** device)
    

    到这里,音频设备打开就完毕了;
    额外说明一下:mDevicesFactoryHal的初始化:

    1// mDevicesFactoryHal初始化是在AudioFlinger初始化的时候进行的:
    //frameworks/av/services/audioflinger/AudioFlinger.cpp
    mDevicesFactoryHal = DevicesFactoryHalInterface::create();
    
    //frameworks/av/media/libaudiohal/DevicesFactoryHalInterface.cpp
    sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
        if (hardware::audio::V4_0::IDevicesFactory::getService() != nullptr) {
            return new V4_0::DevicesFactoryHalHybrid();
        }
        if (hardware::audio::V2_0::IDevicesFactory::getService() != nullptr) {
            return new DevicesFactoryHalHybrid();
        }
        return nullptr;
    }
    DevicesFactoryHalHybrid:Hybrid混合,包含了本地通讯方式,也包含了HIDL通讯方式:
    //frameworks/av/media/libaudiohal/impl/DevicesFactoryHalHybrid.cpp
    DevicesFactoryHalHybrid::DevicesFactoryHalHybrid(sp<IDevicesFactory> hidlFactory)
            : mLocalFactory(new DevicesFactoryHalLocal()),
              mHidlFactory(new DevicesFactoryHalHidl(hidlFactory)) {
    }
    
    status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
        if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
            strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
            return mHidlFactory->openDevice(name, device);
        }
        return mLocalFactory->openDevice(name, device);
    }
    到这里,mDevicesFactoryHal的初始化介绍就完毕了。
    但是Android 10以后是mDevicesFactoryHal的初始化是这样实现的:
    //frameworks/av/media/libaudiohal/DevicesFactoryHalInterface.cpp
    sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
       return createPreferredImpl<DevicesFactoryHalInterface>(
                "android.hardware.audio", "IDevicesFactory");
    }
    通过是从服务中根据名称"android.hardware.audio", "IDevicesFactory"获取的,暂不深究。
    

    2.3、打开输出流

    //frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp  AudioPolicyManager::initialize()
     const DeviceVector &supportedDevices = outProfile->getSupportedDevices();
                const DeviceVector &devicesForType = supportedDevices.getDevicesFromType(profileType);
                String8 address = devicesForType.size() > 0 ? devicesForType.itemAt(0)->mAddress
                        : String8("");
                audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
                status_t status = outputDesc->open(nullptr, profileType, address,
                                               AUDIO_STREAM_DEFAULT, AUDIO_OUTPUT_FLAG_NONE, &output);
                                               
    //frameworks/av/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
    status_t SwAudioOutputDescriptor::open(const audio_config_t *config,
                                           const DeviceVector &devices,
                                           audio_stream_type_t stream,
                                           audio_output_flags_t flags,
                                           audio_io_handle_t *output)
    {
        mDevices = devices;
        sp<DeviceDescriptor> device = devices.getDeviceForOpening();
        LOG_ALWAYS_FATAL_IF(device == nullptr,
                            "%s failed to get device descriptor for opening "
                            "with the requested devices, all device types: %s",
                            __func__, dumpDeviceTypes(devices.types()).c_str());
    
        audio_config_t lConfig;
        if (config == nullptr) {
            lConfig = AUDIO_CONFIG_INITIALIZER;
            lConfig.sample_rate = mSamplingRate;
            lConfig.channel_mask = mChannelMask;
            lConfig.format = mFormat;
        } else {
            lConfig = *config;
        }
    
        // if the selected profile is offloaded and no offload info was specified,
        // create a default one
        if ((mProfile->getFlags() & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) &&
                lConfig.offload_info.format == AUDIO_FORMAT_DEFAULT) {
            flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD);
            lConfig.offload_info = AUDIO_INFO_INITIALIZER;
            lConfig.offload_info.sample_rate = lConfig.sample_rate;
            lConfig.offload_info.channel_mask = lConfig.channel_mask;
            lConfig.offload_info.format = lConfig.format;
            lConfig.offload_info.stream_type = stream;
            lConfig.offload_info.duration_us = -1;
            lConfig.offload_info.has_video = true; // conservative
            lConfig.offload_info.is_streaming = true; // likely
        }
    
        mFlags = (audio_output_flags_t)(mFlags | flags);
    
        ALOGV("opening output for device %s profile %p name %s",
              mDevices.toString().c_str(), mProfile.get(), mProfile->getName().c_str());
    
        status_t status = mClientInterface->openOutput(mProfile->getModuleHandle(),
                                                       output,
                                                       &lConfig,
                                                       device,
                                                       &mLatency,
                                                       mFlags);
     mClientInterface:是在AudioPolicyManager调用的时候传进去的
     AudioPolicyClientInterface *mpClientInterface;  // audio policy client interface
    

    到了AudioPolicyClientInterface 这个就明朗了,调用如下:
    在这里插入图片描述
    大概流程就是AudioPolicyManager–>AudioPolicyService–>AudioFlinger–>audio hal层了。至此,output 输出流打开成功。
    最后将已经打开的输出流和输出设备保存到相应的数组里面,后续有流需要播出的时候直接进行查找,如果查找不到会尝试向底层查询,如果查询支持将会打开,如果查询不到,则会将其流类型改变并且将其加入到默认输出流里面进行播放处理。

    小结:
    这里的一切都是为音频播放做准备,后续将介绍音频路由部分。

    展开全文
  • AudioAudio Session

    千次阅读 2014-07-23 09:17:52
    在iOS开发中,音视频最重要的framework就是...要掌握音频的使用,首先要了解的就是Audio Session了。 1、什么是Audio Session? 先给出官方的英文解释: An audio session is the intermediary between
  • Android8.0 Audio系统之AudioPolicy

    千次阅读 2018-08-14 11:43:43
    上一篇我们跟踪分析了AudioFlinger,它是Audio系统的核心,但是AudioFlinger却不能脱离AudioPolicy工作。AudioPolicy模块承载着音频切换,音轨路由的重要工作,没有它,音频输出将乱套。在分析AudioFlinger的时候...
  • 这个文件是Audio Jungle音频素材中的纯净人声,自行百度去水印方法,去除水印必须有纯净的人声,这是去水印必备的
  • pulseaudio9.0 源码

    2018-10-15 09:02:36
    pulseaudio9.0开源代码。 PulseAudio可以适用于所有audio应用场景,本地或者网络流,为他们提供统一接口
  • Audio System 二 之 Audio系统框架

    千次阅读 2019-09-29 10:16:02
    Audio System 二 之 Audio系统框架二、Linux Audio系统框架2.1 Application 层2.2 Framework 层2.3 Libraries 层2.4 HAL 层2.5 Tinyalsa 层2.6 Kernel部分2.7 Audio Devices 部分三、Qualcomm 平台 - Audio系统框架...
  • Web Audio is an upcoming industry standard for web audio processing. Using the API, developers today can develop web games and applications with real-time audio effects to rival their desktop ...
  • CTS Verifier Audio测试步骤,包括audio speaker frequency test,audio microphone frequency test等
  • Unity的声音 —— AudioSource 和 AudioListener AudioSource AudioSource 是 Unity 中的 Audio 组件,其主要用来播放游戏场景中的 AudioClip,AudioClip 就是导入到 Unity 中的声音文件。Unity 可导入的音频...
  • Android Audio AudioEffect

    千次阅读 2011-10-14 07:23:48
    在看AudioSessionId相关代码的时候了解到,共用一个AudioSessionId的AudioTrack和MediaPlayer会共用一个AudioEffect。 今天就来看看AudioEffect是个什么东东。 看这个类的目的,主要是为了搞清楚AudioEffect
  • AudioSource 声音源: 常用的成员: Play Pause UnPause Stop PlayOneShot:使用该方法可以在一个AudioSource播放多个声音 PlayClipAtPoint:静态方法,可以在固定地点播放声音     AudioListener...
  • USB Audio Class v2.0

    2019-01-03 18:20:38
    USB官方声卡2.0协议文档(USB Audio Class v2.0),其中包括 USB audio 2.0标准,USB audio frmts 2.0文档,USB audio termt 2.0文档。该文档是硬件工程师和驱动工程师及USB声卡开发学习的必备文档。
  • E/AudioFlinger( 151): int android::load_audio_interface(const char*, audio_hw_device_t**) couldn't load audio hw module audio.primary (Invalid argument) I/AudioFlinger( 151): loadHwModule() error -22...
  • Android Audio 3: Audio的实现

    千次阅读 2016-08-05 10:06:39
    1. 实现HALAudio HAL由三部分构成,这三部分必须实现。 hardware/libhardware/include/hardware/audio.h 表征了一个audio device的主要方法 hardware/libhar
  • Java sun audio

    2018-10-16 21:56:37
    java sun.audio包,里面包含大部分音频操作的类,给需要的朋友
  • audio.js插件

    2018-09-14 10:34:05
    audio.js插件,播放音频,简介,方便,易操控,样式自定义
  • 理解 Audio 音频系统三 之 AudioFlinger三、AudioFlinger1. AudioFlinger 的启动2. AudioFlinger 构造函数 断断续续写了好多天,终于把《理解 Audio 音频系统二 之 audioserver & AudioPolicyService》 写好...
  • Android是多任务系统,Audio系统是竞争资源。Android2.2之前,没有内建的机制来解决多个程序竞争Audio的问题,2.2引入了称作AudioFocus的机制来管理对Audio资源的竞争的管理与协调。本文主要讲解AudioFocus的使用。 ...
  • Android Audio 2: Audio相关术语

    千次阅读 2016-08-04 15:37:16
    Audio相关的术语包含广泛运用的通用术语和Android专属的术语。 1.通用术语 Audio通用术语具有传统和符合人们通用习惯的含义。 1.1 数字音频(Digital Audio) 数字音频相关的术语涉及处理那些使用数字形式编码的...
  • audio 自动播放

    千次阅读 2019-04-04 14:24:30
    audio id="voice" loop src="/photo/aa.mp3" id="audio" autoplay preload="auto">该浏览器不支持audio属性</audio> src:音频地址 autoplay:音频加载完毕后自动播放。 controls:显示播放控制条。 loop:...
  • AudioRecord

    千次阅读 2016-11-08 01:14:14
    AudioRecord简介1.AudioRecord与MediaRecorder一样用来录制音频的 2.AudioRecord可以对录制的数据进行实时的处理,比如降噪,除杂,或者将音频进行实时传输,比如IP电话,对讲功能等操作。 3.AudioRecord比...
  • audio flinger

    千次阅读 2015-09-23 12:45:20
       audioflinger 2013-02-23 17:22 1648人阅读 评论(0) ...如果要转载请注明原创作者是蝈蝈OverViewAudio Policy and Audio Hardware ...1 Audio Hardware Device2 Audio Policy Service3 Audio Po
  • Android中的Audio播放:竞争AudioAudio Focus的应用

    万次阅读 多人点赞 2012-04-03 06:36:11
    田海立2012-04-03 Android是多任务系统,Audio系统是竞争资源。Android2.2之前,没有内建的机制来解决多个程序竞争Audio的问题,2.2引入了称作AudioFocus的机制来管理对Audio资源的竞争的管理与协调。本文主要讲解...
  • 浅谈Audio

    千次阅读 2018-02-12 16:16:59
    资料 1 HTML 5 audio 标签 ...创建Audio 1 audio 标签 2 JS生成Audio元素 1 documentcreateElement 2 new Audio 操作Audio 1 检测音频类型是否可以播放canPlayType 2 更改音频来源重载音频load 1 需要使用l...
  • AudioAudio对象的方法和属性

    千次阅读 2016-03-29 17:23:21
    Audio是英文单词,有多种含义:Audio是AU格式一种经过压缩的数字声音格式的详写;Audio是音频的单词;Audio是听觉的单词。听觉声波作用于听觉器官,使其感受细胞兴奋并引起听神经的冲动发放传入信息,经各级听觉中枢...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 272,139
精华内容 108,855
关键字:

audio