💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
路由这个词听上去很专业,其实它的目的很简单,就是为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<AudioSystem::audio_devices>(device), static_cast<AudioSystem::device_connection_state>(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<IAudioPolicyService>& 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<IAudioFlinger>& 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<ThreadBase> 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; } ...... } ~~~