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

视频挂 -vf setpts、音频挂 -af atempo,两条链并行。 只改一边,容器时长取长的那条,文件看着正常、里面全是错位。

📝 本节和前面几节的关系

7.3.1 改了画面、7.3.2 改了声音,这一节把两条链挂在同一条命令上。 这一节之后,倍速才算真正能用。

一、两条链一起上

命令就是两半拼起来:

ffmpeg -i s7.mp4 -vf setpts=2*PTS -af atempo=0.5 slow.mp4

-vf 与 -af 是并行挂上去的两条链,谁都不影响谁。 要的是「一起变慢」,所以两边都得改,缺一边就成了音画不同步。

倍数要对应:画面慢一倍是 2*PTS,声音慢一倍是 atempo=0.5。 一个是乘 2、一个是除 2,方向正好相反,这是最常见的写错点。

二、三种组合,真编出来量时长

每一轨的时长单独量,不能只看容器:

怎么改的 视频轨 音频轨 差多少


两边都改(放慢) 3.96 秒 4.03 秒 74 毫秒 两边都改(加快) 1.00 秒 1.02 秒 22 毫秒 只改视频 3.96 秒 2.04 秒 1917 毫秒

前两行两边差不多长,差几十毫秒,那是 atempo 分块和 AAC 帧对齐留下的零头。 第三行差了整整 1.9 秒——视频拉到两倍长,声音一点没动。

74 毫秒这个量级听不出来,也看不出来。 但如果是给字幕组做对齐,几十毫秒已经够让口型对不上了。

三、差出来的那截是什么样

画面还在慢慢放,声音早就放完了,后一半就是无声的默片。 反过来只改音频更难受:声音提前结束,后面几秒画面还在动,观众会以为卡了。

容器时长取两轨里长的那条,所以文件看起来是 3.96 秒,里面却是错位的。 拿播放器一放,前两秒正常、后面对不上,问题就暴露了。

这也是为什么不能只看 ffprobe 报的总时长。 要看每条流各自的时长,两边对一下才知道有没有错位。

四、两种写法都对

除了 -vf 加 -af,也能写进一张图:

-filter_complex "[0:v]setpts=2*PTS[v];[0:a]atempo=0.5[a]" -map "[v]" -map "[a]"

写法 什么时候用


-vf 加 -af 两路各自一条链 -filter_complex 里分两条 链里还要分叉、要贴图

效果完全一样,只是把两条链塞进了同一张图,方便后面再加工。 一旦要在倍速的同时贴水印、裁边、拼接,就必须走第二种。

别两种混着写。-vf 与 -filter_complex 同时出现时, ffmpeg 会优先用复杂滤镜那张图,-vf 被悄悄忽略,画面不动也不报错。

五、动手 Demo:三条命令,三组时长

下面这段代码把三种组合各编一个 mp4,再把视频轨与音频轨的时长分开量。 第三行就是「只改视频」的错位样本。

import av
from fractions import Fraction

def encode(dst, vexpr, tempo, fps=None):
    c = av.open(u"s7.mp4")
    stb = c.streams.video[0].time_base
    c.close()
    g = av.filter.Graph()
    b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=stb)
    p = b
    for name, args in ((u"setpts", vexpr), (u"fps", fps)):
        if args:
            f = g.add(name, args)
            p.link_to(f)
            p = f
    sk = g.add(u"buffersink")
    p.link_to(sk)
    g.configure()
    ag = av.filter.Graph()
    a0 = ag.add_abuffer(sample_rate=44100, format=u"s16", layout=u"stereo",
                        time_base=Fraction(1, 44100))
    af = ag.add(u"atempo", tempo)
    ask = ag.add(u"abuffersink")
    a0.link_to(af)
    af.link_to(ask)
    ag.configure()
    oc = av.open(dst, u"w")
    vs = oc.add_stream(u"libx264", rate=25)
    vs.width, vs.height = 160, 90
    vs.pix_fmt = u"yuv420p"
    as_ = oc.add_stream(u"aac", rate=44100)
    as_.layout = u"stereo"
    c0 = av.open(u"s7.mp4")
    for fr in c0.decode(video=0):
        b.push(fr)
        while True:
            try:
                o = sk.pull()
            except Exception:
                break
            for pk in vs.encode(o):
                oc.mux(pk)
    b.push(None)
    while True:
        try:
            o = sk.pull()
        except Exception:
            break
        for pk in vs.encode(o):
            oc.mux(pk)
    for pk in vs.encode(None):
        oc.mux(pk)
    c0.close()
    c0 = av.open(u"s7.mp4")
    rs = av.AudioResampler(format=u"s16", layout=u"stereo", rate=44100)
    aos = av.AudioResampler(format=u"fltp", layout=u"stereo", rate=44100)
    for fr in c0.decode(audio=0):
        for f2 in rs.resample(fr):
            a0.push(f2)
        while True:
            try:
                o = ask.pull()
            except Exception:
                break
            for f3 in aos.resample(o):
                f3.pts = None
                for pk in as_.encode(f3):
                    oc.mux(pk)
    a0.push(None)
    while True:
        try:
            o = ask.pull()
        except Exception:
            break
        for f3 in aos.resample(o):
            f3.pts = None
            for pk in as_.encode(f3):
                oc.mux(pk)
    for pk in as_.encode(None):
        oc.mux(pk)
    c0.close()
    oc.close()
    c = av.open(dst)
    vd = ad = -1
    for s in c.streams:
        d = float(s.duration * s.time_base) if s.duration else -1
        if s.type == u"video":
            vd = d
        else:
            ad = d
    c.close()
    return vd, ad

