7.4.3 声音路由切换实例分析
路由这个词听上去很专业,其实它的目的很简单,就是为DSP选择数据出口,例如是从耳机、听筒还是扬声器传出。下面分析这样一个场景:
假设我们在用扬声器听歌,这时把耳机插上,会发生什么呢?
1.耳机插拔事件处理
耳机插上后,系统会发一个广播,Java层的AudioService会接收这个广播,其中的内部类AudioServiceBroadcastReceiver会处理该事件,处理函数是onReceive。
这段代码在AudioSystem.java中。一起来看——
(1)耳机插拔事件接收
看这段代码,如下所示:
[—>AudioSystem.java:AudioServiceBroadcastReceiver的onReceive()]
private class AudioServiceBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context,Intent intent){
String action=intent.getAction();
……
//如果该事件是耳机插拔事件。
else if(action.equals(Intent.ACTION_HEADSET_PLUG)){
//取得耳机的状态。
int state=intent.getIntExtra("state",0);
int microphone=intent.getIntExtra("microphone",0);
if(microphone!=0){
//查看已连接设备是不是已经有了耳机,耳机的设备号为0x4,
//这个和AudioSystem.h定义的设备号是一致的。
boolean isConnected=mConnectedDevices.containsKey(
AudioSystem.DEVICE_OUT_WIRED_HEADSET);
//如果之前有耳机而现在没有,则认为是耳机拔出事件。
if(state==0&&isConnected){
//设置Audio系统的设备连接状态,耳机为Unavailable。
AudioSystem.setDeviceConnectionState(
AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
"");
//从已连接设备中去掉耳机设备。
mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
}//如果state为1,并且之前没有耳机连接,则处理这个耳机插入事件。
else if(state==1&&!isConnected){
//设置Audio系统的设备连接状态,耳机为Available。
AudioSystem.setDeviceConnectionState(
AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE,
"");
//在已连接设备中增加耳机。
mConnectedDevices.put(
new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET),
"");
}
}
……
}
从上面的代码中可看出,不论耳机是插入还是拔出,都会调用AudioSystem的setDeviceConnectionState函数。
(2)setDeviceConnectionState:设置设备连接状态
这个函数被定义为Native函数。下面是它的定义:
[—>AudioSystem.java]
public static native int setDeviceConnectionState(int device,int state,
String device_address);
//注意我们传入的参数,device为0X4表示耳机,state为1,device_address为""。
该函数的Native实现,在android_media_AudioSystem.cpp中,对应函数是:
[->android_media_AudioSystem.cpp]
static int android_media_AudioSystem_setDeviceConnectionState(
JNIEnv*env,jobject thiz,jint
device,jint state,jstring device_address)
{
const char*c_address=env->GetStringUTFChars(device_address,NULL);
int status=check_AudioSystem_Command(
//调用Native AudioSystem的setDeviceConnectionState。
AudioSystem:setDeviceConnectionState(
static_cast<AudioSystem:audio_devices>(device),
static_cast<AudioSystem:device_connection_state>(state),
c_address));
env->ReleaseStringUTFChars(device_address,c_address);
return status;
}
从AudioSystem.java转入到AudioSystem.cpp,现在来看Native的对应函数:
[—>AudioSystem.cpp]
status_t AudioSystem:setDeviceConnectionState(audio_devices device,
device_connection_state state,
const char*device_address)
{
const sp<IAudioPolicyService>&aps=
AudioSystem:get_audio_policy_service();
if(aps==0)return PERMISSION_DENIED;
//转到AP去,最终由AMB处理。
return aps->setDeviceConnectionState(device,state,device_address);
}
Audio代码不厌其烦地把函数调用从这一类转移到另外一类,请直接看AMB的实现:
[—>AudioPolicyManagerBase.cpp]
status_t AudioPolicyManagerBase:setDeviceConnectionState(
AudioSystem:audio_devices device,
AudioSystem:device_connection_state state,
const char*device_address)
{
//一次只能设置一个设备。
if(AudioSystem:popCount(device)!=1)return BAD_VALUE;
……
//根据设备号判断是不是输出设备,耳机肯定属于输出设备。
if(AudioSystem:isOutputDevice(device)){
switch(state)
{
case AudioSystem:DEVICE_STATE_AVAILABLE:
//处理耳机插入事件,mAvailableOutputDevices保存已连接的设备。
//这个耳机是刚连上的,所以不走下面if分支。
if(mAvailableOutputDevices&device){
//启用过了,就不再启用了。
return INVALID_OPERATION;
}
//现在已连接设备中多了一个耳机。
mAvailableOutputDevices|=device;
……
}
//①getNewDevice之前已分析过了,这次再看。
uint32_t newDevice=getNewDevice(mHardwareOutput,false);//②更新各种策略使用的设备。
updateDeviceForStrategy();
//③设置新的输出设备。
setOutputDevice(mHardwareOutput,newDevice);
……
}
这里面有三个比较重要的函数,前面也已提过,现将其再进行一次较深入的分析,旨在加深读者对它的理解。
(3)getNewDevice
来看代码,如下所示:
[->AudioPolicyManagerBase.cpp]
uint32_t AudioPolicyManagerBase:getNewDevice(audio_io_handle_t output,
bool fromCache)
{//注意我们传入的参数,output为mHardwardOutput,fromCache为false。
uint32_t device=0;
//根据output找到对应的AudioOutputDescriptor,这个对象保存了一些信息。
AudioOutputDescriptor*outputDesc=mOutputs.valueFor(output);
if(mPhoneState==AudioSystem:MODE_IN_CALL||
outputDesc->isUsedByStrategy(STRATEGY_PHONE))
{
device=getDeviceForStrategy(STRATEGY_PHONE,fromCache);
}
else if(outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION))
{
device=getDeviceForStrategy(STRATEGY_SONIFICATION,fromCache);
}
else if(outputDesc->isUsedByStrategy(STRATEGY_MEDIA))
{
//应用场景是正在听歌,所以会走这个分支。
device=getDeviceForStrategy(STRATEGY_MEDIA,fromCache);
}
else if(outputDesc->isUsedByStrategy(STRATEGY_DTMF))
{
device=getDeviceForStrategy(STRATEGY_DTMF,fromCache);
}
return device;
}
策略是怎么和设备联系起来的呢?秘密就在getDeviceForStrategy中,来看代码:
[—>AudioPolicyManagerBase.cpp]
uint32_t AudioPolicyManagerBase:getDeviceForStrategy(
routing_strategy strategy,bool fromCache)
{
uint32_t device=0;
if(fromCache){//如果为true,则直接取之前的旧值。
return mDeviceForStrategy[strategy];
}
//如果fromCache为false,则需要重新计算策略所对应的设备。
switch(strategy){
case STRATEGY_DTMF://先处理DTMF策略的情况。
if(mPhoneState!=AudioSystem:MODE_IN_CALL){
//如果不处于电话状态,则DTMF策略和MEDIA策略对应同一个设备。
device=getDeviceForStrategy(STRATEGY_MEDIA,false);
break;
}
//如果处于电话状态,则DTMF策略和PHONE策略用同一个设备。
case STRATEGY_PHONE:
//是PHONE策略的时候,先要考虑是不是用户强制使用了某个设备,例如强制使用扬声器。
switch(mForceUse[AudioSystem:FOR_COMMUNICATION]){
……
case AudioSystem:FORCE_SPEAKER:
……//如果没有蓝牙,则选择扬声器。
device=mAvailableOutputDevices&
AudioSystem:DEVICE_OUT_SPEAKER;
break;
}
break;
case STRATEGY_SONIFICATION://SONIFICATION策略
if(mPhoneState==AudioSystem:MODE_IN_CALL){
/*
如果处于来电状态,则和PHONE策略用同一个设备。例如通话过程中我们强制使用扬声器,那么如果这个时候按拨号键,按键声则也会从扬声器出来。
*/
device=getDeviceForStrategy(STRATEGY_PHONE,false);
break;
}
device=mAvailableOutputDevices&AudioSystem:DEVICE_OUT_SPEAKER;
//如果不处于电话状态,则SONIFICATION和MEDIA策略用同一个设备。
case STRATEGY_MEDIA:{
//AUX_DIGITAL值为0x400,耳机不满足该条件。
uint32_t device2=mAvailableOutputDevices&
AudioSystem:DEVICE_OUT_AUX_DIGITAL;
if(device2==0){
//也不满足WIRED_HEADPHONE条件。
device2=mAvailableOutputDevices&
AudioSystem:DEVICE_OUT_WIRED_HEADPHONE;
}
if(device2==0){
//满足这个条件,所以device2为0x4,WIRED_HEADSET。
device2=mAvailableOutputDevices&
AudioSystem:DEVICE_OUT_WIRED_HEADSET;
}
if(device2==0){
device2=mAvailableOutputDevices&
AudioSystem:DEVICE_OUT_SPEAKER;
}
device|=device2;//最终device为0x4,WIRED_HEADSET
}break;
default:
break;
}
return device;
}
getDeviceForStrategy是一个比较复杂的函数。它的复杂在于选取设备时,需考虑很多情况。简单的分析仅能和读者一起领略一下它的风采,在实际工作中反复琢磨,或许才能掌握其中的奥妙。
好,getNewDevice将返回耳机的设备号0x4。下一个函数是updateDeviceForStrategy。这个函数和getNewDevice没有什么关系,因为它没用到getNewDevice的返回值。
(4)updateDeviceForStrategy
同样来看相应的代码,如下所示:
[—>AudioPolicyManagerBase.cpp]
void AudioPolicyManagerBase:updateDeviceForStrategy()
{
for(int i=0;i<NUM_STRATEGIES;i++){
//重新计算每种策略使用的设备,并保存到mDeviceForStrategy中,起到了cache的作用。
mDeviceForStrategy[i]=getDeviceForStrategy((routing_strategy)i,false);
}
}
updateDeviceForStrategy会重新计算每种策略对应的设备。
另外,如果updateDeviceForStrategy和getNewDevice互换位置,会节省很多不必要的调用。如:
updateDevicdForStrategy();//先更新策略
//使用cache中的设备,节省一次重新计算。
uint32_t newDevice=getNewDevice(mHardwareOutput,true);
不必讨论这位码农的功过了,现在来看最后一个函数setOutputDevice。它会对新选出来的设备做如何处理呢?
(5)setOutputDevice
继续看setOutputDevice的代码,如下所示:
[—>AudioPolicyManagerBase.cpp]
void AudioPolicyManagerBase:setOutputDevice(audio_io_handle_t output,
uint32_t device,bool force,int delayMs)
{
……
//把这个请求要发送到output对应的AF工作线程中。
AudioParameter param=AudioParameter();
//参数是key/vlaue键值对的格式。
param.addInt(String8(AudioParameter:keyRouting),(int)device);
//mpClientInterface是AP对象,由它处理。
mpClientInterface->setParameters(mHardwareOutput,
param.toString(),delayMs);
//设置音量,不做讨论,读者可自行分析。
applyStreamVolumes(output,device,delayMs);
}
setParameters最终会调用APS的setParameters,代码如下所示:
[—>AudioPolicyService.cpp]
void AudioPolicyService:setParameters(audio_io_handle_t ioHandle,
const String8&keyValuePairs,int delayMs)
{
//把这个请求加入到AudioCommandThread中处理。
mAudioCommandThread->parametersCommand((int)ioHandle,
keyValuePairs,delayMs);
}
AudioPolicyService创建时会同时创建两个线程,其中一个用于处理各种请求。现在看看它是怎么做的。
2.AudioCommandThread
AudioCommandThread有一个请求处理队列,AP负责往该队列中提交请求,而AudioCommandThread在它的线程函数threadLoop中处理这些命令。请直接看命令是如何处理的。
说明 这种通过一个队列来协调两个线程的方法,在多线程编程中非常常见,它也属于生产者/消费者模型。
(1)AudioCommandThread中的处理
先来看相应的代码,如下所示:
[—>AudioPolicyService.cpp]
bool AudioPolicyService:AudioCommandThread:threadLoop()
{
nsecs_t waitTime=INT64_MAX;
mLock.lock();
while(!exitPending())
{
while(!mAudioCommands.isEmpty()){
nsecs_t curTime=systemTime();
if(mAudioCommands[0]->mTime<=curTime){
AudioCommand*command=mAudioCommands[0];
mAudioCommands.removeAt(0);
mLastCommand=*command;
switch(command->mCommand){
case START_TONE:
……
case STOP_TONE:
……//TONE处理
mLock.lock();
}break;
case SET_VOLUME:{
//设置音量
delete data;
}break;
case SET_PARAMETERS:{
//处理路由设置请求。
ParametersDatadata=(ParametersData)command->mParam;
//转到AudioSystem处理,mIO的值为mHardwareOutput。
command->mStatus=AudioSystem:setParameters(
data->mIO,
data->mKeyValuePairs);
if(command->mWaitStatus){
command->mCond.signal();
mWaitWorkCV.wait(mLock);
}
delete data;
}break;
……
default:
}
}
Audio系统真是非常绕!来看AudioSystem的setParameters。
(2)AudioSystem的setParameters
AudioSystem将设置请求转移给AudioFlinger处理,代码如下所示:
[—>AudioSystem.cpp]
status_t AudioSystem:setParameters(audio_io_handle_t ioHandle,
const String8&keyValuePairs)
{
const sp<IAudioFlinger>&af=AudioSystem:get_audio_flinger();
//果然是交给AF处理,ioHandle看来一定就是工作线程索引号了。
return af->setParameters(ioHandle,keyValuePairs);
}
离真相越来越近了,接着看代码,如下所示:
[—>AudioFlinger.cpp]
status_t AudioFlinger:setParameters(int ioHandle,
const String8&keyValuePairs)
{
status_t result;
//ioHandle==0表示和混音线程无关,需要直接设置到HAL对象中。
if(ioHandle==0){
AutoMutex lock(mHardwareLock);
mHardwareStatus=AUDIO_SET_PARAMETER;
//调用AudioHardwareInterface的参数设置接口。
result=mAudioHardware->setParameters(keyValuePairs);
mHardwareStatus=AUDIO_HW_IDLE;
return result;
}
sp<ThreadBase>thread;
{
Mutex:Autolock_l(mLock);
//根据索引号找到对应的混音线程。
thread=checkPlaybackThread_l(ioHandle);
}
//我们只有一个MixerThread,交给它处理,这又是一个命令处理队列。
result=thread->setParameters(keyValuePairs);
return result;
}
return BAD_VALUE;
}
好了,最终的请求处理在MixerThread的线程函数中,接着往下看。
(3)MixerThread最终处理
代码如下所示:
[—>AudioFlinger.cpp]
bool AudioFlinger:MixerThread:threadLoop()
{
……
while(!exitPending())
{
processConfigEvents();
mixerStatus=MIXER_IDLE;
{//scope for mLock
Mutex:Autolock_l(mLock);
//checkForNewParameters_l最有嫌疑
if(checkForNewParameters_l()){
……
}
……//其他处理
}
[—>AudioFlinger.cpp]
bool AudioFlinger:MixerThread:checkForNewParameters_l()
{
bool reconfig=false;
while(!mNewParameters.isEmpty()){
status_t status=NO_ERROR;
String8 keyValuePair=mNewParameters[0];
AudioParameter param=AudioParameter(keyValuePair);
int value;
……
//路由设置需要硬件参与,所以直接交给代表音频输出设备的HAL对象处理。
status=mOutput->setParameters(keyValuePair);
return reconfig;
}
至此,路由设置所经历的一切轨迹,我们都已清晰地看到了,可总还有点意犹未尽的感觉,HAL的setParameters到底是怎么工作的呢?不妨再来看一个实际的HAL对象处理例子。
(4)真实设备的处理
这个实际的Hardware,位于hardware/msm7k/libaudio-qsd8k的Hardware.cpp中,它提供了一个音频处理实际的例子,这个Hardware针对的是高通公司的硬件。直接看它是怎么处理音频输出对象setParameters的,代码如下所示:
[—>AudioHardware.cpp AudioStreamOutMSM72xx:setParameters()]
status_t AudioHardware:AudioStreamOutMSM72xx:setParameters(
const String8&keyValuePairs)
{
AudioParameter param=AudioParameter(keyValuePairs);
String8 key=String8(AudioParameter:keyRouting);
status_t status=NO_ERROR;
int device;
if(param.getInt(key,device)==NO_ERROR){
mDevices=device;
//调用doRouting,mHardware就是AudioHardware对象。
status=mHardware->doRouting();
param.remove(key);
}
……
return status;
}
[—>AudioHardware.cpp]
status_t AudioHardware:doRouting()
{
Mutex:Autolock lock(mLock);
uint32_t outputDevices=mOutput->devices();
status_t ret=NO_ERROR;
int sndDevice=-1;
……
//做一些判断,最终由doAudioRouteOrMute处理。
if((vr_mode_change)||(sndDevice!=-1&&sndDevice!=mCurSndDevice)){
ret=doAudioRouteOrMute(sndDevice);
mCurSndDevice=sndDevice;
}
return ret;
}
[—>AudioHardware.cpp]
status_t AudioHardware:doAudioRouteOrMute(uint32_t device)
{
uint32_t rx_acdb_id=0;
uint32_t tx_acdb_id=0;
//只看看就行,对于硬件相关的代码,咱们就是打打酱油。
return do_route_audio_dev_ctrl(device,
mMode==AudioSystem:MODE_IN_CALL,rx_acdb_id,tx_acdb_id);
}
[—>AudioHardware.cpp]
static status_t do_route_audio_dev_ctrl(uint32_t device,bool inCall,
uint32_t rx_acdb_id,uint32_t tx_acdb_id)
{
uint32_t out_device=0,mic_device=0;
uint32_t path[2];
int fd=0;
//打开音频控制设备
fd=open("/dev/msm_audio_ctl",O_RDWR);
path[0]=out_device;
path[1]=rx_acdb_id;
//通过ioctl切换设备,一般系统调用都是返回-1表示出错,这里返回0表示出错。
if(ioctl(fd,AUDIO_SWITCH_DEVICE,&path)){
close(fd);
return-1;
}
……
}