7.4.3 声音路由切换实例分析
最后更新于:2022-04-02 05:51:43
路由这个词听上去很专业,其实它的目的很简单,就是为DSP选择数据出口,例如是从耳机、听筒还是扬声器传出。下面分析这样一个场景:
- 假设我们在用扬声器听歌,这时把耳机插上,会发生什么呢?
1. 耳机插拔事件处理
耳机插上后,系统会发一个广播,Java层的AudioService会接收这个广播,其中的内部类AudioServiceBroadcastReceiver会处理该事件,处理函数是onReceive。
这段代码在AudioSystem.java中。一起来看:
(1)耳机插拔事件接收
看这段代码,如下所示:
**AudioSystem.java::AudioServiceBroadcastReceiver的onReceive()**
~~~
private class AudioServiceBroadcastReceiverextends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
......
//如果该事件是耳机插拔事件
elseif (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**
~~~
publicstatic 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)
{
constchar *c_address = env->GetStringUTFChars(device_address, NULL);
intstatus = check_AudioSystem_Command(
//调用Native AudioSystem的setDeviceConnectionState
AudioSystem::setDeviceConnectionState(
static_cast(device),
static_cast(state),
c_address));
env->ReleaseStringUTFChars(device_address, c_address);
returnstatus;
}
~~~
从AudioSystem.java转入到AudioSystem.cpp,现在来看Native的对应函数:
**AudioSystem.cpp**
~~~
status_tAudioSystem::setDeviceConnectionState(audio_devices device,
device_connection_state state,
const char *device_address)
{
constsp& aps =
AudioSystem::get_audio_policy_service();
if(aps == 0) return PERMISSION_DENIED;
//转到AP去,最终由AMB处理
returnaps->setDeviceConnectionState(device, state, device_address);
}
~~~
Audio代码不厌其烦地把函数调用从这一类转移到另外一类,请直接看AMB的实现:
**AudioPolicyManagerBase.cpp**
~~~
status_tAudioPolicyManagerBase::setDeviceConnectionState(
AudioSystem::audio_devicesdevice,
AudioSystem::device_connection_statestate,
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_tAudioPolicyManagerBase::getNewDevice(audio_io_handle_t output,
bool fromCache)
{ //注意我们传入的参数,output为mHardwardOutput,fromCache为false
uint32_tdevice = 0;
//根据output找到对应的AudioOutputDescriptor,这个对象保存了一些信息
AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
if(mPhoneState == AudioSystem::MODE_IN_CALL ||
outputDesc->isUsedByStrategy(STRATEGY_PHONE))
{
device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION))
{
device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_MEDIA))
{
//应用场景是正在听歌,所以会走这个分支
device= getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_DTMF))
{
device = getDeviceForStrategy(STRATEGY_DTMF, fromCache);
}
return device;
}
~~~
策略是怎么和设备联系起来的呢?秘密就在getDeviceForStrategy中,来看:
**AudioPolicyManagerBase.cpp**
~~~
uint32_tAudioPolicyManagerBase::getDeviceForStrategy(
routing_strategystrategy, bool fromCache)
{
uint32_t device = 0;
if (fromCache){//如果为true,则直接取之前的旧值
return mDeviceForStrategy[strategy];
}
//如果fromCache为false,则需要重新计算策略所对应的设备
switch(strategy) {
caseSTRATEGY_DTMF://先处理DTMF策略的情况
if(mPhoneState != AudioSystem::MODE_IN_CALL) {
//如果不处于电话状态,则DTMF的策略和MEDIA策略对应同一个设备
device = getDeviceForStrategy(STRATEGY_MEDIA, false);
break;
}
//如果处于电话状态,则DTMF策略和PHONE策略用同一个设备
caseSTRATEGY_PHONE:
//是PHONE策略的时候,先要考虑是不是用户强制使用了某个设备,例如强制使用扬声器
switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
......
case AudioSystem::FORCE_SPEAKER:
...... //如果没有蓝牙,则选择扬声器
device = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_SPEAKER;
break;
}
break;
caseSTRATEGY_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;
}
returndevice;
}
~~~
getDeviceForStrategy是一个比较复杂的函数。它的复杂,在于选取设备时,需考虑很多情况。简单的分析仅能和读者一起领略一下它的风采,在实际工作中反复琢磨,或许才能掌握其中的奥妙。
好,getNewDevice将返回耳机的设备号0x4。下一个函数是updateDeviceForStrategy。这个函数和getNewDevice没有什么关系,因为它没用到getNewDevice的返回值。
(4)updateDeviceForStrategy
同样是来看相应的代码,如下所示:
**AudioPolicyManagerBase.cpp**
~~~
voidAudioPolicyManagerBase::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);
~~~
OK,不必讨论这位码农的功过了,现在看最后一个函数setOutputDevice。它会对新选出来的设备做如何处理呢?
(5)setOutputDevice
继续看setOutputDevice的代码,如下所示:
**AudioPolicyManagerBase.cpp**
~~~
void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_toutput,
uint32_t device,bool force, int delayMs)
{
......
//把这个请求要发送到output对应的AF工作线程中
AudioParameterparam = 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**
~~~
voidAudioPolicyService::setParameters(audio_io_handle_t ioHandle,
constString8& keyValuePairs, int delayMs)
{
//把这个请求加入到AudioCommandThread处理
mAudioCommandThread->parametersCommand((int)ioHandle,
keyValuePairs, delayMs);
}
~~~
AudioPolicyService创建时会同时创建两个线程,其中一个用于处理各种请求。现在看看它是怎么做的。
2. AudioCommandThread
AudioCommandThread有一个请求处理队列,AP负责往该队列中提交请求,而AudioCommandThread在它的线程函数threadLoop中处理这些命令。请直接看命令是如何处理的。
* * * * *
**说明**:这种通过一个队列来协调两个线程的方法,在多线程编程中非常常见,它也属于生产者/消费者模型。
* * * * *
(1)AudioCommandThread中的处理
**AudioPolicyService.cpp**
~~~
boolAudioPolicyService::AudioCommandThread::threadLoop()
{
nsecs_twaitTime = 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: {
//处理路由设置请求
ParametersData *data =(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_tAudioSystem::setParameters(audio_io_handle_t ioHandle,
constString8& keyValuePairs)
{
constsp& af = AudioSystem::get_audio_flinger();
//果然是交给AF处理,ioHandle看来一定就是工作线程索引号了
returnaf->setParameters(ioHandle, keyValuePairs);
}
~~~
离真相越来越近了,接着看代码,如下所示:
**AudioFlinger.cpp**
~~~
status_t AudioFlinger::setParameters(intioHandle,
constString8& 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 thread;
{
Mutex::Autolock _l(mLock);
//根据索引号找到对应混音线程。
thread = checkPlaybackThread_l(ioHandle);
}
//我们只有一个MixerThread,交给它处理,这又是一个命令处理队列
result = thread->setParameters(keyValuePairs);
returnresult;
}
returnBAD_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**
~~~
boolAudioFlinger::MixerThread::checkForNewParameters_l()
{
boolreconfig = 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.cppAudioStreamOutMSM72xx::setParameters()**
~~~
status_tAudioHardware::AudioStreamOutMSM72xx::setParameters(
const String8& keyValuePairs)
{
AudioParameter param = AudioParameter(keyValuePairs);
String8 key = String8(AudioParameter::keyRouting);
status_tstatus = NO_ERROR;
intdevice;
if(param.getInt(key, device) == NO_ERROR) {
mDevices = device;
//调用doRouting,mHardware就是AudioHardware对象
status = mHardware->doRouting();
param.remove(key);
}
......
returnstatus;
}
~~~
**AudioHardware.cpp**
~~~
status_t AudioHardware::doRouting()
{
Mutex::Autolock lock(mLock);
uint32_t outputDevices = mOutput->devices();
status_t ret = NO_ERROR;
intsndDevice = -1;
......
//做一些判断,最终由doAudioRouteOrMute处理
if((vr_mode_change) || (sndDevice != -1 && sndDevice != mCurSndDevice)) {
ret = doAudioRouteOrMute(sndDevice);
mCurSndDevice = sndDevice;
}
returnret;
}
~~~
**AudioHardware.cpp**
~~~
status_t AudioHardware::doAudioRouteOrMute(uint32_tdevice)
{
uint32_t rx_acdb_id = 0;
uint32_t tx_acdb_id = 0;
//只看看就行,对应硬件相关的代码,咱们就是打打酱油
returndo_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_tdevice, 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;
}
......
}
~~~
';