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

news/2024/11/29 4:39:00/

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


本章关键点总结 & 说明:

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


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

1.1 切换声音通道情景分析

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

USB声卡插入:

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

在primary中插上耳机:

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

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

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

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

3个核心步骤如下:

@1 checkOutputsForDevice

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

@2 checkOutputForAllStrategies / checkOutputForStrategy

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

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

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

@@2.2 如果迁移:

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

  1. audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);
  2. audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);
  3. SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);
  4. 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.1 checkOutputsForDevice分析

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.2 checkOutputForAllStrategies分析

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;
}

@1 restoreTrack_l()分析

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

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

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

@2 proxy的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。

 


http://www.ppmy.cn/news/380325.html

相关文章

Android Framework 音频子系统(09)耳麦插拔之流程分析

该系列文章总纲链接&#xff1a;专题分纲目录 Android Framework 音频子系统​​​​​​​ 本章关键点总结 & 说明&#xff1a; 本章节主要关注➕ 以上思维导图左上 耳麦插拔 部分 即可。本章节主要分析耳麦插拔流程&#xff0c;耳机发生插拔后&#xff0c;android是如何在…

openwrt+Linkit7688+wm8960:粗略实现wm8960耳麦和喇叭音频输出

引言&#xff1a; 本文只是介绍怎么使用linkit7688开发板上的wm8960的输出喇叭声音和耳机声音&#xff0c;主要是怎么输出喇叭声音&#xff0c;默认的音频驱动是只有耳麦的音频输出而没有喇叭音频输出的。别人指导我&#xff0c;有点成果也给大家参考一下。 感谢huangkj-hena…

耳麦浅度DIY

作为一个伪音乐爱好者&#xff0c;自己DIY一个耳麦肯定是必须的。 抽时间做一次非深度DIY。 在马云爸爸的淘宝上选了些东西&#xff0c; 喇叭单元&#xff0c;做工还可以&#xff0c;买不起高端货&#xff0c;150左右RMB凑合用吧&#xff1a; 对于一个小白来说&#xff0c;焊…

【转载】Android音频(7)——项目实战——耳麦插拔

Android音频(7)——项目实战——耳麦插拔 7.4.3 声音路由切换实例分析 深入理解Android&#xff1a;卷1 看云 一、驱动程序上报耳麦拔插事件 1. 在有些Android版本中并不会在状态栏上显示耳麦图标。切换声道也不在系统中实现&#xff0c;而是在驱动中实现的。 2. headset …

8.5.1耳麦拔插_驱动程序上报耳麦拔插事件

目录 分析+概念 驱动框架​ 输入系统流程 编写驱动程序如下 switch dev驱动的流程

USB耳麦_从无到有(一)

我想做一个基于STM32F1芯片的USB耳麦。不确定能不能搞出来&#xff0c;简单记录一下这个过程。 首先&#xff0c;这个项目需要先学USB&#xff0c;再学音频。 那就先学USB。 USB 我对USB有一点了解&#xff0c;但忘得差不多了。我复习一下基本的USB协议&#xff0c;再开始学音…

08.音频系统:第005课_项目实战1_耳麦拔插:第001节_驱动程序上报耳麦拔插事件

在接下来的几个小节中&#xff0c;会讲解几个音频的项目&#xff0c;现在想讲解第一个项目&#xff1a;对 耳麦拔插事件的处理&#xff0c;一起分为四个小节左右&#xff1a; 5.1 驱动程序上报耳麦拔插事件 5.2 在状态栏显示耳麦图标 5.3 耳麦拔插事件调用流程分析 5.4 切换声音…

8.5.3耳麦拔插事件调用流程分析

目录 回顾 input系统方法 回顾 在前面的小节中,我们编写了一个驱动程序,模拟耳机的插拔事件,其可以上报耳机的拔插事件,并且修改了android的源代码,可以根据耳机的拔插事件,在状态栏上现实或者消除耳麦的图标,这节视频我们讲解耳麦插拔事件导致的程序调用流程。 …