失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Android Framework 音频子系统(11)耳麦插拔之声音通道切换

Android Framework 音频子系统(11)耳麦插拔之声音通道切换

时间:2022-03-06 22:54:35

相关推荐

Android Framework 音频子系统(11)耳麦插拔之声音通道切换

该系列文章总纲链接:专题分纲目录 Android Framework 音频子系统​​​​​​​

本章关键点总结 & 说明:

本章节主要关注➕ 以上思维导图左上耳麦插拔部分中的 声音通道切换 部分即可。主要说明了声道切换的原理和声道切换的流程分析。

1 耳麦插拔 声音通道切换 原理说明

1.1 切换声音通道情景分析

这里分成两种情况进行分析,一种是USB声卡的插入,另一种是primary设备上插入耳麦。

USB声卡插入:

从配置文件audio_policy中可以找到usb中对应的modle,一定有outputs包含usb_accessory、usb_device这类output。插上USB声卡后会创建output和对应的playbackthread。(usb_accessory、usb_device 这两个output各对应一个)。之前与板载声卡建立联系的playBackThread线程,要切换成USB创建的playBackThread,APP的AudioTrack从原来的playbackthread/output模式切换到新的playbackthread/output。在新的playBackThread中,每个APP创建对应的Track。在output中选择Device,重新做一些设置,决定从耳机还是喇叭播放声音。

在primary中插上耳机:

无需创建output和playbackthread,因为这种情况下他们所涉及的线程并没有改变,所以不需要重新去创建output与playBackThread。无需切换output。在原来的output中选择Device(Headset)。

流程上相差不大,首先需要判断一下,是否需要创建output,播放的声音是否需要新的线程进行处理,最后在output中选择device。

1.2 切换声音通道 核心三步骤

硬件插上耳麦发生中断, 在中断处理程序中设置声卡让声音从耳机中输出,驱动程序上报音频拔插事件,该事件为某个device插入或拔出,接下来把输出通道的选择权交给android系统,由Android系统进行声音通道的切换操作。Android系统切换声音通道的

3个核心步骤如下:

@1checkOutputsForDevice

针对该device, 打开新的output, 创建新的playbackthread。从audio_policy.conf中确定"本该有多少个output"可以支持它,mOutputs表示"已经打开的output",两者对比即可确定"尚未打开的output"

@2checkOutputForAllStrategies / checkOutputForStrategy

对所有的strategy分组声音,判断是否需要迁移到新的output, 如果需要则迁移对应Track到新的output,这里涉及2个判断

@@2.1 判断是否需要迁移:

对于该strategy, 得到它的oldDevice, 进而得到它的outputs (srcOutputs);对于该strategy, 得到它的newDevice, 进而得到它的outputs (dstOutputs);如果这2个srcOutputs、dstOutputs不相同, 表示需要迁移

@@2.2 如果迁移:

把对应的Track设置为invalidate状态即可,App写AudioTrack时发现它是invalidate状态, 就会重新创建新的Track

audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);

@3 getNewOutputDevice/setOutputDevice这需要操作HAL层

2 耳麦插拔声音通道切换 源码解读

这里从AudioService中的onSetWiredDeviceConnectionState开始分析,代码实现如下:

