☰
Android 8.1多应用同时录音:AudioPolicy源码级修改方案与踩坑实践
2026/10/5 1:25:44 网站建设 项目流程

手上有台Android 8.1的开发板,之前做会议室项目时遇到一个特别棘手的场景:客户要求录屏的时候把系统内部声音也录进去,同时还要让语音助手一直开着录音监听。结果一测试就发现,系统最多只允许一个应用持有录音通道,第二个应用一调用MediaRecorder就直接抛异常。当时查了一堆资料,中文社区里讲这个问题的文章基本都停留在“Android不支持多应用同时录音”的结论上,很少有讲清楚怎么改的。后来我把AudioPolicyManager和AudioFlinger的源码翻了几天,才终于把这条路走通了。

这篇文章就围绕“Android 8.1系统下如何支持多应用同时录音”这个点,把原理、改法、验证方法和踩坑记录一次性讲透。内容适合做ROM定制、系统开发或者音视频中间层的工程师,也适合对Android音频架构感兴趣的进阶开发者。我把代码路径和关键函数都标注出来,你照着改就能复现。

1. 先搞清楚系统到底在拦什么

网上很多人说“Android默认只允许一个应用录音”,这句话只说对了一半。真正去读源码你会发现,系统并不是在AudioRecord的构造阶段就禁止第二个录音者,而是通过一套“能力协商+路由切换”的机制,让后到的录音请求无法拿到可用的录音设备。理解这一点非常重要,因为修改方案不是去“强行打开第二个设备”,而是要让系统策略层接受“同一个录音设备可以被多个Input流共享”这种状态。

1.1 音频服务端的三把锁

Android的音频架构从底往上可以分成三层:HAL层、AudioFlinger工作线程、AudioPolicy策略层。与录音冲突最直接相关的,其实是AudioPolicy这一层,它的职责是决定“哪个应用可以用哪个设备录音”,并且维护一个活动Input流列表。

第一把锁在AudioPolicyManager的getInputForAttr方法里,它会遍历mInputs中已打开的输入流,检查新的请求与已有的流是否共享同一个AudioSource。如果两个应用都用MediaRecorder.AudioSource.MIC来录音,系统会认为这是“非法的并发请求”,直接返回PERMISSION_DENIED或者DEAD_OBJECT。

第二把锁在AudioPolicyService的setInputsForDevice逻辑里,当新的Input流打开后,系统会尝试把所有与该设备相关的旧Input流切换成“暂停”状态。如果旧流的客户端没有准备处理暂停事件,就会出现录音数据断流或者应用直接崩溃。

第三把锁藏在HAL层。很多硬件平台的音频HAL只实现了“单实例录音”的驱动路径,也就是同一时间只有一个录音通道可以被真正激活。即使你在上层把权限判断全部放开,如果HAL不支持并发打开PCM capture节点,底层照样会返回BUSY。

这也就解释了为什么网上的改法千奇百怪,有的说改AudioPolicyManager就能好,有的说改了也没用——因为不同平台的HAL能力不一样。高通平台的HAL通常支持多路并发,而某些低端全志、瑞芯微平台只做了一路通路,这时候你除了改上层策略,还得改HAL的声卡路由。

1.2 为什么Android 8.1这个版本值得动手

Android 8.1(API 27)在整个音频架构演进里处于一个比较特殊的位置:它已经全面使用了AudioPolicyManager的C++实现,但还没有像Android 10那样引入AudioPolicyExecutor和更复杂的并发策略框架。也就是说,8.1的策略代码相对集中,修改点少,适合作为学习样本和ROM定制基线。

另外,8.1的AudioFlinger在录音路径上已经开始支持fast capture模式,并且在RecordTrack内部区分了standby和active状态。如果你只是想让第二个应用“能拿到录音数据”,其实并不需要动AudioFlinger,只需要在AudioPolicy层放行即可。这个“下层HAL支持、上层策略限制”的错位,恰好给了我们一个比较干净的切入点。

2. 方案核心:改AudioPolicy的“占用判定”逻辑

