一. Box基本结构
1. class 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;
}
}
2. class 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;
}
flags is a 24-bit integer with flags; the following values are defined:
Track_enabled: 0x000001. 是否激活track.
Track_in_movie: 0x000002. track是否用于呈现.
Track_in_preview: 0x000004. track是否用于预览.
二. Box
1. File Type Box(`ftyp’)
Container: FileMandatory: Yes
Quantity: Exactly one (but see below)
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
}
2. Movie Box(‘moov’)
Container: FileMandatory: Yes
Quantity: Exactly one
aligned(8) class MovieBox extends Box(‘moov’)
{
}
2.1 Movie Header Box(‘mvhd’)
Container: Movie Box (‘moov’)Mandatory: Yes
Quantity: Exactly one
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: 文件创建时间 utc from 1904:1:1. 如果从Unix的元年1970年开始计算,需要加上66年,所以程序中通常为加上固定值2082758400ULL。
modification_time: 文件修改时间 utc from 1904:1:1
timescale:时间尺度
duration: 文件长度(时间尺度计算). 文件的duration应该是所有track中duration最长的那个. 如果长度未知,用timesacle去表示为1S.
rate:播放速率 (fixed point 16.16)
volume: 优先播放的声音大小(fixed point 8.8)
matrix: 用于视频的变换矩阵, 公式见下. 存储顺序 {a,b,u, c,d,v, x,y,w}. 其中(a,b,c,d,x,y)为 fixed point 16.16.(u,v,w) 为fixed point 2.30,恒为
值(0,0,1),表示为2.30的浮点数为(0,0,0x40000000).
(p q 1) * | a b u | = (m n z)
| c d v |
| x y w |
next_track_ID: 用于向MP4中增加track, 因此大于现有MP4文件中trackId.
2.2 Track Box(‘trak’)
Container: Movie Box (‘moov’)Mandatory: Yes
Quantity: One or more
aligned(8) class TrackBox extends Box(‘trak’)
{
}
Track Box的作用:
(a) to contain media data (media tracks)
(b) to contain packetization information for streaming protocols (hint tracks).
2.2.1 Track Header Box(‘tkhd’)
Container: Track Box (‘trak’)Mandatory: Yes
Quantity: Exactly one
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;
}
creation_time:track的创建时间
modification_time:track的修改时间
track_ID: track id,此值唯一
duration: track的时长. timescale定义在mvhd中.
layer: 用于视频track的显示层次,和photoshop中的层类似, 越小越前.
alternate_group: track的组集合.值为0,表示和其他track无关. 在同一时间,组内只有一条track能够被显示.
volume: 音量. fixed point, 8.8.
matrix: 视频track显示矩阵.
width: 图像宽. fixed point, 16.16.
height: 图像高. fixed point, 16.16.
2.2.2 Edit Box(‘edts’)
Container: Track Box (‘trak’)Mandatory: No
Quantity: Zero or one
aligned(8) class EditBox extends Box(‘edts’)
{
}
Edit boxs 定义了创建movie中一个track的一部分媒体.所有的edit都在一个表里面,包括每一部分的时间偏移量和长度.
Edit boxs 的类型是'edts'.如果没有该表,则此track会被立即播放.一个空的edit用来偏移track的起始时间.如果没有edit box 或edit list box,
则此track使用全部媒体.Edit boxs是一个容器box,本身没有特别的字段,需要子box来进一步说明有效的内容.
2.2.2.1 Edit List Box(‘elst’)
Container: Edit Box (‘edts’)Mandatory: No
Quantity: Zero or one
aligned(8) class EditListBox extends FullBox(‘elst’, version, 0)
{
unsigned int(32) entry_count;
for (i=1; i <= entry_count; i++)
{
if (version==1)
{
unsigned int(64) segment_duration;
int(64) media_time;
}
else
{ // version==0
unsigned int(32) segment_duration;
int(32) media_time;
}
int(16) media_rate_integer;
int(16) media_rate_fraction = 0;
}
}
这个Box定义了一个track中Edit段的时间表,其中包含了多个entry,每个entry定义了track中Edit时间表的一部分. 通过匹配媒体的时间表,
或者通过显示"empty"时间,或者通过定义一个"dwell"点(用于在媒体播放时暂停一段时间).
需要注意的是:编辑段(Edits)并不严格匹配于帧(Sample)时间.这意味着进入一个编辑段(Edit)时,有可能必须做两件事:
1. 备份一个同步点,或者从此点开始一段预播
2. 必须小心原始数据track的第一个帧的持续时间,如果编辑段进入了一个track的原始数据段,此帧可能会被截断.
典型的应用是:
对一个tracks进行偏移,可以使用一个预定义的空edit.比如说:如果想要,从一个track的起始开始播放,连续播放30秒,但是第10秒才开始呈现播放内容.
(简单的说,就是延迟10秒,然后从原始视频的第0秒开始播放track30秒),可以如下设置:
Entry-count = 2
Segment-duration = 10 seconds
Media-Time = -1
Media-Rate = 1
Segment-duration = 30 seconds (could be the length of the whole track)
Media-Time = 0 seconds
Media-Rate = 1
entry_count:实体个数
segment_duration:编辑段的持续时间(值用MVHD中的timescale表示)
media_time: 编辑段要在media上开始的时间点.
media_rate:播放编辑段的速率.为0的话,编辑段为一个"dwell",即画面停止.画面会在media-time点上停止segment_duration时间.否则这个值始终为1.
并不是所有播放器都会解析这个box.
2.2.3 Media Box(‘mdia’)
Container: Track Box (‘trak’)Mandatory: Yes
Quantity: Exactly one
aligned(8) class MediaBox extends Box(‘mdia’)
{
}
2.2.3.1 Media Header Box(‘mdhd’)
Container: Media Box (‘mdia’)Mandatory: Yes
Quantity: Exactly one
aligned(8) class MediaHeaderBox extends FullBox(‘mdhd’, 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;
}
bit(1) pad = 0;
unsigned int(5)[3] language; // ISO-639-2/T language code
unsigned int(16) pre_defined = 0;
}
creation_time: track中数据的创建时间,多同‘tkhd’中creation_time.
modification_time: track中数据的修改时间,多同‘tkhd’中modification_time.
timescale: 媒体中时间尺度. 通常和'mvhd'中的timescale不同,精度更高.
duration: 媒体的长度(时间尺度表示).
language:语言,符合ISO-639-2/T标准.
2.2.3.2 Handler Reference Box(‘hdlr’)
Container: Media Box (‘mdia’) or Meta Box (‘meta’)Mandatory: Yes
Quantity: Exactly one
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;
}
该box用于声明处理类型.
handler_type: 4字节固定整数
1. 在‘mdia’ Box中,类型为:
‘vide’ Video track
‘soun’ Audio track
‘hint’ Hint track
‘meta’ Timed Metadata track
‘auxv’ Auxiliary Video track
2. 在‘meta’ Box中,类型为:
‘null’
name:handle的名字,人为添加.
2.2.3.3 Media Information Box(‘minf’)
Container: Media Box (‘mdia’)Mandatory: Yes
Quantity: Exactly one
aligned(8) class MediaInformationBox extends Box(‘minf’)
{
}
仅是‘vmhd’, ‘smhd’, ’hmhd’, ‘nmhd’的容器.
2.2.3.3.1 Media Information Header Boxes(‘vmhd’, ‘smhd’, ’hmhd’, ‘nmhd’)
Container: Media Information Box (‘minf’)Mandatory: Yes
Quantity: Exactly one specific media header shall be present
a. Video Media Header Box(‘vmhd’)
aligned(8) class VideoMediaHeaderBox extends FullBox(‘vmhd’, version = 0, 1)
{
template unsigned int(16) graphicsmode = 0; // copy, see below
template unsigned int(16)[3] opcolor = {0, 0, 0};
}
graphicsmode: video的合成模式
opcolor:为合成模式预设的R,G,B值
b. Sound Media Header Box(‘smhd’)
aligned(8) class SoundMediaHeaderBox extends FullBox(‘smhd’, version = 0, 0)
{
template int(16) balance = 0;
const unsigned int(16) reserved = 0;
}
balance: fixed-point 8.8, 单声道音轨在立体声中的播放位置. 中间 0; 左 -1.0;右 1.0.
2.2.3.3.2 Media Information Header Boxes(‘dinf’)
Container: Media Information Box (‘minf’) or Meta Box (‘meta’)Mandatory: Yes (required within ‘minf’ box) and No (optional within ‘meta’ box)
Quantity: Exactly one
aligned(8) class DataInformationBox extends Box(‘dinf’)
{
}
“dinf”一般包含一个“dref”,即data reference box;“dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据.
简单的说,track可以被分成若干段,每一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track.
一般情况下,当数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的.
2.2.3.3.2.1 Data Reference Box(‘url‘, ‘urn‘, ‘dref’)
Container: Data Information Box (‘dinf’)
Mandatory: Yes
Quantity: Exactly one
a. DataEntryUrlBox(‘url‘)
aligned(8) class DataEntryUrlBox (bit(24) flags) extends FullBox(‘url ’, version = 0, flags)
{
string location;
}
b. DataEntryUrnBox(‘urn‘)
aligned(8) class DataEntryUrnBox (bit(24) flags) extends FullBox(‘urn ’, version = 0, flags)
{
string name;
string location;
}
c. DataReferenceBox(‘dref’)
aligned(8) class DataReferenceBox extends FullBox(‘dref’, version = 0, 0)
{
unsigned int(32) entry_count;
for (i=1; i <= entry_count; i++)
{
DataEntryBox(entry_version, entry_flags) data_entry;
}
}
DataEntryBox指的是DataEntryUrnBox和DataEntryUrlBox.
2.2.3.3.3 Sample Table Box(‘stbl’)
Container: Media Information Box (‘minf’)Mandatory: Yes
Quantity: Exactly one
aligned(8) class SampleTableBox extends Box(‘stbl’)
{
}
2.2.3.3.3.1 Sample Description Box(‘stsd’)
Container: Sample Table Box (‘stbl’)
Mandatory: Yes
Quantity: Exactly one
这个box定义了视频的编码类型、宽高、长度,音频的声道、采样等信息.
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;
}
data_reference_index:关联的数据引用值的序号.数据引用值定义在Dref Box当中.
1. 视频
class CleanApertureBox extends Box(‘clap’)
{
unsigned int(32) cleanApertureWidthN;
unsigned int(32) cleanApertureWidthD;
unsigned int(32) cleanApertureHeightN;
unsigned int(32) cleanApertureHeightD;
unsigned int(32) horizOffN;
unsigned int(32) horizOffD;
unsigned int(32) vertOffN;
unsigned int(32) vertOffD;
}
class PixelAspectRatioBox extends Box(‘pasp’)
{
unsigned int(32) hSpacing;
unsigned int(32) vSpacing;
}
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;
// other boxes from derived specifications
CleanApertureBox clap; // optional
PixelAspectRatioBox pasp; // optional
}
width: 视频宽
height:视频高
horizresolution:水平分辨率(pixels-per-inch, fixed 16.16 number)
vertresolution:垂直分辨率(pixels-per-inch, fixed 16.16 number)
frame_count: 每个sample中压缩了多少帧
compressorname:名字,出于信息显示作用.
depth:位深.0x18表示24色,没有alpha值.
CleanApertureBox:纯净光圈,和长宽比有关.纯净光圈下,水平像素和垂直像素尺寸不同.
PixelAspectRatioBox:长宽比
2. 音频
class AudioSampleEntry(codingname) extends SampleEntry (codingname)
{
const unsigned int(32)[2] reserved = 0;
template unsigned int(16) channelcount = 2;
template unsigned int(16) samplesize = 16;
unsigned int(16) pre_defined = 0;
const unsigned int(16) reserved = 0 ;
template unsigned int(32) samplerate = { default samplerate of media}<<16;
}
channelcount:声道数.1为单声道,2为立体声.
SampleSize:Sample大小.
SampleRate:采样率 (a fixed 16.16 number)
2.2.3.3.3.2 Decoding Time to Sample Box(‘stts’)
Container: Sample Table Box (‘stbl’)
Mandatory: Yes
Quantity: Exactly one
这个Box给出了解码时间和帧关系表,也就是说标识了每一帧的解码时间:
DT(n+1) = DT(n) + STTS(n),其中DT(n) = SUM(for j=0 to n-1 of delta(j)).
上述时间表的表示,并未匹配到整个文件的timescale,且不考虑任何elst Box.
aligned(8) class TimeToSampleBox extends FullBox(’stts’, version = 0, 0)
{
unsigned int(32) entry_count;
int i;
for (i=0; i < entry_count; i++)
{
unsigned int(32) sample_count;
unsigned int(32) sample_delta;
}
}
sample_count: 拥有同样duration的连续sample数目
sample_delta: 两帧之间的增量时间,用timescale表示.
2.2.3.3.3.3 Composition Time to Sample Box(‘ctts’)
Container: Sample Table Box (‘stbl’)
Mandatory: No
Quantity: Zero or one
这个box给出了dts和cts之间的偏移值.dts和cts之间的有如下关系:
在version为0的协议中,dts要小于cts,且dts和cts的偏移值必须是无符号整数.
CT(n) = DT(n) + CTTS(n).
在version为1的协议中,dts和cts的偏移值定义为整数.
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;
}
}
}
‘stts’和‘ctts’的关系,可见下表:
协议47页
version:box协议版本
entry_count: 实体数目
sample_count: 拥有共同偏移的实体数目
sample_offset: CT和DT之间的偏移值,CT(n) = DT(n) + CTTS(n).
2.2.3.3.3.4 Composition to Decode Box(‘cslg’)
Container: Sample Table Box (‘stbl’)
Mandatory: No
Quantity: Zero or one
该box在‘ctts’ Box存在,并且‘ctts’ Box中整形的偏移值被使用时,才会被使用.它用于解释一些由于‘ctts’使用整形偏移值所带来的歧义.
一般用不到,跳过.
class CompositionToDecodeBox extends FullBox(‘cslg’, version=0, 0)
{
signed int(32) compositionToDTSShift;
signed int(32) leastDecodeToDisplayDelta;
signed int(32) greatestDecodeToDisplayDelta;
signed int(32) compositionStartTime;
signed int(32) compositionEndTime;
}
2.2.3.3.3.5 Sync Sample Box(‘stss’)
Container: Sample Table Box (‘stbl’)
Mandatory: No
Quantity: Zero or one
这个Box描述了流中的所有I帧的顺序,并且严格按照帧顺序递增.如果这个box不存在,所有的帧都是I帧.stss被认为标记了随机存取点
aligned(8) class SyncSampleBox extends FullBox(‘stss’, version = 0, 0)
{
unsigned int(32) entry_count;
int i;
for (i=0; i < entry_count; i++)
{
unsigned int(32) sample_number;
}
}
entry_count:流中的I帧数目.如果entry_count为0,则流中没有I帧?
sample_number:给出了流中同步帧的帧号.
2.2.3.3.3.6 Sample To Chunk Box(‘stsc’)
Container: Sample Table Box (‘stbl’)
Mandatory: Yes
Quantity: Exactly one
aligned(8) class SampleToChunkBox extends FullBox(‘stsc’, version = 0, 0)
{
unsigned int(32) entry_count;
for (i=1; i <= entry_count; i++)
{
unsigned int(32) first_chunk;
unsigned int(32) samples_per_chunk;
unsigned int(32) sample_description_index;
}
}
当添加samples到media时,用chunks组织这些sample,这样可以方便优化数据获取.
一个trunk包含一个或多个sample,chunk的长度可以不同,chunk内的sample的长度也可以不同.
sample-to-chunk box存储sample与chunk的映射关系.
第一个first chunk减去第二个first chunk就是一共有多少个trunk包含相同的sample数目,
这样通过不断的叠加,就可以得到一共多少个trunk,每个trunk包含多少个sample,以及每个trunk对应的description.
entry_count:用于指示下表中有多少个实体
first_chunk:用于指示连续的拥有相同sample数的chuck组中的第一个chunk号.chunk号从0开始.
samples_per_chunk:用于指示拥有相同sample数的chuck组中sample数目
sample_description_index:与这些sample关联的sample description的序号
2.2.3.3.3.7 Chunk Offset Box(‘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 <= 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 <= entry_count; i++)
{
unsigned int(64) chunk_offset;
}
}
Chunk offset boxs 定义了每个chunk在媒体流中的位置,它的类型是'stco'.位置有两种可能,32位的和64位的,后者对非常大的电影很有用.
在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,
这样做就可以直接在文件中找到媒体数据,而不用解释box.需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了.
entry_count: 下表中给出的entry 个数.
chunk_offset: 每个chunk对于media文件的偏移值.
2.2.3.3.3.8 Sample Size Box(‘stsz’)
Container: Sample Table Box (‘stbl’)
Mandatory: Yes
Quantity: Exactly one variant must be present
sample size boxs定义了每个sample的大小,它的类型是'stsz',包含了媒体中全部sample的数目和一张给出每个sample大小的表,
这样,媒体数据自身就可以没有边框的限制,
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; i <= sample_count; i++)
{
unsigned int(32) entry_size;
}
}
}
sample_size: 指明了默认的sample大小,如果所有的帧有相同的大小,这里有值,如果帧有不同的大小,每一帧的值存于接下去的表中,
sample_count:整数值给出了track中的帧数目,
entry_size:整数值,指明帧的大小,
2.2.3.3.3.9 Compact Sample Size Box(‘stz2’)
Container: Sample Table Box (‘stbl’)
Mandatory: Yes
Quantity: Exactly one variant must be present
作用同‘stsz’,用简洁格式表示sample的大小。两个sample中可能被"0"填充。
aligned(8) class CompactSampleSizeBox extends FullBox(‘stz2’, version = 0, 0)
{
unsigned int(24) reserved = 0;
unisgned int(8) field_size;
unsigned int(32) sample_count;
for (i=1; i <= sample_count; i++)
{
unsigned int(field_size) entry_size;
}
}
field_size:整形值,指定接下去表中每个entries的位数。该值必须为4,8,16。如果该值为4的话,每个字节如此解释:entry[i]<<4 + entry[i+1]
3. Media Data Box(‘mdat’)
Container: FileMandatory: No
Quantity: Zero or more
这个box用于存储实际的媒体数据。在存在视频轨的文件中,box中存的是视频帧数据。一个视频MP4文件可能存在零个或多个mdat Box。如果是零个,说明实际媒体数据存储于另外的文件中。
aligned(8) class MediaDataBox extends Box(‘mdat’)
{
bit(8) data[];
}
4. Free Space Box(‘free’)
Container: File or other boxMandatory: NoQuantity: Zero or more这个box是无关紧要的,用于数据填充,可以删除。在删除时,数据偏移需要重新计算。
aligned(8) class FreeSpaceBox extends Box(free_type)
{
unsigned int(8) data[];
}
三. Box 简述
1. ftyp
文件级别的box,描述文件的兼容性
2. moov
meta信息的容器,无内容
3. mvhd
文件的timescale,rate,duration,nextTrackId,createTime/modifacationTime
4. trak
track 信息的容器box
5. tkhd
track的duration,alternateGroup,trackId。
视频:width/height
音频:volume
6. mdia
媒体数据容器box
7. mdhd
数据的duration,timescale
8. hdlr
数据处理方式,指明video,audio,text等
9. minf
媒体信息容器
10. vmhd
指明视频 graphicsMode,opcolor,一般没用
11. smhd
指明音频的 balance
12. stbl
sampel table, 容器box
13. stsd
包含 visual sample entry或者audio sample entry。visual sample entry中有 height,width,framecoute,depth信息。audio sample entry有sampleRate,sampleSize,channelCount等信息.
14. stts
描述decode时间到sample的关系,根据时间可以得到帧号
15. ctts
描述cts和dts的差值
16. stss
描述关键帧号
17. stsc
描述sample号和chuck序号的映射关系
18. stsz
描述每一帧大小
19. stco
描述chuck序号和文件地址偏移关系
四. 常用的计算
以下转自 wqyuwss的专栏1. 计算电影长度
a. 从Movie Header Box(‘mvhd’) 中找到time scale和duration,duration除以time scale即是整部电影的长度。time scale相当于定义了标准的1秒在这部电影里面的刻度是多少。
b. 首先计算出共有多少个帧(从 Sample Size Box(‘stsz’)中获得),然后整部电影的duration = 每个帧的duration之和(从Decoding Time to Sample Box(‘stts’)中得到)
2. 计算电影声音采样频率
从 Sample Description Box(‘stsd’)中的AudioSampleEntry获取3. 计算电影图像宽度和高度
从 Sample Description Box(‘stsd’)中的VisualSampleEntry获取4. 计算视频帧率
首先计算出整部电影的duration,和帧的数目。然后“帧率 = 整部电影的duration / 帧的数目”5. 计算电影的比特率
整部电影的尺寸除以长度,即是比特率6. 查找sample
当播放一部电影或者一个track的时候,对应的media handler必须能够正确的解析数据流,对一定的时间获取对应的媒体数据。如果是视频媒体, media handler可能会解析多个box,才能找到给定时间的sample的大小和位置。具体步骤如下:
a.确定时间,相对于媒体时间坐标系统
b.检查time-to-sample box(Decoding Time to Sample Box)来确定给定时间的sample序号。
c.检查sample-to-chunk box(Sample To Chunk Box(‘stsc’))来发现对应该sample的chunk。
d.从chunk offset box(Chunk Offset Box(‘stco’, ‘co64’))中提取该trunk的偏移量。
e.利用sample size box(Sample Size Box(‘stsz’))找到sample在trunk内的偏移量和sample的大小。
例如,如果要找第1秒的视频数据,过程如下:
a.第1秒的视频数据相对于此电影的时间为600
b. 检查time-to-sample box(Decoding Time to Sample Box),得出每个sample的duration是40,从而得出需要寻找第600/40 = 15 + 1 = 16个sample
c. 检查sample-to-chunk box(Sample To Chunk Box(‘stsc’)),得到该sample属于第5个chunk的第一个sample,该chunk共有4个sample
d. 检查chunk offset box(Chunk Offset Box(‘stco’, ‘co64’))找到第5个trunk的偏移量是20472
e. 由于第16个sample是第5个trunk的第一个sample,所以不用检查sample size box(Sample Size Box(‘stsz’)),trunk的偏移量即是该sample的偏移量20472。
如果是这个trunk的第二个sample,则从sample size box中找到该trunk的前一个sample的大小,然后加上偏移量即可得到实际位置。
f. 得到位置后,即可取出相应数据进行解码,播放
7. 查找关键帧
查找过程与查找sample的过程非常类似,只是需要利用sync sample box来确定key frame的sample序号a. 确定给定时间的sample序号
b. 检查sync sample box来发现这个sample序号之后的key frame
c. 检查sample-to-chunk box来发现对应该sample的chunk
d. 从chunk offset box中提取该trunk的偏移量
e. 利用sample size box找到sample在trunk内的偏移量和sample的大小
8. Random access
Seeking主要是利用sample table box里面包含的子box来实现的,还需要考虑edit list的影响。可以按照以下步骤seek某一个track到某个时间T,注意这个T是以movie header box里定义的time scale为单位的:
如果track有一个edit list,遍历所有的edit,找到T落在哪个edit里面。将Edit的开始时间变换为以movie time scale为单位,得到EST,T减去EST,
得到T',就是在这个edit里面的duration,注意此时T'是以movie的time scale为单位的。然后将T'转化成track媒体的time scale,得到T''。
T''与Edit的开始时间相加得到以track媒体的time scale为单位的时间点T'''。
这个track的time-to-sample表说明了该track中每个sample对应的时间信息,利用这个表就可以得到T'''对应的sample NT。
sample NT可能不是一个random access point,这样就需要其他表的帮助来找到最近的random access point。
一个表是sync sample表,定义哪些sample是random access point。使用这个表就可以找到指定时间点最近的sync sample。
如果没有这个表,就说明所有的sample都是synchronization points,问题就变得更容易了。
另一个shadow sync box可以帮助内容作者定义一些特殊的samples,它们不用在网络中传输,但是可以作为额外的random access point。
这就改进了random access,同时不会影响正常的传输比特率。这个表指出了非random access point和random access point之间的关系。
如果要寻找指定sample之前最近的shadow sync sample,就需要查询这个表。总之,利用sync sample和shadow sync表,就可以seek到NT
之前的最近的access point sample Nap。
找到用于access point的sample Nap之后,利用sample-to-chunk表来确定sample位于哪个chunk内。
找到chunk后,使用chunk offset找到这个chunk的开始位置。
使用sample-to-chunk表和sample size表中的数据,找到Nap在此chunk内的位置,再加上此chunk的开始位置,就找到了Nap在文件中的位置。
五 : 简单总结
MP4文件的目的是用来播放。在播放器上最常用的动作就是拖动时间条,所以在播放端的驱动是以时间轴计算的。同时MP4是一种文件格式,文件是按帧存储在磁盘上,搜索磁盘以字节为单位。所有媒体格式的基本功能就是需要能从播放时间获取到播放数据。
为此,MP4格式内容中包括了两部分信息,meta信息("moov")和data信息("mdat")。
由于一个媒体文件多是音视频的混合体,所以在meta的层次上定义了track信息。通过track信息可以得到每条视频或者音频的具体信息。meta最基本的信息就是帧数,timescale,视频宽高,音频通道数。通过这些信息,就可以得到视频长度,每帧的dts,pts等。
所有帧在track内是顺序存放的,如果指定时间,通过timescale信息,可以得到计算中间体帧号。如果有一个表来定义每帧大小或者每帧的起始段,那么通过计算或者查表就可以知道某个时间的帧真实数据。MP4格式设计也是如此。帧的大小一般不大,但文件中的帧数却可能很大。如果只有表格存储每帧大小,那么查找文件尾部的帧,计算量就会很大。如果只有表格来存储每帧起始段,查找起来很快。但是表格本身也需要存入MP4文件中,而且越是文件结尾的帧,存储起始地址需要的存储空间会变大。因此需要在快速查找和存储大小之间找到平衡。
MP4通过把数据分成块(chuck)来解决这个问题。为chuck块来提供直接的文件地址偏移,让帧在truck块中搜索。
六 : 其他
1. CENC
CENC全称为"Common Encryption",即通用加密方案,这个方案定义于ISO_IEC_23001-7(上面这个链接是自己翻译的,需要原版的话自己再找一下)中。通用加密保护规范定义了标准的加密方法和key的匹配方法,目的是让一个或者多个DRM系统能够解密相同的文件(也就是不同的DRM系统之间能够以某种方式兼容)。简单的说这个方案就是把加密MP4数据的KEY,映射成一个KEYID,然后把KEYID和对应的IV存储于MP4数据中。
这个规范定义了一个通用格式,这种格式只被用于加密“保护流解密所需的必要元数据”。而权限匹配,key的获取和存储,DRM适应性规则,以及DRM系统或者是支持"cenc"的方案等细节都没有涉及。个人的理解是,这个方案是各个DRM厂商博弈的结果。不多说些什么,直接介绍实现该方案需要实现的相关的一些box。
1.1 pssh
<pre name="code" class="plain">aligned(8) class ProtectionSystemSpecificHeaderBox extends FullBox(‘pssh’, version=0, flags=0)
{
unsigned int(8)[16] SystemID;
unsigned int(32) DataSize;
unsigned int(8)[DataSize] Data;
}
SystemID: 内容保护系统的唯一ID。每个DRM厂商只能对应唯一一个ID。在
DashIF上可以查到一些ID和厂商的对应关系。比如:
Adobe: F239E769-EFA3-4850-9C16-A903C6932EFB
DIVX: 35BF197B-530E-42D7-8B65-1B4BF415070F
Microsoft: 9a04f079-9840-4286-ab92-e65be0885f95
DataSize: 指定Data域的数据长度.
Data: 不同保护系统自定义的数据. 由各个DRM厂商自定义。
1.2 encv/enca/enct/encs:
1.3 schm:
"schm"定义了MP4的保护或者限制方案。很明显MP4的保护方案不止"CENC"。aligned(8) class SchemeTypeBox extends FullBox('schm', 0, flags)
{
unsigned int(32) scheme_type; // 4CC identifying the scheme unsigned int(32) scheme_version; // scheme version
if (flags & 0x000001)
{
unsigned int(8) scheme_uri[]; // browser uri
}
}
scheme_type:一个代码用于定义保护或限制方案. 对于CENC方案来说,scheme_type的值就是“cenc”.
scheme_version:方案的版本号(用于创建内容)
scheme_URI:一个选项,用来指引用户链接到方案的web页面,如果在用户的机器上没有安装这个方案的话。它是一个UTF-8编码的"\0"结尾的绝对路径。
1.4 schi:
aligned(8) class SchemeInformationBox extends Box('schi')
{
Box scheme_specific_data[];
}
对于“cenc”方案来说,schi下的box为"tenc"。
1.5 tenc:
"tenc"box包含了整条加密轨,所使用的默认"IsEncrypted","IV_size","KID"值。当然如果帧进行分组,那么组的参数可能会覆盖轨的参数。如果文件中的每条轨只使用一个key去加密的话,那么这个box可以用来保存加密的基本参数,这样可以不必为轨中的每个帧单独指定加密参数。aligned(8) class TrackEncryptionBox extends FullBox(‘tenc’, version=0, flags=0)
{
unsigned int(24) default_IsEncrypted;
unsigned int(8) default_IV_size;
unsigned int(8)[16] default_KID;
}
default_IsEncypted:表示轨中每一帧默认的加密状态。 default_IV_size:表示默认的初始化向量。
default_KID:表示轨中所有帧的默认key的标识。
1.6 sgpd:
这个box用来描述MP4文件中的帧的分组信息,即包含了些什么样的组。比如说可以为帧的加密进行分组,可以为帧的显示的层次进行分组。这个box只描述分组,至于分组中帧的具体信息则由"sbgp"提供。// Sequence Entry
abstract class SampleGroupDescriptionEntry (unsigned int(32) grouping_type)
{
}
abstract class VisualSampleGroupEntry (unsigned int(32) grouping_type) extends SampleGroupDescriptionEntry (grouping_type)
{
}
abstract class AudioSampleGroupEntry (unsigned int(32) grouping_type) extends SampleGroupDescriptionEntry (grouping_type)
{
}
abstract class HintSampleGroupEntry (unsigned int(32) grouping_type) extends SampleGroupDescriptionEntry (grouping_type)
{
}
aligned(8) class SampleGroupDescriptionBox (unsigned int(32) handler_type) extends FullBox('sgpd', version, 0)
{
unsigned int(32) grouping_type;
if (version==1) { unsigned int(32) default_length; } unsigned int(32) entry_count;
int i;
for (i = 1 ; i <= entry_count ; i++)
{
if (version==1)
{
if (default_length==0)
{
unsigned int(32) description_length;
}
}
switch (handler_type)
{
case ‘vide’: // for video tracks VisualSampleGroupEntry (grouping_type); break;
case ‘soun’: // for audio tracks AudioSampleGroupEntry(grouping_type); break;
case ‘hint’: // for hint tracks HintSampleGroupEntry(grouping_type); break;
}
}
}
version: 一个整数用来表示box的版本号
grouping_type: 一个整数用来表示分组类型。相互关联的"sgpd"和"sbgp"中的值必须一致。 对于"CENC"方案来说,”grouping_type“值为”seig“.
entry_count:表示接下去的表中的实例个数。
default_length:表示每个分组实例的长度(如果长度是个常数),或者0,表示长度为变量。
description_length: 表示每个独立的组实体的长度,因此每个实例的长度会不一致。此时default_length域的取值必须为0.
1.7 sbgp:
这个box定义了每种具体分组下帧的信息,如每个分组下有多少帧,那些帧。aligned(8) class SampleToGroupBox extends FullBox(‘sbgp’, version, 0)
{
unsigned int(32) grouping_type; if (version == 1)
{
unsigned int(32) grouping_type_parameter;
}
unsigned int(32) entry_count; for (i=1; i <= entry_count; i++)
{
unsigned int(32) sample_count;
unsigned int(32) group_description_index;
}
}
version: 整形值,非0即1.用于指定box的版本
grouping_type:整形值,用于标识帧集的类型(划分组的依据),通过它可以关联到帧集的描述表中的相同的组类型值。对于一个轨来说,具有相同grouping_type值(如果使用grouping_type_parameter也是一样)的"sbgp"box只能出现一次。
grouping_type_parameter:组的子类型标志
entry_count:整形值,给出了下表的实例数
sample_count:整形值,具有相同帧集标识的连续帧的数目。如果"sbgp"box中帧的总数量小于帧的总数,那么使用者认为剩下的所有帧都是不分组的。如果"sbgp"box中帧的总数量大于帧的总数,这种行为被认为是个错误。使用者可以自定义行为。
1.8 saiz:
这个box给出了每个帧的辅助信息的长度和格式信息。但是具体数据的存储位置则由saio表述。对于cenc方案来说,帧的辅助信息就是每个帧是否加密,加密数据使用的KEYID,对应的IV。aligned(8) class SampleAuxiliaryInformationSizesBox extends FullBox(‘saiz’, version = 0, flags)
{
if (flags & 1)
{
unsigned int(32) aux_info_type; unsigned int(32) aux_info_type_parameter;
}
unsigned int(8) default_sample_info_size; unsigned int(32) sample_count;
if (default_sample_info_size == 0)
{
unsigned int(8) sample_info_size[ sample_count ];
}
}
</pre><pre code_snippet_id="411503" snippet_file_name="blog_20150721_43_6216647" name="code" class="plain">
1.9 saio:
这个box给出了帧辅助信息存储位置,也就是该信息在MP4文件中的偏移。在通用加密方式下指向了”senc“box.class SampleAuxiliaryInformationOffsetsBox extends FullBox(‘saio’, version, flags)
{
if (flags & 1)
{
unsigned int(32) aux_info_type; unsigned int(32) aux_info_type_parameter;
}
unsigned int(32) entry_count; if ( version == 0 )
{
unsigned int(32) offset[ entry_count ];
}
else
{
unsigned int(64) offset[ entry_count ];
}
}
"aux_info_type":一个整数用来定义帧辅助信息的类型。在"saiz"box中,具有相同"aux_info_type"和"aux_info__type_parameter"的对的实例只能存在一个。
"aux_info__type_parameter": 用于区别轨中具有相同"aux_info_type"值的流。"aux_info__type_parameter"的语义由"aux_info_type"的值决定。
"entry_count"给出接下去表中的实体个数。如果"saio"box出现在"stbl"中时,那么这个值要么为1,要么必须和"stco", "co64"box里的实体数的值一致。如果"saio"box出现在"traf"中时,那么这个值要么为1,要么必须和"trun"box里的实体数的值一致。
“offset”给出了文件中的辅助信息在每个块或者"trun"中的位置。如果“entry_count”的值为1,那么对于所有块的或者"trun"的帧辅助信息在块中或者"trun"中必须连续。如果"saio"box出现在"stbl"中时,那么偏移值是绝对值。如果在"trun"中,那么值是相对于同一个track中的fragment“tfhd”的偏移值。
1.10 senc:
该box定义于HbbTV-specification-1-5中,严格意义上来说这只是一个电视台的私有标准,但事实上已经成为行业的默认行为。aligned(8) class SampleEncryptionBox extends FullBox(„senc‟, version=0, flags=0)
{
unsigned int(32) sample_count;
{
unsigned int(IV_size*8) InitializationVector;
if (flags & 0x000002)
{
unsigned int(16) subsample_count;
{
unsigned int(16) BytesOfClearData;
unsigned int(32) BytesOfEncryptedData;
} [ subsample_count ]
}
}[ sample_count ]
}
sample_count: 帧的数目
InitializationVector: 初始化IV
subsample_count:子帧的数目,就是把一帧化为多个部分。这里需要注意的是,所哟NAL头和子帧大小必须是明文。
BytesOfClearData:明文字节数
BytesOfEncryptedData:密文字节数
2. MP4存储其他流
2.1 "esds"
3. IODS
box的定义可能在《MPEG-4 Part 11》中,没有找到相关的下载。《MPEG-4 Part 11》的介绍见百度百科MPEG-4 Part 11。
找到一篇Object Descriptor Framework描述的文章,帮助理解。
4. ISMA
5. LASeR(Lightweight Application Scene Representation)
6. AVCC( AVCDecoderConfigurationRecord)
AVCDecoderConfigurationRecord
// Visual Sequences
class AVCConfigurationBox extends Box(‘avcC’)
{
AVCDecoderConfigurationRecord() AVCConfig;
}
class MPEG4BitRateBox extends Box(‘btrt’)
{
unsigned int(32) bufferSizeDB;
unsigned int(32) maxBitrate;
unsigned int(32) avgBitrate;
}
class MPEG4ExtensionDescriptorsBox extends Box(‘m4ds’)
{
Descriptor Descr[0 .. 255];
}
class AVCSampleEntry() extends VisualSampleEntry (‘avc1’)
{
AVCConfigurationBox config;
MPEG4BitRateBox (); // optional
MPEG4ExtensionDescriptorsBox (); // optional
}
7. 音频 mp4a
aligned(8) class ESDBox extends FullBox(‘esds’, version = 0, 0)
{
ES_Descriptor ES;
}
// Audio Streams class MP4AudioSampleEntry() extends AudioSampleEntry ('mp4a')
{
ESDBox ES;
}
其中音频的ESDBox见《ISO_IEC_14496-3》中“AudioSpecificConfig”定义。
七 : 资料链接
1. wqyuwss的专栏
3. mp4文件格式解析
5. QuickTime File Format Specification
7. http://www.mp4ra.org/object.html