先讲一件让我印象很深的事
我用一套脚本流水线批量做音频内容,几十个文件,全部渲染完成,全部听过一遍,「没问题」。
交付时被退回来了。
原因不是音质,不是时长,不是响度------是一个编号规则。对方要求所有内容文件按顺序连续编号,从 1 开始。我的文件从 2 开始编,因为我把片头和片尾也算进了序号里。
他们那边看到的是「第 1 部分缺失」。
这件事最扎心的地方不是规则本身,而是:每一个环节都没有报错。 文件渲染成功了,我听过没问题,上传成功了,页面也收下了。问题出在「没有人用机器的方式验证过一遍」。
从那之后我想明白一件事:这条流程里最不可靠的环节,是「人来判断行不行」。
一、AI 没有耳朵
给人用的音频软件------Audacity、Audition、剪映------它们的设计有一个隐含前提:用户有耳朵。
波形图是给你看的,播放按钮是给你听的,「这儿有点爆」是你的耳朵告诉你的。整个验证闭环建立在人的感官上。
AI 没有这条通道。
它能渲染出一段音频,但它不知道自己做得对不对。你让它「把整条音轨的响度控制在 -20 dBFS 附近」,它会照做,然后回复你「已完成」。至于完成得是否达标,它不知道,你也不知道------直到某个下游环节把你拦下来。
于是分工变得很别扭:AI 干了 95% 的活,然后人肉去核对那 95% 到底对不对。而核对往往比生产还慢。
问题的本质不是「AI 做不好音频」,而是「音频这个领域没有为 AI 设计的反馈通道」。
二、换个思路:把音频后期当成编译
我想要的其实不是「更好用的音频工具」,而是一个编译器。
编译器都有什么?三样东西:
| 编译器 | 音频后期 |
|---|---|
| 源码 | 一份声明式的工程文件(IR) |
| 编译 | 一次确定性的构建(render) |
| 诊断信息 | 一份 PASS/FAIL 报告 + 可执行的修法 |
音频后期完全可以这样拆。于是有了 acomp:
markdown
写 IR (project.yml) → acomp render → 产物 + 测量报告(数字 / PASS-FAIL)
↑ │
└────────── 读报告,改一行,再来一次 ────────────┘
IR 是源码,render 是编译,报告是编译器的诊断信息。音频库、有声书、播客、课件------只要是一堆片段拼成一条音轨,都归它管。
三、它长什么样
没有波形图,没有「你听一下」。render 打出来的就是验收结果:
scss
ID DUR(s) PEAK(dB) RMS(dB) LEAD TRAIL RESULT FAILED TARGETS
-------- ---------- --------- --------- -------- -------- ------- --------------------------
S01 9.00 -16.99 -20.51 0.00 1.00 PASS
S02 9.00 -0.91 -4.44 0.00 1.00 FAIL peak_dbfs,rms_dbfs
S03 8.00 -16.99 -20.00 0.00 0.00 PASS
MASTER 25.20 -0.91 -8.84 0.00 0.00 FAIL peak_dbfs,rms_dbfs
结果:2/3 段通过,master FAIL,整体 NEEDS FIX
这一段就是核心:把「听起来还行」换成 PASS/FAIL。
IR 本身是一份普通的 YAML:
yaml
version: 1
project:
name: chapter_01
out_dir: ./out
targets: # 统一 [min, max],任一端可写 null
total_duration: [300.0, 420.0]
peak_dbfs: [null, -3.0]
true_peak_dbfs: [null, -3.0] # 真峰值,交付验收口径
rms_dbfs: [-23.0, -18.0]
lead_silence: [null, 0.25]
trail_silence: [null, 0.25]
segment_checks: [peak_dbfs, rms_dbfs]
segments:
- id: S01
src: raw/S01.wav
trim: {start: 0.0, end: null} # 绝对时间戳,不是「裁多少秒」
gain_db: 0.0
pad_after: 1.0 # 段尾留 1s 静音
crossfade_next: 0.4
output:
master: chapter_01_master.wav
limiter:
enabled: true
limit_db: -3.2
attack: 5
release: 50
mp3:
bitrate: 192k
它有三个好处,是命令行脚本给不了的:
- 可以进 git。 音频工程的「状态」不再散落在命令历史和一些没人看得懂的 shell 脚本里,而是一份可 review 的文本。改了什么,
git diff说话。 - 可以 diff。 两份 IR、或者两份测量报告,
acomp diff给出的是语义级差异(「MASTER 从 FAIL 变 PASS」),不是文本差异。 - 可以被程序生成。 这条后面单独讲,是最有用的一条。
四、三个我觉得关键的决策
1. 退出码要有语义
一般脚本要么「成功=0 / 失败=1」。但在音频验收里,「构建失败」和「构建成功但数字不达标」是完全不同的两件事。所以 acomp 的退出码是:
| 退出码 | 含义 |
|---|---|
0 |
全部达标 |
1 |
构建成功,但有 FAIL(不达标 ≠ 失败:产物在,只是数字不过关) |
2 |
出错(源文件缺失、参数非法等),stderr 是结构化 JSON |
这个区分看起来小,作用很大:1 是可以自动重试的------读报告、改一个参数、重跑;2 是必须人来处理的。自动化流程里,这两条路的差别是「能不能无人值守」。
2. 错误信息要可执行
出错时不该让调用方猜。举一个真实的错误对象:
json
{
"code": "SRC_MISSING",
"what_failed": "段落 S02 的源文件不存在:/work/raw/S02.wav",
"minimal_fix": "把文件放到该路径,或改 segments[1].src",
"retryable": false
}
注意 minimal_fix:它写的是「把 X 改成 Y」,不是「请检查配置」。
这是给程序(尤其是 AI)读的字段------它必须知道下一刀切在哪里,而不是知道「出了个问题」。
3. 先预演,再动手
validate(只校验 IR)和 plan(探测素材、预估时长、判定目标是否可达)都不写盘。
所以整个流程可以是这样:AI 先跑 plan,零成本地知道「照这个参数做到什么程度」;有把握了再 render。
这一条对 AI 协作特别重要------它把「试错」的成本压到了零。
五、一段真实的算术:限幅不是审美,是数学
这个坑我觉得值得单独说,因为它经常被当成「音质问题」,其实是个代数问题。
常见交付要求是两条:Peak ≤ -3 dBFS ,同时 RMS ≥ -23 dBFS。
把这两条放在一起看:
scss
crest(峰均比) = peak - rms ≤ (-3) - (-23) = 20 dB
而素材的 crest 是由内容决定的,调增益只能整体平移,改不了 crest。
所以当某段素材的 crest 已经超过 20 dB 时,任何纯增益方案在数学上都不可能同时满足这两条------必须压峰值(限幅或压缩)。这跟母带审美没有关系,是算术。
acomp plan 会在 render 之前做这道判定:测出每段的真实内容窗口、算出 master 的 crest,如果判定「纯增益无解」,直接告诉你「只能靠限幅达成」,并给出建议的增益和限幅阈值。
bash
python acomp.py probe <file> # 看 crest:peak 减 rms 超过 20 就别指望调增益能过
python acomp.py plan proj.yml # 动手之前先问「能不能到」
省下的是「先试一遍、发现不行、再试一遍」那种来回。
顺带说两个会骗人的口径
① 采样峰值 ≠ 真峰值。 mp3 编码后,采样点之间的真实波形峰值会比采样峰值高 0.2~0.4 dB。你拿 astats 测出「peak −3.24,达标」,用 ebur128 一测真峰值是 −2.9------其实超了。所以限幅阈值要按真峰值倒推,不能只给采样峰值留余量。
② RMS ≠ LUFS。 同一段音频,RMS −22.4,LUFS 可能是 −17.8,差 4 dB 以上。下游按哪个口径验收,就在 targets 里写哪个,别拿 RMS 当 LUFS 用。
这两个坑的共同点是:它是「口径问题」而不是「质量问题」,但后果跟质量问题一样------被退回。
六、IR 是数据,所以可以被程序生成
这是整套设计里我最满意的一条。
因为 IR 就是一份普通的数据结构,所以它不需要人手写------可以程序生成。
仓库里带了一个例子 tools/batch_fix.py:
bash
# 只体检:打印每个文件的电平/静音/需要补多少增益,不写任何音频
python tools/batch_fix.py --src /path/to/delivery_package
# 体检 + 渲染修复版 + 按交付口径复检
python tools/batch_fix.py --src /path/to/delivery_package --render
它做的事是:扫一遍目录,测每个文件的 peak / rms / crest / 首尾静音,算出该补多少增益、该裁多少静音,然后生成对应的 IR 交给 render 去构建。
全程没有人打开过音频编辑器。
这就是「状态是文本」带来的复利:你写的是一份数据,于是它能被脚本生成、被程序批量修改、被 AI 直接产出。
七、怎么跑起来
不装任何东西,不需要手动配 ffmpeg(由 imageio-ffmpeg 自带,也不需要 ffprobe):
bash
# 国内网络建议用 Gitee 镜像
git clone https://gitee.com/dusknook/acomp.git && cd acomp
git clone https://github.com/dusknook/acomp.git && cd acomp
python demo.py # Windows 双击 demo.bat;macOS/Linux 用 ./demo.sh
demo.py 会自动建虚拟环境、装依赖,然后跑完整个闭环:造 3 段合成测试音(其中一段故意过响)→ validate → plan → render(过响版:报 FAIL,退出码 1)→ 改一行 gain_db → render(全部 PASS,退出码 0)→ diff 两份报告。
整个过程十几秒,跑完你就看到「一个不达标的音频是怎么被定位、被改掉、被复验的」。
依赖只有 pyyaml / numpy / imageio-ffmpeg 三个,MIT 许可,随便商用改。
八、已知的限制(不藏着)
- 只做非线性编辑里最常见的几种操作:裁剪 / 增益 / 淡入淡出 / 静音填充 / 交叉淡化。降噪、EQ、压缩这类没进 IR------它们参数空间太大,目前建议先交给 ffmpeg 滤镜字符串。
- 真峰值是逐段实测(ebur128),不是经验公式估算,所以
plan会慢一点,想跳过可以加--no-feasibility。 - 可选的对齐检查依赖
faster-whisper,没装就静默跳过,不影响主流程。 - 目前只支持音频;视频容器、多轨混音不在范围内。
最后
这个工具解决的不是「音频做得好不好听」,而是**「音频做得对不对,能不能不靠耳朵就知道」**。
如果你也在用脚本或 AI 批量处理音频,被「交付标准对不上、来回返工」烦过,欢迎试试看,也欢迎把你的 IR 例子提上来------交流真实案例比点赞有用得多。
仓库:
- GitHub: github.com/dusknook/ac...
- Gitee: gitee.com/dusknook/ac...
有 issue 和 PR 都欢迎。