14、音频录制通路:RecordThread详解、录音数据流向、回声消除(AEC)集成、录音效果处理

各位同学,今天我们来聊聊音频录制这条链路。说实话,很多做音频开发的朋友,对播放通路如数家珍,但一提到录音就有点含糊。其实录音通路比播放要复杂得多——你想想看,播放只是把数据送出去,而录音要处理来自麦克风的原始信号,还要跟播放数据做各种“斗争”。

我个人习惯把录音通路比作一个“反向的播放管道”,但这里面坑不少。咱们今天就把RecordThread、数据流向、AEC集成、效果处理这几个硬骨头啃下来。

14.1 RecordThread:录音的“心脏”

RecordThread是AudioFlinger里专门负责录音的线程。它跟PlaybackThread是兄弟关系,但职责完全不同。

RecordThread的核心职责:

  • 从音频驱动层读取原始PCM数据
  • 管理多个录音客户端(比如同时录音和语音识别)
  • 处理录音数据的格式转换、增益控制
  • 集成AEC、降噪等预处理效果
  • 将处理后的数据分发给各个客户端

我记得在Android 8.0之前,录音线程的调度策略有点“佛系”,导致某些低端设备上录音经常出现断流。后来Google在RecordThread里引入了fast capture模式,专门处理低延迟录音场景。

关键点:RecordThread有两种工作模式——常规模式快速模式。快速模式下,线程会绑定到专用CPU核心,使用更小的buffer size,延迟可以做到10ms以内。

14.2 录音数据流向:从麦克风到APP

数据是怎么从麦克风一路跑到你的APP里的?咱们一步步拆解:

  1. 硬件层:麦克风采集模拟信号 → ADC转换成PCM数据 → 通过I2S或SLIMbus送到SoC
  2. 驱动层:ALSA驱动将硬件数据搬运到内核缓冲区
  3. HAL层:Audio HAL的read()函数从驱动读取数据
  4. AudioFlinger层:RecordThread通过RecordTrack对象接收HAL层数据
  5. 效果处理层:经过AEC、降噪、增益调整等预处理
  6. 客户端层:通过AudioRecord的JNI回调,把数据送到APP

这里有个细节——数据在RecordThread内部是“一读多播”的。什么意思?就是一份原始数据,可以同时分发给多个录音客户端。我在项目中遇到过一个问题:语音助手和通话录音同时开启时,数据拷贝出了问题,导致其中一个客户端拿到的数据是静音。后来发现是RecordTrack::buffer的引用计数没处理好。

避坑指南:我曾经在调试录音数据流时,发现某些设备上录音数据会“跳帧”。排查了两天才找到原因——HAL层的read()返回的数据大小跟预期不一致。解决方案是在RecordThread里加了一层resampler做数据对齐。

14.3 回声消除(AEC)集成

AEC是录音通路里最头疼的部分,没有之一。它的原理说起来简单:把扬声器播放的参考信号,从麦克风采集的信号中减去。但做起来嘛……

AEC在Android中的集成方式:

  • 硬件AEC:由DSP芯片直接处理,延迟低、效果好。但各家方案不统一
  • 软件AEC:通过AudioEffect框架,在RecordThread里做后处理
  • 混合方案:硬件做粗处理,软件做精细补偿

Android从8.0开始,在AudioPolicyManager里引入了AEC参考信号的概念。说白了,就是RecordThread需要拿到播放线程的参考数据,才能做回声消除。

注意:AEC的参考信号必须跟播放数据严格同步。如果参考信号延迟了5ms,那AEC效果基本就废了。我在项目中见过一个案例:蓝牙耳机通话时回声很大,就是因为蓝牙的播放延迟不稳定,导致参考信号跟麦克风信号对不上。

AEC集成的关键参数:

参数 典型值 说明
参考信号延迟 < 20ms 超过这个值,AEC效果急剧下降
滤波器阶数 512 ~ 2048 阶数越高,收敛越慢但效果越好
双讲检测阈值 0.3 ~ 0.7 用于区分近端说话和远端回声
采样率 16kHz / 48kHz 16kHz是语音通信标准,48kHz用于高清录音

14.4 录音效果处理

录音效果处理,说白了就是在数据送到APP之前,做一层“美颜”。Android提供了AudioEffect框架来支持这些处理。

常见的录音效果:

  • 自动增益控制(AGC):自动调整录音音量,防止爆音或声音太小
  • 噪声抑制(NS):滤除环境噪声,比如风扇声、空调声
  • 高通滤波器(HPF):滤除50Hz以下的低频噪声(比如风噪)
  • 动态范围压缩(DRC):压缩动态范围,让声音更饱满

这些效果在RecordThread里是通过EffectChain来管理的。每个RecordTrack可以挂载多个Effect,形成一个处理链。

实现细节:效果处理的顺序很重要。我建议的典型顺序是:HPF → AEC → NS → AGC → DRC。先滤除低频噪声,再做回声消除,然后降噪,最后调整增益和动态范围。顺序搞反了,效果会大打折扣。

嗯,这里要注意——不是所有效果都能同时开启。比如AGC和DRC就有冲突,两者都会调整增益,同时开会导致“抢控制权”。我在项目中就踩过这个坑,用户反馈录音声音忽大忽小,排查后发现是AGC和DRC打架了。

14.5 录音通路的核心流程图

下面这张图展示了录音数据从麦克风到APP的完整流程,以及AEC和效果处理的位置:

录音数据流向与AEC集成架构图 麦克风 / ADC ALSA驱动 Audio HAL read() RecordThread 效果处理链 HPF → AEC → NS → AGC → DRC 播放参考信号 APP AudioRecord 参考信号同步 图14-1 录音数据流向与AEC集成架构

14.6 实战经验总结

最后,分享几个我在录音通路调试中的经验:

  • Buffer Size选择:录音的buffer size不是越小越好。太小会导致CPU频繁唤醒,反而增加功耗。我一般建议常规录音用256帧,低延迟场景用128帧。
  • AEC调试顺序:先调延迟对齐,再调滤波器参数,最后调双讲检测。顺序搞反了,你调三天都调不好。
  • 效果处理的开销:每个效果处理都会增加延迟。如果对延迟敏感(比如K歌场景),建议只开AEC和NS,关掉AGC和DRC。
  • 多客户端场景:如果多个APP同时录音,RecordThread会创建多个RecordTrack。这时候要注意每个track的独立效果配置,不要互相影响。

个人建议:调试录音通路时,最好先用dumpsys media.audio_flinger查看RecordThread的状态。重点关注underrun countframe count,这两个指标能快速定位问题。

好了,录音通路的内容就讲到这里。记住一句话:录音比播放难,但掌握了RecordThread的运作机制,你就掌握了音频录制的核心。下一章我们会聊音频策略管理,到时候再细说。


公众号:蓝海资料掘金营,微信deep3321