初次学习有关音视频这一块的开发,主要是基于 flv.js 的学习。这一块的知识概念实在是太多太深了,所以本人是先在本地做记录,后面会整理慢慢地上传与各位分享,假如有地方说错,请勘误。谢谢指点。
概述
MP4(MPEG-4 Part 14)是一种常见的多媒体容器格式,它是在“ISO/IEC 14496-14”标准文件中定义的,属于MPEG-4的一部分,是“ISO/IEC 14496-12(MPEG-4 Part 12 ISO base media file format)”标准中所定义的媒体格式的一种实现,后者定义了一种通用的媒体文件结构标准。MP4是一种描述较为全面的容器格式,被认为可以在其中嵌入任何形式的数据,各种编码的视频、音频等都不在话下,不过我们常见的大部分的MP4文件存放的AVC(H.264)或MPEG-4(Part 2)编码的视频和AAC编码的音频。MP4格式的官方文件后缀名是“.mp4”,还有其他的以mp4为基础进行的扩展或者是缩水版本的格式,包括:M4V, 3GP, F4V等。
在HTML5播放器中,目前仅支持WebM和MPEG H.264 AAC的编码格式,而WebM的在浏览器的支持度没有mp4的支持度好。而fmp4区别于mp4,主要是因为可以通过fmp4的moof+mdat
的格式结构,很好的在不同质量的码流做码率切换。
MP4是由一个个的Box组成的,也就是可以说Box是MP4的最小单元。官方文档可以查看:ISO_IEC_14496-12。而Box的类型有太多太多了,可以自行查看文档中的Table 1 — Box types, structure, and cross-reference部分,这里我就不贴出来了。
BOX
以下是fmp4跟mp4的结构图,可以很清楚的看到两者的区别。
从官方文档看,可以知道,除了Box,还有一种Full Box。Box的结构图如下:
Box的官方示例代码如下:
1 | // Box |
在mp4-generator.js
中,生成box的代码如下:
1 | // Generate a box |
FTYP
File Type Box
1 | // 官方文档代码 |
MOOV
Movie Box,继承box,那么就可以知道有4个字节,这4个字节是子box的长度。可以在上图看到,moov主要是为了存放trak、mvhd,fmp4还会存放mvex box。
1 | // 官方文档代码 |
MOOV::MVHD
Movie Header Box,该box有且只有一个,主要是记录整个容器的各种信息。
1 | // 官方文档代码 |
MOOV::TRAK
Track Box,主要是存一个或多个media box的。
1 | aligned(8) class TrackBox extends Box(‘trak’) { } |
MOOV::TRAK::TKHD
Track Header Box,trak box 的第一个box,主要是记录以下信息(一开始我还以为与上面的冲突了)。
1 | // 官方文档代码 |
MOOV::TRAK::EDTS
Edit Box,不是必要的,主要是将时间线映射到media时间线上,是Edit List Box的容器。
1 | // 官方文档代码 |
MOOV::TRAK::EDTS::ELST
Edit List Box,不是必要的,主要是保存切确的时间表,使得某个track的时间戳产生偏移,能达到延迟播放的作用。mp4文件elst研究
1 | // 官方文档代码 |
MOOV::TRAK::MDIA
Media Box,保存媒体信息的容器。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MDHD
Media Header Box,存放媒体信息。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::HDLR
Handler Reference Box,主要是用来跟踪中媒体数据被呈现的过程。例如,video track将由video handler box处理。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF
Media Information Box。
1 | aligned(8) class MediaInformationBox extends Box(‘minf’) { } |
MOOV::TRAK::MDIA::VMHD/SMHD/HMHD/NMHD
Media Information Header Boxes。每种音轨类型都有不同的媒体信息头(对应media handler-type); 匹配的头文件应该存在,可以是这里定义的头文件之一,或者派生的规范中定义的头文件之一。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::DINF
Data Information Box。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::DINF::DREF
Data Reference Box。有三种子box类型,‘url ‘, ‘urn ‘, ‘dref’。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::DINF::DREF::URL/URN
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::STBL
Sample Table Box。由图可以看出,是作为一个容器存在。
1 | aligned(8) class SampleTableBox extends Box(‘stbl’) { } |
MOOV::TRAK::MDIA::MINF::STBL::STSD
Sample Description Box。 box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。
视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::STBL::STTS
Decoding Time to Sample Box。存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。
每个sample的显示时间可以通过如下的公式得到:
1 | D(n+1) = D(n) + STTS(n) |
其中,STTS(n)是sample n的时间间隔,包含在表格中;D(n)是sample n的显示时间。
因此有DT(2) = DT(1) + STTS(1),其中STTS就是Decode delta(1)=10。那么sample_count跟sample_delta的关系就是如下表:
那么entry_count是什么?假如这个媒体流存在9个samples,这里的entry和chunk不是对应的。sample 4、5和6在同一个chunk中,但是,由于他们的时长不一样,sample 4的时长为3,而sample 5和6的时长为1,因此,通过不同的entry来描述。
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::STBL::CTTS
Composition Time to Sample Box。每一个视频sample都有一个解码顺序和一个显示顺序。对于一个sample来说,解码顺序和显示顺序可能不一致,比如H.264格式,因此,CTTS就是在这种情况下被使用的。
- 如果解码顺序和显示顺序是一致的,CTTS就不会出现。STTS既提供了解码顺序也提供了显示顺序,并能够计算出每个sample的开始时间和结束时间。
- 如果解码顺序和显示顺序不一致,那么STTS既提供解码顺序,CTTS则通过差值的形式来提供显示时间。
依旧看Table 2,那么sample_count跟sample_offset的关系如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 官方文档代码
aligned(8) class CompositionOffsetBox extends FullBox(‘ctts’, version = 0, 0) {
unsigned int(32) entry_count;
int i;
if (version==0) {
for (i=0; i < entry_count; i++) {
unsigned int(32) sample_count;
unsigned int(32) sample_offset;
}
} else if (version == 1) {
for (i=0; i < entry_count; i++) {
unsigned int(32) sample_count;
signed int(32) sample_offset;
}
}
}
MOOV::TRAK::MDIA::MINF::STBL::STCO
Chunk Offset Box。Chunk的偏移量表,指定了每个chunk在文件中的位置。如下图:
需要注意的是,box中只是给出了每个chunk的偏移量,并没有给出每个sample的偏移量。因此,如果要获得每个sample的偏移量,还需要用到Sample Size Box和Sample-To-Chunk Box。
stco 有两种形式,如果你的视频过大的话,就有可能造成 chunkoffset 超过 32bit 的限制。所以,这里针对大 Video 额外创建了一个 co64 的 Box。它的功效等价于 stco,也是用来表示 sample 在 mdat box 中的位置。只是,里面 chunk_offset 是 64bit 的。基本格式为:
1 | // 官方文档代码 |
MOOV::TRAK::MDIA::MINF::STBL::STSC
Sample To Chunk Box。用chunk组织sample可以方便优化数据获取,一个chunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample,当然每个table entry可能包含一个或者多个chunk。以下是table entry布局。
每个table entry包含一组chunk,enrty中的每个chunk包含相同数目的sample。而且,这些chunk中的每个sample都必须使用相同的sample description。任何时候,如果chunk中的sample数目或者sample description改变,必须创建一个新的table entry。如果所有的chunk包含的sample数目相同,那么该table只有一个entry。
一个简单的例子,如图所示。图中看不出来总共有多少个chunk,因为entry中只包含第一个chunk号,因此,对于最后一个entry,在某些情况下需要特殊的处理,因为无法判断什么时候结束。
1 | // 官方文档代码 |
这 3 个字段实际上决定了一个 MP4 中有多少个 chunks,每个 chunks 有多少个 samples。这里顺便普及一下 chunk 和 sample 的相关概念。在 MP4 文件中,最小的基本单位是 Chunk 而不是 Sample。
- sample: 包含最小单元数据的 slice。里面有实际的 NAL 数据。
- chunk: 里面包含的是一个一个的 sample。为了是优化数据的读取,让 I/O 更有效率。
前面说了,在 MP4 中最小的单位是 chunks,那么通过 stco 中定义的 chunk_offsets 字段,它描述的就是 chunks 在 mdat 中的位置。每一个 stco chunk_offset 就对应于某一个 index 的 chunks。那么,first_chunk 就是用来定义该 chunk entry 开始的位置。那这样的话,stsc 需要对每一个 chunk 进行定义吗?不需要,因为 stsc 是定义一整个 entry,即,如果他们的samples_per_chunk,sample_description_index 不变的话,那么后续的 chunks 都是用一样的模式。
即,如果你的 stsc 只有:1
2
3first_chunk: 1
samples_per_chunk: 4
sample_description_index: 1
也就是说,从第一个 chunk 开始,每通过切分 4 个 sample 划分为一个 chunk,并且每个 sample 的表述信息都是 1。它会按照这样划分方法一直持续到最后。当然,如果你的 sample 最后不能被 4 整除,最后的几段 sample 就会当做特例进行处理。
通常情况下,stsc 的值是不一样的:
按照上面的情况就是,第 1 个 chunk 包含 2 个 samples。第 2-4 个 chunk 包含 1 个 sample,第 5 个 chunk 包含两个 chunk,第 6 个到最后一个 chunk 包含一个 sample。
MOOV::TRAK::MDIA::MINF::STBL::STSZ
Sample Size Boxes。指定了每个sample的size。
1 | // 官方文档代码 |
这里就是mp4的一个大致的结构,下一篇我会给出剩下的,也就是FMP4区别于MP4的结构部分。
Be the first person to leave a comment!