🎬 第五章 FFmpeg命令行实现音视频转码
📄 一句话概括

降采样容易,省空间;升采样补不出高频,只会把文件撑大。 「采样率高」不等于「高频多」,这是重采样里最大的误解。

📝 本节和前面几节的关系

上一节改的是「存几份」,这一节改的是「每秒记多少个点」。 这两个参数合起来决定音频的原始体积:字节 = 点 × 声道 × 每点字节。

一、三个采样率,时长都一样

同一段声音按三档采样率各存一份:

采样率 采样点 字节 时长


44100 89088 178176 2.02 秒 22050 44544 89088 2.02 秒 48000 96967 193934 2.02 秒

第四列三行相同,说明转采样率不改时长;变的是「每秒记多少个点」。 22050 那一点数正好是一半,字节也是一半——这就是「降采样省空间」的字面含义。

48000 那一行点数比 44100 多了百分之九,字节也多了百分之九。 这一档是为视频准备的:视频的帧率多是 24、25、30 的倍数,音频取 48 kHz 才好对齐。

二、升回来是空的

拿一个 15 kHz 的正弦波做实验:先降到 22050,再升回 44100:

阶段 采样点 均方根


原始 44100(15 kHz) 44100 0.354 降到 22050 22034 — 再升回 44100 44068 0.001

第三行的均方根掉到 0.001,几乎归零——声音没了。 因为 22050 的奈奎斯特频率只有 11025 Hz,15 kHz 在那一步就被彻底滤掉了。

升回 44100 只是把点补密,补出来的全是零。「采样率高」不等于「高频多」。 所以「把 44.1 kHz 升到 96 kHz 提升音质」这件事,物理上不可能成立。

三、什么时候该改采样率

三种需求,三种做法:

需求 做法


体积太大 先降采样率,再降码率 设备不认 只改到设备要求的那一档 想「提升音质」 做不到,重采不能凭空造信息

第一行要注意顺序:先降采样率再降码率,比只降码率省得多。 因为码率是压缩后的目标,采样率直接砍掉了一半的原始数据。

第三行是很多人踩过的坑:升采样之后文件变大了,声音一点没变好, 而且下游还得为多出来的这些零付存储与带宽。

四、动手 Demo:三档采样率各存一份,再试一次升回来

把素材音频按 44100、22050、48000 各解一份裸 PCM,比点数与字节。 再拿一个 15 kHz 的正弦波走一遍「降下去再升回来」,看高频能不能回来。

import av, os
import numpy as np

def to_pcm(dst, rate, src=u"s5.mp4"):
    c1 = av.open(dst, u"w", format=u"s16le")
    s1 = c1.add_stream(u"pcm_s16le", rate=rate)
    s1.layout = u"mono"
    rs = av.AudioResampler(format=u"s16", layout=u"mono", rate=rate)
    c0 = av.open(src)
    n = 0
    for f in c0.decode(audio=0):
        for rf in rs.resample(f):
            n += rf.samples
            for o in s1.encode(rf):
                c1.mux(o)
    for rf in rs.resample(None):
        n += rf.samples
        for o in s1.encode(rf):
            c1.mux(o)
    for o in s1.encode(None):
        c1.mux(o)
    c1.close()
    c0.close()
    return n, os.path.getsize(dst)

for sr in (44100, 22050, 48000):
    n, b = to_pcm(u"sr%d.pcm" % sr, sr)
    print(u"%d Hz  %d 点  %d 字节  %.2f 秒" % (sr, n, b, n / float(sr)))

SR = 44100
t = np.arange(SR) / float(SR)
sig = (0.5 * np.sin(2 * np.pi * 15000 * t)).astype(np.float32)
fr = av.AudioFrame.from_ndarray(np.ascontiguousarray(sig.reshape(1, -1)),
                                format=u"fltp", layout=u"mono")
fr.sample_rate = SR
down = av.AudioResampler(format=u"fltp", layout=u"mono", rate=22050)
up = av.AudioResampler(format=u"fltp", layout=u"mono", rate=SR)
mid = down.resample(fr)
back = up.resample(mid[0]) + up.resample(None)
out = np.concatenate([b.to_ndarray()[0] for b in back])
r0 = float(np.sqrt((sig.astype(np.float64) ** 2).mean()))
r1 = float(np.sqrt((out[:len(sig)].astype(np.float64) ** 2).mean()))
print(u"原始 15 kHz 均方根 %.3f" % r0)
print(u"降到 22050 得 %d 点" % mid[0].samples)
print(u"升回 44100 得 %d 点 均方根 %.3f" % (len(out), r1))

实跑输出:

--------------------------------------------------------------------
5.5.2 采样率转换——降下去容易,升回来是空的
--------------------------------------------------------------------

**① 单声道文件,三个采样率各存一份**

  采样率     采样点     字节       时长
  -----------------------------------------
  44100      89088      178176     2.02 秒
  22050      44544      89088      2.02 秒
  48000      96967      193934     2.02 秒

**第四列三行相同,说明转采样率不改时长;变的是「每秒记多少个点」。**
**22050 那一点数正好是一半,字节也是一半——这就是「降采样省空间」的字面含义。**

**② 升回来能不能恢复高频**

  阶段                     采样点     均方根
  -------------------------------------------
  原始 44100(15 kHz)     44100      0.354
  降到 22050               22034      —
  再升回 44100             44068      0.001

**第三行的均方根掉到 0.001,几乎归零。**
**因为 22050 的奈奎斯特频率只有 11025 Hz,15 kHz 在那一步就被彻底滤掉了。**
**升回 44100 只是把点补密,补出来的全是零——「采样率高」不等于「高频多」。**

**③ 所以转换的正确顺序**

  需求               做法
  ----------------------------------------------
  体积太大           先降采样率,再降码率
  设备不认           只改到设备要求的那一档
  想「提升音质」     做不到,重采不能凭空造信息

**第三行是很多人踩过的坑:把 44.1 kHz 的素材「升」到 96 kHz,文件变大了,声音一点没变好。**

AudioResampler 用完要再次调用 resample(None) 收尾, 把内部缓冲的尾巴吐出来——不收尾会少几十个点。

15 kHz 这个频率是故意选的:它高于 22050 的一半,所以必然被滤掉。 换成 5 kHz 再试一次,你会看到几乎完整的还原——这就是奈奎斯特线的意义。

那段正弦波是代码自己造的,不需要外部音频文件, 所以这个实验可以脱离素材单独跑。

30 秒实验

  1. 命令行对照:ffmpeg -i 源.mp4 -ar 48000 出.wav。
  2. 看奈奎斯特线:把 15000 改成 5000,均方根会保住。
  3. 只降码率不降采样率:对比字节,体会两种省法的差别。

常见误区

  1. 以为采样率越高音质越好。超过原始素材的采样率,多出来的全是补的零。
  2. 降采样后想升回去。被滤掉的高频找不回来,升回去只是把点补密。
  3. 忘记 resample(None) 收尾。最后几十个点会丢掉,时长差一丁点。
自测三问(点开对答案) 1. 为什么 22050 的奈奎斯特频率是 11025? —— 奈奎斯特频率等于采样率的一半,超过这个频率的成分无法被还原。 2. 把 44.1 kHz 升到 96 kHz 能提升音质吗? —— 不能。原始信号里没有的信息,重采样造不出来,只会让文件变大。 3. 降采样为什么比降码率省得多? —— 降采样直接砍掉原始数据量,降码率只是让压缩更狠,前者是减法不是压缩。

相关笔记