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

一个文件两条流,视频挂 -vf、音频挂 -af,各走各的。 scale 改像素,atempo 改时长,两者别混。

📝 本节和前面几节的关系

前三节都在处理视频。实际文件里还有一条音频流,它也能挂滤镜。 两条流并行处理,是转码命令的常态。

一、两条流,两条链

一条命令里同时动视频和音频,就是这么写的:

ffmpeg -i s7.mp4 -vf scale=80:45 -af atempo=2.0 out.mp4

-vf 管视频,-af 管音频,两个参数各管一条流。 它们是并行的两条链,不是一个接一个。

并行的意思是:视频那条链拿不到音频的帧,音频那条也拿不到视频的。 所以两条链的滤镜不能混用——-vf atempo 会报「找不到滤镜」。

二、实测:一边缩、一边变速

同一个素材,视频缩一半、音频快一倍:

流 处理前 滤镜 处理后


视频 50 帧 scale=80:45 50 帧 音频 89088 点 atempo=2.0 44032 点

视频帧数一个没少——缩放不改帧数,只改每一帧的大小。 音频点数几乎减半——变速改的就是「多久放完」,点数必然跟着变。

44032 不是精确的一半,占原来的 49.4%。 因为 atempo 按 512 点一块处理,最后凑不满一块的尾料被丢掉了。

三、为什么一个不变、一个变

这两件事看起来矛盾,其实是同一个道理的两面:

滤镜 改的是什么 帧数或点数


scale 每帧的像素 不变 atempo 每秒塞多少内容 变

scale 属于「改画面」:一帧还是一帧,只是像素少了。 时间轴一点没动,所以帧数不变。

atempo 属于「改时间」:两秒的内容压进一秒。 内容总量没变,塞得更密,所以点数减半、时长减半。

分不清这两类,就会犯「用 scale 想把视频变短」这种错。 想让画面播得快,改的是时间戳,不是尺寸。

四、同时变速要两条流一起改

想让整段视频快一倍,视频那边用 setpts,音频那边用 atempo:

-vf setpts=0.5*PTS -af atempo=2.0

两个都得改。只改视频,画面快了一倍,声音还是原来的长度, 两条流就对不齐,越到后面差得越多。

7.3 会专门讲这一对参数怎么配,以及 setpts 那个必踩的坑: 把时间戳乘 0.5 会让两帧撞到同一个时间点,封装器直接拒绝。

复杂滤镜里,两条流写在一张图上,各起一个标签:

-filter_complex "[0:v]scale=80:45[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]"

这时不再用 -vf 与 -af,两条链都在图里,再用 -map 挑出要的标签。 两种写法效果一样,选哪种看哪条链更复杂。

五、动手 Demo:一条视频链、一条音频链,各量各的

下面这段代码给视频挂 scale、给音频挂 atempo,两条链各跑一遍。 再补一个不带滤镜的对照,确认点数变化真是滤镜带来的。

import av
from fractions import Fraction

SRC = u"s7.mp4"

def audit(tempo=None, w=160, h=90):
    g = av.filter.Graph()
    prev = g.add_abuffer(sample_rate=44100, format=u"s16",
                         layout=u"stereo", time_base=Fraction(1, 44100))
    f = g.add(u"atempo", tempo) if tempo else g.add(u"anull")
    sk = g.add(u"abuffersink")
    prev.link_to(f)
    f.link_to(sk)
    g.configure()
    rs = av.AudioResampler(format=u"s16", layout=u"stereo", rate=44100)
    c0 = av.open(SRC)
    nin = 0
    nout = 0
    for fr in c0.decode(audio=0):
        for rf in rs.resample(fr):
            prev.push(rf)
            nin += rf.samples
        while True:
            try:
                nout += sk.pull().samples
            except Exception:
                break
    prev.push(None)
    while True:
        try:
            nout += sk.pull().samples
        except Exception:
            break
    c0.close()
    return nin, nout

a, b = audit()
print(u"音频 原样   入 %d 出 %d  比例 %.3f" % (a, b, b / float(a)))
a, b = audit(u"2.0")
print(u"音频 2 倍速 入 %d 出 %d  比例 %.3f" % (a, b, b / float(a)))

g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p",
                 time_base=Fraction(1, 25))