我最开始尝试的方式非常粗暴——直接在getInputForAttr里把PERMISSION_DENIED相关的分支注释掉。结果编译烧录后,第二个应用是可以启动了,但第一个应用的录音数据开始一卡一卡的,底层返回的帧数也有问题。后来仔细读代码才发现,按住一个Input流不放会影响设备切换策略,系统会认为新请求“抢占”了设备,于是把旧请求静音。

2.1 先定位到关键文件和函数

要修改的内容集中在frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp(在Android 8.1里是managerdefault,不是manager目录)和对应的AudioPolicyManager.h。

你需要重点关注几个函数:

  • getInputForAttr:决定是否允许打开新的输入流。
  • startInput:决定这个输入流能否真正进入运行状态。
  • setOutputsForDevice:设备路由变化后,对旧输入流的处理逻辑。
  • checkInputsVisibility:判断输入流在设备路由变化时是否可见。

第一步要改的是getInputForAttr里的策略判断。原始代码里有类似于这样的逻辑:当新请求的inputSource和已存在流的inputSource相同,并且不是AUDIO_SOURCE_VOICE_RECOGNITION之类有特殊标记的源时,返回NO_INPUT。修改的思路是把“拒绝”改成“允许并行”,但要注意保留对AUDIO_SOURCE_VOICE_COMMUNICATION这类独占源的隔离,否则通话录音会乱掉。

代码上可以这样做:

// AudioPolicyManager.cpp // 在 getInputForAttr() 中,原来类似这样的判断: if (!strcmp(attr->mTags, "allow-multiple-inputs")) { // 放行多输入,不检查旧的活跃输入流 } else { // 旧逻辑:检查是否已有相同 source 的活跃输入流 }

当然,具体到8.1的源码里不是直接用字符串判断,你需要在AudioPolicyManager中增加一个成员变量或者标志位,用于表示“当前已打开的输入流是否允许共享”。我实际用的方案是重写一个isInputUnique判断,让它永远返回true,同时把设备路由判断保留下来。

2.2 让旧输入流不被动暂停

这一步是很多人忽略的。即使getInputForAttr放行了,startInput内部执行时,会调用setOutputsForDevice,把与该输入流相关联的“非当前”流全部暂停。源码里有一段遍历mInputs并调用setInputDevice的逻辑,它会判断新的设备是否与原设备一致,如果不一致则要求旧流暂停。

要支持多应用同时录音,最优雅的方式是让设备路由变化时,不对已有的Input流做pause操作。也就是说,你要把setOutputsForDevice中针对input的处理逻辑暂时跳过,或者让其只记录日志、不真正执行暂停。

不过这里有个副作用要提前知道:如果新应用确实指定了不同的录音设备,比如一个用主麦克风、一个用耳机麦克风,系统的物理路由并不会按你的意愿自动切换,最终两个流都会采到同一个设备的数据。这个问题不解决的话,多应用录音虽然能启动,但录到的内容可能是同一个音源。

2.3 用“同时录音”而非“录音叠加”的方式来看待共享

一旦你放开startInput的限制,系统允许多个Input流同时打开同一个设备,但HAL层的行为可能是“每个流独立采一遍”,也可能是“只采一遍然后分发”。前者是各采各的,后者取决于HAL实现。对大多数平台,每个AudioRecord最终都会open一个pcm节点,所以多个流共享麦克风数据是“同时打开、各自拷贝”的方式。

实际操作中,你还需要检查AudioFlinger的RecordThread是不是真的为每个客户端创建了独立的线程。8.1的实现里,RecordThread是以“设备+采样率+通道数”为key来复用的,如果两个流的采样率不同,会生成两个不同的RecordThread,这样会导致其中一个流用重采样器从另一个流拉数据。这个场景下,HAL必须支持多路并发,否则就会出现openInput失败。

3. 从应用层验证修改是否生效

系统层面改完之后,不能光靠“应用不崩溃”来判断。我建议你写一个非常简单的验证App,同时使用两个不同的录音通道来测试:一个用MediaRecorder录音到文件,另一个用AudioRecord直接读取PCM数据。

3.1 验证App的设计思路

