


ATS3089P 自研 AEC 通话、PCM 导出与试听
对应 `app aec status` 版本 **261015-v4**。控制指令可用普通 UART;二进制音频通过板上 USB CDC 导出,或者保存到 NAND 后切换 USB U 盘复制。
本版根据真实双讲快照 `D:\logs\aec\aec_20261015_161145_793515` 修正:原版无条件 NLMS 出现输出增能和参考很弱时误消除近端。新增前后台滤波、弱参考门限、NLMS 正则项、双讲慢学与失配回退;优化 AEC 热点循环。原始数据保持不变,重放试听与中文分析在 `D:\logs\aec\aec_20261015_161145_793515_v4_review`。重放执行修改后的生产 ARM C,不能代替重新烧录后的真实通话验证。
1. 处理前后是什么声音
| 信号 | 获取位置 | 内容 |
| --- | --- | --- |
| MIC / before | ADC0,进入 CPU 算法前 | 近端人声 + 扬声器返回的远端回声 + 环境噪声 |
| REF / reference | 原厂 PA→ADC1 芯片内部回采 | 扬声器电气参考,不是第二个麦克风 |
| OUT / after | CPU AEC 输出,送原通话编码器前 | 目标是保留近端人声,抑制与 REF 相关的回声 |
**不能保证 OUT 只剩人声。** 基础线性 AEC 仍可能有残余回声、环境噪声和扬声器非线性失真。v4 加入启发式双讲保护和安全发布,但没有完整商业双讲检测、非线性回声抑制、时钟漂移补偿或长混响处理。参考弱时透传 MIC 是保护人声,不能当作强消回声。
三路按同一个输入样本索引保存,长度相同。OUT 是编码前 PCM;最终远端听感还受到蓝牙编码、手机与网络处理的影响。所有试听文件保持原始增益,没有分别归一化处理前后幅度。
2. 电脑现有试听样例
工程 `application/bt_watch/outdir/aec_listen_demo/` 下已生成 5 秒样例。进入其中本次的 `aec_...` 子目录,打开:
-
`mic_before.wav`:处理前。
-
`reference.wav`:参考。
-
`aec_after.wav`:处理后。
-
`before_then_after.wav`:先听完整处理前,静音半秒,再听完整处理后。
-
`before_L_after_R.wav`:左声道处理前,右声道处理后。
这组样例使用已有 MATLAB 合成数据与实际生产 C 内核的 ARM 仿真结果,前 3 秒模拟纯远端,后 2 秒加入模拟近端声音。**它是算法演示,不是开发板实录通话。**
3. 板端自检也能保存三路 PCM
先挂断电话。原来的 selftest 只在内存计算;带 `dump` 才预约快照:
```text
app aec selftest lms dump
app aec status
```
等待最终 `aec: CPU offline done result=0 algo=lms ...`,以及快照 `capturing=0 ready=1 frames=64000 source=2`,再按第 5/6 节导出。NLMS 对应:
```text
app aec dump clear
app aec selftest nlms dump
```
`selftest OK` 表示任务提交成功,完成以 `CPU offline done result=0` 为准。自检是 4 秒、16 kHz 的已知纯回声,不含真人近端讲话;可听到噪声被抵消。每路 PCM 为 128000 字节。
自检固定参数:NLMS 256 taps / mu=200‰,LMS 64 taps / mu=800‰。最后一秒纯回声 ERLE 超过 10 dB 且没有数值复位才通过。`selftest_erle_decidb=784` 表示合成模型 78.4 dB,不能作为实测通话指标。
4. 真实蓝牙通话与采集
先 `app aec stock` 验证原厂通话正常。建议挂断后选择 CPU NLMS:
```text
app aec livecfg nlms
app aec nlms
```
重新拨号,确认双方有声音,再执行 `app aec status`:
```text
aec: version=261015-v4 requested=nlms call_active=1 active=nlms ... custom_live=1
aec live: running=1 ... frames=持续增加 ... raw_drop=0 out_drop=0 fallback=0 error=0
```
`custom_live=1` 表示固件包含能力;只有 `active=nlms/lms` 且 `running=1` 才是当前实际运行自研通话。也支持通话中切换 stock/off/nlms/lms:由原通话线程重开本地媒体,出现短暂中断,复用原 SCO,不发送音源切换 AT 命令。以最终 `AEC mode=... restart=1 error=0` 和 status 为准。
各指令是否关闭原厂 AEC
| 指令 | 原厂 DSP AEC 的 SDK 开关 | CPU 自研 AEC |
| --- | --- | --- |
| `app aec stock` | 开启 | 不启用 |
| `app aec off` | 关闭 | 不启用 |
| `app aec nlms` | 关闭 | NLMS 加当前保护层 |
| `app aec lms` | 关闭 | LMS 加当前保护层 |
`app aec livecfg ...` 只设置下次自研通话参数,不切换运行模式。`app aec freeze off` 只恢复自研系数学习;`freeze on` 冻结学习,但已有系数仍用于抵消回声。`app aec dump start 10` 只采集当前三路数据,不切换原厂/自研算法。
实现链路:`src/btcall/btcall_aec.c` 中仅 stock 将 `owner_enabled` 设为 true,媒体打开前调用 `audio_policy_set_voice_aec_enabled()`;`framework/audio/audio_policy.c:audio_policy_get_record_channel_support_aec()` 将该值返回给 SDK 初始化。nlms/lms 同时注册 `aec_live` 钩子,CPU 处理后再交给原 DSP 编码器。DSP 仍承担原编解码任务,PA→ADC1 硬件回采仍保留,`adc_aec_config 1` 表示回采开启,不能据此认定原厂 DSP AEC 也开启。关闭范围是 SDK 暴露的 AEC 通路;不能由此断言闭源 DSP 内所有 ANS/AGC 等模块均关闭。
保持通话,先将需要保留的上一组快照导出,再开始新的一组:
```text
app aec freeze off
app aec dump clear
app aec dump start 10
```
必须确认 start 返回"请求已接收"。若返回 `请求失败 (-17)`,表示已有快照且新采集被拒绝;此时执行电脑 export 会重新导出旧快照。电脑目录名是导出保存时间,不能证明录音已经更新。`clear` 后再 start 才会建立新快照。
建议正式验证用 `app aec dump start 20`:0~8 秒仅远端讲话、板旁安静;8~14 秒仅板旁讲话、远端安静;14~20 秒双方同时讲话。前几秒供滤波器收敛。这样可分别判断消回声、保留近端和双讲表现。
10 秒采满自动停止快照,通话和算法继续。也可提前停止:
```text
app aec dump stop
app aec status
```
`capturing=0 ready=1` 表示可导出。时长范围 1~20 秒;最长三路占 PSRAM 1920000 字节。通话实时线程不写 NAND 或 USB。挂断、切 stock/off 或关闭媒体,会结束未采满快照,保留已处理部分。
下一通电话不自动覆盖快照。导出后准备下一组时显式执行 `app aec dump clear`。
实录核对示例:2026-10-15 18:21:17 的 start 返回 -17,18:22 保存目录 `aec_20261015_182224_788514` 的三路 PCM 及 metadata 与 17:59 的 `aec_20261015_175903_397023` 逐字节完全相同。这是同一快照重复导出,不能当成新一次 AEC 实验。
5. USB CDC 一条电脑命令导出
插板上 USB 数据线。如果当前是 U 盘模式,先在 Windows 安全弹出,再用普通 UART 发送:
```text
app udisk off
app usbserial on
```
已有 USB 自动监听模式的设备,插线后确认 CDC 枚举即可。CDC/U 盘/USB 声卡共享一个 USB 控制器。普通 UART 可以继续观察日志;电脑工具要用板上 **USB CDC COM 口**。
PowerShell 安装依赖、查询端口:
```powershell
python -m pip install pyserial
python -m serial.tools.list_ports
```
在工程 `` 根目录执行,COM14 改为实际端口:
```powershell
python .\application\bt_watch\tools\aec\aec_dump.py export --port COM14 --output-dir D:\logs\aec
```
工具自己发送 `app aec dump export`,不需要提前手动发送。它导出已有冻结快照,不重新采集;可在通话继续运行时导出,USB 线程优先级低于实时算法。关闭占用同一 CDC COM 口的串口助手,否则 Windows 拒绝访问;普通 UART 端口可保持打开。
长度和 CRC32 校验通过后,生成新的时间戳子目录:
| 电脑文件 | 用途 |
| --- | --- |
| `mic_before.pcm/.wav` | AEC 前 MIC |
| `reference.pcm/.wav` | 扬声器 REF |
| `aec_after.pcm/.wav` | CPU AEC 后 OUT |
| `before_then_after.wav` | 先前后后顺序试听 |
| `before_L_after_R.wav` | 左前右后对比 |
| `metadata.json` | 源类型、算法参数、采样数、丢样、数值复位、耗时、CRC |
| `试听说明.txt` | 试听方法 |
三路均为 16 kHz、16 bit、有符号小端、单声道。WAV 只添加文件头。CRC 错误、缺尾帧、导出失败时不发布成功音频;快照仍在板上,可重试。
确定 [1,3) 秒没有近端讲话时,可以额外计算 ERLE:
```powershell
python .\application\bt_watch\tools\aec\aec_dump.py export --port COM14 --output-dir D:\logs\aec --far-only 1 3
```
ERLE=`10*log10(MIC纯回声功率/OUT剩余功率)`。双讲时不能把近端声音当残余回声。本工具默认不猜纯远端区间。
6. 保存到 NAND,再通过 U 盘复制
先挂断,确保电脑没有接管 NAND:
```text
app aec dump save
app aec status
```
等待 `exporting=0 export_error=0` 及 `aec dump: saved ...` / `save done`,再 `app udisk on`。第一组根目录文件为:
| 文件 | 内容 |
| --- | --- |
| `AEC000M.PCM` | MIC,处理前 |
| `AEC000R.PCM` | REF,参考 |
| `AEC000O.PCM` | OUT,处理后 |
| `AEC000.TXT` | 参数、源类型、三路 CRC;完成后最后写入 |
后续依次 AEC001、AEC002。同组任一文件存在就跳过整组,避免覆盖。每路 `frames*2` 字节,10 秒为每路 320000 字节。
把 U 盘 PCM 转为可试听 WAV,例如 U 盘为 E:
```powershell
python .\application\bt_watch\tools\aec\aec_dump.py convert --mic E:\AEC000M.PCM --ref E:\AEC000R.PCM --out E:\AEC000O.PCM --output-dir D:\logs\aec
```
通话中保存 NAND 返回忙,避免慢速文件写影响通话。保存与原录音/LC3 共用预约及 FAT 锁;保存期间 USB U 盘切换和自动离线日志导出返回忙,完成后恢复可用。
7. 命令、参数与排查
| 命令 | 作用 |
| --- | --- |
| `app aec stock` | 原厂 DSP 前处理,开机默认 |
| `app aec off` | 旁路原厂前处理,单 MIC,不追加硬件 REF |
| `app aec nlms` / `app aec lms` | CPU 自研算法处理真实通话上行 |
| `app aec livecfg nlms` | 下次自研媒体默认 128 taps / mu=200‰ / delay=0 / swap=0 |
| `app aec livecfg lms` | 下次自研媒体默认 128 taps / mu=800‰ / delay=0 / swap=0 |
| `app aec livecfg nlms 128 200 32 0` | 自定义 taps、mu、delay、swap |
| `app aec freeze on/off` | 停止/恢复学习,已有回声估计仍参与抵消;也作用于离线任务 |
| `app aec dump start 10` | 实际自研通话的三路快照 |
| `app aec dump stop` | 结束快照,不停止通话算法 |
| `app aec dump export/save/clear` | CDC 导出 / NAND 保存 / 显式清空 RAM 快照 |
| `app aec stop` | 仅取消离线任务;退出自研通话应切 stock/off |
实时配置范围:taps 32~256,mu 1~1000‰,delay 0~4096 点,swap 0/1。运行时修改配置返回忙;先切 stock 并等完成,再 livecfg,最后切回 nlms/lms。切换会重置系数;不写 NVRAM,重启恢复 stock。
128 taps 在 16 kHz 覆盖 8 ms,256 taps 覆盖 16 ms。delay 补偿固定延迟,不能替代更长滤波器覆盖混响。实时默认 128 taps,与离线 NLMS 自检的 256 taps 不同。必须实板观察最坏耗时和丢样,不能仅凭软件测试保证实时效果。
`swap=0` 按 ADC0=MIC、ADC1=REF 拆分,依据板配置 `CONFIG_AUDIO_ADC_0_AEC_SEL=1`。上板需核对:只有远端讲话时 REF 明显有声;远端安静、只在板旁讲话时 MIC 明显有声而 REF 接近安静。如果相反,先切 stock,再 `app aec livecfg nlms 64 200 0 1`,重新切 nlms 测试。文件等长不能证明实际通道顺序、极性、回采幅度和声学延迟都已正确。
状态重点:
-
`call_error=0 pending=0`:模式请求已处理且未报告错误。
-
`running=1 frames持续增长`:CPU 线程正在处理。
-
`aec guard: weak/doubletalk/reject/promote`:参考弱的块数、疑似双讲块数、前台失配回退块数、后台系数发布次数。`promote=0` 长期不增长可能未得到可用模型;计数增加本身不能证明消回声足够。
-
`rms_mic/rms_ref/rms_out`:最近一块 RMS,单位为 int16 原幅度,不做独立音量归一化。
-
`raw_drop=0 out_drop=0 resets=0 fallback=0 error=0`:未检测到队列/编码输入丢样、数值复位或降级。
-
每块 160 点对应 10 ms,`overruns` 统计超过 10 ms 的算法块。平均值低不代表最坏情况下实时。
-
原始队列溢出会停止快照并 MIC 透传降级,明确 `fallback=1`;恢复 stock、降低 taps 或检查负载后重试,不能把缺帧数据当成功对比。
-
快照 source:1=合成 NLMS,2=合成 LMS,3=真实通话 NLMS,4=真实通话 LMS。
-
`-16`=忙;`-17`=已有快照,先导出再 clear;`-61`=无可导出数据;`-107`=CDC 未连接。
双讲学习实验可先让远端发声几秒,待系数学习后 `app aec freeze on`,再加入近端讲话观察。人工冻结不等于自动双讲检测。
8. 实现原理与 CPU/DSP 分工
```text
SCO下行 → 原DSP解码 → DAC/PA → 扬声器
└→ 内部PA→ADC1 REF ─┐
MIC → ADC0 ──────────────────────────────────┤
同一ADC FIFO / DMA交织 ┘
↓
DMA回调只复制到8KiB队列
↓
CPU FPU线程拆MIC/REF
↓
后台LMS/NLMS学习与验证
↓
前台固定滤波 + 保护 → OUT
↓
原录音stream → 原DSP编码 → SCO上行
MIC + REF + OUT → PSRAM快照 → USB CDC / NAND→U盘
```
没有再开一个 ADC 抢原厂设备,也没有修改闭源 DSP 参数。现有 `audio_record` 增加 VOICE 钩子,媒体服务仍管理 ADC 和编码器;CPU 消费双时隙输入后,将单声道结果写回原编码输入流。
stock:DSP AEC + 硬件回采;off:旁路 DSP AEC,无追加回采;lms/nlms:旁路 DSP AEC,保留硬件回采,由 CPU 拆分和处理。不能把原始 MIC/REF 交织数据当单声道上行。
实时线程优先级 8,栈 6144 字节、`K_FP_REGS`;打开自研采集期间持 PARTIAL 唤醒锁和高性能 DVFS,关闭释放。中断不做浮点、不等待算法、不操作 USB/FAT。停止先禁止新块,等待 worker 离开,再释放 stream/ADC,避免挂断后访问已销毁内存。
默认 stock 未注册钩子,继续原通话链路。切换失败尝试恢复旧模式,复用原 SCO/HFP 与 K1 音源策略。当前为单 MIC + REF,不等于双 MIC 降噪。
原厂回采依据:`CONFIG_AUDIO_VOICE_HARDWARE_REFERENCE=y`、`CONFIG_AUDIO_ADC_0_AEC_SEL=1`;`audio_record_create()` → `hal_ain_set_aec_record_back()` → `AIN_CMD_AEC_CONTROL` → `phy_audio_adc.c:adc_aec_config()`,开启 ADC1 `PAL2AD1_EN` 和 FIFO 时隙。MIC/REF 同属 ADC 采样域,无须外接回采线。
原厂前处理和 CVSD/MSBC 编解码沿用 `adMUSIC.dsp`。自研滤波在 CPU 上运行。闭源 DSP 内部 ANS/AGC/NLP 边界没有完全公开,stock/off 是 SDK AEC 前处理通路选择,不能保证只切一个内部线性模块。
v4 通话保护原理
`aec_guard.c` 在经典内核外维护前台与后台两个上下文。前台冻结系数,负责实际输出;后台学习。后台连续三个 10 ms 块的残差能量均小于前台和 MIC 的 75% 时,才将系数发布给前台,下一块使用新系数。短时能量判断有局限,不能等同完整商业双讲检测。
参考门限取 `max(0.00004, 最近参考峰值功率*0.01)`,信号归一化到 int16/32768;最低门限约为 207 LSB RMS,约 -44 dBFS。此值根据当前板采集的弱参考/串扰量级选择,参考增益变化后要重新核对。参考低于门限时保留 delay+taps 对应的短尾音,随后冻结学习并透传 MIC;历史仍连续推进。
通话 NLMS 分母改为 `sum(X^2)+1e-8+taps*参考门限功率`,避免参考暂停时很小的分母放大学习步长。已有模型解释不了 MIC 的明显增能时,后台步长降到原来的 1/16,允许慢速跟踪路径变化;不直接把后台残差输出到通话。前台残差功率超过 MIC 的 105% 时,该块返回原 MIC,防止失配产生额外声音。这里只限制异常增能,没有把整段音量压低来制造消回声效果。
`aec_lms.c` 将环形卷积拆成两个连续区间,旧状态每块检查一次,系数更新仍逐项检查 NaN/Inf/越界;AEC 内核与保护层单独 `-O3` 编译,不启用 fast-math。实时性仍以实板 `peak_us/overruns/raw_drop/out_drop` 为准,ARM 指令仿真耗时不是板端实时耗时。
9. 原离线文件与 MATLAB 功能
等长、单声道 S16LE 的 MIC.PCM/REF.PCM,8/16 kHz,根目录 ASCII 8.3 文件名;复制到 U 盘、安全弹出后:
```text
app udisk off
app aec config nlms 256 200 0
app aec run /NAND:/MIC.PCM /NAND:/REF.PCM /NAND:/OUT.PCM 16
app aec status
```
等待离线 `busy=0 result=0` 后 `app udisk on` 导出。已有输出拒绝覆盖。离线 config 支持 32~512 taps,与实时 livecfg 独立;离线任务不切通话算法,遇到电话会取消。
```text
MIC dn = sn + vn + h*xn
Xn = ref\[n-delay, ..., refn-delay-taps+1]
en = dn - Wn'*Xn
mu = mu_permille/1000
LMS: Wn+1 = Wn + (mu/taps)*en*Xn
NLMS: Wn+1 = Wn + mu/(sum(Xn^2)+1e-8)*en*Xn
```
内核 float32 运算,块间保留系数和历史;低参考能量停止学习,输出饱和 S16,数值异常复位并计数。LMS 步长额外除 taps,不能直接套教材中未缩放的 mu。
MATLAB R2021b 的生成、分析、逐样本核对说明见 tools/aec/README.md(tools/aec/README.md),只需基础 MATLAB。真实导出的数据可这样分析,`1 3` 必须确认为纯远端区间:
```matlab
addpath('D:/Master/6project/ATS3089C_to_ATS3089P//application/bt_watch/tools/aec');
d = 'D:/logs/aec/本次导出目录';
analyze_aec(fullfile(d,'mic_before.pcm'), fullfile(d,'reference.pcm'), ...
fullfile(d,'aec_after.pcm'), 16000, 1 3, d);
```
10. 代码位置、编译和验证
| 文件 | 职责 |
| --- | --- |
| `src/main/aec_lms.c/.h` | 离线/实时共用 LMS/NLMS 内核 |
| `src/main/aec_guard.c/.h` | v4 前后台滤波、正则化、弱参考/双讲与失配保护 |
| `src/main/aec_live.c/.h` | 原始队列、FPU 线程、拆分、编码前回注、耗时与丢样 |
| `src/main/aec_capture.c/.h` | 三路快照、CDC 长度/CRC 协议、NAND 自动编号 |
| `src/main/aec_research.c/.h` | 自检、文件处理、自检推送快照 |
| `src/main/system_app_shell.c:shell_aec()` | 普通 UART/CDC 指令、USB 模式互斥 |
| `src/btcall/btcall_aec.c/.h` | owner 异步模式切换、状态、失败回滚 |
| `src/btcall/btcall_media.c`、`btcall_event.c` | 实际媒体生命周期和消息 |
| `framework/audio/audio_record.c/.h` | VOICE 钩子、回采和关闭同步,保持 SDK 结构体布局 |
| `framework/audio/audio_policy.c/.h` | 原厂 AEC 策略,默认 stock |
| `tools/aec/aec_dump.py` | Windows/Python 上位机:CDC 接收、CRC、分路、WAV、试听、ERLE |
| `tools/aec/analyze_capture.py` | 原始 CRC 校验、RMS/削波/相关峰、统一纵轴波形与中文报告,不把双讲能量差当作 ERLE |
| `tools/aec/inspect_capture.py` | 标准库检查三路实录、相同块比例、每秒电平与 MIC-OUT 差值试听;输出中文检查报告 |
| `tools/aec/*.m` | MATLAB 参考和分析 |
| `tests/aec_live/` | 生产实时/导出与电脑工具回归 |
重要修改有 `//zk+`、`//zk+-` 与中文注释,Python/Kconfig/CMake 用对应 `#zk+`、`#zk+-`。
软件测试直接执行生产 C 的 Cortex-M33/FPU 指令,检查 MIC/REF→CPU→编码输入、三路等长、不覆盖、CRC、冻结、静音、溢出降级和资源释放。硬件/RTOS/存储边界使用替身,不能替代实板 ADC、声学、手机兼容性和最坏负载测试。
```powershell
python application/bt_watch/tests/aec_live/run_tests.py
python application/bt_watch/tests/aec_live/run_guard_tests.py
python application/bt_watch/tests/aec_live/test_pc_tool.py
python application/bt_watch/tests/aec_btcall/run_tests.py
python application/bt_watch/tests/aec/run_research_tests.py
python application/bt_watch/tests/aec/run_shell_tests.py
python application/bt_watch/tests/hfp_route/run_tests.py
python application/bt_watch/tests/spp/run_usb_tests.py
```
编译并打包,在 `` 根目录:
```powershell
Push-Location .\zephyr
python build.py -n -b ats3089p_dev_watch -a bt_watch
Pop-Location
```
固件输出目录 `application/bt_watch/outdir/ats3089p_dev_watch/_firmware/`:`ats3089p_dev_watch_261015_aec_live_v4.fw` 与 `ats3089p_dev_watch_261015_aec_live_v4_debug.fw`。
烧录后先确认版本,再做 stock 通话、CPU NLMS 三阶段采样和导出。核对通道、raw_drop/out_drop、peak_us、第二/第三次通话;最后复查显示、录音、ASR/TTS、音乐/歌词、Find my、NTC 和离线日志等原有功能。
重放本次真实 MIC/REF,生成处理结果
以下需要 ARM GCC 与工程已有的 Unicorn/pyelftools 测试依赖;重放执行生产 C,不是 Python 算法替代。输出目录必须是新目录,拒绝覆盖录音。重放从零系数开始,不能恢复快照开始前的滤波器状态。
```powershell
python application/bt_watch/tests/aec_live/run_guard_tests.py --capture-dir D:\logs\aec\aec_20261015_161145_793515 --output-dir D:\logs\aec\本次新的重放目录
```
生成 `aec_after_v4_replay.pcm/.wav` 和 `replay.json`。分析图与中文报告需要额外安装分析依赖;日常 USB 导出仍只需要 pyserial:
```powershell
python -m pip install numpy scipy matplotlib
python application/bt_watch/tools/aec/analyze_capture.py --capture-dir D:\logs\aec\aec_20261015_161145_793515 --replay-dir D:\logs\aec\本次新的重放目录 --window 6 8
```
`--window 6 8` 是人工指定的参考较弱区间,不表示已知纯近端。打开 `v3_then_v4_replay.wav` 或 `mic_then_v3_then_v4_replay.wav` 比较;`分析报告.md` 与 `analysis.json` 记录数值和局限。原始快照长度只有 10 秒;直接打开 WAV,避免 Audacity 原始 PCM 导入采样率错误导致时长/音调变化。
检查"处理前后几乎一样"的实录
只检查板端实际输出、不运行重放时,使用标准库工具:
```powershell
python application/bt_watch/tools/aec/inspect_capture.py --capture-dir D:\logs\aec\aec_20261015_173655_821933 --output-dir D:\logs\aec\aec_20261015_173655_821933_check01
```
新目录生成中文 `实录检查.md`、`inspection.json` 和原增益差值 `mic_minus_after.wav`。差值表示实际减去的信号,可能包含误消除的人声,不能当作干净回声真值。工具验证三路 CRC/长度,按秒统计 MIC/REF/OUT RMS、总电平变化,以及 10 ms 块逐点相同的比例。
本次 17:36 导出的 10 秒数据中,74.8% 的块完全相同,总电平仅变化 -0.0447 dB。该结果确认处理变化很小,当前 v4 不能据此视为完成实录 AEC 效果验证。弱参考透传、失配回退和系数发布门限都可能导致相同块;音频文件没有保存逐块保护原因,不能反推出每一块的真实 guard 状态。128 抽头仅覆盖约 8 ms,且当前发布条件要求连续三个块至少降低 25% 能量,仍需检查对实际声学路径及双讲的适用性。
下一轮先单独采 10 秒纯远端:板端扬声器持续播放远端声音,板旁保持安静,记录该次 `app aec status`;再分别采纯近端、双讲。只有明确确认纯远端起止秒数,才使用 `--far-only START END` 计算能量法 ERLE。总电平变小、波形改变和去回声不是同一个判定,需同时验证近端人声保留。
2026-10-15 17:59 新快照 `aec_20261015_175903_397023` 经用户确认仍是双讲:拨打 10086,同时在板旁讲话。用户进一步确认扬声器背向 MIC、回声较小,录音以近端为主。该组处理前后总电平变化 -0.1247 dB,69.2% 的 10 ms 块相同,不能由此认定回声抑制不足或计算 ERLE。近端占主导时,AEC 保留近端会使整体波形接近;回声抑制与近端保留需分别验证。
检查工具新增 `--scenario doubletalk` 等用户声源标注,随中文报告及 JSON 保存;默认仍为 unknown,禁止仅凭波形自动判断纯远端。完整使用方法见 tools/aec/README.md(tools/aec/README.md)。本次更新检查工具与说明,不改变板端 AEC 系数和通话音量。