FFmpeg 视频拼接的案例分析

海外业务有视频拼接的需求,简单的说就是将两个视频拼接在一起形成新的视频(原视频+EC视频)。

调研后发现,视频拼接有如下方式:

一:单独使用ffmpeg的分离器:concat (解码分离器之一,总概念是:demuxer)

(具体可以前往Wiki查看基础介绍篇:FFmpeg 音视频处理涉及的基础概念梳理。)

把所有要拼接的视频报存到一个文件中,然后利用ffmpeg的插件concat进行拼接。

方式如下:mylist.txt

file '/xxx/a.mp4'
file '/xxx/b.mp4'

然后使用如下命令:

ffmpeg -f concat -i mylist.txt -c copy c.mp4

这种方式拼接速度最快,因为不涉及编码和解码,几乎只有文件的磁盘io操作。

然而,可以很负责任的告诉你,结果会出现问题:

  1. 文件时长不对!

对于文件时长出错的问题,可以换一种方式解决:先把ab视频转为ts格式,然后再同样进行拼接操作。

方式:

ffmpeg -i '/xxx/a.mp4' -codec copy -bsf:v h264_mp4toannexb '/xxx/a.ts'  
ffmpeg -i '/xxx/b.mp4' -codec copy -bsf:v h264_mp4toannexb '/xxx/b.ts'
ffmpeg -f concat -i mylist.txt -c copy c.mp4

-bsf 是比特流过滤器选项 查看其他用:

ffmpeg -bsfs

官方推荐mp4转ts的时候 带上这个参数

-bsf:v h264_mp4toannexb
 #https://ffmpeg.org/ffmpeg-bitstream-filters.html

依样画葫芦,把 a.ts和b.ts都按前面的方式写入mylist.txt ,然后执行上面一样的命令。

然而,亲试结果依然会出现问题:

  1. 播放到衔接处花屏 (Quicktime 播放器)
图片

此处出现一个新的概念:TS格式  这里简单的认识就是传输流(transport stream)格式

:使用concat协议

方式:

ffmpeg -i "concat:/xxx/a.ts|/xxx/b.ts" -c copy c.mp4

注意这种方式是使用协议,作用层面也是在文件层,也不会导致编码和解码,所以速度也是很快。

然而,可以很负责任的告诉你, 结果依然很容易出现可能的问题:

  1. 播放到衔接处花屏 (Quicktime 播放器)
  2. 播放到衔接处屏幕异常(定住了)声音正常或声音不正常视频正常

:使用ffmpeg的concat滤镜

方式:

ffmpeg -i a.mp4 -i b.mp4 -filter_complex '[0:0][0:1][1:0][1:1]concat=n=2:v=1:a=1[v][a]' -map [v] -map [a] c.mp4  
#简单解释一下,[0:0]第一个文件视频流,[0:1]第一个文件音频流,如此类推
#concat的参数是n=2:v=1:a=1 意思是输入文件是2个,输出视频流1个,音频流1个分别映射在[v]和[a]中 ,最后封装到c.mp4

(更详细的使用方式可以前往Wiki查看:FFmpeg实践应用之——Complex filter)

这种方式跑下来的特点是:

  1. 速度相对较慢
  2. CPU消耗高
  3. 有点小复杂

然而,可以很负责任的告诉你, 结果依然很容易出现前面说的问题。

到这里,先小总结一下, 以上三种方式

  1. 一和二拼接上都很快
  2. 都容易出现异常的花屏等问题。
  3. concat 命令格式下是作用于流的层面,而在协议格式下是作用于文件的层面的

好,先提几个问题:

  1. 为何拼接上前两种都很快?

答:在协议的文件层面很好理解,就是类似linux的cat或者win的copy命令所以快,而在流式的命令行层面为何也挺快呢?其实关键是参数: -c copy 该参数的意思是对解构出来的流 完全不做处理直接拷贝,所以快!(网上的解释很多其实是错的 不是因为使用了concat的命令格式的原因)

第三种之所以慢是因为它走了

【解开容器】->【提取数据流】->【从新编码数据流】->【重新融合到容器】

的流程(大致流程)该过程就是耗GPU的过程

  1. 为何都容易出现花屏或者画面跟音频不同步等的问题?

答:原因是两个源文件的流编码格式可能不同,时基可能不同、采样率可能不同、两个文件的所有流可能长度不一样,这里贴一下官方的解释:

https://ffmpeg.org/ffmpeg-formats.html#concat

图片

此处出现新的概念:时基,后面解释。

好回到主线:

为了使视频拼接结果正常,我们不得不考虑官方说的上述影响因素:

  1. 原始文件的编码格式
  2. 原始文件的时基
  3. 原始文件的采样率

于是解决思路先按这个思路来

先确保文件的编码格式相同、时基、采样率都一样!

查看文件信息:

ffprobe -i /xxx/a.mp4
ffprobe -i /xxx/b.mp4
图片
图片

