经过视频编码后的帧数据,需要放到视频容器里,才能成为一个常规的视频文件。我们以mp4容器为例子,聊一聊代码层面上帧数据如何放到mp4容器里。
一个友好的mp4视频结构,如下图,ftyp是基本信息,moov是头部信息,mdat是帧数据。moov在mdat前面,支持流媒体边下边播。
开源代码库mp4v2,作为mp4容器操作工具,是如何实现帧数据的容器打包的呢?
下面是mp4标准定义的box结构。
在mp4v2里,用MP4Atom对象定义一个box。虚拟出root box,是MP4RootAtom对象,继承自MP4Atom。
MP4RootAtom
MP4AtomArray是MP4Atom组成的动态数组,动态指数组长度可以动态增长。
函数调用经历以下流程:
一、
----- MP4Create(pFileName, 0);
1. 创建m_pRootAtom1. 创建m_pRootAtom
MP4Atom::CreateAtom()第3个形参是const char* type,取NULL,就意味着创建MP4RootAtom。
Generate()会主动创建它的第一个孩子,moov atom。现在是这样子的:
2. 创建ftyp atom
说到这个InsertChildAtom()就很牛逼了,它把ftyp atom插入child atom array的0位置,已有的元素并不会被覆盖,而是偏移。
现在是这样子的:
3. 创建mdat atom
add_ftyp是为1的,mdat atom被插入child atom array的1位置。现在成了这样子:
4. 创建free atom,并把ftyp atom和free atom写入文件。
MP4RootAtom::BeginWrite()做了这个事情。
注意最后一行,往文件里写mdat的序曲正式拉开了。
二、
---- MP4AddH264VideoTrack()
---- MP4AddH264SequenceParameterSet()
---- MP4AddH264PictureParameterSet()
---- MP4WriteSample()
5. pps, sps, nal数据,都会写到mdat里。
三、
---- MP4Close()
6. 核心数据mdat写完了,会写上moov和free。
MP4RootAtom:FinishWrite()做了这个事情。
于是乎我们得到了这样的视频:
可以看到,现在视频的结构里,有两个问题,一是存在冗余的free box,一是moov在mdat后面。
四、
---- MP4Optimize()
提供了Optimize()接口,可以做到把上面的视频转变为:
7. 读入文件的所有一级atom
8. 写入ftyp和moov
MP4RootAtom::BeginOptimalWrite()做了这个事情:
最后一行开始写mdat。
9. 写入mdat
至此,moov调整到了mdat前面,一个友好的mp4结构就打包完成了。
回头思考一下,既然moov需要在mdat前面,那么为什么mp4v2打包的过程,要反过来把moov写在mdat之后呢?
因为在mdat写完之前,moov的长度是不确定的。所以为了不影响往文件里写mdat,就把moov挪到了mdat后面,等mdat写完之后,再写入moov。
作者简介:tao, 天天P图 AND 工程师
文章后记
天天 P 图是由腾讯公司开发的业内领先的图像处理,相机美拍的 APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习!