海外业务有视频拼接的需求,简单的说就是将两个视频拼接在一起形成新的视频(原视频+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操作。
然而,可以很负责任的告诉你,结果会出现问题:
- 文件时长不对!
对于文件时长出错的问题,可以换一种方式解决:先把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 ,然后执行上面一样的命令。
然而,亲试结果依然会出现问题:
- 播放到衔接处花屏 (Quicktime 播放器)
此处出现一个新的概念:TS格式 这里简单的认识就是传输流(transport stream)格式
二:使用concat协议
方式:
ffmpeg -i "concat:/xxx/a.ts|/xxx/b.ts" -c copy c.mp4
注意这种方式是使用协议,作用层面也是在文件层,也不会导致编码和解码,所以速度也是很快。
然而,可以很负责任的告诉你, 结果依然很容易出现可能的问题:
- 播放到衔接处花屏 (Quicktime 播放器)
- 播放到衔接处屏幕异常(定住了)声音正常或声音不正常视频正常
三:使用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)
这种方式跑下来的特点是:
- 速度相对较慢
- CPU消耗高
- 有点小复杂
然而,可以很负责任的告诉你, 结果依然很容易出现前面说的问题。
到这里,先小总结一下, 以上三种方式
- 一和二拼接上都很快
- 都容易出现异常的花屏等问题。
- concat 命令格式下是作用于流的层面,而在协议格式下是作用于文件的层面的
好,先提几个问题:
- 为何拼接上前两种都很快?
答:在协议的文件层面很好理解,就是类似linux的cat或者win的copy命令所以快,而在流式的命令行层面为何也挺快呢?其实关键是参数: -c copy 该参数的意思是对解构出来的流 完全不做处理直接拷贝,所以快!(网上的解释很多其实是错的 不是因为使用了concat的命令格式的原因)
第三种之所以慢是因为它走了
【解开容器】->【提取数据流】->【从新编码数据流】->【重新融合到容器】
的流程(大致流程)该过程就是耗GPU的过程
- 为何都容易出现花屏或者画面跟音频不同步等的问题?
答:原因是两个源文件的流编码格式可能不同,时基可能不同、采样率可能不同、两个文件的所有流可能长度不一样,这里贴一下官方的解释:
https://ffmpeg.org/ffmpeg-formats.html#concat
此处出现新的概念:时基,后面解释。
好回到主线:
为了使视频拼接结果正常,我们不得不考虑官方说的上述影响因素:
- 原始文件的编码格式
- 原始文件的时基
- 原始文件的采样率
于是解决思路先按这个思路来
先确保文件的编码格式相同、时基、采样率都一样!
查看文件信息:
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 举报,一经查实,本站将立刻删除。