zephyrrtos.cnZephyr 生态 · 中国驱动软件系统
v1.0.0 · RK3588 AMP · 音频子系统

AMP-AUDIO v1.0.0:把音频播放交给一颗 Zephyr guest

场景:RK3588 上想做低延迟、高确定性的音频输出,又不想放弃 Linux 的解码与混音生态。 传统做法是 Linux 独占 I2S——音频路径里塞着整个内核调度、电源管理和驱动栈,上游一卡就爆音。 这一版把末端的时序敏感部分挪到 AMP 从核:A76 跑 Linux 做解码与混音,A55 上的 Zephyr guest 直接驱动 I2S + PL330 DMA 出声, 两域之间用共享内存 ring + 邮箱门铃通信,中间带一个 240 ms 级抖动缓冲。

本包由 zephyrrtos.cn 工作组在 DSH 标准工具链下完成(远程操作 ROCK 5B+ 沙盒改内核模块、guest 镜像与脚本,每一步留收据)· 验证平台 Radxa ROCK 5B+ · Linux 6.1.75-abdbg · guest build 0x00003344

拓扑:谁负责什么

解码、多轨混音、音效Linux(A76)——有文件系统、有 libav、有充足算力
I2S 时序、DMA 搬运、抖动吸收Zephyr guest(A55 core3)——确定性:100 帧/s 固定节拍,不被 Linux 调度干扰
                 ┌──────────────── ROCK 5B+ (RK3588) ────────────────┐
   mp3 / 混音     │                                                  │
   /dev/ndp_pcm   │  ┌─────────── A76 ×4 · Linux 6.1.75 ──────────┐  │
       │           │  │  ndp_cons.ko                               │  │
       └──────────►│  │    · /dev/ndp_pcm  (用户态注入 1920 B 帧)   │  │
                   │  │    · L2Z ring  → 写入共享内存               │  │
                   │  │    · 邮箱门铃 → 通知 guest                  │  │
                   │  └───────────────────┬────────────────────────┘  │
                   │                      │ 共享内存 0x7800000 (4 MiB)│
                   │                      │ 邮箱 fec60000             │
                   │  ┌───────────────────▼──── A55 core3 · Zephyr ──┐ │
                   │  │  rk_i2s_tdm.c   I2S TDM 驱动                 │ │
                   │  │  rk_pl330_dma.c PL330 DMA API 驱动           │ │
                   │  │  rk_es8316_i2c.c 编解码器配置                │ │
                   │  └───────────────────┬──────────────────────────┘ │
                   │                      │ I2S0 0xfe470000            │
                   │                      ▼                            │
                   │              ES8316 codec ──► 耳机/喇叭            │
                   └───────────────────────────────────────────────────┘

两者之间不是「Linux 写 FIFO」,而是共享内存 ring + 门铃:Linux 侧往 ring 里放 10 ms 一帧的 PCM, guest 按自己的节拍取走、交给 DMA。Linux 卡一下,guest 靠缓冲顶住。Linux 侧永不等 ack—— 积压超过上限就丢最旧的(DROP_OLD),生产端永不阻塞,丢失量单独计数。

三条命令上手

bash scripts/verify-release.sh          # 1. 校验和 + 环境自检(不需要 root)
sudo bash scripts/amp-audio-up.sh       # 2. 拉起链路(装模块、点火 guest)
sudo python3 scripts/amp-tone.py        # 3. 听琶音 —— 通了就是通了

amp-tone.py 自带音频发生器,不需要任何 mp3 素材:它直接合成 1920 B/10 ms 的 PCM 帧写进 /dev/ndp_pcm。这正是「伴音发生器」的最小可用形态:一条跨域音频通路,能被三行命令验证, 而不是一堆需要先凑齐素材才能试的代码。

