KunSpace
返回博客

Audioserver 挂死:ACR 里 apply_volume_16to32 传错 bytes

2026年7月4日
尚无阅读

读完了?别空手走啊 😏

白嫖可以,但点个赞显得有教养

写评论

赞同、吐槽、提问都行,评论区开放

这篇写什么?

一类很折磨人的问题:一开 ACR(Echo Reference / 内容识别参考通路)相关宏,audio 服务就挂——有时是接 Soundbar / 切 HDMI-IN,有时是开机播 YouTube。墓碑里常见:

  • Scudo ERROR: corrupted chunk header
  • 或 ASan 在 malloc / free 时直接 SIGABRT

表面像「MS12 和 DSPC 抢内存」,真正根因往往更朴素:某次 16bit→32bit 转换把字节数传大了一倍,堆被写烂,崩点落在别的 free/malloc 上。

一句话:

消融实验 → 只有 ENABLE_ACR_APP 必现
  → 墓碑在 close/free 或 MS12 malloc(堆已坏)
  → 假读仍崩,关掉 apply_volume_16to32 不崩
  → 应传 bytes/2,却传了 bytes → 越界踩堆

ACR 导致 Audioserver 崩溃的排查路径

相关:ACR:LoopbackB 从 TDM-A 取 48 kHz RefALSA period 与缓冲


一、现象:两类复现,同一类挂死

场景 A:Zoom + 语音键 + HDMI-IN

双屏电视在 Zoom 会议中 多次连按遥控器语音键,再切到 HDMI-IN / dongle 播音乐,出现 audioserver 挂死。

场景 B:开了 ACR 就「开机播 YouTube 挂」

后来改成 ACR 分离声卡 / TDM-A 策略后,旧复现路径弱了,但只要打开 ACR,仍可能在 开机播 YouTube 等路径挂死——说明根因仍在 ACR 读通路,而不只是「和 MS12 同生命周期切换」。

HAL 侧常同时存在三块宏(名称因项目略有差异):

大致职责
ENABLE_DSPC_AEC DSPC 回声消除一类
ENABLE_ACR_APP ACR / Echo Reference 采集与处理
ENABLE_DSPC_EQ DSPC EQ 等

要用 消融实验 先锁模块,再抠代码。


二、消融实验:锁死是 ACR

统一在「Zoom 连按语音键 → HDMI-IN 播音乐」场景下测:

# AEC ACR EQ 墓碑 声音 备注
1 off off off 基线正常
2 off off on 音量键有「连加/连减」副作用,但未挂死
3 off on on 开始出墓碑
4 on off on AEC+EQ 未单独复现挂死
5 off on off 有/无 均见过 开 ACR 即异常
6 on on on 全开最差

结论很清楚:

只要打开 ENABLE_ACR_APP,就容易出问题;关 ACR 后同类操作可稳住。


三、初期误判:像「MS12 被 DSPC 踩内存」

接 Soundbar 时,上层 new device → AudioPolicy 开新 output → adev_open_output_stream_new创建 / 释放 MS12。挂死点曾落在:

  • get_dolby_ms12_cleanup 附近
  • 或 MS12 内部再次 malloc 时 ASan 直接炸

DSPC EQ / Talk 等可能各占几十 MB 量级,切源时又在释放 MS12,再加上 ACR 使用额外 FIFO / 临时缓冲——很容易怀疑是 堆布局冲突

因此做过一版 分离路径 方案(另开声卡、TDM-A 读写、少跟系统音量搅在一起),意图躲开 MS12↔DSPC 同堆撕扯。这对「切换设备时立刻崩」有帮助,但 开 ACR 仍崩,说明还有更直接的写越界。


四、墓碑在说什么:崩点 ≠ 根因

分离策略之后,tombstone 常落在 关输出流 free 临时缓冲

Scudo ERROR: corrupted chunk header at address 0x...
...
#n  adev_close_output_stream → aml_audio_free(audioeffect_tmp_buffer)

tombstone:close output stream 时 Scudo 报坏块

调用链经 adev_close_output_stream_newadev_close_output_stream

close_output_stream_new 调到 close

真正 free 的位置:

aml_audio_free(audioeffect_tmp_buffer)

更早一版 ASan 则在 MS12 malloc 时 abort——同一类「堆元数据已坏」:

MS12 malloc 时 ASan abort

要点:

观察 含义
崩在 free / malloc 堆元数据早被踩坏
崩在 effect buffer / MS12 只是「第一个发现坏掉的人」
必须回溯 谁先越界写 不能只改 close 路径

五、往回追:谁踩了 audioeffect_tmp_buffer

malloc / free 前后打印缓冲地址、大小,以及指针前方 16 字节(chunk header 附近):

分配 audioeffect_tmp_buffer 时打印地址与 header

free 前再次打印 header

