注意:本文部分内容之前发表在此文。但是经过补充后单独成章比较好,就移动到这边来了。
将一张图片转成视频的基本命令很简单:
ffmpeg -loop 1 -i {img} -t {dur} -vf format=yuv420p output.mp4
默认的fps是25,可以用-r指定成别的。这里重点注意-vf format=yuv420p部分,这个保证视频会被正确转换为最常见的YUV420格式,否则会使用YUV444格式。这个等效于-pix_fmt yuv420p。
同理,如果需要其他格式(YUV444、YUV422之类),只需要替换成其他的pixel format即可。如果想要full range,可以使用yuvj420p。
但是仅仅这样做,有两个问题。
选择的正确RGB->YUV转换矩阵
第一个问题是RGB->YUV的转换矩阵。ffmpeg默认是使用BT.601矩阵来进行这个转换的,如果你的输出视频是SD分辨率,这并没有问题。但是如果是HD视频,HD默认的标准是BT.709,所以这样就不对了。最明显的就是纯红色255, 0, 0会变成255, 25, 0。
解决办法,首先自然可以给输出结果显式加上BT.601的metadata:
-colorspace smpte170m
(虽然NTSC和PAL有各种微妙的区别(参见此文),但是这里用PAL的bt470bg也可以,因为这单说矩阵,这俩实际上是一样的。)
但是,不推荐这种做法。有的滤镜/渲染器根本无视元数据。HD视频还是老老实实用BT.709就好。所以最好的办法是进行正确的BT.709转换。
ffmpeg有N个video filter可以实现这个,最常见的是scale:
ffmpeg -loop 1 -i {img} -vf scale=out_color_matrix=bt709,format=yuv420p -color_primaries 1 -color_trc 1 -colorspace 1 -t {dur} output.mp4
其中-vf scale=out_color_matrix=bt709部分是把图片转换成BT.709(Rec. 709的另外一个名称)。因为我们还有个format的vf,直接把两者用逗号串起来(filter chain)。
后面的-color_primaries 1 -color_trc 1 -colorspace 1的是在元数据里标注。如果用x264编码(默认如此),也可以直接用-x264opts colormatrix=bt709。当然,如上所述,如果是HD视频可以略去不写(不推荐)。
除了-vf scale,还可以用:
-vf colormatrix=bt601:bt709,format=yuv420p:从命令来看,感觉实质是先转601再转709。呃,这个有点问题,实际上你要只转换matrix不动Primaries和Transfer function,否则红色会色偏。太麻烦不推荐使用了。如果感兴趣的话,正确的命令是:-vf colorspace=iall=bt601-6-625:all=bt709:format=yuv420p:同上。另外注意,这里的format是colorspace这个vf的一部分(冒号分割而非逗号),而不是再调用format。-vf colorspace=iprimaries=bt709:primaries=bt709:ispace=smpte170m:space=bt709:itrc=bt709:trc=bt709:format=yuv420p-vf zscale=matrix=709:r=limited,format=yuv420p:这是一个比较新的filter,来自zlib。21-08-08更新:在本文写成时,如果用yuv420p,会自动imply limited range(同理是yuvj420pfull range)。但是最新版本的ffmpeg下的zlib已经不是这样(疑似是因为这个改动)。所以,请显式加上limit range的参数r=limited来保证产出的视频是支持最广的limited range视频。
swscale的颜色误差bug
我之前一直是使用上述的-vf scale来转的,但是总是发现颜色有点偏——白色(255, 255, 255)会变成略微发黄的颜色(252, 255, 252),我以为这仅仅是精度不足的缘故也没太在意。直到有一天无意发现,如果我先将输入的BMP图片另存为PNG格式,就不会发生偏色。这完全不make sense!于是我报到了ffmpeg。
经过快2个月无人问津后,我忍不住去ffmpeg的emaillist发了个帖,果然引来了玉——除了一些workaround之外(下述),最重要的是有人指向了真正的bug:#979。
简单来说,swscale这个库(libswscale)——包括scale滤镜——有一个奇怪的bug:从bgr24转换为YUV会有色差,但是rgb24就不会(两者应可以无损转换)。上面BMP和PNG结果不同也是因为PNG用的是rgb24的pixel format,而BMP是bgr24。
既然知道了问题所在,我们只需要先转换一次即可,把上面的vf前再串一个format:
-vf format=rgb24,scale=out_color_matrix=bt709,format=yuv420p
就可以啦!那么上面提到的其他几种转换滤镜,有没有同样的问题呢?
colormatrix:同样的bug,这个filter应该也是基于libswscale。colorspace:如果你使用上述的方式,使用colorspace内置的format参数来转换成yuv420p而不是串一个formatvf,可以避免这个bug。zscale:无此bug。
另外,这个bug还可以通过添加scale的flag,accurate_rnd(精确rounding)来修复(这里还加上了另外一个增加精度的flag,full_chroma_int,不过这里accurate_rnd其实就够):
-vf scale=out_color_matrix=bt709:flags=full_chroma_int+accurate_rnd,format=yuv420p
当然,这并不是说这个bug仅仅是精度的问题:否则无法解释为什么rgb24就无问题。
另外,colormatrix之类的vf虽然没有flags参数,但是你可以增加-sws_flags accurate_rnd,也可以修复问题。



















