🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

音频倍速用 atempo,它真去重采样,不像 setpts 那样光改时间戳。 合法区间是 0.5 到 100,越界报的是 Result too large 这种驴唇不对马嘴的错。

📝 本节和前面几节的关系

上一节把画面改快了,声音还没动。这一节把声音那一半补上。 两个都懂了,7.3.3 才能把它们接在一起。

一、换了个滤镜:atempo

命令只换一个滤镜名:

ffmpeg -i s7.mp4 -af atempo=2.0 fast.m4a

atempo 不能像 setpts 那样光改时间戳——声音是连续波形, 硬把时间戳压一半,播放器会当噪声放。它得真去重采样,把波形压紧或拉松。

所以音频这一侧没有「撞车」问题,但也因此没有「零成本」这回事: 每一个采样点都得重新算一遍。

二、七个 tempo 实测

同一段 89088 点的音轨,换七个 tempo 值各跑一遍:

tempo 入多少点 出多少点 占原比例


0.5 89088 176862 198.5% 0.8 89088 110935 124.5% 1.0 89088 89088 100.0% 1.5 89088 59630 66.9% 2.0 89088 44032 49.4% 2.5 89088 34816 39.1% 3.0 89088 29948 33.6%

tempo=2.0 出来 44032 点,占 49.4%,不是正好一半。 因为 atempo 是 512 点一块处理的,边界那一块会多留或少留。

0.5 那一行是 198.5% 而不是 200%,同样差这一块的零头。 倍数越极端,零头占比越大,3.0 那行理论 33.3%、实测 33.6%。

三、越界报的错很误导

往下越过 0.5 会怎样:

atempo=0.4 → OverflowError [Errno 34] Result too large

tempo 的合法区间是 0.5 到 100。0.4 越过了下界, 但报的不是「参数无效」,而是 Result too large。

因为它内部先算了个比值再按比值分配缓冲,除以 0.4 之后缓冲区大得离谱。 这个报错信息跟真正的原因差得很远,照着它查会一路查偏。

记住一条就够:看到 Result too large 先回头看 tempo 是不是小于 0.5。

四、想更慢就把两段串起来

0.5 已经是下限了,再慢就得串:

-af atempo=0.5,atempo=0.5

链 出多少点 占原比例


atempo=0.5 176862 198.5% atempo=0.5 接 atempo=0.5 352612 395.8%

串两段 0.5,长度变成 395.8%,差不多就是四倍,等于 tempo=0.25。 命令行里的逗号就是「串起来」,物理上等于两个滤镜前后接力。

两段都带了误差,所以 395.8% 而不是 400%。 三段、四段也能串,误差会一层层滚上去,但听感上察觉不到。

五、一个容易混的点:音高

atempo 只改速度,不改音高。 改成 2 倍速,人声还是原来的调门,只是说话变快了。

这是它跟「改采样率」最大的区别: 改采样率会让声音变尖(像快进的磁带),atempo 不会。

想要「快放带升调」的效果,得另配 asetrate 与 aresample,那是另一套玩法。 atempo 保住音高,正是它慢(要重采样)的原因。

六、动手 Demo:七个 tempo、一次越界、一次串联

下面这段代码把七个 tempo 值各跑一遍,数入点与出点。 再试 0.4 看它报什么,最后把两段 0.5 串起来。

import av
from fractions import Fraction

def atempo(tempos):
    g = av.filter.Graph()
    src = g.add_abuffer(sample_rate=44100, format=u"s16", layout=u"stereo",
                        time_base=Fraction(1, 44100))
    p = src
    for t in tempos:
        f = g.add(u"atempo", t)
        p.link_to(f)
        p = f
    sk = g.add(u"abuffersink")
    p.link_to(sk)
    g.configure()
    nin = nout = 0
    c0 = av.open(u"s7.mp4")
    rs = av.AudioResampler(format=u"s16", layout=u"stereo", rate=44100)
    for fr in c0.decode(audio=0):
        for f2 in rs.resample(fr):
            src.push(f2)
            nin += f2.samples
        while True:
            try:
                o = sk.pull()
            except Exception:
                break
            nout += o.samples
    src.push(None)
    while True:
        try:
            o = sk.pull()
        except Exception:
            break
        nout += o.samples
    c0.close()
    return nin, nout

