BLE 录音控制
本功能在 `bt_watch` 的蓝牙引擎就绪后注册独立的 GATT 服务。原日志中的设备名为
`zkBLE`,已成功连接并协商 MTU=247;本协议每条指令仅 4 字节,默认 MTU 也可使用。
服务和特性
| 项目 | UUID / 属性 |
| --- | --- |
| 录音服务 | `7b2e1000-8e7f-4d3a-9c61-2a3089c00001` |
| 录音控制特性 | `7b2e1001-8e7f-4d3a-9c61-2a3089c00001` |
| 特性属性 | Read、Write(有响应)、Notify |
| CCC 描述符 | `0x2902`,写入 `01 00` 订阅通知 |
连接后发现服务,通过 UUID 找到特性。服务 UUID 不额外加入现有广播包。
手机若缓存了旧服务列表,需要刷新 GATT 服务缓存或重新发现服务。
4 字节协议
以下取值是这次新增协议的默认约定,可在 `src/main/ble_record_protocol.h` 修改。
UUID 定义位于 `src/main/ble_record_service.c`。
| 字节偏移 | 字段 | 含义 |
| --- | --- | --- |
| 0 | 帧头 | 固定 `0xAA` |
| 1 | set/get | `0x00`=GET,`0x01`=SET |
| 2 | 控制字段 | SET:`0x00` 停止、`0x01` 启动、`0x02` 暂停、`0x03` 恢复;GET 请求固定填 `0x00` |
| 3 | CRC | 前 3 字节的 CRC-8 |
CRC 参数:宽度 8,多项式 `0x07`,初值 `0x00`,结果异或值 `0x00`,
输入/输出均不反射。标准检查向量 ASCII `123456789` 的结果为 `0xF4`。
每次 GATT Write 必须恰好提交一帧;不支持分片、拼接、非零 offset 或 Prepare Write。
| 操作 | 发送的 HEX 字节 | 成功通知的 HEX 字节 |
| --- | --- | --- |
| 启动录音 | `AA 01 01 DD` | `AA 01 01 DD` |
| 停止录音 | `AA 01 00 DA` | `AA 01 00 DA` |
| 暂停录音 | `AA 01 02 D4` | `AA 01 03 D3`(状态为暂停) |
| 恢复录音 | `AA 01 03 D3` | `AA 01 01 DD`(状态为录音中) |
| 查询状态 | `AA 00 00 CF` | 停止:`AA 00 00 CF`;录音中:`AA 00 01 C8`;暂停:`AA 00 03 C6` |
**请在 BLE 调试工具选择 HEX 输入,上表不是要发送的 ASCII 字符串。**
通知沿用请求的 set/get 字节,控制字段表示执行结果;CRC 重新计算。
直接 Read 特性返回 GET 格式的实时状态。串口启停也会反映到 Read/GET。
串口暂停/恢复同样更新该状态。**请求的控制字段是动作,应答的控制字段是执行后的状态,
因此暂停/恢复的成功应答并非原样回显请求。** 原有启动、停止、GET 和忙状态取值保持兼容。
| 应答控制字段 | 含义 |
| --- | --- |
| `0x00` | 已停止 |
| `0x01` | 正在录音(包含沿用原实现的通话音频接管阶段) |
| `0x02` | 有请求待处理,稍后再读取;仅用于 Read,完整帧 `AA 00 02 C1` |
| `0x03` | 用户已暂停,文件保留;GET 完整帧 `AA 00 03 C6` |
| `0x81` | 录音通道被其他用途占用或已创建的配置不兼容;SET 通知 `AA 01 81 54` |
| `0x82` | 创建、启动、文件写入或停止收尾失败;SET 通知 `AA 01 82 5D` |
| `0x83` | 当前没有已启动的文件录音,无法暂停/恢复;SET 通知 `AA 01 83 5A` |
`0x81/0x82/0x83` 是执行结果,实际状态可再用 Read/GET 查询。串口日志含具体负错误码。
通知只在执行 GET/SET 后发送,不主动推送每次串口状态变化或之后发生的磁盘错误。
推荐先订阅 Notify,再发指令,以获得执行失败的明确应答;未订阅时仍可写入和读取状态。
通知因链路原因失败时会输出日志,可 Read/GET 重新查询。
ATT Write Response 仅表示指令已被接收。文件创建、ADC 启停、PCM 排空和 Opus 收尾在
应用主线程执行,完成后才发送通知。客户端应等待通知或 Read 不再返回 `0x02`,再发下一条。
最多接受一个待处理请求;忙时返回 ATT `0x11`。队列投递失败同样返回 `0x11`,且不会执行指令。
非法帧返回 ATT 错误并且不会改变录音:长度错误 `0x0D`,帧头/操作/控制字段/CRC 错误
`0x13`,非零 offset `0x07`,Prepare Write `0x06`。使用有响应的 Write 才能看到这些错误。
录音行为
首次 SET 启动自动完成以下串口操作对应的底层流程:
```text
app rec_max 0
audio record create sr=16 ch0_gain=200 dump_len=16 rec_file
audio record start
```
采用 ADC microphone 0、16 kHz、16-bit、增益 200(20 dB)、dump_len=16,无时长上限。
文件沿用 `CONFIG_APP_FAT_DISK/rec.pcm`;当前 ATS3089P 配置为 `/NAND:/rec.pcm`。
保留原来的并行 `/NAND:/rec.opus` 编码和通话录音接管行为;Opus 启动失败时沿用原逻辑,只录 PCM 并输出警告。
-
已在录音时重复 START 不重建、不截断文件,并移除原先的时长限制。
-
已暂停时 START(包括串口 `audio record start`)恢复原文件,不重建文件;BLE START 还会移除原先的时长限制。
-
UART 已创建且参数相同的录音实例可由 BLE 启动;不同参数的未启动实例返回忙。
-
STOP 停止采集,等待写入任务及 Opus 收尾,关闭文件并释放通道;重复 STOP 成功。
-
STOP 后再次执行串口 `audio record start`(默认 `chl_id=0`)会自动创建新的默认 PCM/Opus 文件并启动,行为与 BLE START 一致;
如果需要自定义采样率、输入设备或增益,请先重新执行 `audio record create ...`。仅执行 create 不会自动开始采集。
- PAUSE 停止接收麦克风和通话音频,排空暂停前的 PCM 缓冲并同步文件。
普通暂停保持 ADC/DMA 和麦克风偏置稳定运行,暂停期间采集的数据直接丢弃;通话需要 ADC 时仍由通话接管逻辑释放。
PCM/Opus 文件与编码器继续保留;Opus 线程可继续处理暂停前已接收的少量音频。
- RESUME 续写原文件,暂停期间不插入静音;正在通话时由原通话桥接继续提供音频。
丢弃恢复后的第一个 DMA 半缓冲,避免把恢复前的采样带入文件(16 kHz 单声道时最多舍弃 16 ms 的恢复后音频)。
16 kHz、16-bit、单声道 ADC 录音在拼接处用 5 ms 从上段最后一个采样过渡到新音频,PCM 和 Opus 使用同一处理结果。
暂停/恢复均保留采样率、增益和已录音数据,单独 RESUME 不修改时长限制。
- 重复 PAUSE 或在录音中重复 RESUME 均成功且不重复初始化。未开始录音、仅执行 create 或已 STOP 时,
PAUSE/RESUME 返回 `0x83`,不会隐式创建新文件。暂停状态也可直接 STOP。
- 用户暂停期间,通话开始/结束不会解除暂停,也不会把通话音频写入该文件。
恢复时 ADC 被其他用途占用会返回忙,保留暂停文件,释放占用后可再次 RESUME。
若已用 `app call_rec auto off` 关闭自动通话接管,通话中的 RESUME 返回忙,通话结束后可重试。
-
下一次新的 START 会重新建立文件,覆盖前一次的 `rec.pcm/rec.opus`,与原串口 create 行为一致。
-
PA/I2S 等其他用途占有录音实例时,BLE 启停返回忙。串口和 BLE 的生命周期操作共用互斥锁。
-
录音和暂停期间保持 CPU 唤醒,保留文件、编码器及通话监测资源;普通暂停也保留 ADC,功耗高于关闭 ADC 的旧实现。
BLE 断开不会自动停止或恢复录音;已接受的请求仍执行。
重连后可查询/停止,旧连接的应答不会发送给新连接。
- 存储打开失败会释放已打开的 ADC;写入失败会停止文件写入并记录错误。
STOP 时向 BLE 返回保存的错误,之后可重新 START。
串口暂停/恢复
沿用原 create/start/stop,新增:
```text
audio record pause
audio record resume
```
默认控制 `chl_id=0`,也支持 `audio record pause chl_id=1` / `audio record resume chl_id=1`
控制已启动的对应文件录音通道。PA/I2S 等非文件录音返回忙;无效通道号返回错误。
BLE 使用通道 0。两种控制入口共享同一状态,可以串口启动、BLE 暂停、串口恢复、BLE 停止。
完整示例:
```text
app rec_max 0
audio record create sr=16 ch0_gain=200 dump_len=16 rec_file
audio record start
audio record pause
audio record resume
audio record stop
```
正常串口日志包含 `recorder0 paused` / `recorder0 resumed`;错误日志包含失败原因的负错误码。
2026-09-30 音量与暂停恢复爆音修复
当前 `ats3089p_dev_watch` 的 `board.c` 将默认 `AUDIO_ANALOG_MIC0` 映射到 ADC0 的差分输入,ADC1 关闭,
且本次构建没有启用 `CONFIG_CFG_DRV` 外部映射。因此旧代码执行 `ch1_gain=200` 时,实际工作的 ADC0 仍是默认的 0 dB,
比预期的 20 dB 小。建议使用上面的 `ch0_gain=200` 指令。
新版也兼容只指定一个增益参数的单声道文件录音:按板级映射将该增益应用到唯一启用的 ADC。
因此用户原指令 `audio record create sr=16 ch1_gain=200 dump_len=16 rec_file` 也能得到 20 dB,
并输出 `mono input ... uses ADC0: ch1_gain=200 applied to ch0_gain`;底层应打印 `ADC channel gain {200, 0}`。
多个显式增益、双声道录音及非文件录音保持逐通道含义;外部驱动映射启用时不推断通道。
旧版暂停调用了 `audio_in_stop/close`,恢复重新打开 ADC;底层启动静音的 `timestamp/already_discard`
却是全局静态变量,第一次采集后不再重置。这样后续上电的模拟启动瞬态会直接进入录音,
暂停前后采样的硬拼接也可能产生点击声。现已改为上述保留 ADC 的暂停方式、DMA 边界丢弃和 5 ms 平滑拼接;
同时将 80 ms 启动静音改为每个 ADC 会话独立保存、每次 DMA 启动重新计时,保护重新录音和通话后重新打开 ADC 的过程。
正常连续录音的采样不做缩放;拼接处理只用于上述 16 kHz、16-bit、单声道格式。
`log_26_9_29.txt` 中的复现链路是:15:59:46 create 成功,15:59:51 start 成功;
16:00:04 stop 释放句柄,16:00:40 再次单独 start 才出现 `audio recorder0 does not create yet`。
这条错误表示没有可启动的 recorder 实例,并非 ADC 打开失败。
工作区现有 `log_26_9_29.txt` 截止到 9 月 29 日,包含历史版本的 `{200, 0}` 和 `{0, 200}` 增益日志,
没有这次用户执行的 `record pause/resume`。以上定位来自当前板级配置和驱动代码,尚需新日志及实机回放确认音质。
构建和验证
`CONFIG_BLE_RECORD_CONTROL` 默认启用,依赖 `BT_MANAGER`、`BT_BLE`、
`AUDIO_DRIVER_STREAM_SHELL`,模拟器中关闭。
本次在已有 `outdir/ats3089p_dev_watch` 配置下构建。已有构建目录可从 SDK 根目录运行:
```powershell
$env:ZEPHYR_BASE = (Resolve-Path zephyr).Path
ninja -C application/bt_watch/outdir/ats3089p_dev_watch
python application/bt_watch/tests/ble_record/run_tests.py
```
2026-09-30 验证结果:固件编译/链接通过,下面的自动化测试通过,烧录包已生成:
```text
outdir/ats3089p_dev_watch/_firmware/ats3089p_dev_watch_260930_rec_audio_fix.fw
outdir/ats3089p_dev_watch/_firmware/ats3089p_dev_watch_260930_rec_audio_fix_debug.fw
```
本次修复的构建和打包日志位于 `outdir/ble_record_tests/rec_audio_fix_build.log` 和
`outdir/ble_record_tests/rec_audio_fix_pack.log`。原 BLE 启停已由板端验证;本次音频修复尚未上板验证。
测试使用实际协议和 GATT 服务源代码,以及从驱动原样提取的公开启停接口,替换蓝牙、
RTOS 和采集硬件调用,并执行实际暂停/恢复、通话接管、通话写入门控及串口入口函数。使用 ARM GCC 编译,在 Unicorn 中执行;依赖 `pyelftools` 和
`unicorn`(也可放在 `outdir/ble_record_tests/deps`)。覆盖 CRC 检查向量、32 个单比特错误、
65,531 种非法操作/控制组合、异步队列与连接引用、重复启停/暂停/恢复、录音参数及失败清理。
另覆盖暂停期间通话不能自动恢复、恢复失败后重试、暂停后 STOP、START 恢复原文件和非法串口通道号。
音频回归覆盖单声道增益映射、普通暂停不关闭/重启 ADC、恢复时 DMA 边界丢弃、连续录音采样逐字节不变、
正负大幅跳变的 5 ms 拼接、跨写入块的拼接状态,以及实际底层 DMA 回调的每次启动保护、两个半缓冲、多会话和计时回绕。
这些检查不包含真实 ADC、闪存、手机协议栈或音频质量验证。
板端验证步骤:
-
启动日志应出现 `BLE record service registered`,连接 `zkBLE` 后找到上述服务/特性。
-
订阅 Notify,发送 `AA 01 01 DD`,确认收到同样的启动成功帧。
-
连续录音超过 32 秒,GET 应仍返回 `AA 00 01 C8`;再次 START 不应重新打开文件。
-
发送 `AA 01 00 DA`,等停止通知后检查 PCM/Opus 文件长度和回放。
-
重复 STOP、再次 START/STOP,并交叉使用串口启停,核对 Read/GET 状态。
-
发送错误 CRC `AA 01 01 DC`、错误长度或非法控制值,确认 ATT 错误且状态不变。
-
录音中断开并重连 BLE,确认录音持续且可停止;通话接管期间 STOP 后应可再次 START。
-
在 PA/I2S 占用、存储不可用等条件下确认错误结果,再恢复条件重试。
-
录 5 秒,发送 `AA 01 02 D4` 暂停 5 秒,确认应答 `AA 01 03 D3`,GET 为 `AA 00 03 C6`。
暂停期间说话,发送 `AA 01 03 D3` 恢复后再录 5 秒并 STOP;回放应约 10 秒且不含暂停段或 5 秒静音。
-
交叉执行 BLE/串口暂停和恢复,并重复执行两者;确认始终续写同一文件。
-
暂停中接听/结束通话,确认始终保持暂停;通话中主动 RESUME 应恢复通话录音。
-
暂停后直接 STOP,再发 RESUME 应返回 `AA 01 83 5A`;再次 START 应开始新文件。
-
分别使用 `ch0_gain=200`、单独 `ch1_gain=200` 创建录音,两者均应打印 `ADC channel gain {200, 0}`,回放音量一致。
-
普通暂停/恢复反复执行 10 次,日志不应出现 ADC close/open;检查 PCM 和 Opus 拼接处无明显爆音,
且暂停期间的声音没有写入。再验证暂停期间接听/挂断电话后恢复,及同一次开机内 STOP 后直接 start。