墓碑里对应地址附近 整段被写成 0——典型「被人用 memset/越界写抹掉了分配器头」:

Scudo 坏块地址附近内存全 0

同进程里,地址较近、且持续读写的,是 ACR 读线程readerin_readaml_alsa_input_read):

reader 线程栈落在 in_read

于是给 ACR in_read / pcm_read 打字节数与 buffer 地址日志:

in_read / pcm_read 字节与地址日志

起初看「ALSA 读本身」并不一定像冲掉邻居——还要继续隔离。


六、关键实验:假读 + 开关转换函数

in_readAUDIO_SOURCE_ECHO_REFERENCE)里 先不读 ALSA

memset(buffer, 0x55, bytes);  // 填固定图案
usleep(32000);                // 约 32ms:12288 字节 @ 32bit 立体声 48k

用 0x55 假读替代 ALSA

结果:

操作 是否仍挂死
假读 + 仍调用 apply_volume_16to32(..., bytes) 仍会
屏蔽 apply_volume_16to32 不挂

说明:锅在 16→32 音量/位宽转换,不在 pcm_read 本身。

再打日志看转换前后「字节数」:

ACR: Read data -- bytes requested: 12288 ...
ACR: Data after volume adjustment -- bytes: 12288

转换前后 bytes 仍是 12288

数字没变,容易误以为「参数没错」——真正要看的是:apply_volume_16to32 把最后一个参数 当成「16bit 侧字节数」 还是 「32bit 侧字节数」


七、根因:bytesbytes/2 差了一倍

ACR 路径典型写法(示意):

// 从声卡读 16bit 数据到临时缓冲,长度是 32bit 请求的一半
ret = aml_alsa_input_read(stream, adev->acrTmpbuffer, bytes / 2);

// 错误:最后一个参数传了 bytes
apply_volume_16to32(1.0, adev->acrTmpbuffer, buffer, bytes);

// 正确:应按 16bit 输入字节数传
apply_volume_16to32(1.0, adev->acrTmpbuffer, buffer, bytes / 2);

标注:应传 bytes/2

函数内部大致是:

void apply_volume_16to32(float volume, int16_t *in_buf, int32_t *out_buf, int bytes)
{
    int16_t *input16  = (int16_t *)in_buf;
    int32_t *output32 = (int32_t *)out_buf;
    for (i = 0; i < bytes / sizeof(int16_t); i++) {
        // 每个 16bit 样本 → 一个 32bit 样本
        output32[i] = ...;
    }
}

循环按 bytes/sizeof(int16_t) 迭代

用数字算一遍(bytes = 12288

正确理解 错误传 bytes
上层 in_read 请求 要填满 12288 字节的 32bit 缓冲 同左
16bit 输入应有字节数 6144bytes/2 仍只有约 6144 有效数据
函数认为要处理的 16bit 样本数 6144/2 = 3072 12288/2 = 6144多一倍
写出的 32bit 字节数 3072 × 4 = 12288(刚好) 6144 × 4 = 24576越界约一倍

结果:读穿 acrTmpbuffer,写穿 buffer,隔壁堆块(effect tmp、分配器 header、甚至别处)被踩成 0 或乱值 → 稍后在 aml_audio_free / MS12 malloc 爆掉。

这也解释了为何:

  • 消融只跟 ACR 绑定
  • 假读仍崩(转换还在)
  • 关转换就不崩
  • 崩点飘忽(close / malloc / Scudo / ASan)——经典 堆损坏延后显现

八、修复与自检

修复(一行语义):

apply_volume_16to32(1.0, adev->acrTmpbuffer, buffer, bytes / 2);

保证:

16bit 输入字节数 == 传给 apply_volume_16to32 的 bytes 参数
32bit 输出字节数 == 上层 in_read 的 bytes(约为输入的 2 倍)

自检清单

  • aml_alsa_input_read(..., bytes/2)apply_volume_16to32(..., bytes/2) 对齐
  • dump 16bit / 32bit raw 时,长度分别用 bytes/2bytes
  • 开 ASan / 看 Scudo,回归:Zoom 语音键风暴、HDMI-IN、开机播流媒体
  • 别被「崩在 MS12/free」带走——先搜最近的 位宽转换 / memcpy 长度

九、小结

阶段 结论
消融 问题绑定 ENABLE_ACR_APP
表象 MS12 cleanup / malloc、audioeffect_tmp_buffer free 挂死
本质 ACR in_readapply_volume_16to32 多传了一倍字节数
修复 第三个长度参数改为 bytes/2

堆损坏类 bug 的教学意义:tombstone 指向的 free,常常只是受害者;真正的凶手是更早的一次越界写。 用假读 + 开关嫌疑函数,比反复猜「模块互踩」更快。


相关阅读

评论

还没有评论,来做第一个吧

赞同、吐槽、提问都行,评论区开放