赞
踩
手机本身有听筒和扬声器作为音频输出,手机本身可能有底部(双)Mic、顶部Mic、背部Mic作为音频输入。
手机可能连接有线耳机、多个蓝牙耳机、多个WiFi音频外设,或者车载设备、VR设备、投屏设备等。
AudioPolicy提供了一个音频输入、输出管理的中心,当然它还有一些其他的作用。
在介绍audiopolicy之前,需要先了解audio focus相关内容。
1、为什么会有音频焦点机制?
我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。
2、使用音频焦点
- //获取焦点
- AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- mAudioManager.requestAudioFocus(cl, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
requestAudioFocus方法有三个参数
第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点。
第二个参数:streamType音频流类型,焦点获得之后的数据传输类型,这个参数不会影响焦点机制,不同的音频流类型同样遵守一个焦点机制。
第三个参数:durationHint,获得焦点的时间长短,
其中GAIN有4种
AUDIOFOCUS_GAIN 永久获取焦点
AUDIOFOCUS_GAIN_TRANSIENT 临时获取焦点
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 临时获取焦点,其它应用可以降低音量
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 临时获取焦点,不会让出
LOSS有3种
AUDIOFOCUS_LOSS 永久失去焦点
AUDIOFOCUS_LOSS_TRANSIENT 临时失去焦点
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 临时失去焦点,可以不静音
看看OnAudioFocusChangeListener 的实现:
- OnAudioFocusChangeListener cl = new OnAudioFocusChangeListener() {
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- switch(focusChange){
- case AudioManager.AUDIOFOCUS_LOSS:
- //长时间丢失焦点,这个时候需要停止播放,并释放资源。根据不同的逻辑,有时候还会释放焦点
- mAudioManager.abandonAudioFocus(cl);
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- //短暂失去焦点,这时可以暂停播放,但是不必要释放资源,因为很快又会获取到焦点
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- //短暂失去焦点,但是可以跟新的焦点拥有者同时播放,并做降噪处理
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- //获得了音频焦点,可以播放声音
- break;
- }
- }
- };
3、获取音频焦点机制流程分析
我们调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。我们看到OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。
- requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
- .......
- registerAudioFocusListener(l);
- .......
- IAudioService service = getService();
- try {
- status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
- mAudioFocusDispatcher, getIdForAudioFocusListener(l),
- getContext().getOpPackageName() /* package name */, flags,
- ap != null ? ap.cb() : null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
IAudioFocusDispatcher 的设计很简洁,主要就是把从AudioService获取到的消息通过handler机制,交给另外的线程处理,从代码看到是交给了请求焦点的线程处理。
- private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
-
- public void dispatchAudioFocusChange(int focusChange, String id) {
- final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
- MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);
- mServiceEventHandlerDelegate.getHandler().sendMessage(m);
- }
-
- };
AudioService是运行在system_server进程里面的系统服务,其中维护了一个栈:Stack mFocusStack,此为维护焦点的关键。
申请焦点主要是如下几点:
a、检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。
b、压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。
c、如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false,不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功。
- boolean focusGrantDelayed = false;
- if (!canReassignAudioFocus()) { //这里判断焦点是否处于电话状态
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- } else {
- // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
- // granted right now, so the requester will be inserted in the focus stack
- // to receive focus later
- focusGrantDelayed = true;
- }
- }
- // focus requester might already be somewhere below in the stack, remove it 此处便是移除栈里面相同clientId的记录
- removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
- //创建新的FocusRequester实例,为入栈做准备
- final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
- clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
-
- if (focusGrantDelayed) {
- // focusGrantDelayed being true implies we can't reassign focus right
- // which implies the focus stack is not empty.延迟
- final int requestResult = pushBelowLockedFocusOwners(nfr);
- if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
- }
- return requestResult;
- } else {
- // propagate the focus change through the stack没有延迟
- if (!mFocusStack.empty()) {
- propagateFocusLossFromGain_syncAf(focusChangeHint);
- }
-
- // push focus requester at the top of the audio focus stack
- mFocusStack.push(nfr);
- }
- notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
- AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- }
4、释放音频焦点流程
释放音频焦点会有以下两种情况:
a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;
b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。
- private void removeFocusStackEntry(String clientToRemove, boolean signal,
- boolean notifyFocusFollowers) {
- // is the current top of the focus stack abandoning focus? (because of request, not death)
- if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
- { //释放焦点的应用端在栈顶
- //Log.i(TAG, " removeFocusStackEntry() removing top of stack");
- FocusRequester fr = mFocusStack.pop();
- fr.release();
- if (notifyFocusFollowers) {
- final AudioFocusInfo afi = fr.toAudioFocusInfo();
- afi.clearLossReceived();
- notifyExtPolicyFocusLoss_syncAf(afi, false);
- }
- if (signal) {
- // notify the new top of the stack it gained focus
- notifyTopOfAudioFocusStack();
- }
- } else {
- //释放焦点的应用端不在栈顶
- // focus is abandoned by a client that's not at the top of the stack,
- // no need to update focus.
- // (using an iterator on the stack so we can safely remove an entry after having
- // evaluated it, traversal order doesn't matter here)
- Iterator stackIterator = mFocusStack.iterator();
- while(stackIterator.hasNext()) {
- FocusRequester fr = stackIterator.next();
- if(fr.hasSameClient(clientToRemove)) {
- Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
- + clientToRemove);
- stackIterator.remove();
- fr.release();
- }
- }
- }
- }
AudioStream在Audio Base.h中定义 typedef enum { |
AudioStream仅用来标识音频的音量,使用音频属性AudioAttributes和AudioStream共同决定AudioStrategy (在之前的版本中,AudioStream对应AudioStrategy,AudioStrategy选择音频输出设备) routing_strategy Engine::getStrategyForUsage(audio_usage_t usage) |
Strategy根据一定的规则,来选择不同音频的输出设备,比如:音乐从耳机发出、闹钟从手机扬声器和耳机同时发出 audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy, |
adb shell dumpsys media.audio_policy
可分析audio_policy日志
/vendor/etc/audio/audio_policy_configuration.xml - Available input devices: |
Policy Engine dump: -STRATEGY_SONIFICATION (id: 16) -STRATEGY_ENFORCED_AUDIBLE (id: 17) -STRATEGY_ACCESSIBILITY (id: 18) -STRATEGY_SONIFICATION_RESPECTFUL (id: 19) -STRATEGY_MEDIA (id: 20) -STRATEGY_DTMF (id: 21) -STRATEGY_TRANSMITTED_THROUGH_SPEAKER (id: 22) -STRATEGY_REROUTING (id: 23) -STRATEGY_PATCH (id: 24) |
音频服务在frameworks/av/media/audioserver/main_audioserver.cpp
中,这里会启动音频的AudioFlinger
和AudioPolicyService
两大组件。 经过上面的流程系统音频服务已经启动处于待命状态,如果有应用需要播放则会通过服务最终选择合适的硬件将声音播出。
audiopolicy时序图如下:
1 AudioFlinger和AudioPolicyService属于binder服务。他们启动都是在同一个进程中,交互表面上是binder IPC交互,其实底层也是指针相互调用。
instantiate方法是发布自身服务到ServiceManager
//framework/av/media/audioserver/main_audioserver.cpp int main(int argc __unused, char **argv) |
2 AudioPolicyManager是AudioPolicyService服务进程下的一个独立功能模块,该模块可以由厂家自行实现(但必须遵循aosp的接口定义),最后提供libaudiopolicymanager.so库,由AudioPolicyService服务load进来调用即可。音频配置文件audio_policy_configuration.xml配置了音频的设备、流以及路由关系,AudioPolicyManager负责解析存储这些信息。
一个Android设备,存在着许多音频设备,如听筒、麦克风、音箱、蓝牙耳机、音箱等等,Android开放给各个厂商开发者AudioPolicyManager模块来管理这些设备。在该模块中,一个audio_policy_configuration.xml文件配置了一个设备有几个module,每个module里面有哪些设备、数据流,以及这些设备和流之间的关系,而且每个module对应hal的处理逻辑也尽不一样,如primary、usb这两个module,一个是针对原生Android音频设备,一个是针对usb连接方式的音频设备,他们在hal乃至kenerl层实现都不一样,所以loadHwModule最终加载的内容均不一样。
AudioPolicyService和AudioPolicyManager之间,相互保存对方的对象指针引用。
void AudioPolicyService::onFirstRef() ...... AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface, bool /*forTesting*/) ///以loadHwModule为例/// mpClientInterface->loadHwModule(hwModule->getName()) //hwModule就是配置文件中的每一个module,它的name是字符串,一般是:primary、a2dp、usb等 audio_module_handle_t AudioPolicyService::AudioPolicyClient::loadHwModule(const char *name) return af->loadHwModule(name); // load工作交给AudioFlinger audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name) sp<DeviceHalInterface> dev; ALOGI("loadHwModule() Loaded %s audio interface, handle %d", name, handle); return handle; } status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) { status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) { //PRIMARY,A2DP, USB,SUBMIX,STUB //AudioFlinger使用Audio音频的Client端,以HIDL方式跨进程去调用服务端的openDevice方法,该方法的参数为module模块的名称 进入HIDL服务端,其服务端实现位于/hardware/interfaces/audio/core/all-versions/default/DeviceFactory.cpp中 Return<void> DevicesFactory::openDevice(IDevicesFactory::Device device, openDevice_cb _hidl_cb) { //模板类创建不同的实例,以primary实例为例会创建PrimaryDevice // static out: |
3 AudioPolicyService上可以与应用层交互,通过它中转去查询、选择音频设备信息,也就是AudioPolicyManager模块,选择完成后最终决定使用哪个设备时,又转给AudioFlinger去与下层HAL交互。
audio_policy_configuration.xml音频策略配置文件包含了两个xml文件: ...... <xi:include href="audio_policy_volumes.xml"/> audio_policy_volumes.xml规定了音频流、输出设备和音量曲线的关系: default_volume_tables.xml规定了具体音频曲线的值,如 audio_policy_configuration.xml中每一个CarAudioDeviceInfo都有一个设备代号,称为busNumber。 AudioControl.cpp中规定了每一种音频流(contextNumber)对应的设备代号,可根据具体需求修改: static int sContextToBusMap[] = { Android又依据音频的contextNumber将不同的音频类型分为几组,在car_volume_groups.xml中,每个group对应一个CarVolumeGroup类,在CarVolumeGroup类中保存了这组中几个音频类型的contextNumber-busNumber-CarAudioDeviceInfo对应关系。 这样通过setGroupVolume的参数groupId就可以方便的控制调节哪些音频类型对应的输出设备的硬件音量。 调节音量时,会根据传入的stream参数先找到VolumeCurvesForStream对象,再根据传入的device参数找到具体的VolumeCurve,最后根据index参数及音量曲线计算出音量的分贝值 |
4 AudioFlinger上可以与应用层交互,下可以和HAL层交互。
AudioFlinger模块加载
构造函数中实例化了设备接口以及音效接口,此时AudioFlinger
模块已经成功创建出来。
AudioPolicyService模块加载
5 AudioFlinger与HAL建立连接
AudioFlinger::AudioFlinger() // static sp<DevicesFactoryHalInterface> createDevicesFactoryHal() { DevicesFactoryHalHybrid::DevicesFactoryHalHybrid() |
音量调节分为两种实现,一种是Android原生通过设置PCM数据直接调节输出的振幅来调节音量,也是就是软音量。另外一种则是通过硬件来实现音量大小控制,也就是所谓的硬音量,通过DSP设置硬件功放等来进行音量大小控制。本篇主要涉及硬音量调节。
在CarAudioService.java中,mUseDynamicRouting=true,setGroupVolume设置硬件音量;mUseDynamicRouting=false,android原生,设置软音量。在CarVolumeGroup.java中根据setGroupVolume方法传入的值和audio_Policy_Configuration.xml中提供的信息,进行计算得到音量gainInMillibels,之后将gainInMillibels一路传到Hal层,(Hal层audio_hw.c/adev_set_audio_port_config方法调节音量曲线)在Hal层根据音频曲线再次计算音量值,最后调用BSP提供的接口设置音量。
Android提供两种接口来调节硬件音量:adjustStreamVolume和setStreamVolume,adjustStreamVolume传入音量调节的方向,setStreamVolume直接传入音量值。调节过程中首先根据音频流类型找到输出设备,再根据音频流类型和输出设备找到音频曲线并计算出音量的db值,最后将音量值设置到对应的混音线程PlayBackThread中,实现音量调节。需要注意的是当音调至0时称为muteAdjust,Android会对这种情况做一些特别处理。
参考文章:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。