前置条件必须自己补:本包不含 Linux 内核(需 CONFIG_ROCKCHIP_AMP=y 自行编译)、 不含 DTB(需按 USAGE §2 加节点)、不含音频素材;binaries/*.ko 是针对 6.1.75-abdbg 编的,换内核必须重编,否则 insmod 报 Invalid module format。

能力与实测数字(全部来自本板运行)

PCM 帧1920 B = 10 ms(48 kHz / 16 bit / 立体声),送数节拍 100 帧/s
抖动缓冲深度241–288 ms(目标区间 150–300 ms);--prime 可调,默认 24 帧 = 240 ms
吸收上游卡顿停送数 200 ms 与 250 ms 均不断音、不产生 underrun(实测,含反向对照)
缓冲容量上限L2Z ring 32 帧 = 320 ms
I2S 数据搬运DMA(PL330 通道,不是 CPU 逐字喂 FIFO):fifo_full_waits=0、播放线程迟到峰值 8.75 µs
长跑4 分钟连续播放:i2s underrun=0、timeouts=0
上游用例Zephyr tests/drivers/dma 12/12、tests/drivers/i2s 15/15 编译用例通过
反向实验把 DT 请求线改成 1:DMA 吞吐从 470 400 塌到 3 840(122 倍),回滚后 md5 逐字节还原——证明数字不是摆设

为什么必须显式预充(最容易踩的一条)

guest 每 10 ms 取 1 帧,Linux 也是 100 帧/s,两者严格 1:1 ⇒ ring 占用恒定在起播时的初值。 初值为 0,ring 就永远是空的,此时的「缓冲」只是纸面参数。所以起播必须先用 --prime 把 ring 垫到目标水位,之后 1:1 的节拍会把它稳定维持在那个深度。

可观测性也是刻意做的:play: underrun / silence 会真实增长,不是假绿灯; 缓冲积压掉到 8 帧以下自动回到填充态(jb_resyncs 计数);jb-receipt.py 用「停送数 + 采样 guest 计数器」给出可复算的收据。

用例:写一段简谱,喂给 DSH,板子出伴音

这是工作组自己的用法:先写歌的简谱——纯文本,几行数字(1=D 76BPM、5 5 4 3 2 3 | 1—), 再把它连同「要什么风格、要不要厅堂、人声靠前还是靠后」一起喂给 DSH。DSH 在这块 ROCK 5B+ 上把谱子 编配成声部、合成成分轨 PCM、排成一条混音时间线,最后按 100 帧/s 灌进 AMP 从核, 由 Zephyr guest 驱动 I2S 出声。听到的是一段有伴奏的歌,不是提示音;伴奏、厅堂、人声、延时都是参数,不是重新录一遍。

从简谱到伴音:简谱 → DSH 编配/合成/混音/效果 → 灌流 → AMP guest → I2S → 喇叭 输入与制作(Linux 侧 · A76) 送达与出声(RK3588 板子) ① 你写:一段简谱(纯文本) 1=D 76BPM 5 5 4 3 2 3 | 1— 副歌 1 2 3 5 | 6 5 3 ② DSH 读谱编配 → 声部 钢琴 / 吉他 / 鼓 / 主旋律(簧片或人声感领奏)——编配方案可改 ③ 合成声部:每轨一段 PCM 合成器配方(谐波/气声/颤音/拨弦),不是采样库 ④ 混音时间线 mix.json 时段 start/end · offset · gain · fade_in/fade_out · 声像 ⑤ 效果参数(混音器现算,改完不必重合成) 厅堂 RT60 / 预延迟 / 干湿 · 人声颤音·气声 · 乐手时差 delay_ms / drift ⑥ 灌流:1920 B/帧 × 100 帧/s → /dev/ndp_pcm 共享内存 ring(32 槽 = 320 ms)+ 邮箱门铃;起播预充 24 帧 ⑦ AMP guest(A55 core3) 抖动缓冲 240 ms → PL330 DMA → I2S0(CPU 不逐字喂 FIFO) ⑧ ES8316 codec → 喇叭 / 耳机 ⇐ 你听到的旋律与伴奏;upstream 卡顿 ≤ 250 ms 不断音 箭头旁的数字都是实测口径:1920 B = 10 ms(48 kHz/16 bit/立体声)、100 帧/s、ring 32 槽、起播预充 24 帧 = 240 ms。

这条链路上的四个可调面

伴奏编配mix.json 的 clips[]:哪个声部、从第几秒到第几秒(start/end)、取音轨哪一段(offset)、多响(gain)、淡入淡出多长、摆在哪(声像)。同一段简谱能出钢琴版 / 吉他版 / 弦乐加厚版,旋律不用重写
剧场(厅堂)hall{rt60_s, pre_delay_ms, early[], damping_hz, wet, dry, stereo_width}:混音器合成 IR 做卷积。《成都》用的是 RT60 1.6 s、预延迟 16 ms 的小场;只改厅堂不必重合成,直接重播
人声领奏按簧片/人声感配方合成:奇次谐波 + 气声 + 5.6 Hz 颤音 + 起音滑音(-20~-40 音分滑回 0);每轨 reverb_send 决定送多少混响,drift_cents(±0.6 音分)给一点人的音准漂移。这是合成音色,不是真人声
延时parts[].delay_ms(0–15 ms)做「乐手反应时差」,配合厅堂预延迟,声音有前后层次而不是一刀切;两者都在灌流时现算
# 谱子与编配(示例:四声部民谣,简谱在 scores/ 下)
python3 make_chengdu.py            # 合成声部 → tracks_chengdu/
python3 make_chengdu_json.py       # 生成 chengdu.json(hall / parts / clips)
# 只试听混音,不出声(存 WAV 便于核对)
python3 amp-mix-play.py --edit chengdu.json --dry-run --wav /tmp/chengdu.wav
# 真放:前置检查 + 点火 guest + 按 100 帧/s 灌流(约 4 分钟)
bash chengdu-play.sh
边界(别把这段读成「任意简谱一键出成品」):编配/合成工具链在仓库 tools/amp-mp3-mix/, 不在 v1.0.0 发布包内;发布包只保证「混音后的 PCM → I2S 出声」这一段。简谱变声部是合成器编曲,不是采样乐团; 《成都》的旋律是凭记忆重建,不保证与原曲逐音一致(原谱只有图片,模型读不了图);所谓「实时效果」是灌流时按参数现算的 厅堂/延时/漂移,不是可交互的插件面板;改音色要重合成,改厅堂/延时/漂移只需重播。

边界与已知限制(诚实清单)

已验证单板 ROCK 5B+ · 内核 6.1.75-abdbg · 48 kHz/16 bit/立体声 · ES8316 耳机输出 · 4 分钟连续播放 · 抖动缓冲 241–288 ms · 停送数 200/250 ms 不断音 · PL330 DMA 搬运 I2S TX
未验证其它板型与内核;44.1 kHz 及其它采样率;录音(RX)路径端到端;多 guest / 多声卡;多小时浸泡(最长连续采样 213 s);上游 dma/i2s 用例本次发布未复跑
shm_mode=2 有一致性前提未证实共享区两域都映射为可缓存,依赖「A76 与 A55 同属一个一致性域」——这个前提没有做过 coh 判定实验,代码注释已标注风险。保守做法:先用 shm_mode=1 跑一轮;切换时两侧必须同时改
停 guest 只能重启amp-audio-down.sh 默认拒绝卸载(退出码 6):guest 活着时 rmmod ndp_cons + amp_clk_hold 会让板子在约 40 秒内无现场复位(时钟/供电域引用被放掉后 Linux 挂起 I2S/DMAC,而 guest 仍在访问寄存器)。--force 才继续;要停 guest 只能重启板子
不含什么Linux 内核、DTB 自动化改动、音频素材、rk_dma_i2s_tx.c 骨架(核查发现本板没有 DW AXI DMAC,未编译未随包)
已知死代码pcm_adapt(水位不足跑快 5%)只在测试钩子里生效,真实 /dev/ndp_pcm 写入路径不触发——别以为设了它就会自动补水位

获取发布包与许可

获取方式仓库 zephyrrtos_china/zephyr_ndp 的标签 v1.0.0-amp-audio(Gitea Release 附 AMP-AUDIO-v1.0.0.tar.gz 与 SHA256SUMS;仓库私有,需账号或会员访问)。本站不托管该二进制包
包内有什么7 份发布文档 + 5 份技术文档 + 5 个脚本 + 6 个产物(zephyr-amp.bin、3 个 ztest 镜像、ndp_cons.ko、amp_clk_hold.ko)+ 两份许可证原文
许可(分组件,非单一)文档 / 脚本 / docs/ / Zephyr guest 镜像 = Apache-2.0;ndp_cons.ko、amp_clk_hold.ko = GPL-2.0(内核模块的对应源码在仓库 linux/ndp_cons/、linux/amp_clk_hold/,转发时必须一并提供)
版权Copyright (c) 2026 zephyrrtos.cn working group
为什么本站不直接托管这个包:包里两个内核模块是 GPL-2.0 目标码,公开分发时接收者必须能拿到对应源码。 仓库(标签 v1.0.0-amp-audio)同时提供源码与二进制,是最不容易出错的对应关系; 站上只发布说明与边界,不制造「有二进制、没源码」的下载点。
问智能体