音频告警与录音:48 kHz 播放、16 kHz 采集与失联门

作者:

在

结论:音频链的风险不只是「响不响」,而是三类语义被混在一起:soundPressureWeightedDb 是未校准的近似响度、不是法规意义的绝对声压级;关闭 RecordAudio 只表示 loggerd 不再把原始音频写入日志,不是麦克风停采、也不是消息总线不再出现音频样本;get_audible_alert() 因失联设置的标志在新消息到达时不会清除。以下基于固定快照 c8fb906 的源码分析与 micd/soundd 假消息探针,未接声卡、功放或做声学校准。

两条运行链

告警播放与麦克风采集是两条彼此独立的链:前者从事件层把当前告警写进 selfdriveState.alertSound,再由 soundd 以 48 kHz 输出;后者从麦克风 ADC 拿到 16 kHz 样本,既发布原始音频,又计算一个供音量自适应使用的声压值。

组件 入口 主要行为
告警源 publish_selfdriveState() 从 AlertManager.current_alert 写 alertSound、文字、等级与状态,由 selfdriveState 同步给声音和画面
声音资源 sound_list、load_sounds() 九种消息告警键加一种内部强告警键(CRITICAL_MAX)映射到 WAV、播放次数和标称音量;断言资源为 48 kHz、16-bit、单声道
PCM 调度 get_sound_data()、callback() PortAudio 每次要 4096 帧,按当前游标填充、循环或补零;输出缓冲第 0 通道乘当前音量
告警切换 update_alert()、get_audible_alert() 收到新的 selfdriveState 就更新告警;循环声在已至少播放一遍后可等到本轮末尾再停
音量 / 输出 calculate_volume()、soundd_thread() 订阅 soundPressure,一阶滤波后调节无告警时音量;严重告警渐增;20 Hz 控制循环驱动音频流
麦克风分析 Mic.callback()、calculate_spl() 先发布原始音频,再累积 1600 样本做 RMS 与 A 加权;update() 按约 10 Hz 发布 soundPressure
原始音频落盘 loggerd_thread() 建立订阅时检查 RecordAudio,决定是否订阅 rawAudioData、写 rlog 与向含音轨的 qcamera 编码器传样本

失联告警与严重告警的时间门

声音告警的强度与是否持续都绑定明确的时间窗,下表汇总关键门限。

项目 门限 / 机制
失联告警 最后一次 selfdriveState 超过 5 秒、缓存消息仍标记 enabled、且超时不超过后续约 10 秒
严重告警渐增 约 4 秒把输出音量推到 1.0
严重告警换音效 约 8 秒把 current_sound 换为内部 CRITICAL_MAX 并把游标归零
环境音量滤波 2.5 秒一阶滤波,仅在 current_alert == none 时更新常规音量
音频流重试 get_stream() 最多 10 次
控制循环 20 Hz

普通设备以 26 dB 为最小音量参考,tizi 使用不同参考与音量底数;calculate_volume() 将范围裁剪后做指数变换。MIN_VOLUME=0.1 是指数变换前的裁剪值,低于环境阈值时实际输出约为 20^-0.9,并非直接输出 0.1。

音频输出侧的韧性也有限:get_stream() 重试最多 10 次,持续失败后没有看到另一个独立的声音后备设备路径。新硬件在按目标麦克风与扬声器重新标定音量曲线之前,不能照搬 26 dB 这类参考值,否则在车内噪声下可能出现最小可听响度不足。

micd 的分析窗与声压语义

micd 的标称采样块是 800 帧、16 kHz、单声道,连续两个块组成一个 100 ms 分析窗;回调按 indata[:, 0] 的实际长度处理,不使用 frames 参数限制样本数。一次大块会处理所有完整的 1600 样本窗,保留不足一窗的尾部,声压字段只保存最后一窗的结果。

calculate_spl() 使用 REFERENCE_SPL=2e-5 算 dB,但输入是归一化数字样本,不是经过麦克风灵敏度校准的帕斯卡值;源码注释也明确称为未校准、近似人耳响度。因此 soundPressureWeightedDb 不能当作法规意义上的绝对声压级。

采集窗的边界由一组隔离探针确认:不足一窗时仍会发送原始音频,两个半窗合成一次分析,一次超大回调只留下尾部,frames 参数不限制实际输入数组长度;若原始音频消息发送异常,会在分析窗累积之前穿透出去。因此「发布了原始音频」与「更新了声压窗」不是同一件事。

RecordAudio 的准确边界

micd 不读取 RecordAudio,持续发布 rawAudioData;服务定义给该主题的默认 should_log=False;loggerd 启动时按 RecordAudio 决定是否额外订阅并录制,qcamera 编码器的 include_audio 也在构造时读取该参数。

因此关闭录音开关的含义是「loggerd 不把原始音频写入这些日志/视频」,不是麦克风停止采集,也不是消息总线不再出现音频样本;任何能订阅本机 rawAudioData 的进程仍可能读取它。开关改变后,loggerd 进程的订阅和编码器配置也不会在本循环动态重建,需要行车周期使配置重新生效。

一个条件性状态问题

get_audible_alert() 因失联把 selfdrive_timeout_alert 设为真后,下一次收到新的 selfdriveState 虽更新告警,却没有清除这个标志;若再下一轮没有新消息且不满足失联窗口,旧标志会请求 update_alert(none)。新循环告警尚未播满一轮时会立即停止,已播满一轮则安排到本轮末尾停止。这里只验证了原方法在假消息状态下的控制流,未证明真实设备发生过可听见的告警中断。

常见问题

soundPressureWeightedDb 是法规意义的声压级吗?

不是。它由归一化数字样本经 RMS 与 A 加权计算,源码注释明确称为未校准、近似人耳响度,不能当作法规意义上的绝对声压级。

关掉 RecordAudio,麦克风就停止采集了吗?

不是。micd 不读取该开关,仍持续发布 rawAudioData;关闭只让 loggerd 不把原始音频写入 rlog/qcamera 音轨,其他能订阅总线的进程仍可能读取。

失联时声音会一直鸣叫吗?

不会无条件持续。需要三个条件同时落在窗口内:最后一次 selfdriveState 超过 5 秒、缓存消息仍标记 enabled、且超时不超过后续约 10 秒。

为什么告警可能被提前取消?

因为因失联设置的旧标志在新消息到达时不会被清除;若再下一轮没有新消息且不满足失联窗口,旧标志会请求 update_alert(none),新循环告警就可能被停下。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/AUDIO_ALERT_CAPTURE_DEEP_DIVE.md(音频采集、声音告警与录音边界,含源码行号索引)
  • 项目 02 —— openpilot-research/probes/test_micd_capture_window_contract.py(采集窗与原始音频发布的六项探针)
  • 项目 02 —— openpilot-research/probes/test_soundd_thread_ramp_contract.py(严重告警渐增与换音效的假时钟探针)

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注