验证App的核心逻辑很简单:

  • 界面放两个按钮,一个“启动录音A”,一个“启动录音B”。
  • 录音A 使用MediaRecorder,输出到/sdcard/test_a.m4a。
  • 录音B 使用AudioRecord,采样率设为44100,单声道,读取原始PCM。
  • 录音B 每读取一帧,通过AudioTrack实时播放出来,这样你能直观听到第二路是否真的有数据。

在未修改的8.1系统上,点击“启动录音B”会立即失败,Logcat里能看到startInput返回DEAD_OBJECT或者AudioRecord构造时抛出ERROR_INVALID_OPERATION。修改完成后,两个录音应该能同时启动,并且录音B能听到与录音A相同的声音。

3.2 AndroidManifest和权限处理

多应用同时录音还有一个隐藏的坑——动态权限。Android 8.1里录音权限是dangerous级别,必须在运行时申请。你只能在主线程申请一次权限,然后两个录音组件共用同一个权限。但如果你同时开了“录音A”和“录音B”,系统权限弹窗可能只出现一次,因为RECORD_AUDIO已经授权过了。这没有问题。

需要额外注意的是,如果你的验证App使用了MediaRecorder,它在启动时会去AudioService注册一个recordingCallback,并且在状态机里标记当前包名为“活跃录音者”。这个状态机也可能会造成干扰。我在调试时发现,即使底层放行了,AudioService的getRecordingAppId在某些场景下会强制kill掉后到的录音客户端。这个机制主要在AudioService的onRecordingConfigurationChanged里,你需要把setActiveRecordingConfigurations的更新逻辑放宽。

3.3 先跑通“双AudioRecord”再测“MediaRecorder+AudioRecord”

我建议你把验证分成两个阶段。第一阶段,两个客户端都用AudioRecord,并且采样率、通道数完全一致,这样系统会尽量复用同一个RecordThread,最容易成功。第二阶段再测MediaRecorder和AudioRecord混合。因为MediaRecorder内部走的是AudioRecord的上层封装,但会有自己的缓冲区和状态管理,排查问题时更难定位。

如果你发现MediaRecorder可以启动,但录出来的文件一直是0字节,那大概率是MediaRecorder的编码器没有收到足够的数据。这不一定是你改策略改错了,可能是底层的AudioFlinger在共享模式下没有正确向各个客户端分发数据。

4. 再深入一层:让两个应用各自独立录音

上面的修改方案解决的是“同一个进程里两个客户端可以同时录”。但你的标题是“多应用同时录音”,意味着两个不同的APK都要能各自打开麦克风。这两个需求在系统层面没有本质差别——因为对AudioPolicy来说,它只认客户端uid和pid,不关心客户端是不是在同一个进程。所以同一套修改方式对多应用场景同样适用,只需要额外注意一下权限策略。

4.1 跨应用录音的权限隔离

Android的录音权限是基于应用uid来判定的,A应用持有了RECORD_AUDIO,B应用也持有,那么两者都能发起录音。系统不会因为A已经在录音就拒绝B的权限申请,但AudioPolicy的单路策略会拒绝B的startInput。

你改完AudioPolicyManager后,跨应用录音会正常,但还要处理AudioService里的“录音独占”逻辑。AudioService中维护了一个mRecordActive的计数,它会在第一个应用开始录音时把系统UI的麦克风图标点亮,并在最后一个应用停止录音时关闭。如果这里的计数没有对“多路同时录音”做适配,可能出现图标状态错乱,但不会影响录音数据的实际传输。

4.2 两个应用录到的“声音”是什么

在单麦克风设备上,两个应用同时录音,录到的内容基本一样。但在某些手机上,麦克风硬件本身会有“降噪”和“回声消除”的差异,不同应用设置的AudioSource不同,底层DSP处理的路径也不同。比如A应用用MIC,B应用用VOICE_RECOGNITION,两者可能触发不同的音频算法通道。所以如果你发现两路录音的噪声底和音质不一致,先检查应用各自指定的AudioSource。

另外,如果你的设备存在通话降噪(ANC)功能,那么当语音通话应用启动时,底层的DSP会强制将麦克风数组切换到“通话模式”,此时其他应用的录音数据会带上明显的窄带滤波特征。这在多应用录音场景里是个不太容易排查的坑,我实际遇到过的现象是:第二路录音波形和第一路明显不同,但单独录又正常。

