mp4文件格式简明教程-mp4文件格式通俗解释



    一直想写一篇文章来描述一下mp4文件格式的组织情况,一开始笔者也是通过在百度搜“mp4文件格式解析” 或“mp4文件格式详解” 或者说的牛X一点就是"mp4文件格式剖析",最后一大堆box把我看晕了,对于box内容的理解确实对于mp4文件格式的了解有很大帮助,但是很不幸,这些文章都是堆叠abox:abox的描述,实在是让人捉摸不透。诚然,box是为了实现某些功能,或者达到目的而产生的。今天我就来写一篇文章,让你对mp4文件的box有个通俗的理解,通俗并不代表吃快餐式的描述,并不是读完本文只有个大概的了解。希望本篇文章能让你醍醐灌顶,立刻懂mp4了。

    mp4由很多box组成,如果你是个初学者,我建议你下载一个mp4info这个软件,然后随便从网上找来一个mp4媒体文件打开。然后你会看到如下所示:使用这个工具便于你对mp4文件格式有个视觉上的了解。box逻辑上是嵌套的,树形的。



看到这几个box,你一定很费解,其实这就是英文缩写,下面解释一下

ftype : file type 文件类型

moov : movie 存放媒体信息,它的子box就是它的内容

mvhd : movie header 媒体头 文件的时长,创建时间等信息,没必要了解那么细

track: track 通道,我这里有两个通道,一个是视频通道,一个是音频通道,两个通道分别描述媒体信息。怎么描述的见下文

mdata: 真正的媒体数据


    顺便说一下右侧的小窗mp4info下的信息,这些信息是从box中解析出来的,你要问从哪个box解析出来的,我只能告诉你,你先知道这么多,知道太多了反而会把你头搞大。mp4的核心在于点播,在于快速定位帧数据,这些媒体总体信息随后你会发现解析太简单了。


    这个小窗的信息分三部分,左侧上部是音频信息,smplrate:sample rate 采样率16000,channel通道个数,我这里是单通道,多通道就是立体声,bitrate就是比特率,单位是秒。 左侧下部是音频信息,with height视频的宽高,这里是1280x720 ,bitrate同样是比特率,这里有更专业的叫法:码率,秒为单位 frames帧个数,我这里是150帧,因为是10s的视频,fps 是frame per second ,我这里是15帧/秒,所以说10s是150帧。右侧的数据有一个地方值得说一下,那就是timescale 时间的粒度,就是一个粒度单位,可以是时间,也可以不是,我们这里是1s的帧数作为粒度单位,也即是说15帧等效于1秒。我们平时都是以1s为一个单位,或是1ms为一个单位,但是我要以10ms为一个单位,那么这个10ms就可以算作一个粒度单位,要是以1s的帧数作为单位,那这个粒度就是帧数,他们从不同的方面描述这1秒的时间。 我们这里是15000,其实是1秒的时长,这个数值和fps帧率有关系,我这里是15帧/秒 ,所以一个粒度单位就是1s,但是这里并没有取时间作为单位,而是以1秒的帧数作为了粒度单位。显然这里乘以了1000,变成了毫秒。

     废了半天口水,总结一下就是timescale从另一个方面度量了1000ms的时间(1s发生的帧数)。一定要理解这个概念,

durateion就是timescale * 10s得来的, totaltime是我们正常概念的时间,10000ms maxaudiosize maxvideosize就不解释了。 videosamplenum 就是视频sample的个数,这里注意1个sample就是1帧,这里150个sample,150帧,帧率15帧/秒,10秒的视频,很好理解。下面进入正题:这些sample如何组织。下文中在遇到sample直接想成帧即可。

    这里以视频数据为例进行解析。打开第一个track,就看到了视频数据的信息,如下图所示:

 这里很重要的一个box就是 stbl,也是mp4中比较复杂的一个box,这才是解开mp4文件格式的主干,其他box暂且不去关心,关心的越多只会让你只见树木不见森林。

stbl :sample table是一个顶层box,数据都在子box中,这里不解释这个box,以免读者觉得摸不着头脑。我们直接解释它的子box。

stsd: sample table sample description ,是描述信息,你只需要知道这里存放的是sample的描述信息就行了,也就是上图小窗的信息,都存在这。

stsc:sample table sample chunk, sample-chunk映射表, 这个box有点复杂,因为引入了一个新概念chunk,chunk是一组sample的集合, 而结构相同的chunk又可以聚集在一起形成一个entry,这个entry就是stsc映射表的项目。(entry 多用于形容map结构的item)

