大家好,欢迎来到停止重构的频道。
从本期起,我们正式进入音视频处理的介绍。
本期我们讨论音视频文件转封装,如将MP4转AVI、MP4转RTMP等。
内容中所提及的代码都会放在GitHub,感兴趣的小伙伴可以到GitHub下载。
我们按这样的顺序展开讨论 :
1、 视频封装的作用
2、 转封装的工作原理
3、 转封装成视频文件
4、 转封装成直播流
在前面《音视频转码工作原理》中讨论过,一个视频文件实质上是分3层的,封装、编码、基础数据。
封装格式的作用,简单地说,就是对音频数据、视频数据、基础信息等数据,按一定的结构规则编排成文件。
数据编排虽然对音视频数据本身不会产生影响,但是会对一些特定场景产生影响。
如视频文件一般会采用MP4、FLV等封装格式,直播流则采用RTMP、HTTP-FLV等协议。
关于MP4、FLV、HLS等视频封装格式的详细说明,可参考往期《视频格式》。
关于RTMP、HTTP-FLV、HLS等流媒体协议的详细说明,可参考往期《直播协议》。
转封装的应用场景有很多,如MP4文件转FLV文件,MP4文件转RTMP直播流, RTMP直播流转MP4文件等。
这里需要注意的是,转封装仅仅是改变文件数据的编排方式。如果需要改变分辨率、码率等设置,则需要转码才能完成。
关于转封装、转码的区别,可参考往期《音视频转码工作原理》。
转封装的工作原理很简单,就是对视频文件或视频流解封装后,按新格式重新封装成视频文件或视频流。
转封装时,音频数据、视频数据不需要区别处理,这个过程是流式的,就是每次只处理一部分数据,循环往复,直到处理完成。
另外,如果是视频文件转直播流,则循环过程中需要加上时间间隔,不然会由于推流过快而导致报错。
后面是具体的FFmpeg操作过程,对应的示例代码也会同步说明。
转封装成视频文件,如将MP4文件转成FLV文件,对应示例代码为remux_tofile.cpp。环境搭建完后,运行程序如图所示:
代码流程为4步。
第一步,打开源文件并获取源文件信息;
第二步,构造输出文件,由于转封装是不需要对音视频数据进行处理的,所以可以直接根据源文件的轨道信息构造输出文件;
第三步,处理数据,循环解封装源数据,并封装写入到输出文件,直到源数据读取完成;
第四步,关闭输入、输出文件,处理完成后需要先对输出文件写入尾信息才能关闭,不然视频文件可能会出现问题。
如果是直播流转成视频文件,那么以上代码也适用,且不需要改动,因为直播流中断或结束时 解封装的API仍然会返回小于0的结果。
如果希望提前中断直播流录制,则需要另找时机对输出文件写入尾信息。
转封装成直播流。
如将MP4文件转成RTMP流,对应示例代码为remux_tostream.cpp,运行程序如图所示:
整体代码与转封装成视频文件的代码是差不多的,只是这里追加了时间间隔,防止过快退流产生错误。
这里需要说明的是,解封装出来的packet数据中,含有dts解码时间戳和pts播放时间戳。
我们一般用dts作为计算时间间隔的依据,当然dts需要乘以timebase才是真正的时间 ,timebase为源视频文件对应轨道的timebase。
一般dts和pts是相等的,但是如果是H264的b帧等情况的话,则可能存在pts大于dts的情况,所以选用pts作为计算时间间隔依据的话,可能会让直播流卡顿。
我们的示例MP4文件是没有b帧的,所以每一个dts和pts都是相等的。
实际录播场景下,也就是视频文件转直播流场景下,视频文件也不应该存在b帧等使得dts和pts不相等的设置,因为即使程序按dts计算时间间隔,但如果某个帧的pts和dts相差太远 也会造成直播卡顿。
如果源文件无法消除b帧,那么单纯的转封装无法保证直播流的流畅,需要加入转码过程才行。但转码是需要消耗更多性能的,如果仅仅为了消除b帧而加入转码过程,则往往得不偿失。
本期内容的代码已经上传Github 需要的朋友可以下载,里面有编译运行环境的搭建说明。
如果对FFmpeg环境安装感到困惑,可以参考往期视频《FFmpeg》。 当然,也可以选择我们做好的docker容器。