4.3 验证两路数据的同步性

多应用同时录音时,很多项目还需要保证两路数据的起始时间和对齐程度。默认情况下,每个AudioRecord客户端拿到的第一帧数据对应的“绝对帧计数”未必相同,因为AudioFlinger在不同客户端之间的启动延迟可能相差几十毫秒。如果你要做双通道分析,比如声源定位或者语音增强,那必须在应用层做时间戳对齐。最简单的方案是两个应用同时获取AudioRecord.getTimestamp(),然后以系统elapsedRealtime为基准做偏移校准。系统本身不会为你保证实时对齐。

5. 常见问题排查与调试命令

我把开发和移植过程中遇到的典型问题整理成一张速查表,这些问题按照出现概率排序,你可以对照排查。

现象直接原因排查路径
第二个应用启动录音立即抛异常AudioPolicyManager的getInputForAttr拦截用logcat AudioPolicyManager:V查看拒绝原因
第二个应用可以启动但录到全零数据HAL层不支持并发打开PCM节点用tinypcminfo检查声卡设备和PCM通道
第一个应用录音出现静音或断流setOutputsForDevice暂停了旧输入流增加日志确认是否有pause/standby事件
系统UI的麦克风图标不消失AudioService的录音计数状态错乱dumpsys audio查看Record active状态
两个应用同时录音,但声音一个有、一个无AudioSource不同导致DSP路径不同统一使用VOICE_RECOGNITION或MIC测试
录音文件时长正常但播放速度翻倍AudioRecord采样率与编码器采样率不一致检查两个应用的采样率与AudioFormat是否做到匹配
系统卡死或重启HAL层锁冲突导致驱动异常抓取/data/anr/下的 traces,结合内核日志定位

5.1 高频调试命令

修改系统音频策略后,你几乎离不开这几条命令:

# 查看当前活动的录音输入流 adb shell dumpsys media.audio_policy | grep -A 20 "Inputs" # 查看AudioService里的录音状态机 adb shell dumpsys audio | grep -B 2 -A 10 "Records" # 查看底层声卡设备是否被占用 adb shell cat /proc/asound/card0/pcm0c/sub0/status

其中dumpsys media.audio_policy里能看到每个Input流的handle、device、samplingRate和active状态。如果你改完策略后,两个应用都启动录音了,但这里只显示一个Input流,说明有一个客户端的请求还是被拦在了更上层。如果这里有两个流,但底层声卡状态显示closed,那问题就在HAL或AudioFlinger的打开逻辑上。

5.2 实测过程中最容易踩的三个坑

第一个坑是只改了AudioPolicyManager却没有同步改AudioPolicyService的binder接口。8.1里AudioPolicyService是独立进程,它通过binder调用AudioPolicyManager。如果你在so库层改了逻辑,但服务进程没有重启,就不会生效。所以每次修改后要做adb push并killall audioserver,或者直接重启系统。

第二个坑是HAL层的“并发能力”并没有一个标准开关,有些平台的audio_policy_configuration.xml里已经定义了多个input设备节点,但HAL库实际只实现了一个。这种情况哪怕上层策略全放行,openInput照样返回-EBUSY。你在购买开发板或者选型时,一定要先问清楚HAL是否支持多路cature,或者直接读hardware/audio.h里open_input_stream的注释。

第三个坑是改完多应用录音后,原来正常的“通话录音”和“语音识别”反而变差了。这是因为你修改了Input流的共享逻辑后,系统不知道当前是“通话优先”还是“普通录音优先”,导致路由判定时把通话用的VOICE_COMMUNICATION和普通的MIC混在一起,这会让底层的AEC(回声消除)模块失效,听筒回声变大。解决方式是在策略层保留AUDIO_SOURCE_VOICE_COMMUNICATION的独占标识,只对普通录音源开放共享。

6. 修改完之后的持续性影响与应对

