能量包络找起点:10 个字全部检到,误差稳定在 10 毫秒(一帧的固有延迟)。 但只要两个字连读,起点就只剩 9 个——按顺序硬对齐,后面 5 个字全部错位 390 毫秒。 这就是「数起点」和「强制对齐」的差距。
上一节剪好了素材。这一节配歌词。 「识别歌词」听起来是一件事,实际是两件:把话听成字,和把字对上时间。
一、两条路线:识别,还是对齐
路线一:语音识别(ASR)。 从音频里听出文字。 适合「我不知道唱的是什么」。 难点是歌声的旋律会破坏语音特征, 用说话数据训的 ASR 直接拿去听歌,准确率会掉很多。 路线二:强制对齐(forced alignment)。 文字已经有了(歌词本),只要把每个字对上时间。 适合「我知道歌词,只要时间轴」。 做 MV 字幕, 绝大多数情况是路线二—— 因为歌词是现成的,你不需要机器去听写。需要的是「这一句在 1 分 23 秒出现」。 📋 实操清单:平台上的「智能识别歌词」通常给你两个产物——文本(听写结果, 要校对)和 LRC 时间轴(每句的起始时间)。校对文本一定要做,尤其是人名、英文、方言词。
二、能量包络找起点:10 个字全中,但晚一帧
最朴素的对齐思路: 把音频切成 10 毫秒一帧,算每帧的能量(RMS),能量从低跃过阈值的那一帧就算一个「字」的起点。 Demo 按这个做,10 个字全部检到,误差整齐地都是 +10 毫秒。 这个 10 毫秒不是随机误差,是系统延迟:判据是「跨过阈值的第一个帧」, 而起音有一个 50 毫秒的爬升过程,所以要爬两三帧才过阈值。 系统延迟可以整体补偿掉——减去 10 毫秒就对齐了。这跟上一章「能量检出的固有延迟」是同一类问题: 先弄清偏差的方向和来源,再谈修不修。 这条路线能用的前提是:每个字之间真的有气口。说话可以,唱歌不行。
三、连读:只漏一个,崩一片
把第 5、6 个字之间的气口去掉(模拟连读),重跑:
| 字序 | 真实起点 | 检测起点 | 误差 |
|---|---|---|---|
| 第 1 到 5 字 | 0.00 到 1.52 | 0.01 到 1.53 | +10 ms |
| 第 6 字(了) | 1.80 | 2.19 | +390 ms |
| 第 7 到 9 字 | 2.18 到 2.94 | 2.57 到 3.33 | +390 ms |
| 第 10 字(了) | 3.32 | 漏检 | — |
| 只少了一个起点,但错了五个字。原因就一句: | |||
| 对齐是「第 i 个字配第 i 个起点」,丢一个,后面全部往前挪一位。 | |||
| 这不是理论风险。 | |||
| 人唱歌本来就会连字、拖长音、抢拍、加气声。所以「数起点」这条路线在歌声上基本不可能稳定。 | |||
| 正确的做法是动态规划式的强制对齐:把「歌词的字序列」和「音频的音素序列」放在一起, | |||
| 穷举所有可能的切分,取总分最高的那一条。这样某个字没有明显起点时,它能靠上下文推断出来, | |||
| 而不是让后面全错位。 | |||
| 但强制对齐要声学模型(把音频映射到音素的神经网络)。 | |||
| 沙箱里没有这种模型,这一层这里如实说测不到——能真跑的只有前面的包络检测和它暴露出来的错位问题。 |
四、LRC 的格式,和三条实践建议
对齐的结果最终要写成 LRC:每行一个 [分:秒.百分秒] 加一句歌词。格式极简,所以它是事实标准。
[00:00.01]月
[00:00.39]亮
[00:00.77]爬
[00:01.15]上
[00:01.53]来
注意 LRC 是「整句对时间」,不是「逐字对时间」。逐字要高亮得用 KRC、QRC 这类逐字格式, 或者自己在 LRC 里塞多个时间标签(兼容性参差不齐)。 三条实践建议。① 时间戳取句首,不取句尾——播放器是按「当前时间 ≥ 时间戳」来切句的。 ② 一句太长就拆。超过 15 个字的句子在手机屏上要换行,拆成两句时间戳,观感立刻好一截。 ③ 生成完一定要人听一遍。自动对齐的错误往往只错一两百毫秒, 而一两百毫秒在字幕上已经是「抢拍」或「慢半拍」了。
五、动手 Demo:歌词对齐:10 个字检到 10 个,连读两个字就崩一片
造一段 10 个音节的「唱句」:每字之间留 0.10 秒气口,用 10 毫秒一帧算能量包络, 能量跃过阈值的第一帧算一个字起点; 再把第 5、6 字之间的气口去掉(模拟连读),重跑一遍,按「第 i 个字配第 i 个起点」硬对齐, 看错位怎么传播。
def _env(x, sr, hop=0.01):
h = int(sr * hop)
n = x.size // h
e = [np.sqrt(np.mean(x[i * h:(i + 1) * h].astype(np.float64) ** 2))
for i in range(n)]
return np.array(e), hop
def _onsets(e, hop, thr=0.12):
top = float(e.max())
out, armed = [], True
for i, v in enumerate(e):
if armed and v > thr * top:
out.append(i * hop); armed = False
elif not armed and v < thr * top * 0.6:
armed = True # 等能量落回去再找下一个
return out
实跑输出:
--------------------------------------------------------------------
歌词对齐:10 个字检到 10 个,连读两个字就崩一片
--------------------------------------------------------------------
每字后留 0.10 秒气口 检到 10 个起点(应 10 个)
字 真实起点(s) 检测起点(s) 误差(ms)
----------------------------------------
月 0.00 0.01 +10
亮 0.38 0.39 +10
爬 0.76 0.77 +10
上 1.14 1.15 +10
来 1.52 1.53 +10
了 1.90 1.91 +10
风 2.28 2.29 +10
也 2.66 2.67 +10
停 3.04 3.05 +10
了 3.42 3.43 +10
**10 个字全检到了**,误差 10 毫秒——这是「帧长」带来的固定延迟,不是随机误差。
起点判定是「能量跨过阈值的第一个 10 毫秒帧」,**所以它天然会比真实起音晚一帧左右。**
LRC 直接就能生成:
[00:00.01]月
[00:00.39]亮
[00:00.77]爬
[00:01.15]上
[00:01.53]来
[00:01.91]了
[00:02.29]风
[00:02.67]也
[00:03.05]停
[00:03.43]了
第 5、6 字连读 检到 9 个起点(应 10 个)
字 真实起点(s) 检测起点(s) 误差(ms)
----------------------------------------
月 0.00 0.01 +10
亮 0.38 0.39 +10
爬 0.76 0.77 +10
上 1.14 1.15 +10
来 1.52 1.53 +10
了 1.80 2.19 +390
风 2.18 2.57 +390
也 2.56 2.95 +390
停 2.94 3.33 +390
了 3.32 漏检 —
**只漏了一个起点,但按顺序硬对齐之后,后面 5 个字全错位了。**
原因很直白:对齐是「第 i 个字配第 i 个起点」,**丢一个,后面全部往前挪一位。**
这不是理论风险——**人唱歌本来就会连字、拖长音、抢拍。**
所以真正做歌词对齐必须用**强制对齐(forced alignment)**:
把「这句话的字序列」和「音频的音素序列」放在一起做动态规划,**而不是一个个数起点。**
这一节能真跑的是包络检测那一步;**强制对齐要声学模型,沙箱里没有,这里如实说测不到。**
done -> _out_39m.txt
第一版 10 个字全检到,误差整齐地都是 +10 毫秒。
这个「整齐」很关键——说明它是系统延迟,不是随机误差。系统延迟可以整体减掉,随机误差不能。
第二版只检到 9 个。
原因就在 armed 那个变量:它要求能量先落回阈值的 60% 以下,
才允许找下一个起点。两个字连读时能量根本没落下去,于是第二个字被吞掉了。
然后就是那张错位表:
第 6 到 9 字全部 +390 毫秒,第 10 字漏检。390 毫秒差不多是一个字加一个气口的长度——
正好是「往前挪了一位」的量。
这个 Demo 的价值不在于「包络检测有多准」,
而在于它把一个抽象说法变成了可见的数:「硬对齐对漏检零容忍」。
说明:这里是合成音频(每字一个纯音加谐波),不是真人演唱。
但「能量不落就没有新起点」这件事,跟着换任何音频都成立。真实演唱里还会有滑音、气声、颤音,
情况只会更难,不会更容易。
30 秒实验
- 把
armed的回落阈值从 0.6 改成 0.95,看连读那一版能检到几个。 会检到 10 个,但也会把同一句里的音量起伏误判成新起点——两个错换着来。 - 把帧长从 10 毫秒改成 5 毫秒,看那个 +10 毫秒的误差变成多少。 会变成 +5 毫秒左右——因为系统延迟本身就是「帧长的一到两倍」。
- 给连读那一版的两个字之间插 20 毫秒的极小气口(能量降到 30%),重跑。 起点就能检到了——说明「气口」是这条路线的生命线。
常见误区
- 「识别歌词就是把文字转出来」。做 MV 字幕要的通常是时间轴,文字是现成的。 这是强制对齐的活,不是听写的活。
- 「对齐差一两百毫秒没关系」。字幕上一两百毫秒就是抢拍或慢半拍,人眼看着画面就能觉出来。
- 「自动对齐完就能发」。连读、拖长音、抢拍会让硬对齐整段错位,必须人听一遍。
自测三问(点开对答案)
1. 识别和对齐是两件什么事? —— 识别(ASR)是把音频听成文字;对齐(forced alignment)是把已知文字对上时间。 做 MV 字幕要的是后者。 2. 为什么漏一个起点会让后面全错位? —— 因为简单对齐是「第 i 个字配第 i 个起点」,丢一个起点,后面每个字都往前挪一位。 3. 正规的强制对齐怎么解决这个问题? —— 用动态规划把「字序列」和「音素序列」一起搜,取总分最高的切分。 某个字没有明显起点时靠上下文推断。第八章到这里结束:从平台、元信息、数据质检出到音高映射、剪辑接缝、歌词对齐, 一条完整的声音链路走完了。第九章开始进行业场景——先看政务与教育。
- ☐ (本章完,下一节 9.1.1 数据隐私防护)