☰
基于炬芯ATS308X CPU方式实现opus解码算法实现
2026/10/15 6:56:11 网站建设 项目流程

实时录音由 `zephyr/drivers/audio/shell/audio_driver_shell.c` 处理:DMA 回调把每个已完成的 512 B 半缓冲复制到 8 KiB 环形暂存区,后台写入器每次取最多 4 KiB,同时写入 PCM 并交给 `opus_stream_feed()`。这样写盘期间 DMA 缓冲变化也不会丢失约 16 ms 的音频块。`rec_finish()` 排空暂存区、停止编码器,再关闭 PCM 文件。
`opus_stream_start/feed/stop` 在驱动中提供弱实现,实际编码实现在 `application/bt_watch/src/main/opus_cpu_file.c`。未链接编码器时,驱动仍能录制 PCM。离线转换与实时编码共用 64 KiB 工作线程栈和 64 KiB 编码状态缓冲区,由 `opus_cpu_token` 串行管理;录音运行时,`app opus_enc` 返回 `-EBUSY`。
`opus_stream_start/feed/stop` 在驱动中提供弱实现,实际编码实现在 `application/bt_watch/src/main/opus_cpu_file.c`。未链接编码器时,驱动仍能录制 PCM。离线编码、离线解码与实时编码共用 64 KiB 工作线程栈和 64 KiB codec 状态缓冲区,由 `opus_cpu_token` 串行管理;实时 Opus 录音运行时,`app opus_enc` 和 `app opus_dec` 都会返回 `-EBUSY`。
NAND FAT 文件系统按卷共享窗口,PCM 写线程和 Opus 编码线程通过 `opus_fs_lock()` / `opus_fs_unlock()` 串行访问。两侧都以 4 KiB 缓冲合并写入,避免小块频繁写盘导致 Opus 线程落后。`opus_cpu_file.c` 中的 `OPUS_CPU_COMPLEXITY` 控制编码复杂度;提高到 1 或 2 可能降低量化噪声,但会增加 CPU 负担。

▲展开20行展开所有行▼展开20行