print(u"%-16s %-10s %-10s %s" % (u"怎么改的", u"视频", u"音频", u"差"))
for tag, ve, tempo, fps in ((u"两边都改放慢", u"2*PTS", u"0.5", None),
                            (u"两边都改加快", u"0.5*PTS", u"2.0", u"25"),
                            (u"只改视频", u"2*PTS", u"1.0", None)):
    vd, ad = encode(u"av_%s.mp4" % tempo, ve, tempo, fps)
    print(u"%-16s %-10.2f %-10.2f %.0f 毫秒" % (tag, vd, ad, abs(vd - ad) * 1000))

实跑输出:

--------------------------------------------------------------------
7.3.3 音视频同时倍速
--------------------------------------------------------------------

**① 视频用 setpts、音频用 atempo,两条链一起上**

`ffmpeg -i s7.mp4 -vf setpts=2*PTS -af atempo=0.5 slow.mp4`

**`-vf` 与 `-af` 是并行挂上去的两条链,谁都不影响谁。**
**要的是「一起变慢」,所以两边都得改,缺一边就成了音画不同步。**

**② 三种组合,真编出来量时长**

  怎么改的             视频轨      音频轨      差多少
  ----------------------------------------------------------
  两边都改(放慢)     3.96 秒     4.03 秒     74 毫秒
  两边都改(加快)     1.00 秒     1.02 秒     22 毫秒
  只改视频             3.96 秒     2.04 秒     1917 毫秒

**前两行两边差不多长,差几十毫秒,那是 atempo 分块和 AAC 帧对齐留下的零头。**
**第三行差了整整 1.9 秒——视频拉到两倍长,声音一点没动。**

**③ 差出来的那截是什么样**

**画面还在慢慢放,声音早就放完了,后一半就是无声的默片。**
**反过来只改音频更难受:声音提前结束,后面几秒画面还在动,观众会以为卡了。**
**容器时长取两轨里长的那条,所以文件看起来是 3.96 秒,里面却是错位的。**

**④ 命令行两种写法都对**

  写法                         什么时候用
  -----------------------------------------------------
  -vf 加 -af                   两路各自一条链
  -filter_complex 里分两条     链里还要分叉、要贴图

**第二种写全一点是这样:**

`-filter_complex "[0:v]setpts=2*PTS[v];[0:a]atempo=0.5[a]" -map "[v]" -map "[a]"`

**效果完全一样,只是把两条链塞进了同一张图,方便后面再加工。**

三行数字跟本节那张表逐条对上:74 毫秒、22 毫秒、1917 毫秒。

注意第一行视频 3.96 秒、音频 4.03 秒,音频反而更长。 因为 atempo=0.5 出来是 198.5% 而不是 200%,多出来的那一点全在音频那边。

第三行是最值得看的:只改视频时两轨差了将近两秒, 而容器时长只报 3.96 秒,光看容器完全发现不了问题。

30 秒实验

  1. 换方向:把第一组改成视频 0.5*PTS,fps=25、音频 atempo=2.0,看差多少。
  2. 只改音频:把视频写成 PTS、音频写 atempo=2.0,看差多少。
  3. 量容器:加一行打印 c.duration,看它取的是哪一轨。

常见误区

  1. 倍数方向写反。画面 2*PTS 配音频 atempo=2.0,一个变慢一个变快。
  2. 只看容器时长。它取最长那条,错位了也看不出来。
  3. 同时写 -vf 与 -filter_complex。-vf 被静默忽略,画面不动。
自测三问(点开对答案) 1. 画面慢一倍、声音也慢一倍,两个参数分别写什么? —— `-vf setpts=2*PTS` 与 `-af atempo=0.5`。 2. 只改视频会怎样? —— 声音提前放完,后半段无声;容器时长按视频那条报,看不出错位。 3. 两轨差几十毫秒是为什么? —— atempo 分块与 AAC 帧对齐的零头,听感上察觉不到。

相关笔记