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;

}

……

}