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

color=c=black:s=1280x720:d=5 才是真黑底,它的 Y 平面是 16、色度是 128。 颜色能写名字、写 0xRRGGBB、写 random,转回 RGB 会有一两个数的偏差。

📝 本节和前面几节的关系

上一节的 nullsrc 造出来的底带绿味,这一节给出正解。 7.4 到这里把「裁剪」与「造画面」两件事收完。

一、命令与参数

跟 nullsrc 的写法几乎一样:

ffmpeg -f lavfi -i color=c=black:s=1280x720:d=5 -c:v libx264 -pix_fmt yuv420p black.mp4

c 是颜色,可以写颜色名、0xRRGGBB,甚至写 random。 其余参数跟 nullsrc 完全一致:s 尺寸、r 帧率、d 时长。

两者唯一的区别就是「内容」:nullsrc 给空白,color 给一个具体的颜色。 所以命令里只换一个名字,别的都不用动。

二、三种写法实测

把首像素打出来,看实际是什么颜色:

写法 首像素 RGB 说明


c=black [0, 0, 0] 真黑 c=red [253, 0, 0] 红不是 255 c=0x336699 [49, 100, 151] 十六进制

c=red 出来是 [253, 0, 0] 而不是 [255, 0, 0], 因为 yuv420p 是有范围压缩的(16 到 235),转回 RGB 时会有个位数的偏差。

写 0x336699 出来是 [49, 100, 151],同样差一两个数。 这不是错,是 RGB 到 YUV 再回 RGB 的来回转换,永远不可能完全无损。

做 UI 底色、品牌色的时候要把这个偏差算进去。 差两三个数肉眼无所谓,但如果要跟另一张图做像素比对,就得接受它。

三、和 nullsrc 的对比

四行说清两者分工:

项 nullsrc color=c=black


Y 平面 0 16 U/V 平面 0 128 看着像 带绿味的黑 真正的黑 适合 画布、占位 黑幕、黑边

要「黑幕」就用 color,要「空地」就用 nullsrc,别混。 黑边补出来带绿味,在深色场景里会很明显——这是最容易返工的一处。

四、三种颜色写法

记三种就够日常用:

写法 例子


颜色名 black、white、red、blue 十六进制 0x336699 随机 random

c=random 每帧换一个颜色,用来做闪色测试很方便。 但压出来几乎不省字节——每一帧都完全不同,编码器找不到可参考的前一帧。

另外还能带透明度:[email protected],一半透明。 但输出要是 yuv420p,透明度会丢掉,得配 yuva420p 与支持 alpha 的容器。

五、收个尾

7.4 这一节把「裁剪」和「凭空造画面」两件事讲完了。 crop 负责从有到有,nullsrc 与 color 负责从无到有。

再往后 7.5 起的倒放、翻转、填充,全是在这两件事上做叠加。 换句话说:一整套滤镜特效,底层都是「取一块、造一块、换一块」。

这一节最该带走的一句话:造黑底用 color,别用 nullsrc。 那一层绿味在浅色场景里看不出来,一到暗场就露馅。

六、动手 Demo:三种颜色,三个首像素

下面这段代码把三种颜色各出一帧,打首像素与 Y 平面最小值。 最后一行是 0x336699,看它转回来是多少。

import av
from fractions import Fraction

TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
fr = next(c0.decode(video=0))
base = fr.to_ndarray(format=u"yuv420p")
full = fr.to_ndarray(format=u"rgb24")
W, H = fr.width, fr.height
c0.close()

def probe(args, cap=1):
    g = av.filter.Graph()
    s0 = g.add(u"color", args)
    sk = g.add(u"buffersink")
    s0.link_to(sk)
    g.configure()
    n = 0
    info = None
    while n < cap:
        try:
            o = sk.pull()
        except Exception:
            break
        if info is None:
            a = o.to_ndarray(format=u"rgb24")
            y = min(bytes(o.planes[0])[:o.planes[0].line_size])
            info = (a[0, 0].tolist(), y)
        n += 1
    return n, info