mp4文件把相同类型的chunk放在一起进行描述,从左到右可以是这样

 chunk0    chunk1    chunk2   chunk3    chunk4    chunk5   chunk6   chunk7   chunk 8   chunk9

每个entry的结构是这样的

struct{

    uint32 first_chunk;//这组chunk的第一个chunk的序号

    uint32 samples_per_chunk;//每个chunk有多少个sample

    uint32 sample_desc_idx;//stsd 中sample desc的索引,这个box上文有提到过。

} entry;


    小总结一下,mp4顺序的把一组相同结构的chunk放在一起进行管理,这么做是为了压缩文件大小。

 比如entry0 包含 chunk0    chunk1    chunk2    , first chunk的索引是0

         entry1 包含 chunk3    chunk4   chunk5    chunk6  , first_chunk的索引是3

每一个entry必须通过她的下一个entry的first_chunk才能知道自己有多少个chunk, 例如entry0 first_chunk为0 entry1为3

那么entry0的chunk个数为3-0=3个。以此类推,最后一个entry就是从first_chunk开始一直到结束为止


stco:sample table chunk offset,这个box记录了每个chunk的文件偏移量,其实就是每个chunk的地址(文件指针)

chunk0    chunk1    chunk2   chunk3    chunk4    chunk5   chunk6   chunk7   chunk 8   chunk9

从这个stcobox中,我们可以拿到chunk的总个数


stsz: sample talbe sample size , 保存了每个sample的大小和sample的数目,体积比较大,从开始到结束。

stss:sample table sync sample ,保存了关键帧列表,关键sample序号列表,从开始到结束

stts: sample table time to sample, 这个表很关键,是时间->sample序号的映射,通过这个表可以找到任何时间的sample序号。不包含sample的字长和指针(通过其他方式计算),只包含sample的时间长度和时间偏移量。这是一个压缩的表,连续相同时间长度的sample会被分成一组组成一个表项。



    到这里,我们知道了每个sample的字节大小,为了便于描述,我们建立一张映射表map_sample_size, 通过get_sample_size(index)这个函数,根据sample序号index来获取对应sample的字节大小。 我们通过get_sample_offset(index) 这个方法来根据给定sample的序号获取其文件偏移量。get_sample_offset(index)很关键,让我们来详细解释一下如何实现这个函数:

     上文中知道了每个entry有多少个类似的chunk, 以及每个chunk有多少个类似的sample,还知道总的chunk个数,还知道每个chunk的偏移量offset,间接得出了每个chunk的大小。还知道每个sample的字节大小。通过这些信息,我们可以建立一张表,这张表可以记录每个sample的文件偏移量,从开始到结束叠加。大家仔细想一下,根据这些信息是如何计算的。然后再看答案。

        那么我们来看如何推算每个sample的offset。假如我知道了第0个sample的offset(其实就是chunk0的offset),我还知道第0个sample的字节大小(stsz中存放)那么第1个sample的offset就可以得出: offset0+sample_size0。以此类推我们就能建立一张表sample序号和其offset的表。这个例子是个特殊情况,不过对于其他的chunk也如此。


    为什么mp4要采用这种entry chunk sample这种方式进行逻辑上的存储呢。总的来说是时间换空间,存储chunk的文件offset要比存储每个sample的文件offset节省空间,我们也看到了sample的offset换算稍微有些复杂,并不那么直接,所以要建立一张临时表,这个表就是sample序号和chunk的映射表,根据给定的sample序号,能找到其所在的chunk,以及其在chunk内部的序号。


   现在我们根据时间就能拿到sample的序号,那么为什么还需要一个关键帧表呢。这是因为解码器在解码视频的时候只能从关键帧进行解码,如果没有关键帧后面的连续帧就不能解码。因此我们还需要一个函数get_key_sample(index)根据给定的sample序号获取其最近的关键帧sample序号。参考stss,假如关键sample序号是0,14,28...,我现在get_key_sample(12)应该返回14. 配合get_sample_offset(index) 就能立刻定位关键帧的数据。

 

          好了写到这里我相信,我没有把mp4的每个box都讲一遍给你让读者看不下去。mp4最关键最核心的就是点播,如何根据seek到的时间戳找到附近的关键帧对应的数据在文件中的位置才是核心。通过本文,我想你已经有了答案。 读者注意一下我在本文中定义的一些伪代码函数,对于理解mp4是如何实现点播的是很有帮助的。剩余的音频数据同理。至于我没详细解说的那些box,相信在读者抓住了主干后可以顺利的各个击破丝毫不会感觉迷失。
















猜你喜欢

转载自blog.csdn.net/mtaxot/article/details/53762444