ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android音频系统:AudioFlinger、ALSA路由与回声消除实践

Android音频系统:AudioFlinger、ALSA路由与回声消除实践 做音频系统这些年最深的体会是真正决定一个设备好不好用的往往不是“能不能响”而是“在复杂场景下还能不能好好响”。这个道理在 Android/嵌入式 Linux 设备上尤其明显应用层随便调一下音量底层可能涉及 AudioFlinger 的策略路由耳机拔插一瞬间的咔哒声背后可能是 ALSA 的 kcontrol 切换顺序问题而“通话对方听不到我”“对讲机里全是回声”这类抱怨通常绕不开回声消除和降噪的设计。这篇文章我会把 AudioFlinger、ALSA 架构、音频路由配置、回声消除与降噪这几块串起来讲分享一些直接在板子上踩过、调过的经验适合正在做安卓系统开发、嵌入式音频驱动、智能音箱/对讲/车载语音模组的朋友参考。1. 音频系统整体架构与设计拆解1.1 分层结构与职责边界Android 音频系统从应用到硬件通常被分成四层每一层的职责边界必须清晰否则调试时你会像无头苍蝇一样不知道问题出在哪个层面。第一层是应用/Java 框架层。App 通过 MediaPlayer、AudioTrack、AudioRecord 等接口发起播放和录音请求这些接口最终通过 Binder 调用系统服务并不直接碰硬件。层的设计是让应用开发者和底层驱动开发者解耦应用只需要关心声道、采样率、音量这些“业务参数”不需要关心今天主板上用的是哪颗 Codec。第二层是音频系统服务层核心就是 AudioFlinger 和 AudioPolicyService。AudioFlinger 负责混音、调度、音效处理可以理解为“所有音频数据的交通枢纽”。AudioPolicyService 负责路由决策也就是决定当前这段声音到底走扬声器、听筒、耳机还是蓝牙。两个服务一内一外一个管数据一个管方向互相配合。第三层是 HAL 层。厂商根据 Android 定义的 Audio HAL 接口实现 audio.primary.so 之类的动态库对上层提供 open_output_stream、out_write、start_output_stream 等标准接口对下层则是操作 ALSA。HAL 存在的意义是屏蔽不同芯片平台之间的差异。高通、联发科、展锐、全志的底层寄存器完全不同但到了 HAL 这一层大家都要遵守同一套接口契约上层就不用为每一颗 SoC 单独适配。第四层是内核/驱动层也就是 ALSA。ALSA 驱动管理声卡的 PCM 设备、Control 设备、定时器等资源真正把数据流送进 Codec、DAC、功放并负责采集麦克风进来的模拟信号转成数字 PCM。调试时我们常用的 tinypcminfo、tinyplay、tinymix 这些工具操作的就是这一层。为什么 Android 宁可绕这么一大圈也不让应用直接调 ALSA直接调的话延迟不是更低吗答案很现实直接调意味着每个应用都要自己处理“和其他应用同时出声”时的混音也要自己管理设备切换还要自己适配不同声卡的差异。这是应用层根本无法承担的复杂度。系统层把这些统一收编应用只需要“发出请求”至于什么时刻真正写到声卡由 AudioFlinger 统一调度。1.2 选型关键考量为什么是 ALSALinux 生态早期用的音频框架是 OSS也就是 /dev/dsp 那一套接口极其简单读写字节流就行。但 OSS 的问题也很致命它不擅长管理多音频流同一时刻只允许一个进程打开设备设备节点命名也不规范参数配置更是僵硬。到了多媒体的时代这种简单反而成了瓶颈。ALSAAdvanced Linux Sound Architecture则把“音频设备”抽象成 Card、Device、Subdevice 三级结构。声卡用索引编号表示比如 card0、card1每个声卡下有多个 PCM 设备比如 playback 和 capture 是分开的每个 PCM 设备还可以有多个 Subdevice。ALSA 同时把“参数配置”和“通路切换”剥离开PCM 设备负责传输音频数据Control 设备负责读写控制项这样你可以用 tinymix 去设置一个叫 Left Output Mixer PCM Playback Switch 的 kcontrol把某条通路的开关打开。从工程角度讲ALSA 真正强大的地方是它让“音频通路”变得可观测、可控制。你可以从用户空间一条条地查看某个 Codec 芯片内部的 ADC、DAC、PGA、MUX 状态从而把一句话的完整路径——麦克风拾音、模拟前端增益、ADC 转换、I2S 传输、内存 DMA——全部追踪出来。这种可观测性在做产品排障时是救命的。内核里现在通用的 ASoCALSA System on Chip框架又把 ALSA 进一步结构化拆成 snd_soc_card、snd_soc_dai_link、codec_driver、platform_driver 几个组件方便把 CPU DAI比如 I2S 控制器、外部 Codec、DMA 平台驱动组合到一起。理解了这套结构你看到设备树里一行compatible qcom,msm8916-wcd-spmi就知道它注册了一个音频 Codec 节点。1.3 音频系统的影响范围音频系统不是孤立的一块它直接影响整机体验的方方面面通话质量、语音助手唤醒率、录音方向性、游戏延迟、铃声/通知音策略、蓝牙 SCO 通话切换、HDMI 音频输出等。项目排期里如果只把音频当成“底层驱动的一部分”来做到了整机联调阶段一定会被各种音频问题拖垮。我的经验是音频系统的需求分析和架构设计应该放在项目前期和系统框架、应用层交互一起定下来。一个典型的例子某智能门铃项目需求是门口有人按铃时室内机要能双向对讲同时又要播放室内摄像头预览画面。这个场景牵扯到音频路由预览声音走喇叭、麦克风走对讲链路、回声消除室内扬声器声音会被麦克风捡回去、降噪门口风吹树叶等环境噪声还要考虑通话策略通话时媒体音量要不要压低。这些需求如果不在前期通过 AudioPolicy 策略设计出来后期只能靠各种 workaround 堆补丁往往还会引发新的问题。2. AudioFlinger 核心细节与实操要点2.1 AudioFlinger 的核心职责与线程模型AudioFlinger 是 Android 音频系统的核心服务很多人把它简单理解成“把几个音频流混在一起输出”但实际职责要比这个多得多。它负责管理所有 AudioTrack 和 AudioRecord 对应的底层 Track 对象。当应用创建一个 AudioTrack经过 Binder 调用到 AudioFlingerAudioFlinger 会在对应的输出线程里创建一个 PlaybackThread::Track并给这个 Track 分配一个 buffer应用通过共享内存把 PCM 数据写进来。录音方向反过来AudioRecord 对应到 RecordThread 的 RecordTrack。它管理和调度输出线程。根据输出设备的属性AudioFlinger 会创建不同类型的线程MixerThread普通混音线程负责把多个 Track 的数据混成一路是最常见的播放形态。OffloadThread硬件卸载线程通常在播放高采样率的 MP3/AAC 等压缩音频时使用数据直接交给 DSP 解码播放CPU 基本不参与。FastMixer/FastCapture低延迟线程用于对延迟敏感的场景比如打电话按键音、游戏音效、语音唤醒时的实时监听。不同的输出设备可能会对应不同的线程。比如扬声器有一个 primary output 线程蓝牙 A2DP 可能有另一个 output 线程通话走 telephony 时又有一套 voice call 路径。理解线程模型对排查“为什么这个声音没出来”极其重要因为你必须知道当前要播的声音到底被路由到了哪个线程。2.2 混音、重采样与音量链路混音不是简单的“把数组相加”。多个 Track 的采样率、通道数、位深可能各不相同需要先做重采样、通道转换、位深对齐再按增益混叠。AudioFlinger 默认会在 48kHz 的混音缓冲区上做处理如果应用侧开到 44.1kHz就需要重采样到 48kHz。重采样算法有质量高低之分AudioFlinger 里默认用 Speex resampler效果可以接受但如果你对音质有更高要求可以在 HAL 层做一些主动配置。音量控制也是 AudioFlinger 的职责。Android 应用层的音量调整最终通过 audio_policy 传下来AudioFlinger 在混音时对每个 Track 做数字增益。要注意的是数字增益如果被设置得过高会产生削波失真听感就是那种“破音”。正常情况下系统有音量限制机制但定制 ROM 或特定场景会绕过限制这时就需要在混音前做 headroom 预留或者加 limiter。实际调项目中我遇到过一个问题某 App 播放采样率是 8kHz 的语音系统混音线程是 48kHz重采样后声音变得“发闷”。最后发现是重采样器的质量参数被调到了最快模式换成高质量模式后明显好了。这种坑在代码评审阶段根本发现不了只能靠耳朵和设备端频谱数据来判断。2.3 AudioPolicy 路由策略与 Effect 链AudioFlinger 不负责“数据该往哪个设备走”这个决策由 AudioPolicyServiceAP负责。AP 启动时会解析 audio_policy_configuration.xml 文件这个文件定义了系统有哪些 audio device、哪些 output profile、每种 device 的采样率和通道数以及模块之间的连接关系。当应用切换场景比如插拔耳机、开启扬声器时AP 会根据策略优先级决定路由。比如 STRATEGY_MEDIA媒体播放和 STRATEGY_PHONE通话同时出现时通话策略优先级更高媒体会被暂时压低音量或直接 mute。这种策略的好处是避免多个应用同时抢占设备导致混乱但也是 Android 音频“反直觉”体验的制造者。比如你在放音乐时收到通知音乐声会自动降低这在很多用户看来是“系统自作主张”但站在系统设计角度这是为了确保重要提示音能被人听到。AudioFlinger 还提供一个 Effect chain音效链的框架。AEC回声消除、NS降噪、AGC自动增益可以通过 Effect API 挂到录音或播放链路上。不过实际上很多平台的 AEC/NS 是在硬件 DSP 或者 HAL 层实现的默认的软件 Effect 链更多用于通话录音、VoIP 这类软件场景。如果你要在自己设备上做软件 AEC需要搞清楚数据到底从哪一层进 Effect不然很容易出现“算法在跑但处理的是错误数据”的问题。3. ALSA 架构与音频路由配置实操3.1 ALSA 核心概念与常用命令在调试任何音频问题时我都会先确认 ALSA 层的设备是否工作正常。工欲善其事必先利其器下面这组命令几乎是每天都要用的cat /proc/asound/cards tinypcminfo -D 0 -P 0 tinypcminfo -D 0 -C 0 tinyplay /data/local/tmp/test.wav -D 0 -P 0 tinycap /data/local/tmp/test_rec.pcm -D 0 -C 0 -c 2 -r 48000 -b 16 tinymix -D 0 tinymix -D 0 PA Gain 3cat /proc/asound/cards列出当前注册的声卡注意看有没有你期望的 Codec 节点tinypcminfo查看某个 PCM 设备的硬件能力包含支持的采样率范围、通道数、位深、buffer 大小限制tinyplay播放一个 wav 文件-D 指定声卡号-P 指定 playback 设备编号tinycap录音到文件-C 指定 capture 设备编号-c 声道数-r 采样率-b 位深tinymix -D 0无参数列出所有 kcontrol带参数则可以设置某个 kcontrol 的值。这套命令可以解决大量“前面软件检查了半天最后发现是驱动没起来”的问题。比如录音全 0 的场景如果 tinycap 裸录也是全 0那基本可以断定问题出在 ALSA/驱动/Codec 通路而不是上层应用。3.2 音频路由的底层本质kcontrol 的组合“音频路由”听起来很高大上但剥开看本质上就是在设置一系列 kcontrol。以最常见的通话场景为例你要让麦克风的声音进到系统里需要在 Codec 里把输入 PGA 打开、把 MIC_IN 引脚接到 ADC、把 ADC 的使能开关打开你要把对方的声音送到扬声器需要把 DAC 输出使能、把功放使能、把 playout path 切到 speaker。这些寄存器操作被 ALSA 驱动导出为一个个 kcontrol名字通常像 MIC1PGA Volume、PCM Playback Switch、SPK Driver Enable 之类。不同厂商的命名各不相同高通和联发科的差异尤其大所以你不可能写一套配置通吃所有平台。做系统集成时通常会把厂商给的通路配置整理成一份“路由表”再根据上层触发的事件耳机插入、免提打开等去加载不同的配置。现代 Android 设备已经不太直接在 HAL 里手动调 kcontrol 了而是使用 UCMUse Case Manager或者厂商自己的 mixer path 配置。UCM 的核心是把“某个使用场景比如 Play Music、Voice Call、VoIP”和“一组 mixer 配置”绑定起来HAL 只要切场景UCM 就自动执行对应的通路切换。这比手动在 HAL 代码里写死 tinymix 命令要优雅得多也方便移植。3.3 一套可复现的路由切换排障流程从实际项目里总结了一套排查路由问题的套路按顺序走基本不会漏先让上层播放一个测试音确认应用层是否把数据送到了 AudioFlinger。用logcat -s AudioFlinger AudioPolicyManagerALSA查看输出线程的日志。用dumpsys media.audio_flinger看看当前播放线程的状态确认 Track 是否 active混音线程是否在正常写 HAL。用dumpsys audio_policy确认当前路由的 Device 是哪个。比如明明插入了耳机但 route 还在 speaker说明政策/驱动层的耳机检测状态不一致。直接上板级工具 tinyplay tinymix手动把通路打开。如果 tinyplay 能出声音说明 ALSA 通路本身是通的问题在 HAL 或上层如果 tinyplay 都出不来那就得回头查驱动和设备树。切换场景时观察有没有爆音或断流。若有通常在切换前需要先 mute 对应通路等寄存器稳定后再切换这在 UCM 配置里可以用enable/disable的动作顺序来控制。这套流程听着简单但真正做到位需要对各层日志都熟悉。有一次排查某板子“从扬声器切到耳机后耳机没声音”我查了整整半天最后发现是 HAL 里音频链路的 device 状态没有刷新policy 层已经切到耳机了但 HAL 的 open_output_stream 还停在旧的 profile 上。这种“上热下冷”的问题靠单层 debug 是找不出来的必须两边同时打日志。4. 回声消除与降噪原理、工程与新技术4.1 回声消除AEC原理与工程难点回声消除的经典场景是通话远端的声音从你自己的扬声器放出来被麦克风采集回去形成一个“远端说话声的延迟副本”如果不处理对方就会听到自己的回声。本质上看AEC 是在估计一条从扬声器经空气传播到麦克风的通道响应包括房间反射等然后把麦克风信号里属于这条通道的部分减掉。经典自适应滤波流程x(n) 是参考信号也就是“扬声器即将播放的内容”通常从播放链路的最末端取出来d(n) 是麦克风采集到的近端混合信号近端语音 扬声器回声自适应滤波器 h(n) 持续估计真实回声路径 h_hat(n)计算估计回声 y_hat(n) x(n) * h_hat(n)误差 e(n) d(n) - y_hat(n)这个 e(n) 就是消掉回声后的信号同时误差还会继续驱动滤波器系数更新。工程上 AEC 有三大难点双讲double-talk问题。双方同时说话时麦克风信号里既有远端回声又有近端语音自适应滤波器如果继续更新很容易发散。工程上通常用一个双讲检测器来检测这个状态检测到双讲时冻结滤波器系数更新。非线性失真。扬声器在大音量下会产生削波和谐波这已经不是线性通道能描述的了。传统线性 AEC 在削波严重的场景效果极差这也是为什么一些低端喇叭的回声“消不干净”。延迟抖动。参考信号和麦克风信号之间的时间对齐非常关键偏移几毫秒就足以让噪声信号不但不消除反而被增强。高通平台上常用 Voice Processor如 QDSP6做硬件 AEC它内部有专门的时间对齐模块软件方案通常需要自己处理这个对齐。4.2 降噪NR方案选型与 Android 平台实现降噪比 AEC 应用场景更广概念也更泛。不管是通话时的环境风噪、录音时的空调声还是 IoT 设备拾音时的道路噪声都需要降噪来做“背景净化”。从实现层次来看降噪可以分成几类单麦克风软件算法常见的有谱减法、维纳滤波、MMSE 估计等。优点是无需额外硬件缺点是非平稳噪声比如键盘敲击声、喇叭声很难处理干净还容易产生“音乐噪声”这种听起来像水声的伪影。双麦克风/麦克风阵列利用空间位置信息做波束形成可以更好地抑制方向性干扰。对麦克风一致性要求高校准不好反而会引入新问题。神经网络降噪DNN/RNN/LSTM最近两三年在中高端设备上非常流行能直接处理语音和噪声重叠的非平稳场景效果明显优于经典算法但算力和内存开销是必须考虑的问题。Android 平台的软件 AEC/NS 一般通过 Effect 链挂在通话或 VoIP 链路上。WebRTC 的音频处理模块里提供了 AECM/AEC3 和 NS很多 VoIP App、小型软硬件项目都直接用这个。WebRTC NS 的配置里有模式选择保守模式对语音损伤小、降噪量小激进模式降噪更彻底但语音会有可感知的损伤。调参时最好先给出一个“可接受语音损伤度”的底线再一点点往激进方向试。4.3 硬件降噪与移动端场景的选型硬件降噪在手机和智能音箱上很常见。高通平台的 QDSP6 里集成了 AEC、NS、AGC 等音频处理模块Codec 芯片比如 Cirrus Logic、TI 的音频芯片里也往往会带硬件 AEC/NS 功能。硬件方案好处是功耗低、延迟低、效果稳定坏处是参数封闭只能通过厂商提供的 tuning 工具调整调不好想深挖会很费劲。移动端的“LR 手机版降噪”这类需求本质上是在直播、短视频、网课等场景下让手机录音在嘈杂环境下依然保留清晰人声。这类需求不局限在系统通话链路上更多时候是在应用层做实时处理。平台自带的降噪不一定能满足直播场景的“人声优先级”所以会出现各种第三方的实时降噪 SDK。选择第三方方案时除了看降噪效果还要重点关注延迟直播场景对延迟极其敏感延迟高了主播会明显地感觉到“嘴形对不上”。在 IoT 场景“告警降噪”是一个经常被误读的需求。比如门铃、报警器在触发告警时环境噪声和目标声音往往混合在一起系统需要先通过降噪把背景分离再做事件识别防止误报。这个场景的关键不是“人声好听”而是“用最小的算力把环境底噪压下去保留足够的目标事件特征”所以经常只做噪声门和谱减法很少上大模型。4.4 新技术方向双信号转换 LSTM、PCL 与端侧降噪最近行业里讨论比较多的一个方向是“双信号转换 LSTM回声消除”。传统 AEC 是把回声路径建模成一个线性系统但实际扬声器和声学环境的组合是非线性的。LSTM 方案不再去显式估计一条回响路径而是将近端麦克风信号和远端参考信号分别变换成特征通常是子带域再送入 LSTM 网络去直接学习“从混合信号里分离出近端语音”的映射。这类方案在处理非线性失真和大房间混响上有优势在会议系统、智能座舱这类复杂声学场景里效果明显。当然代价也很实际模型推理需要额外的 NPU/DSP 资源掉电、内存、时延都要重新评估。工程落地时通常要做模型剪枝、算子和 INT8 量化不是拿到模型就能直接跑。我见过不少团队在 demo 阶段效果惊艳一上嵌入式板卡就被算力卡死最后又退回传统 AEC 方案。还有一个我在传感器项目里关注的方向是“通过 PCL 降噪处理”。这里的 PCL 一般指点云库原本主要用在激光雷达/视觉的 3D 数据处理上但最近有人尝试把它用在空间声学预处理阶段先把麦克风阵列拾取的声场信息映射到空间点云剔除异常反射簇比如墙角的强反射点再送到 AEC/波束形成链路。这个思路还比较初步但用到会议一体机这类设备上确实能看到房间反射引起的回声残留有明显下降。另外“合宙:降噪”这类词在 IoT 开发群里很常见说的是合宙等蜂窝模组自带音频降噪/AEC 功能。比如在某些模组上使用 AT 指令ATECHO1和ATNOISERED1就能把对讲通话的底噪和回声压下来对做低成本无线呼叫、老人机、定位对讲之类的产品来说不用自己搞 DSP 算法直接用模组内置功能是最省力的方案。不过要注意模组内置算法的效果上限有限如果产品对通话音质要求高还是得自己做音频方案。4.5 AEC/NS 参数调优的硬核经验针对参数调优分享一些我踩过坑后记住的要点。AEC 滤波器长度。决定能补偿多长的回声路径。16kHz 采样率下2048 个 tap 大约覆盖 128ms 回声尾音基本够普通房间使用如果遇到大混响环境要适当加长但滤波器过长会带来收敛慢、算力高的问题。AEC 参考信号的接入点。参考信号应该取“功放之前、之后再加任何可能引入非线性的环节之前”的位置。取太早没有包含功放带来的非线性取太晚可能已经无法对齐。常见做法是从播放线程的 out_write buffer 中拿参考数据在 HAL 里直接送到 AEC 模块。NLP非线性处理力度。很多软件 AEC 在完成线性消回声后还会加一个中心限幅器把残余回声抹掉。NLP 太强语音会断断续续太弱能听到回声尾巴。好的算法会在 NLP 后补一段舒适噪声CN让语音尾部自然过渡不至于“一顿一顿”。降噪的时间常数。噪声门Noise Gate的打开和释放时间如果设置太短会出现“说话时背景噪声突然跳进跳出”的感觉俗称呼吸效应。建议打开时间设置在 10-20ms释放时间设置在 100-200ms让背景噪声的平滑感更好。我在一个对讲机项目里调试时发现AEC 参数调好后开启降噪反而出现“电子感口语”。原因是降噪模块把残余回声的尾巴误判成了环境噪声做了一轮强烈的抑制。后来把降噪模式从激进调到标准同时保留 AEC 的 NLP 强度效果才平衡。所以 AEC 和 NS 不是两个孤立模块它们在同一链路里相互影响联合调参是常态。5. 常见问题与排查技巧实录5.1 典型问题速查表现象可能原因优先排查点播放没有声音输出线程未启动 / PA 未打开 / Codec 通路未使能logcat tinymix 查看 master 与 path录音全 0PCM 设备未打开 / MIC 未接对 / ADC 未使能 / micbias 没电压tinycap 裸录 查 kcontrol通话有回声参考信号不干净 / 滤波器未收敛 / NLP 太弱查线程对齐调 AEC 参数切路由时爆音切换顺序未先 mute 后切通路调整 UCM 或 HAL 的 mute 时序背景噪声明显降噪没生效 / 参数过于保守 / 噪声门时间太短看频谱调 NS 模式和时间常数声音变“闷”重采样质量低 / 采样率转换不当检查混音线程采样率与重采样算法耳机没声音耳机检测状态不一致 / 路由未切到 headsetdumpsys audio_policy 查设备状态5.2 三个亲测有效的排查步骤第一个步骤把音频链路拆到最小可验证集合。一旦出现“没声音”“声音不对”不要急着改上层代码先用 tinycap 录一段 1kHz 正弦测试音再用 tinyplay 播放确认 ALSA 层的通路是否正常。这个最小闭环如果都不通问题就在驱动或硬件如果通了再去查 HAL 与上层。这个习惯帮我省掉了大量无意义的 debug。第二个步骤验证参考信号的时间对齐。AEC 失效时我第一件事就是把参考信号和麦克风信号同时录出来在音频软件里看它们的互相关峰值。如果峰值偏移超过 10ms几乎可以断定是时间对齐问题而不是算法问题。曾经有个项目dumpsys 里显示的延迟只有 5ms但实际 AEC 效果极不稳定最后查出来是 HAL 在 out_write 里加了一个内部环形缓冲导致参考信号晚到了近 40ms。这种问题靠听感根本定位不出来必须靠数据。第三个步骤用频谱图对比调参。调降噪不能只看“耳朵觉得清不清楚”。用 Audacity 或 Sonic Visualiser 打开处理前后的 PCM观察 300-3000Hz 语音频段内信噪比是否提升、高频有没有被切掉、语音段和非语音段之间是否平滑。我见过很多人觉得降噪“效果好”一看频谱整个 8kHz 以上全被砍平了语音听感确实“干净”了但也失去了自然度。5.3 独家注意事项千万别在 AudioFlinger 的普通 MixerThread 里做重量级 DSP 处理。混音线程本身已经承担了大量数据拷贝和混音任务你再加一个神经网络降噪进去很容易导致音频中断。需要低延迟处理时应该把处理放到 HAL 层或者专门的音频 DSP 上而不是塞进 AudioFlinger 的业务逻辑里。UCM 配置和 mixer path 配置要做好版本管理。同一个 SoC 升级内核小版本后kcontrol 名称都可能变化典型现象是“升级完所有声音消失”。建议把厂商原始配置留底并在每次升级后先跑一遍最小播放/录音回归第一时间发现 kcontrol 名称变化。回声消除不要靠“加延时”来躲。有些工程师为了绕过回声路径对齐问题会故意给播放链路加几十毫秒延迟让回声变得“不那么明显”。这其实是用通话体验换暂时的假象人耳对通话延迟非常敏感超过 100ms 会感觉明显“不通畅”。正确的做法是把参考信号对齐点做对而不是用大 buffer 掩盖问题。最后再分享一点个人体会如果你正在接手一个“症状百出”的音频项目我的建议是不要急着把锅甩给某一层也不要迷信某个“万能方案”。音频系统的每一层——AudioFlinger 的策略调度、HAL 的实现细节、ALSA 的通路配置、AEC/NS 的算法参数——都在互相影响。最好的调试状态是你能从应用层一路看到 ALSA 寄存器做到“哪一层出了问题心里有数”。哪怕只是打通一次“播放一首歌”的最小链路把每一层的日志和状态都过一遍后面遇到复杂问题都会有方向感。音频这行没有捷径但把基本功做扎实了坑会少踩一大半。
返回列表