for c, tag in ((u"black", u"c=black"), (u"red", u"c=red"),
               (u"0x336699", u"c=0x336699")):
    n, info = probe(u"c=%s:s=160x90:r=25:d=2" % c)
    print(u"%-12s %d 帧  首像素 %s  Y=%d" % (tag, n, info[0], info[1]))

实跑输出:

--------------------------------------------------------------------
7.4.6 使用 color 滤镜生成黑色背景的视频
--------------------------------------------------------------------

**① 命令与参数**

`ffmpeg -f lavfi -i color=c=black:s=1280x720:d=5 -c:v libx264 -pix_fmt yuv420p black.mp4`

**`c` 是颜色,可以写颜色名、`0xRRGGBB`,甚至写 `random`。**

**② 三种写法实测出来是什么颜色**

  写法           首像素 RGB         说明
  ------------------------------------------------
  c=black        [0, 0, 0]          真黑
  c=red          [253, 0, 0]        红不是 255
  c=0x336699     [49, 100, 151]     十六进制

**`c=red` 出来是 `[253, 0, 0]` 而不是 `[255, 0, 0]`,**
**因为 yuv420p 是有范围压缩的(16 到 235),转回 RGB 时会有个位数的偏差。**
**写 `0x336699` 出来是 `[49, 100, 151]`,同样差一两个数,这不是错,是色彩空间的来回转换。**

**③ 和 nullsrc 的对比**

  项           nullsrc        color=c=black
  ---------------------------------------------
  Y 平面       0              16
  U/V 平面     0              128
  看着像       带绿味的黑     真正的黑
  适合         画布、占位     黑幕、黑边

**要「黑幕」就用 color,要「空地」就用 nullsrc,别混。**

**④ 常见的三种颜色写法**

  写法         例子
  ----------------------------------------
  颜色名       black、white、red、blue
  十六进制     0x336699
  随机         random

**`c=random` 每帧换一个颜色,用来做闪色测试很方便,但压出来几乎不省字节——**
**每一帧都完全不同,编码器找不到可参考的前一帧。**

**⑤ 收个尾**

**7.4 这一节把「裁剪」和「凭空造画面」两件事讲完了。**
**crop 负责从有到有,nullsrc 与 color 负责从无到有。**
**再往后 7.5 起的倒放、翻转、填充,全是在这两件事上做叠加。**

三行输出对上本节那张表:[0, 0, 0]、[253, 0, 0]、[49, 100, 151]。 注意 c=black 的 Y 是 16,c=red 的 Y 是 81,都不是直觉里的数。

c=red 的 Y=81,是因为红色在 YUV 里亮度很低—— 人眼对红不敏感,这一套系数延续了几十年,从黑白电视时代就定下来了。

代码里取 Y 用的是 planes[0] 的第一行最小值,因为纯色帧每一行都一样。 换成有内容的画面就不能这么取样了,得按行号乘 line_size 去切。

30 秒实验

  1. 试白色:把 c=black 换成 c=white,看 Y 是 235 还是 255。
  2. 试透明:写 [email protected],看 pix_fmt 会不会被自动改成 yuva420p。
  3. 试 random:写 c=random:d=2,数一下出来多少种颜色。

常见误区

  1. 拿 nullsrc 当黑幕。它带绿味,暗场能看出来。
  2. 以为 c=red 就是 255。实测 253,范围压缩的必然结果。
  3. 以为颜色写错会报错。写个不存在的名字,它可能悄悄当成别的色。
自测三问(点开对答案) 1. 为什么 c=red 出来是 253 而不是 255? —— yuv420p 是 16 到 235 的有限范围,来回转换会有几个数的偏差。 2. c=black 的 Y 平面值是多少? —— 16,这是有限范围里的黑。nullsrc 是 0,那是非法值。 3. c=random 压出来为什么省不了字节? —— 每帧颜色都不同,编码器找不到可参考的前一帧。

相关笔记