多说一句,这个改动不是“一次性编译完就结束”,它会引出几个上游的适配问题。你在量产项目里如果采用了类似方案,需要提前和上层音频服务、音频策略配置和HAL供应商都做好对齐,不然很容易出现“系统层支持了,但上层策略和底层驱动互相打架”的局面。

6.1 对AudioService和旁路应用的影响

Android 8.1的AudioService里有一个使用AppOpsManager来检查录音权限并且追踪活跃录音应用的功能。默认状态下,系统假设同一时间只有一个活跃录音应用,并用它来显示状态栏麦克风图标、通知用户“当前有应用正在录音”。当你支持多路录音后,这里需要改成“记录集合”而不是“记录单值”。否则,第一个应用停止录音后,状态栏图标直接消失,哪怕第二个应用还在录。

这部分改动在AudioService的AudioRecordingCallback和MediaRecorder的isRecording状态管理里,代码量不大,但逻辑分支繁多。最简单的方式是让AudioService不要只跟踪“最后一个活跃录音者”,而是维护一个ArrayMap<IBinder, RecordingState>,每次回调时按现有状态重建“当前活跃列表”。

6.2 对HAL层数据通路的影响

如果后续你遇到“两个应用同时录音时,音频数据偶发出现爆音”,十有八九是HAL层的DSP带宽不够。以我手里那台高通SDM450平台为例,单路录音时音频DSP占用非常低,但两路并发后,如果DSP固件里没有为多路并发预留资源,就会出现周期性的XRUN。这个不是软件改Application能解决的,要么换HAL版本,要么在HAL层做一个“录音共享分发”的抽象:只打开一个底层PCM节点,然后在HAL层把数据复制到多个stream。

这种“HAL层分发”的实现方式,会把并发能力从硬件限制中解放出来,但会增加录音延迟,且需要自行管理各路的缓冲水位。适合对低延迟没有苛刻要求的项目,比如会议记录、语音笔记、安防监控这类场景。

7. 一个更优雅的替代方案:AudioMixer虚拟设备

如果你的产品不需要“多个应用同时占用真实麦克风”,而是希望“多个应用同时获取同一路真实录音数据”,还有一个更干净的做法——在HAL层之上增加一个虚拟录音设备。

这个方案我在做车机语音助手项目时用过。思路是把真实麦克风的数据通过AudioFlinger的MixerThread混音后,送进一个“虚拟输入流”的设备节点。然后在audio_policy_configuration.xml里注册一个新的attachedDevices,给它分配一个特定的address,并让各个需要录音的应用通过指定AudioRecord的device参数来打开这个虚拟设备。

这样做的优点是完全不用修改AudioPolicyManager的并发逻辑,因为虚拟设备本身可以同时服务于多个客户端。缺点是实现难度高,需要在HAL层写一套数据回环逻辑,还得处理采样率转换。对大多数ROM定制团队来说,修改AudioPolicyManager的共享策略已经是性价比最高的方案了。

从开发量、稳定性和适配范围三个维度看,我个人的建议是:

  • 如果只做演示或内部项目,改AudioPolicyManager放行共享,最快,风险可控。
  • 如果要做量产,并且你有HAL源码,优先考虑HAL层多实例分发,因为系统上层改得越少,后续升级越省心。
  • 如果只是想验证“多应用同时录音”对业务的价值,可以先不改系统,用外部USB声卡插两个录音设备,各应用指定不同声卡路由,同样能达到“同时录多路”的效果。

写在最后的个人体会

做这个修改让我最深的感受是:Android的音频系统并不是一个纯粹的技术架构,它是一套充满“商业取舍”的策略集合。默认的录音独占规则,与其说是技术限制,不如说是为了保护用户体验和硬件可靠性而做的保守选择。真正理解AudioPolicyManager的工作方式后,你会发现修改它并不难,难的是想清楚“放开之后,你愿意承担哪些后果”。有些并发场景会导致回声、爆音、采样率冲突,这些问题的责任不再归咎于“系统默认行为”,而是你的策略设计。所以每一次放行之前,都值得先问一句:“这条路并发打开后,底层驱动的行为和上层业务的预期是否真的对齐了?” 想清楚这一点,比改十行代码更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询