该系列文章总纲链接:专题分纲目录 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。

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

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

    Android音频(7)--项目实战--耳麦插拔 7.4.3 声音路由切换实例分析 · 深入理解Android:卷1 · 看云 一.驱动程序上报耳麦拔插事件 1. 在有些Android版本中并不会在状 ...

  2. Android Framework 电源子系统(01)PowerManagerService启动分析

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ 以上思维导图即可.该章节 主要是 对 PMS 启动的分析,从sy ...

  3. Android Framework 电源子系统(05)核心方法updatePowerStateLocked分析-3 更新屏保  发送通知  更新wakelock

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ updatePowerStateLocked 方法中 更新屏保 ...

  4. Android Framework 电源子系统(04)核心方法updatePowerStateLocked分析-2 循环处理  更新显示设备状态

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ updatePowerStateLocked 方法中 循环处理 ...

  5. Android Framework 窗口子系统 (08)窗口动画之动画系统框架

    该系列文章总纲链接:专题分纲目录 Android Framework 窗口子系统 本章关键点总结 & 说明: 导图是不断迭代的,这里主要关注➕ 左上角 Android 窗口动画系统部分(因为导 ...

  6. 笔记本插拔电源声音怎么关_在笔记本电脑运行时将其拔出再插回电源是否安全?

    笔记本插拔电源声音怎么关 Our laptops allow us to be more mobile than ever before, but still always in search of ...

  7. windows外接显示器,息屏后老是发出USB插拔的声音

    在将电脑息屏后,电脑总是发出USB插拔的声音,尝试了很多方法以后,最终解决方案是在显示器(不是电脑设置)的菜单栏关闭了自动检测功能,之后电脑就不会异响了,如果你也遇到这个问题,欢迎评论分享.

  8. android u盘挂载监听,Android SD卡及U盘插拔状态监听及内容读取

    本篇是通过系统方法来对sd卡及U盘插拔监听及数据获取,Android盒子端开发,有系统权限,当然,这个比较简单,知道具体方法,可以通过反射来实现. 先贴上效果图: 获取外置存储设备并监听插拔状态 获取 ...

  9. Android SD卡及U盘插拔状态监听和内容读取

    本篇是通过系统方法来对sd卡及U盘插拔监听及数据获取,Android盒子端开发,有系统权限,当然,这个比较简单,知道具体方法,可以通过反射来实现. 先贴上效果图: 获取外置存储设备并监听插拔状态 获取 ...

  10. 【Android 高性能音频】AAudio 音频流 样本缓冲 相关配置 ( 通道数 | 样本格式 | 帧缓冲 | 采样率 | 每帧样本数 == 通道数 )

    文章目录 I . AAudio 音频流创建流程 II . AAudio 音频流构建器 设置 通道数 AAudioStreamBuilder_setChannelCount III . AAudio 音 ...

最新文章

  1. 【Java】数据结构---二叉树 详解
  2. 大二上学期软件工程概论学习进度表(第十二周)
  3. 开源!100 页机器学习教程全面开放,附完整代码
  4. 智能推荐:“相关性搜索”只给你最想要的
  5. python的使用说明_Python 的基本使用说明
  6. Ethercat解析(四)之搭建RTAI实时内核(Ubuntu12.04)
  7. 模拟轮盘抽奖游戏 python_“吃鸡”4位美女在现实中,穿上游戏的“新军需”,这身材绝了...
  8. 递归 非递归 遍历二叉树
  9. linux svn下载文件到本地
  10. Log4j日志等级设置详解
  11. 主板检测卡c5_电脑主板检测卡代码大全
  12. 第12期 《博观而约取,厚积而薄发》6月刊
  13. RTU和DTU的区别是什么?
  14. HTMLParser(一个比较流行的html代码解析、处理开源项目)学习,总结
  15. 【Kickstart】2019 Round A - Parcels
  16. 安卓APP在运行时对全局进行网络状态监听的实现
  17. Android Activity中实现Fragment切换功能效果
  18. 转区系统开放艾欧尼亚转入服务器,【英雄联盟】转区系统开放艾欧尼亚转入服务...
  19. 数据结构与算法——RB树简介
  20. linux下/proc/pid/maps和pmap命令详解

热门文章

  1. EBU5502 Database Coursework Specifications
  2. strut1和strut2的区别
  3. Strut2的工作流程
  4. OSU双足步行机器人 Cassie利用强化学习站立的源码实现
  5. 【杂记】各项异性滤波简介Anisotropic Filtering(AF)
  6. J - 山峰和山谷 Ridges and Valleys
  7. linux adb 驱动 home,Ubuntu下adb驱动问题
  8. matlab函数性质探讨答案,matlab函数性质探讨
  9. AlarmClock slow alarm Alarm
  10. 小钛掐指一算,今年的尖货市场不简单 | 活动预告