private void onSetWiredDeviceConnectionState(int device, int state, String name){synchronized (mConnectedDevices) {//...//关键点1:声道切换入口handleDeviceConnection((state == 1), device, (isUsb ? name : ""));if (state != 0) {//...if ((device & mSafeMediaVolumeDevices) != 0) {sendMsg(mAudioHandler,MSG_CHECK_MUSIC_ACTIVE,SENDMSG_REPLACE,0,0,null,MUSIC_ACTIVE_POLL_PERIOD_MS);}//...} else {//...}if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {//关键点2:通过AMS上报intentsendDeviceConnectionIntent(device, state, name);}}}

之前我们关注这里的两个关键点:声道切换入口handleDeviceConnection和 给AMS上报intent的sendDeviceConnectionIntent。本章节我们从handleDeviceConnection开始分析,Java层AudioService的handleDeviceConnection方法最终可以直接调用到Native层AudioPolicyManager的setDeviceConnectionStateInt,setDeviceConnectionStateInt代码实现如下:

status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t device,audio_policy_dev_state_t state,const char *device_address){if (!audio_is_output_device(device) && !audio_is_input_device(device)) return BAD_VALUE;sp<DeviceDescriptor> devDesc = getDeviceDescriptor(device, device_address);// handle output devices/*判断上报的是否为output_device*/if (audio_is_output_device(device)) {SortedVector <audio_io_handle_t> outputs;ssize_t index = mAvailableOutputDevices.indexOf(devDesc);mPreviousOutputs = mOutputs;switch (state){// handle output device connectioncase AUDIO_POLICY_DEVICE_STATE_AVAILABLE: {//代表存在直接返回,否则代表为新添加的if (index >= 0) {return INVALID_OPERATION;}//添加到可用设备index = mAvailableOutputDevices.add(devDesc);if (index >= 0) {//根据device在可用的设备列表中查找sp<HwModule> module = getModuleForDevice(device);if (module == 0) {mAvailableOutputDevices.remove(devDesc);return INVALID_OPERATION;}mAvailableOutputDevices[index]->mId = nextUniqueId();mAvailableOutputDevices[index]->mModule = module;} else {return NO_MEMORY;}//关键点1:针对该device, 打开新的output, 创建新的playbackthread.if (checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress) != NO_ERROR) {mAvailableOutputDevices.remove(devDesc);return INVALID_OPERATION;}// Set connect to HALsAudioParameter param = AudioParameter(devDesc->mAddress);param.addInt(String8(AUDIO_PARAMETER_DEVICE_CONNECT), device);mpClientInterface->setParameters(AUDIO_IO_HANDLE_NONE, param.toString());} break;// handle output device disconnectioncase AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE: {// Set Disconnect to HALsAudioParameter param = AudioParameter(devDesc->mAddress);param.addInt(String8(AUDIO_PARAMETER_DEVICE_DISCONNECT), device);mpClientInterface->setParameters(AUDIO_IO_HANDLE_NONE, param.toString());// remove device from available output devicesmAvailableOutputDevices.remove(devDesc);checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress);} break;default:ALOGE("setDeviceConnectionState() invalid state: %x", state);return BAD_VALUE;}//.../*关键点2:对所有的strategy分组声音,判断是否需要迁移*到新的output, 如果需要则迁移对应Track到新的output*/checkOutputForAllStrategies();//...for (size_t i = 0; i < mOutputs.size(); i++) {audio_io_handle_t output = mOutputs.keyAt(i);if ((mPhoneState != AUDIO_MODE_IN_CALL) || (output != mPrimaryOutput)) {audio_devices_t newDevice = getNewOutputDevice(mOutputs.keyAt(i),true /*fromCache*/);bool force = !mOutputs.valueAt(i)->isDuplicated()&& (!deviceDistinguishesOnAddress(device)// always force when disconnecting (a non-duplicated device)|| (state == AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE));setOutputDevice(output, newDevice, force, 0);}}mpClientInterface->onAudioPortListUpdate();return NO_ERROR;} // end if is output device//... Audio input 处理return BAD_VALUE;}

接下来主要针对checkOutputsForDevice方法和checkOutputForAllStrategies方法进行分析(标志位变换),之后对 数据写入部分(AudioTrack的write函数)进行分析。

2.1checkOutputsForDevice分析

checkOutputsForDevice的代码实现如下:

status_t AudioPolicyManager::checkOutputsForDevice(const sp<DeviceDescriptor> devDesc,audio_policy_dev_state_t state,SortedVector<audio_io_handle_t>& outputs,const String8 address){audio_devices_t device = devDesc->mDeviceType;//...if (state == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) {//...for (ssize_t profile_index = 0; profile_index < (ssize_t)profiles.size(); profile_index++) {sp<IOProfile> profile = profiles[profile_index];// nothing to do if one output is already opened for this profile//...if (j != outputs.size()) {continue;}//...status_t status = mpClientInterface->openOutput(profile->mModule->mHandle,&output,&config,&desc->mDevice,address,&desc->mLatency,desc->mFlags);if (status == NO_ERROR) {//...if (output != AUDIO_IO_HANDLE_NONE) {addOutput(output, desc);if (deviceDistinguishesOnAddress(device) && address != "0") {//...} else if ((desc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT) == 0) {//...// open a duplicating output thread for the new output and the primary outputduplicatedOutput = mpClientInterface->openDuplicateOutput(output,mPrimaryOutput);//...}}} else {output = AUDIO_IO_HANDLE_NONE;}//...}//...} else { // Disconnect//...}return NO_ERROR;}

配置文件audio_policy.conf中 每个output都会被描绘成一个profile,即checkOutputsForDevice会检测所有的profile(output),查找每个profile是否都存在对应的线程,如果没有则进行创建。

2.2checkOutputForAllStrategies分析

checkOutputForAllStrategies的代码实现如下:

void AudioPolicyManager::checkOutputForAllStrategies(){if (mForceUse[AUDIO_POLICY_FORCE_FOR_SYSTEM] == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED)checkOutputForStrategy(STRATEGY_ENFORCED_AUDIBLE);checkOutputForStrategy(STRATEGY_PHONE);if (mForceUse[AUDIO_POLICY_FORCE_FOR_SYSTEM] != AUDIO_POLICY_FORCE_SYSTEM_ENFORCED)checkOutputForStrategy(STRATEGY_ENFORCED_AUDIBLE);checkOutputForStrategy(STRATEGY_SONIFICATION);checkOutputForStrategy(STRATEGY_SONIFICATION_RESPECTFUL);checkOutputForStrategy(STRATEGY_ACCESSIBILITY);checkOutputForStrategy(STRATEGY_MEDIA);checkOutputForStrategy(STRATEGY_DTMF);checkOutputForStrategy(STRATEGY_REROUTING);}

这里详细分析下checkOutputForStrategy,代码实现如下:

void AudioPolicyManager::checkOutputForStrategy(routing_strategy strategy){/**对于该strategy, 得到它的oldDevice, 进而得到它的outputs (srcOutputs);*对于该strategy, 得到它的newDevice, 进而得到它的outputs (dstOutputs);*/audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);// also take into account external policy-related changes: add all outputs which are// associated with policies in the "before" and "after" output vectorsfor (size_t i = 0 ; i < mPreviousOutputs.size() ; i++) {const sp<AudioOutputDescriptor> desc = mPreviousOutputs.valueAt(i);if (desc != 0 && desc->mPolicyMix != NULL) {srcOutputs.add(desc->mIoHandle);}}for (size_t i = 0 ; i < mOutputs.size() ; i++) {const sp<AudioOutputDescriptor> desc = mOutputs.valueAt(i);if (desc != 0 && desc->mPolicyMix != NULL) {dstOutputs.add(desc->mIoHandle);}}//如果这2个srcOutputs、dstOutputs不相同, 表示需要迁移if (!vectorsEqual(srcOutputs,dstOutputs)) {// mute strategy while moving tracks from one output to anotherfor (size_t i = 0; i < srcOutputs.size(); i++) {sp<AudioOutputDescriptor> desc = mOutputs.valueFor(srcOutputs[i]);if (desc->isStrategyActive(strategy)) {setStrategyMute(strategy, true, srcOutputs[i]);setStrategyMute(strategy, false, srcOutputs[i], MUTE_TIME_MS, newDevice);}}// Move effects associated to this strategy from previous output to new outputif (strategy == STRATEGY_MEDIA) {audio_io_handle_t fxOutput = selectOutputForEffects(dstOutputs);SortedVector<audio_io_handle_t> moved;for (size_t i = 0; i < mEffects.size(); i++) {sp<EffectDescriptor> effectDesc = mEffects.valueAt(i);if (effectDesc->mSession == AUDIO_SESSION_OUTPUT_MIX &&effectDesc->mIo != fxOutput) {if (moved.indexOf(effectDesc->mIo) < 0) {mpClientInterface->moveEffects(AUDIO_SESSION_OUTPUT_MIX, effectDesc->mIo,fxOutput);moved.add(effectDesc->mIo);}effectDesc->mIo = fxOutput;}}}// Move tracks associated to this strategy from previous output to new outputfor (int i = 0; i < AUDIO_STREAM_CNT; i++) {if (i == AUDIO_STREAM_PATCH) {continue;}if (getStrategy((audio_stream_type_t)i) == strategy) {/** 把对应的Track设置为invalidate状态即可,* App写AudioTrack时发现它是invalidate状态,* 就会重新创建新的Track*/mpClientInterface->invalidateStream((audio_stream_type_t)i);}}}}

最后的操作invalidateStream是AudioFlinger的invalidateStream操作,代码实现如下:

status_t AudioFlinger::invalidateStream(audio_stream_type_t stream){Mutex::Autolock _l(mLock);for (size_t i = 0; i < mPlaybackThreads.size(); i++) {PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();thread->invalidateTracks(stream);}return NO_ERROR;}

这里 thread的invalidateTracks代码实现如下:

void AudioFlinger::PlaybackThread::invalidateTracks(audio_stream_type_t streamType){Mutex::Autolock _l(mLock);size_t size = mTracks.size();for (size_t i = 0; i < size; i++) {sp<Track> t = mTracks[i];if (t->streamType() == streamType) {t->invalidate();}}}

这里 Track的invalidate 代码实现如下:

void AudioFlinger::PlaybackThread::Track::invalidate(){// FIXME should use proxy, and needs workaudio_track_cblk_t* cblk = mCblk;//设置标志位android_atomic_or(CBLK_INVALID, &cblk->mFlags);android_atomic_release_store(0x40000000, &cblk->mFutex);// client is not in server, so FUTEX_WAKE is needed instead of FUTEX_WAKE_PRIVATE(void) syscall(__NR_futex, &cblk->mFutex, FUTEX_WAKE, INT_MAX);mIsInvalid = true;}

实际上这个操作android_atomic_or(CBLK_INVALID, &cblk->mFlags);就是设置一个标志位,那么设置标志位之后,在APP重新写入数据就可以检测到标志位的变化,就会进行相应的操作。接下来就以APP的AudioTrack 写入数据为契机进行分析。

2.3 切换后的数据写入

根据前面的分析,执行Java层AudioTrack的write方法,一定会调用到Native层AudioTrack的obtainBuffer方法,因此这里直接从AudioTrack的obtainBuffer方法开始分析,代码如下:

status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,struct timespec *elapsed, size_t *nonContig){//...do {//...newSequence = mSequence;// did previous obtainBuffer() fail due to media server death or voluntary invalidation?if (status == DEAD_OBJECT) {// re-create track, unless someone else has already done soif (newSequence == oldSequence) {status = restoreTrack_l("obtainBuffer");//...}}//...status = proxy->obtainBuffer(&buffer, requested, elapsed);} while ((status == DEAD_OBJECT) && (tryCounter-- > 0));//...return status;}

@1restoreTrack_l()分析

这里的restoreTrack_l()函数代码实现如下:

status_t AudioTrack::restoreTrack_l(const char *from){//...result = createTrack_l();//...return result;}

根据前面章节的分析,createTrack_l在这里就重新创建了Track。

@2proxy的obtainBuffer分析

这里专注分析proxy的obtainBuffer的实现,代码如下:

status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,struct timespec *elapsed){//...for (;;) {int32_t flags = android_atomic_and(~CBLK_INTERRUPT, &cblk->mFlags);// check for track invalidation by server, or server death detectionif (flags & CBLK_INVALID) {ALOGV("Track invalidated");status = DEAD_OBJECT;//被设置成CBLK_INVALIDgoto end;}//...}end://...return status;}

这里将根据标识位CBLK_INVALID将返回的Status设置为DEAD_OBJECT。

简单总结下:写入数据的过程中干掉了 旧的Track,创建了新的Track。

如果觉得《Android Framework 音频子系统(11)耳麦插拔之声音通道切换》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。