从上面两张图上可以看得到 两个文件的视频编码格式都是 h264, 音频编码格式是aac ,所以:

原始文件的编码格式是相同的。

其中的概念通过网络搜索资料得到解释:

fps:每秒帧数(frame per second)

tbr: 表示帧率(time base rate),该参数倾向于一个基准,往往tbr跟fps相同

tbn: 视频流的时间基准,就是把时间拆解得更细的份数,以便于协调其它时间节奏。

tbc:视频流的解码时间基准(不是很懂,但跟解码视频流时有关)

现在解决第2和第3的差异,经过搜索可以用下面的方式:

ffmpeg -i /xxx/b.mp4  -r 25 -ar 48000 -video_track_timescale 12800 /xxx/bb.mp4 -y 
#-r 视频采样率
#-ar 音频采样率
#-video_track_timescale 视频轨道时间刻度 时间基参数,
#还有一个参数:-time_base 但我试了效率很低 重新编码,效率很低,不知为何。

经过上面的重新编码,结果为:

图片

实现了 两个视频的时间基一致、采样率一致。

在此基础上,将它们转为ts格式,然后进行拼接即可(参照上面的 生成ts)

ffmpeg -i 'concat:/xxx/a.ts|/xxx/b.ts' -acodec copy -vcodec copy -absf aac_adtstoasc -threads 2 '/xxx/c.mp4' -y 
#最终生成我们要的c.mp4

现在,我们来粗略理解一下视频播放时候发生的事情。

图片

当一帧帧画面呈现出流畅动画的感觉,在真实的视频播放过程中每一帧图片都是经过压缩的(因为完整的图片帧的数据太大),而在这个领域NB的人为了压缩而设计视频流中的画面帧的概念:

即分为:I帧、P帧、B帧 (帧间编码技术)

记住核心原理就是:任何编码都意味着压缩!任何解码都意味着解压缩!

I帧:即关键帧,通俗的理解就是它这一帧反映了画面的变化(好像以前我们做动画时候的关键帧)或呈现了完整意义的画面内容。

P帧:是采用了向前时间预测的编码方式。

B帧:是采用了双向时间预测的编码方式。也即它提高了压缩比,但是它依赖于前面的帧和后面的帧来作为参考。

总结为:I 帧可以不依赖其他帧就解码出一幅完整的图像,而 P 帧、B 帧不行。P 帧需要依赖视频流中排在它前面的帧才能解码出图像。B 帧则需要依赖视频流中排在它前面或后面的帧才能解码出图像。

(H.264 的编码格式就是使用了上面的帧间编码最新压缩技术。)

画个图来表达一下:

图片

假设画面A经过压缩后变为I帧图像数据,可以理解为I帧数据基本上完整保留了画面应该呈现的内容。(几乎没压缩)

画面B,经过压缩后变为B帧数据,单单解读它的时候无法还原回画面B

画面C,经过压缩后变为P帧数据,单单解读它也无法还原回画面C

但是 P帧+I帧 = 画面C ,也即P帧只需要知道前面的I帧就可以还原为画面C

图片

类似的, B帧需要 + I帧 + P帧 才能还原画面B

图片

B帧压缩的内容最大,但它的还原需要结合后面的P帧才可以。

所以这里画面数据帧的解码有顺序的问题,涉及时间的协调,播放时画面的呈现时间,跟画面帧的解码的时间是不一样,这涉及两个时间概念: DTS和PTS

DTS:解码时间戳。

PTS:显示时间戳。

这两个时间戳在使用上,还需要以时间基为参照,所谓时间基(timebase)可以简单的理解为时间刻度范围。

比如把1s分为25等分,每等分就是1/25 ,time_base={1,25},h265、h265的时间基一般用90000,即,time_base={1,90000}

所以显示的时间戳=pts*time_base

当 pts =0 time_base={1,25} 则显示时间戳为 0

当 pts =1 time_base={1,25} 则显示时间戳为 1*1/25

当 pts =2 time_base={1,25} 则显示时间戳为 2*1/25

类似的解码时间戳也是这么算的,只不过解码的帧会根据帧类型进行先后次序的调整。

事实上,时间基概念的引入不仅仅是视频解码播放的优化,在协同音频流上也是有这个作用的。

总结:根据理解:视频出现花屏、卡屏等的原因可能有如下情况:

视频解码的时候 丢失了B帧、P帧,解码失败就会马赛克。

视频如果丢失了I帧,画面应该是卡顿,直到下一个I帧出现。

视频的时基协调不一致的情况下(两个视频拼接)很容易出现跳画面或者音画不同步等情况。

另外,关于获取视频的基础信息,需要借助ffprobe 这个工具:

ffprobe -v quiet -print_format json -show_streams '/xxx/a.mp4'

参考:

https://trac.ffmpeg.org/wiki/Concatenate

https://blog.51cto.com/u_14653665/5105745

作者:黄坚林 | 来源:公众号——37DATA

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论