print(u"%-6s %-10s %-10s %s" % (u"tempo", u"入", u"出", u"占原比例"))
for t in (u"0.5", u"0.8", u"1.0", u"1.5", u"2.0", u"2.5", u"3.0"):
    n, o = atempo([t])
    print(u"%-6s %-10d %-10d %.1f%%" % (t, n, o, 100.0 * o / n))

print(u"--- 越界")
try:
    atempo([u"0.4"])
except Exception as ex:
    print(u"atempo=0.4 报错 %s %s" % (type(ex).__name__, ex))

print(u"--- 串两段")
n1, o1 = atempo([u"0.5"])
n2, o2 = atempo([u"0.5", u"0.5"])
print(u"单段 0.5      %d 点  %.1f%%" % (o1, 100.0 * o1 / n1))
print(u"0.5 接 0.5    %d 点  %.1f%%" % (o2, 100.0 * o2 / n2))

实跑输出:

--------------------------------------------------------------------
7.3.2 音频倍速
--------------------------------------------------------------------

**① 音频这一侧换了个滤镜:atempo**

`ffmpeg -i s7.mp4 -af atempo=2.0 fast.m4a`

**`atempo` 不能像 setpts 那样光改时间戳——声音是连续波形,**
**硬把时间戳压一半,播放器会当噪声放。它得真去重采样,把波形压紧或拉松。**

**② 实测各个 tempo 值**

  tempo     入多少点     出多少点     占原比例
  ------------------------------------------------
  0.5       89088        176862       198.5%
  0.8       89088        110935       124.5%
  1.0       89088        89088        100.0%
  1.5       89088        59630        66.9%
  2.0       89088        44032        49.4%
  2.5       89088        34816        39.1%
  3.0       89088        29948        33.6%

**源音轨 89088 个采样点。tempo=2.0 出来 44032 点,占 49.4%。**
**比例不是正好一半,因为 atempo 是 512 点一块处理的,边界那一块会多留或少留。**

**③ 低于 0.5 直接报错**

`atempo=0.4` → `OverflowError [Errno 34] Result too large`

**tempo 的合法区间是 0.5 到 100。0.4 越过了下界,报的不是「参数无效」,**
**而是 `Result too large`——它内部先算了个比值再分配缓冲,除以 0.4 之后缓冲区大得离谱。**
**这个报错信息跟真正的原因差得很远,很容易查偏方向。**

**④ 想更慢,就把两段 atempo 串起来**

  链                           出多少点     占原比例
  ------------------------------------------------------
  atempo=0.5                   176862       198.5%
  atempo=0.5 接 atempo=0.5     352612       395.8%

**串两段 0.5,长度变成 395.8%,差不多就是四倍,等于 tempo=0.25。**
**命令行写成 `-af atempo=0.5,atempo=0.5`,逗号就是「串起来」。**

**⑤ 一个容易混的点:音高**

**atempo 只改速度,不改音高;改成 2 倍速,人声还是原来的调门,只是说话变快。**
**想要「快放带升调」的效果,得另配 `asetrate` 与 `aresample`,那是另一套玩法。**

七行数字跟本节那张表逐条对上,注意 0.5 是 198.5% 而不是 200%。

atempo([u"0.4"]) 那一行打的错是 OverflowError, 错误类型跟「参数越界」这四个字一点关系都没有,这就是它误导人的地方。

最后两行是单段与串联的对比:352612 除以 89088 得 395.8%。 串起来不是简单相加,是两个比值相乘,再加上各自的零头。

30 秒实验

  1. 试上界:把 3.0 换成 100,看还能不能跑、出来多少点。
  2. 串三段:写 atempo=0.5 三次,看占比是不是接近 800%。
  3. 换采样率:把源换成 48000 Hz,看点数比例变不变。

常见误区

  1. 以为 tempo 上界是 2.0。现在能到 100,2.5、3.0 都能跑。
  2. 把 Result too large 当成内存问题。它其实是 tempo 小于 0.5。
  3. 以为串联是相加。两个 0.5 是四倍,不是 1.0。
自测三问(点开对答案) 1. atempo 的合法区间是多少? —— 0.5 到 100。低于 0.5 会报 `Result too large`。 2. 为什么 tempo=2.0 出来不是正好一半的点? —— 它按 512 点一块处理,边界那一块会多留或少留。 3. 想要 0.25 倍速怎么写? —— `atempo=0.5,atempo=0.5`,两段串起来。

相关笔记