sc = g.add(u"scale", u"80:45")
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
c0 = av.open(SRC)
n = 0
for fr in c0.decode(video=0):
    vf = av.VideoFrame.from_ndarray(fr.to_ndarray(), format=u"yuv420p")
    vf.pts = n
    vf.time_base = Fraction(1, 25)
    p.push(vf)
    n += 1
print(u"视频 原样 %d 帧" % n)
m = 0
while True:
    try:
        sk.pull()
        m += 1
    except Exception:
        break
print(u"视频 缩放后喂 %d 帧,出来 %d 帧" % (n, m))
c0.close()

实跑输出:

--------------------------------------------------------------------
7.1.4 流和滤镜的结合使用
--------------------------------------------------------------------

**① 一个文件里通常有两条流:一条视频、一条音频。**
**两条流可以各挂一条滤镜链,互不干扰。**

`ffmpeg -i s7.mp4 -vf scale=80:45 -af atempo=2.0 out.mp4`

**`-vf` 管视频,`-af` 管音频。这是两条并行的链,不是一条。**

**② 实测:视频缩一半,音频快一倍**

  流       处理前       滤镜            处理后
  --------------------------------------------------
  视频     50 帧        scale=80:45     50 帧
  音频     89088 点     atempo=2.0      44032 点

**视频帧数一个没少——缩放不改帧数,只改每一帧的大小。**
**音频点数少了一半——变速改的就是「多久放完」,点数必然跟着变。**
**准确说是 89088 的 49.4%,不是整整一半:**
**atempo 按 512 点一块处理,最后凑不满一块的那点尾料被丢掉了。**

**③ 为什么视频没变短、音频变短了**

**这两件事看起来矛盾,其实是同一个道理的两面:**

  滤镜       改的是什么         帧数或点数
  --------------------------------------------
  scale      每帧的像素         不变
  atempo     每秒塞多少内容     变

**scale 属于「改画面」,一帧还是一帧,只是变小了。**
**atempo 属于「改时间」,两秒的内容压进一秒,点数字节都得减半。**

**④ 音视频同时变速要注意配平**

**想让整段视频快一倍,视频那边要用 setpts,音频那边要用 atempo,两个都得改:**

`-vf setpts=0.5*PTS -af atempo=2.0`

**只改视频,画面快了一倍,声音还是原来的长度,两条流就对不齐了。**
**7.3 会专门讲这一对参数怎么配,以及 setpts 那个必踩的坑。**

**⑤ 复杂滤镜里两条流写在一张图上**

`-filter_complex "[0:v]scale=80:45[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]"`

**这时不再用 -vf 与 -af,两条链都写在图里,各自起个标签,再用 -map 挑出来。**
**两种写法效果一样,选哪种看哪条链更复杂。**

第一行比例是 1.000,说明不带滤镜时点数一点不动——这就是对照组。 第二行才掉到 0.494,证明点数变化确实是 atempo 带来的。

视频那边喂进去 50 帧、出来 50 帧,一帧不少。 缩放只改每帧的大小,不改帧的个数。

有个细节:视频这条链必须先 push 完再 pull,中间的帧会堆在 sink 里。 所以本段代码先数完帧数,最后一次性把 sink 拉空。

30 秒实验

  1. 加三倍速:把 atempo=2.0 换成 3.0,看是报错还是照样跑。
  2. 换音量:把 atempo 换成 volume=0,听(或量)输出是不是全零。
  3. 只改一边:视频加 setpts、音频不动,量两条流的总时长差多少。

常见误区

  1. 用 scale 想缩短视频。scale 只改像素,时间轴原封不动。
  2. 只改视频时间戳。音频还在原速,两条流越走越错位。
  3. video 滤镜挂到 -af 上。两条链的帧类型不同,滤镜根本不认。
自测三问(点开对答案) 1. `-vf` 与 `-af` 是两条串联的链吗? —— 不是,是并行的两条链,各处理各的流,互不交接。 2. 为什么缩放后帧数不变? —— 缩放改的是每帧的尺寸,不碰时间戳,所以帧数原样。 3. atempo 输出为什么不是精确的一半? —— 它按 512 点一块处理,凑不满一块的尾料被丢掉。

相关笔记