xDroid's Blog

与 FFmpeg 共舞

这篇博文注定也是会比较细碎。

需求是这样的:有一系列索大法某 DV 录制的音乐会视频,现在需要压制然后按照节目分割。一开始我的想法是这样子的:

压制 => 分割

乍一看没什么问题,然后打开小丸工具箱准备压制的时候懵逼了,因为导出的源文件不是以 MPEG-4 封装的
(这中间有一个小插曲,我花了很大的力气才搞清楚 容器格式编码格式 间的区别)。
幸好 x264 编码器还算认识索大法的 AVCHD 格式,因为

$ ffprobe 00015.m2ts
...
Input #0, mpegts, from '00015.m2ts':
  Duration: 00:00:05.22, start: 1.040000, bitrate: 16886 kb/s
  Program 1 
    Stream #0:0[0x1011]: Video: h264 (High) (HDMV / 0x564D4448), yuv420p(top first), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 50 tbr, 90k tbn, 50 tbc
    Stream #0:1[0x1100]: Audio: ac3 (AC-3 / 0x332D4341), 48000 Hz, 5.1(side), fltp, 448 kb/s
    Stream #0:2[0x1200]: Subtitle: hdmv_pgs_subtitle ([144][0][0][0] / 0x0090), 1920x1080

(这并不是原始的封装格式,经过一次封装转换,主要是为了换到 vlc 能播放的格式)
可以看到这个 m2ts 的视频部分就是用 H.264 编码嘛,可以直接压制的啊(所以网上辣么多转换器啥的不是瞎忽悠嘛= =)。


要是事情真有这么顺利就好了= =压制完发现音画不同步Σ(っ °Д °;)っ视频比音频快了不到一秒的样子。由于音乐会视频是一个延迟相对容忍度比较小的场合,这种延迟必须想办法纠正掉。

所以是在哪里引入的呢?用 vlc 播放了一下原视频,发现应该是完全合上的。编码器的帧数设置也没有问题,难道?再拿小丸工具箱看一看

...
Compression mode                         : Lossy
Delay relative to video                  : -80 ms
Stream size                              : 285 KiB (3%)

原来本来音视频在录制(准确地说在编码到储存空间的时候)就有一个延迟了,之所以播放的时候没有显现出来是因为 vlc 读到这个 metadata 然后自动平移了= =所以现在问题就是怎么把这个偏移量“纠正”回来?


重新组织一下语言就是:怎么在不重新编码的情况下使得音视频产生特定的延迟量?方法其实非常简单(参见 In ffmpeg, how to delay only the audio of a .mp4 video without converting the audio?):

ffmpeg -i "input.mp4" -itsoffset xx:yy:zz -i "input.mp4" -map 1:v -map 0:a -c copy "delayed.mp4" # video is delayed
ffmpeg -i "input.mp4" -itsoffset xx:yy:zz -i "input.mp4" -map 0:v -map 1:a -c copy "delayed.mp4" # audio is delayed

ffmpeg 真是一个好东西。(


视频的分割不算是一个非常烧脑的操作。
首先,由于录制的时候原视频按照 FAT32 的单文件上限分割成了 ~<2GB 的一些文件,所以压制完需要先将它们接起来。 很简单:

ffmpeg -f concat -safe 0 -i sep.list -c copy output.mp4

其中 sep.list 是形如

# this is a comment
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'

的列表文件(偷懒的话可以用 ls > sep.list 生成 23333)。

接下来给合并后的文件施加合适的偏移量,然后分割:

ffmpeg -i input.mp4 -ss xx:yy:zz -to uu:vv:ww output.mp4

-ss 选项写在 -i 的前面和后面有不同的玄学作用。见 ffmpeg Documentation


之后把视频发给了其他人,结果得到反馈是严重的音画不同步= =

啥?

玄学 debug 后觉得问题大概出在播放器上面,毕竟暴风影音和 WMP 都不能正常播放,但是 vlc 和 chrome 内置的解码器都可以正常播放。那怎么才能把这个延迟从 pts 标签转移到文件的 metadata 信息内呢?这时候又需要面向 StackOverFlow 了(见 ffmpeg mapping to mkv introduces “fake” delay relative to video)。

ffmpeg -i input.mp4 -c copy output.mkv

改个封装就可以了……真 TM 简单……


但是事情还没有结束……因为从上面的信息可以知道,录制出来的视频虽然看起来是 25fps 的,实际上应该是 50i 的意思。所以无论怎么分割压制,出来的还是交错的内容,一些 naive 的播放器是不会自动进行反交错的(或者效果非常糟糕)。

比较常用的反交错插件有 yadif ,使用方法是……呃我直接把 yadif 加在 x264 的选项里面了

-vf yadif:2:-1

具体可以看 FFmpeg Filters Documentation

这次压制了一晚上还没压制完……有一种说法是 yadif 的算法是单线程实现的= =不知道是不是真的。