手机本身有听筒和扬声器作为音频输出,手机本身可能有底部(双)Mic、顶部Mic、背部Mic作为音频输入。
手机可能连接有线耳机、多个蓝牙耳机、多个WiFi音频外设,或者车载设备、VR设备、投屏设备等。
AudioPolicy提供了一个音频输入、输出管理的中心,当然它还有一些其他的作用。
在介绍audiopolicy之前,需要先了解audio focus相关内容。
一 音频焦点
1、为什么会有音频焦点机制?
我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。
android10 音频焦点仲裁关键类及变量列表 | ||
---|---|---|
变量 | 类型 | 说明 |
AudioFocusInfo | 类 | 描述焦点申请者属性 |
FocusEntry | 内部类 | 对AudioFocusInfo和Context的封装 |
sInteractionMatrix | 二维数组 | 仲裁焦点申请结果 |
mFocusHolders | 全局变量,HashMap | 保存当前焦点持有者 |
mFocusLosers | 全局变量,HashMap | 保存暂时失去焦点并等待重新获得焦点的申请 |
losers | 局部变量,ArrayList | 保存失去焦点但失去焦点类型尚未确定的申请 |
blocked | 局部变量,ArrayList | 保存mFocusLosers中可以被当前申请者抢占的申请 |
permanentlyLost | 局部变量,ArrayList | 保存永久失去焦点的申请 |
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) |
三 audio服务启动流程
音频服务在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会对这种情况做一些特别处理。
参考文章: