MP4格式详解

Posted 狗蛋儿l

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MP4格式详解相关的知识,希望对你有一定的参考价值。

mp4概述

MP4文件中的所有数据都装在box(QuickTime中为atom)中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。

下面是一些概念:

  • track 表示一些sample的集合,对于媒体数据来说,track表示一个视频或音频序列。
  • hint track 这个特殊的track并不包含媒体数据,而是包含了一些将其他数据track打包成流媒体的指示信息。
  • sample 对于非hint track来说,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频,它们统称sample。对于hint tracksample定义一个或多个流媒体包的格式。
  • sample table 指明sampe时序和物理布局的表。
  • chunk 一个track的几个sample组成的单元。

基本概念

  1. 文件,由许多Box和FullBox组成。
  2. Box,每个Box由Header和Data组成。
  3. FullBox,是Box的扩展,Box结构的基础上在Header中增加8bits version和24bits flags。
  4. Header,包含了整个Box的长度size和类型type。当 size == 0时,代表这是文件中最后一个Box;当size==1时,意 味着Box长度需要更多bits来描述,在后面会定义一个64bits的largesize描述Box的长度;当type是uuid时,代表Box中的数据是用户自定义扩展类型。
  5. Data,是Box的实际数据,可以是纯数据也可以是更多的子Boxes。
  6. 当一个Box的Data中是一系列子Box时,这个Box又可成为Container Box。

结构如下图:

MP4文件格式概览

MP4文件由多个box组成,每个box存储不同的信息,且box之间是树状结构,如下图所示。

box类型有很多,下面是3个比较重要的顶层box:

  • ftyp:File Type Box,描述文件遵从的MP4规范与版本;
  • moov:Movie Box,媒体的metadata信息,有且仅有一个。
  • mdat:Media Data Box,存放实际的媒体数据,一般有多个;


虽然box类型有很多,但基本结构都是一样的。下一节会先介绍box的结构,然后再对常见的box进行进一步讲解。
下表是常见的box,稍微看下有个大致的印象就好。

MP4 Box简介

1个box由两部分组成:box header、box body。

  • box header:box的元数据,比如box type、box size。
  • box body:box的数据部分,实际存储的内容跟box类型有关,比如mdat中body部分存储的媒体数据。

box header中,只有type、size是必选字段。当size==0时,存在largesize字段。在部分box中,还存在version、flags字段,这样的box叫做Full Box。当box body中嵌套其他box时,这样的box叫做container box。

Box Header

字段定义如下:

  • type:box类型,包括 “预定义类型”、“自定义扩展类型”,占4个字节;
    • 预定义类型:比如ftyp、moov、mdat等预定义好的类型;
    • 自定义扩展类型:如果type==uuid,则表示是自定义扩展类型。size(或largesize)随后的16字节,为自定义类型的值(extended_type)
  • size:包含box header在内的整个box的大小,单位是字节。当size为0或1时,需要特殊处理:
    • size等于0:box的大小由后续的largesize确定(一般只有装载媒体数据的mdat box会用到largesize);
    • size等于1:当前box为文件的最后一个box,通常包含在mdat box中;
  • largesize:box的大小,占8个字节;
  • extended_type:自定义扩展类型,占16个字节;
    Box的伪代码如下:
aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) 
    unsigned int(32) size;
    unsigned int(32) type = boxtype;
    if (size==1) 
        unsigned int(64) largesize;
     else if (size==0) 
        // box extends to end of file
    
    if (boxtype==‘uuid’) 
        unsigned int(8)[16] usertype = extended_type;
     


Box Body

box数据体,不同box包含的内容不同,需要参考具体box的定义。有的 box body 很简单,比如 ftyp。有的 box 比较复杂,可能嵌套了其他box,比如moov。

Box vs FullBox

在Box的基础上,扩展出了FullBox类型。相比Box,FullBox 多了 version、flags 字段。

  • version:当前box的版本,为扩展做准备,占1个字节;
  • flags:标志位,占24位,含义由具体的box自己定义;

FullBox 伪代码如下:

aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f) extends Box(boxtype) 
	unsigned int(8) version = v;
	bit(24) flags = f;

FullBox主要在moov中的box用到,比如 moov.mvhd,后面会介绍到。

aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0) 
	// 字段略... 

ftyp(File Type Box)

ftyp用来指出当前文件遵循的规范,在介绍ftyp的细节前,先科普下isom。

什么是isom

isom(ISO Base Media file)是在 MPEG-4 Part 12 中定义的一种基础文件格式,MP4、3gp、QT 等常见的封装格式,都是基于这种基础文件格式衍生的。

MP4 文件可能遵循的规范有mp41、mp42,而mp41、mp42又是基于isom衍生出来的。

  • 3gp(3GPP):一种容器格式,主要用于3G手机上;
  • QT:QuickTime的缩写,.qt 文件代表苹果QuickTime媒体文件;

ftyp定义
ftyp 定义如下:

aligned(8) class FileTypeBox extends Box(‘ftyp’)   
  unsigned int(32) major_brand;  
  unsigned int(32) minor_version;  
  unsigned int(32) compatible_brands[]; // to end of the box  
  

下面是是 brand 的描述,其实就是具体封装格式对应的代码,用4个字节的编码来表示,比如 mp41。

A brand is a four-letter code representing a format or subformat. Each
file has a major brand (or primary brand), and also a compatibility
list of brands.

ftyp 的几个字段的含义:

  • major_brand:比如常见的isom、mp41、mp42、avc1、qt等。它表示“最好”基于哪种格式来解析当前的文件。举例,major_brand 是A,compatible_brands 是 A1,当解码器同时支持 A、A1规范时,最好使用A规范来解码当前媒体文件,如果不支持A规范,但支持A1规范,那么,可以使用A1规范来解码;
  • minor_version:提供 major_brand 的说明信息,比如版本号,不得用来判断媒体文件是否符合某个标准/规范;
  • compatible_brands:文件兼容的brand列表。比如 mp41 的兼容 brand 为 isom。通过兼容列表里的brand 规范,可以将文件 部分(或全部)解码出来; 在实际使用中,不能把 isom 做为major_brand,而是需要使用具体的brand(比如mp41),因此,对于 isom,没有定义具体的文件扩展名、mime type。

在实际使用中,不能把 isom 做为 major_brand,而是需要使用具体的brand(比如mp41),因此,对于isom,没有定义具体的文件扩展名、mime type。

下面是常见的几种brand,以及对应的文件扩展名、mime type,更多brand可以参考 这里 。


下面是实际例子的截图,不赘述。


关于AVC/AVC1

在讨论 MP4 规范时,提到AVC,有的时候指的是“AVC文件格式”,有的时候指的是"AVC压缩标准(H.264)",这里简单做下区分。

  • AVC文件格式:基于 ISO基础文件格式 衍生的,使用的是AVC压缩标准,可以认为是MP4的扩展格式,对应的brand 通常是avc1,在MPEG-4 PART 15 中定义。
  • AVC压缩标准(H.264):在MPEG-4 Part 10中定义。
  • ISO基础文件格式(Base Media File Format) 在 MPEG-4 Part 12 中定义。

moov(Movie Box)

Movie Box,存储 mp4 的 metadata,一般位于mp4文件的开头。

aligned(8) class MovieBox extends Box(‘moov’) 

moov中,最重要的两个box是 mvhd 和 trak:

  • mvhd:Movie Header Box,mp4文件的整体信息,比如创建时间、文件时长等;
  • trak:Track Box,一个mp4可以包含一个或多个轨道(比如视频轨道、音频轨道),轨道相关的信息就在trak里。trak是container box,至少包含两个box,tkhd、mdia;

mvhd针对整个影片,tkhd针对单个track,mdhd针对媒体,vmhd针对视频,smhd针对音频,可以认为是从 宽泛 >具体,前者一般是从后者推导出来的。

mvhd(Movie Header Box)
MP4文件的整体信息,跟具体的视频流、音频流无关,比如创建时间、文件时长等。

定义如下:

aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0)  if (version==1) 
      unsigned int(64)  creation_time;
      unsigned int(64)  modification_time;
      unsigned int(32)  timescale;
      unsigned int(64)  duration;
    else  // version==0
      unsigned int(32)  creation_time;
      unsigned int(32)  modification_time;
      unsigned int(32)  timescale;
      unsigned int(32)  duration;

template int(32) rate = 0x00010000; // typically 1.0
template int(16) volume = 0x0100; // typically, full volume const bit(16) reserved = 0;
const unsigned int(32)[2] reserved = 0;
template int(32)[9] matrix =
 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 ;
      // Unity matrix
   bit(32)[6]  pre_defined = 0;
   unsigned int(32)  next_track_ID;

字段含义如下:

  • creation_time:文件创建时间;
  • modification_time:文件修改时间;
  • timescale:一秒包含的时间单位(整数)。举个例子,如果timescale等于1000,那么,一秒包含1000个时间单位(后面track等的时间,都要用这个来换算,比如track的duration为10,000,那么,track的实际时长为10,000/1000=10s);
  • duration:影片时长(整数),根据文件中的track的信息推导出来,等于时间最长的track的duration;
  • rate:推荐的播放速率,32位整数,高16位、低16位分别代表整数部分、小数部分([16.16]),举例 0x0001 0000代表1.0,正常播放速度;
  • volume:播放音量,16位整数,高8位、低8位分别代表整数部分、小数部分([8.8]),举例 0x01 00 表示1.0,即最大音量;
  • matrix:视频的转换矩阵,一般可以忽略不计;
  • next_track_ID:32位整数,非0,一般可以忽略不计。当要添加一个新的track到这个影片时,可以使用的track id,必须比当前已经使用的track id要大。也就是说,添加新的track时,需要遍历所有track,确认可用的track id;

tkhd(Track Box)
单个 track 的 metadata,包含如下字段:

  • version:tkhd box的版本;

  • flags:按位或操作获得,默认值是7(0x000001 | 0x000002 |0x000004),表示这个track是启用的、用于播放的 且 用于预览的。

  • Track_enabled:值为0x000001,表示这个track是启用的,当值为0x000000,表示这个track没有启用;

  • Track_in_movie:值为0x000002,表示当前track在播放时会用到;

  • Track_in_preview:值为0x000004,表示当前track用于预览模式;

  • creation_time:当前track的创建时间;

  • modification_time:当前track的最近修改时间;

  • track_ID:当前track的唯一标识,不能为0,不能重复;

  • duration:当前track的完整时长(需要除以timescale得到具体秒数);

  • layer:视频轨道的叠加顺序,数字越小越靠近观看者,比如1比2靠上,0比1靠上;

  • alternate_group:当前track的分组ID,alternate_group值相同的track在同一个分组里面。同个分组里的track,同一时间只能有一个track处于播放状态。当alternate_group为0时,表示当前track没有跟其他track处于同个分组。一个分组里面,也可以只有一个track;

  • volume:audio track的音量,介于0.0~1.0之间;

  • matrix:视频的变换矩阵;

  • width、height:视频的宽高;

定义如下:

aligned(8) class TrackHeaderBox 
  extends FullBox(‘tkhd’, version, flags) 
	if (version==1) 
	      unsigned int(64)  creation_time;
	      unsigned int(64)  modification_time;
	      unsigned int(32)  track_ID;
	      const unsigned int(32)  reserved = 0;
	      unsigned int(64)  duration;
	    else  // version==0
	      unsigned int(32)  creation_time;
	      unsigned int(32)  modification_time;
	      unsigned int(32)  track_ID;
	      const unsigned int(32)  reserved = 0;
	      unsigned int(32)  duration;
	
	const unsigned int(32)[2] reserved = 0;
	template int(16) layer = 0;
	template int(16) alternate_group = 0;
	template int(16) volume = if track_is_audio 0x0100 else 0; const unsigned int(16) reserved = 0;
	template int(32)[9] matrix=  0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 ; // unity matrix
	unsigned int(32) width;
	unsigned int(32) height;

例子如下:


hdlr(Handler Reference Box)
声明当前track的类型,以及对应的处理器(handler)。

handler_type的取值包括:

  • vide(0x76 69 64 65),video track;
  • soun(0x73 6f 75 6e),audio track;
  • hint(0x68 69 6e 74),hint track;

name为utf8字符串,对handler进行描述,比如 L-SMASH Video Handler(参考 这里)。

aligned(8) class HandlerBox extends FullBox(‘hdlr’, version = 0, 0)  
	unsigned int(32) pre_defined = 0;
	unsigned int(32) handler_type;
	const unsigned int(32)[3] reserved = 0;
   	string   name;

stbl(Sample Table Box)

MP4文件的媒体数据部分在mdat box里,而stbl则包含了这些媒体数据的索引以及时间信息,了解stbl对解码、渲染MP4文件很关键。

在MP4文件中,媒体数据被分成多个chunk,每个chunk可包含多个sample,而sample则由帧组成(通常1个sample对应1个帧)
stbl中比较关键的box包含stsd、stco、stsc、stsz、stts、stss、ctts。下面先来个概要的介绍,然后再逐个讲解细节。

stco / stsc / stsz / stts / stss / ctts / stsd 概述
下面是这几个box概要的介绍:

  • stsd:给出视频、音频的编码、宽高、音量等信息,以及每个sample中包含多少个frame;
  • stco:thunk在文件中的偏移;
  • stsc:每个thunk中包含几个sample;
  • stsz:每个sample的size(单位是字节);
  • stts:每个sample的时长;
  • stss:哪些sample是关键帧;
  • ctts:帧解码到渲染的时间差值,通常用在B帧的场景;

stsd(Sample Description Box)
stsd给出sample的描述信息,这里面包含了在解码阶段需要用到的任意初始化信息,比如 编码 等。对于视频、音频来说,所需要的初始化信息不同,这里以视频为例。

伪代码如下:

aligned(8) abstract class SampleEntry (unsigned int(32) format) extends Box(format)
	const unsigned int(8)[6] reserved = 0;
	unsigned int(16) data_reference_index;


// Visual Sequences
class VisualSampleEntry(codingname) extends SampleEntry (codingname) 
	unsigned int(16) pre_defined = 0;
	const unsigned int(16) reserved = 0;
	unsigned int(32)[3] pre_defined = 0;
	unsigned int(16) width;
	unsigned int(16) height;
	template unsigned int(32) horizresolution = 0x00480000; // 72 dpi 
	template unsigned int(32) vertresolution = 0x00480000; // 72 dpi 
	const unsigned int(32) reserved = 0;
	template unsigned int(16) frame_count = 1;
	string[32] compressorname;
	template unsigned int(16) depth = 0x0018;
	int(16) pre_defined = -1;


// AudiosampleEntry、HintSampleEntry 定义略过


aligned(8) class SampleDescriptionBox (unsigned int(32) handler_type) extends FullBox('stsd', 0, 0)
	int i ;
	unsigned int(32) entry_count;
	for (i = 1 ; i u entry_count ; i++) 
	      switch (handler_type)
	        case ‘soun’: // for audio tracks
				AudioSampleEntry();
				break;
			case ‘vide’: // for video tracks
			   VisualSampleEntry();
			   break;
			case ‘hint’: // Hint track
			   HintSampleEntry();
			   break;	         
		
	


在SampleDescriptionBox 中,handler_type 参数 为 track 的类型(soun、vide、hint),entry_count 变量代表当前box中 smaple description 的条目数。

stsc 中,sample_description_index 就是指向这些smaple description的索引。

针对不同的handler_type,SampleDescriptionBox 后续应用不同的 SampleEntry 类型,比如video track为VisualSampleEntry。

VisualSampleEntry包含如下字段:

  • data_reference_index:当MP4文件的数据部分,可以被分割成多个片段,每一段对应一个索引,并分别通过URL地址来获取,此时,data_reference_index
    指向对应的片段(比较少用到);
  • width、height:视频的宽高,单位是像素;
  • horizresolution、vertresolution:水平、垂直方向的分辨率(像素/英寸),16.16定点数,默认是0x00480000(72dpi);
  • frame_count:一个sample中包含多少个frame,对video track来说,默认是1;
  • compressorname:仅供参考的名字,通常用于展示,占32个字节,比如 AVC Coding。第一个字节,表示这个名字实际要占用N个字节的长度。第2到第N+1个字节,存储这个名字。第N+2到32个字节为填充字节。compressorname可以设置为0;
  • depth:位图的深度信息,比如 0x0018(24),表示不带alpha通道的图片;

In video tracks, the frame_count field must be 1 unless the specification for the media format explicitly documents this template field and permits larger values. That specification must document both how the individual frames of video are found (their size information) and their timing established. That timing might be as simple as dividing the sample duration by the frame count to establish the frame duration.

例子如下:


stco(Chunk Offset Box)
chunk在文件中的偏移量。针对小文件、大文件,有两种不同的box类型,分别是stco、co64,它们的结构是一样的,只是字段长度不同。

chunk_offset 指的是在文件本身中的 offset,而不是某个box内部的偏移。

在构建mp4文件的时候,需要特别注意 moov 所处的位置,它对于chunk_offset 的值是有影响的。有一些MP4文件的 moov 在文件末尾,为了优化首帧速度,需要将 moov 移到文件前面,此时,需要对 chunk_offset 进行改写。

stco 定义如下:

# Box Type: ‘stco’, ‘co64’
# Container: Sample Table Box (‘stbl’) Mandatory: Yes
# Quantity: Exactly one variant must be present

aligned(8) class ChunkOffsetBox
	extends FullBox(‘stco’, version = 0, 0)  
	unsigned int(32) entry_count;
	for (i=1; i u entry_count; i++) 
		unsigned int(32)  chunk_offset;
	


aligned(8) class ChunkLargeOffsetBox
	extends FullBox(‘co64’, version = 0, 0)  
	unsigned int(32) entry_count;
	for (i=1; i u entry_count; i++) 
		unsigned int(64)  chunk_offset;
	


如下例子所示,第一个chunk的offset是47564,第二个chunk的偏移是120579,其他类似。


stsc(Sample To Chunk Box)
sample 以 chunk 为单位分成多个组。chunk的size可以是不同的,chunk里面的sample的size也可以是不同的。

  • entry_count:有多少个表项(每个表项,包含first_chunk、samples_per_chunk、sample_description_index信息);
  • first_chunk:当前表项中,对应的第一个chunk的序号;
  • samples_per_chunk:每个chunk包含的sample数;
  • sample_description_index:指向 stsd 中 sample description 的索引值(参考stsd小节);
aligned(8) class SampleToChunkBox
	extends FullBox(‘stsc’, version = 0, 0)  
	unsigned int(32) entry_count;
	for (i=1; i u entry_count; i++) 
		unsigned int(32) first_chunk;
		unsigned int(32) samples_per_chunk; 
		unsigned int(32) sample_description_index;
	


前面描述比较抽象,这里看个例子,这里表示的是:

  • 序号1~15的chunk,每个chunk包含15个sample;
  • 序号16的chunk,包含30个sample;
  • 序号17以及之后的chunk,每个chunk包含28个sample;
  • 以上所有chunk中的sample,对应的sample description的索引都是1;
first_chunksamples_per_chunksample_description_index
1151
16301
17281


stsz(Sample Size Boxes)
每个sample的大小(字节),根据 sample_size 字段,可以知道当前track包含了多少个sample(或帧)。

有两种不同的box类型,stsz、stz2。

stsz:

  • sample_size:默认的sample大小(单位是byte),通常为0。如果sample_size不为0,那么,所有的sample都是同样的大小。如果sample_size为0,那么,sample的大小可能不一样。
  • sample_count:当前track里面的sample数目。如果 sample_size==0,那么,sample_count等于下面entry的条目;
  • entry_size:单个sample的大小(如果sample_size==0的话);
aligned(8) class SampleSizeBox extends FullBox(‘stsz’, version = 0, 0)  
	unsigned int(32) sample_size;
	unsigned int(32) sample_count;
	if (sample_size==0) 
		for (i=1以上是关于MP4格式详解的主要内容,如果未能解决你的问题,请参考以下文章

MP4格式详解

win10 qlv格式怎么转为mp4格式?cmd要怎么输写?求详解,谢谢

使用 ffmpeg 禁用默认字幕轨道

使用 SharpDx 或 IMSourceReader 从 mp4 文件中读取第二个音轨流

知识:H5中<video> 标签的知识

在碎片MP4中运行的轨道是否必须以关键帧开始?