上游源码:https://github.com/xiph/opus/tree/v1.3.1;下载地址:https://codeload.github.com/xiph/opus/tar.gz/refs/tags/v1.3.1。源码列表来自上游 `opus_sources.mk`、`celt_sources.mk` 和 `silk_sources.mk`,原始源码及许可保留在 `thirdparty/lib/opus_cpu`。
ATS3089P 的 GCC 编译和链接已在本地验证;设备上的转换、听感和运行时负载仍需实机验证。
## 将 U 盘中的 Opus 离线解压为 PCM
录音得到的 `rec.opus` 是 Ogg 容器格式,不是把每个 Opus 帧简单拼接得到的裸码流。离线解码时,固件先从 NAND/FAT 文件中读取 Ogg 页,按页中的 segment table 还原出完整的 Opus packet,再把 packet 交给解码器,最后把解码后的 16 位小端有符号 PCM 写回 U 盘文件。
### 命令
如果设备当前处于 USB MSC/U 盘模式,先在设备 shell 执行 `app udisk off` 退出 U 盘模式;文件系统由电脑占用时,固件不能同时读取同一个 NAND 卷。
```text
app opus_dec /NAND:/rec.opus /NAND:/rec_dec.pcm
```
输入和输出路径都可以自定义。省略参数时使用以下默认值:
```text
app opus_dec
# 输入:/NAND:/rec.opus
# 输出:/NAND:/rec_dec.pcm
```
输入必须是当前设备生成的 Ogg Opus:16 kHz、单声道、mapping family 0、output gain 0。输出路径不能与输入相同;如果目标文件已存在,命令会拒绝覆盖,请先换一个文件名或删除旧文件。
输入文件应为本工程录音生成的标准 Ogg Opus,当前离线流程按 16 kHz、单声道处理;输出是没有 WAV 头的裸 PCM,格式为 16 位小端有符号采样。可用下面的命令在板上试听:
```text
app pcm_play /NAND:/rec_dec.pcm 16
```
转换是阻塞操作,直到输入读完、输出同步并关闭后命令才返回。转换期间不要同时录音、播放同一个文件,也不要让 USB U 盘模式访问正在读写的 NAND。失败时固件会尝试删除本次新建的输出文件;若文件系统错误导致删除失败,须手动清理残留文件。
### 解码流程
1. `fs_stat` 检查输入是非空文件,打开输入和输出文件。
2. 扫描 `OggS` 页头,检查版本、页段数和页长度;根据 segment table 拼接跨页 packet。
3. 第一个 packet 必须是 `OpusHead`,读取声道数、输入采样率和 `pre-skip`;`OpusTags` 只作为元数据跳过,不送入 Opus 解码器。
4. 创建 16 kHz 解码器,逐个调用 `opus_decode`,将每帧的 `int16_t` 采样写入输出文件。写入前按 `pre-skip` 丢弃编码器延迟对应的采样,文件尾按 Ogg granule position 去掉填充采样。
5. 输入结束后 `fs_sync`,关闭两个文件并释放解码器状态;任何页格式、CRC、解码或文件系统错误都会终止本次转换并打印错误码。
输出 PCM 的采样率和声道数必须与命令支持的格式一致,否则不能直接使用 `app pcm_play` 播放。需要保留完整 Ogg 元数据或处理多声道、48 kHz 文件时,应先在电脑上使用 FFmpeg 转换。
## ATS3089P 的 DSP 接口和当前选择
ATS3089 SDK 公开了 DSP 会话层,相关头文件为 `zephyr/framework/include/dsp_hal.h` 和 `zephyr/framework/include/dsp_hal_defs.h`。CPU 侧的典型入口和职责如下:
| 接口 | 作用 |
| --- | --- |
| `dsp_open_global_session()` | 加载 DSP 镜像、申请 DSP 资源并启动会话 |
| `dsp_session_submit_command()` | 向 DSP 命令环提交初始化、配置、开始/结束等命令 |
| `dsp_session_buf_init/alloc()` | 建立 CPU 与 DSP 共用的环形数据缓冲区 |
| `dsp_session_buf_write/read()` | CPU 写入压缩帧、读取 PCM 或状态数据 |
| `dsp_session_kick()` / `dsp_session_wait()` | 通知 DSP 有新数据、等待 DSP 产生输出 |
| `dsp_close_global_session()` | 停止会话、断电并释放镜像和缓冲区 |
媒体框架的 `framework/media/codec_config.c` 预留了 `OPUS_TYPE` 的硬件解码配置:理论上打开 `CONFIG_DECODER_OPUS` 和 `CONFIG_DECODER_OPUS_HW_ACCELERATION` 后,解码器会标记为 DSP 加速,并使用 `adMUSIC.dsp`。ATS3089P 的板级文件位于 `boards/ats3089p_dev_watch/sdfs_k/adMUSIC.dsp`,运行时默认从 `CONFIG_DSP_IMAGE_PATH` 指定的目录加载。但当前工程配置没有启用 `CONFIG_DECODER_OPUS`,也没有给出可调用的 `.opus` 本地文件播放器入口。
这组接口说明芯片和 SDK 具备 DSP 解码通道,但并不等于可以直接把 `rec.opus` 文件交给 DSP。当前工程有以下边界:
- `adMUSIC.dsp` 是预编译二进制镜像,没有随工程提供 DSP Opus 解码器源码、DSP 工程模板或 packet 级应用协议;不能在本工程中修改其中的 Opus 算法。当前 `dsp_hal_defs.h` 中可见的是 `DSP_SUPPORT_OPUS_ENCODER` 编码侧能力,不能据此推断已有可用的离线 DSP 解码接口。
- `framework/media/parser_config.c` 没有 Ogg Opus parser,`local_player` 的文件扩展名识别也没有注册 `.opus`。DSP 解码配置只描述 codec,不负责从 Ogg 页中拆 packet。
- 因此 CPU 必须至少完成 Ogg demux、`OpusHead`/`OpusTags` 处理、packet 排队、共享缓冲区和播放/写盘节拍;若没有与 `adMUSIC.dsp` 匹配的私有命令格式,不能可靠地自行拼出 DSP 调用。
本命令采用已集成的 CPU `libopus` 解码器,并在固件侧增加 Ogg packet 处理和错误检查;不依赖 U 盘侧额外工具,也不会占用录音使用的 DSP 会话。代码已通过 ATS3089P 编译,实际文件转换和音质仍需上板验证。CPU 与文件系统之间通过 `opus_cpu_token` 和 `opus_fs_lock()` 串行化,避免离线转换与实时 Opus 编码同时使用同一套状态或 NAND 窗口。
### 后续接入 DSP 的方式
如果后续从 Actions 获取与当前 `adMUSIC.dsp` 版本匹配的 Opus decoder 协议,应保留上面的 Ogg demux 层,把每个完整 packet 写入 DSP 输入 ring,调用 `dsp_session_kick()`;DSP 输出 ring 中的 PCM 由 CPU 读取并写入 `.pcm` 文件。初始化顺序应为:打开 session、配置 `DSP_CODEC_FORMAT_OPUS`/采样率/声道、建立输入输出 buffer、提交 begin 命令、循环 packet、提交 end 命令、排空输出、关闭 session。每次切换 DSP 镜像或 SDK 版本都必须重新验证 packet 格式、buffer 地址映射、丢帧计数和输出采样数,不能把 Ogg 页头直接送入 DSP。
DSP 编程入口和 CPU-DSP 交互层已经在 SDK 中开放,但具体 Opus packet 命令、DSP 内存布局和镜像构建工具不在当前工程公开范围内;在拿到厂商协议前,CPU 路径是可重复构建和可现场验证的实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询