Mp4格式解析

前言

结合Mp4分析工具,能够更直观的学习Mp4的各个Box,工具链接:
Mp4解析工具Java版

Mp4 Box介绍

多媒体封装格式(也叫容器格式),是指按照一定的规则,将视频数据、音频数据等,放到一个文件中。常见的 MKV、AVI 以及本文介绍的 MP4 等,都是封装格式。

MP4是最常见的封装格式之一,因为其跨平台的特性而得到广泛应用。MP4文件的后缀为.mp4,基本上主流的播放器、浏览器都支持MP4格式。

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


image2021-6-2_15-0-22.png

结构定义:

所有Box,开头均由8个字节组成,前4个字节表示该box的size,后面4个字节表示该box的类型,ftyp、moov、tkhd等。

class Box { 
  unsigned int(32) size; 
  unsigned int(32) type; 
} 

在Box的基础上,扩展出了FullBox类型。相比Box,FullBox 多了 version、flags 字段,在moov中,叶子节点的Box都是FullBox

class FullBox extends Box { 
  unsigned int(8) version; 
  unsigned int(24) flag; 
}

ftyp

ftyp用来指出当前文件遵循的规范,根据ftyp判断当前文件的格式,详见格式嗅探。

结构定义:

class FileTypeBox extends Box { 
  unsigned int(32) major_brand; 
  unsigned int(32) minor_version; 
  unsigned int(32) compatible_brands[]; // to end of the box 
}

字段含义

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 规范,可以将文件 部分(或全部)解码出来;

moov

moov属于container box,这个box中不包含具体媒体数据,但包含本文件中所有媒体数据的宏观描述信息,moov box下有mvhd和trak box;mvhd中记录了创建时间、修改时间、时间度量标尺、可播放时长等信息;trak中的一系列子box描述了每个媒体轨道的具体信息。

mvhd

Movie Header Box,mp4文件的整体信息,比如创建时间、文件时长等。

结构定义:

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;
}
int(32) rate = 0x00010000; // typically 1.0
int(16) volume = 0x0100; // typically, full volume const bit(16) reserved = 0;
const unsigned int(32)[2] reserved = 0; // 保留位,可忽略
int(32)[9] matrix =
{ 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
  
/**
 * 似乎也可以忽略的值
 * Preview Time
 * Preview Duration
 * Poster Time
 * Selection Time
 * Current Time
 * Next Track ID
 */
 int(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;

trak

Track Box,一个mp4可以包含一个或多个轨道(比如视频轨道、音频轨道),轨道相关的信息就在trak里。trak是container box,至少包含两个box,tkhd、mdia。

tkhd

单个 track 的 metadata,包含时长、音量、宽高等信息。

结构定义:

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;
}

字段含义:

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:视频的宽高;

edts

一个可选的container box,包含了编辑片段的信息

elst

具体的编辑片段信息

结构定义:

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(32) media_rate;
  } 
}

字段含义:

segment_duration: 表示该edit段的时长,以Movie Header Box(mvhd)中的timescale为单位,即 segment_duration/timescale = 实际时长(单位s)

media_time: 表示该edit段的起始时间,以track中Media Header Box(mdhd)中的timescale为单位。如果值为-1(FFFFFF),表示是空edit,一个track中最后一个edit不能为空。

media_rate: edit段的速率为0的话,edit段相当于一个”dwell”,即画面停止。画面会在media_time点上停止segment_duration时间。否则这个值始终为1。

media

container box,包含track 媒体数据信息的Box。

mdhd

包含创建时间、文件时长等信息,mvhd针对整个影片,tkhd针对单个track,mdhd针对媒体,vmhd针对视频,smhd针对音频,可以认为是从 宽泛 > 具体,前者一般是从后者推导出来的。

结构定义:

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;
  }
  unsigned int(16) language; // ISO-639-2/T language code
  unsigned int(16) quality = 0; // 媒体的回放质量
}

字段含义:

creation_time: track中数据的创建时间,多同‘tkhd’中creation_time.
modification_time: track中数据的修改时间,多同‘tkhd’中modification_time.
timescale: 媒体中时间尺度. 通常和'mvhd'中的timescale不同,精度更高.
duration: 媒体的长度(时间尺度表示).
language:语言,符合ISO-639-2/T标准.

hdlr

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

结构定义:

class HandlerBox extends FullBox(‘hdlr’, version = 0, 0) {
    unsigned int(32) componentType = 0; // 可忽略
    unsigned int(32) componentSubType;
    unsigned int(32) componentManufacture; // 可忽略
    unsigned int(32) componentFlags; // 可忽略
    unsigned int(32) componentFlagsMask; // 可忽略
    string componentName;
}

字段含义:

componentSubType:
即handler_type,表示track类型,andler_type的取值包括

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

componentName:name为utf8字符串,对handler名称进行描述

minf

该Box是Container Box,下面一般含有三大必须的子Box:

媒体信息头Box: Vmhd Box或者Smhd Box;
数据信息Box:Dinf Box
采样表Box:Stbl Box

vmhd

针对视频的box,包含视频合成模式、合成信息。

结构定义:

class VideoMediaHeaderBox extends FullBox(‘vmhd’, version = 0, 0) {
    unsigned int(16) graphicsMode = 0;
    unsigned int(16) opcolorRed;
    unsigned int(16) opcolorGreen;
    unsigned int(16) opcolorBlue;
}

字段含义:

Graphics mode: 0,直接拷贝原始图片, 否则与opcolor进行合成
Opcolor: 三个16位值,用于指定图形模式字段中指示的传输模式操作的红色,绿色和蓝色。

smhd

针对音频的box。

结构定义:

class VideoMediaHeaderBox extends FullBox(‘vmhd’, version = 0, 0) {
    unsigned int(16) balance = 0;
    unsigned int(16) reserved; // 保留位
}

字段含义:

balance: 一个16位整数,指定此声音媒体的声音平衡。声音平衡是控制计算机两个扬声器之间混音的设置。该字段通常设置为0。平衡值表示为16位定点数,范围从-1.0到+1.0。
高阶8位包含值的整数部分;低8位包含小数部分。负值使平衡器向左扬声器加权;正值强调正确的渠道。将天平设置为0对应于中性设置。

dinf

这个Box也是一个container box,一般用来定位媒体信息。一般会包含一个Dref Box即data reference box。

dref

dref下面会有若干个Url Box或者也叫 Urn Box,这些Box组成一个表,用来定位Track的数据。
Track可以被分成若干个段,每一段都可以根据Url或者Urn指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track,一般情况下当数据完全包含在文件中,Url和Urn Box的字符串是空的。
这个Box存在的意义就是允许MP4文件的媒体数据分开最后还能进行恢复合并操作,但是实际上,Track的数据都保存在文件中,所以该字段的重要性还体现不出来。

结构定义:

class DrefListBox extends FullBox(‘dref’, version, 0) {
   unsigned int(32)  entry_count;
   for (i=1; i <= entry_count; i++) {
         unsigned int(32) size;
         unsigned int(32) type;
         unsigned int(8) version;
         unsigned int(24) flag;
         byte* data;
    }
}

字段含义:

entry结构图:


img

entry type类型说明:


img
stbl

container box, MP4文件的媒体数据部分在mdat box里,而stbl则包含了这些媒体数据的索引以及时间信息。

stsd

该box给出视频、音频的编码、宽高、音量等信息,以及每个sample中包含多少个frame,stsd给出sample的描述信息,这里面包含了在解码阶段需要用到的任意初始化信息,比如 编码 等。对于视频、音频来说,所需要的初始化信息不同。

结构定义:

class StsdBox extends FullBox(‘dref’, version, 0) {
   unsigned int(32)  entry_count;
   SampleEntry * sampleEntry;
}

根据不同的track类型,sampleEntry有不同的结构定义

class SampleEntry {
   
  /**
  * Entry大小
  * 4个字节
  */
 unsigned int(32) size;
 
 /**
  * 解码器id
  * 4个字节
  */
  unsigned int(32) format;
 
 
  
   /**
   * 保留位1
   * 4个字节
   */
  unsigned int(32)  reserved;
 
   /**
   * 保留位2
   * 2个字节
   */
   unsigned int(16) reserved2;
 
   /**
   * 当MP4文件的数据部分,可以被分割成多个片段,每一段对应一个索引,并分别通过URL地址来获取,此时,data_reference_index 指向对应的片段(比较少用到);
   */
   unsigned int(16) data_reference_index;
}

视频结构定义:

class VideoSampleEntry extends SampleEntry {
    // 可忽略
    unsigned int(16) version;
    // 可忽略
    unsigned int(16) reversionLevel;
    // 可忽略
    String vendor;
    // 可忽略
    unsigned int(32) temporalQuality;  
    // 可忽略
    unsigned int(32) spatialQuality; 
    
    unsigned int(16) width;
    unsigned int(16) height;
    unsigned int(32) horizontalSolution;
    unsigned int(32) verticalResolution;
    const unsigned int(32) reserved = 0;
    template unsigned int(16) frameCount = 1;
    string codecName;
    template unsigned int(16) depth = 0x0018;
    /**
    * 拓展信息,如avc用于存放sps,pps等必要信息
    */
    byte[] extensions;
}

字段含义:

width、height:视频的宽高,单位是像素;

horizontalSolution、verticalResolution:水平、垂直方向的分辨率(像素/英寸),16.16定点数,默认是0x00480000(72dpi);

frameCount:一个sample中包含多少个frame,对video track来说,默认是1;

codecName:仅供参考的名字,通常用于展示,占32个字节,比如 AVC Coding。第一个字节,表示这个名字实际要占用N个字节的长度。第2到第N+1个字节,存储这个名字。第N+2到32个字节为填充字节。
compressorname 可以设置为0;

depth:位图的深度信息,比如 0x0018(24),表示不带alpha通道的图片;

音频结构定义:

class AudioSampleEntry extends SampleEntry {
    // 可忽略
    unsigned int(16) version;
    // 可忽略
    unsigned int(16) reversionLevel;
    // 可忽略
    String vendor;
    
    unsigned int(16) channelCount;
    unsigned int(16) sampleSize;
    unsigned int(16) audioCid;
    unsigned int(16) packetSize;
    unsigned int(16) sampleRate;
    /**
    * 拓展信息,如mp4a
    */
    byte[] extensions;
}

字段含义:

channelCount:声道数

sampleSize:每一个Sample的大小

sampleRate:采样率

stts

stts包含了DTS到sample number的映射表,主要用来推导每个帧的时长

结构定义:

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;
    }
}

字段含义:

entry_count:stts 中包含的entry条目数;

sample_count:单个entry中,具有相同时长(duration 或 sample_delta)的连续sample的个数。

sample_delta:sample的时长(以timescale为计量)

stss

mp4文件中,关键帧所在的sample序号。如果没有stss的话,所有的sample中都是关键帧。

结构定义:

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:entry的条目数,可以认为是关键帧的数目;

sample_number:关键帧对应的sample的序号;(从1开始计算)

ctts

从解码(dts)到渲染(pts)之间的差值。

对于只有I帧、P帧的视频来说,解码顺序、渲染顺序是一致的,此时,ctts没必要存在。

对于存在B帧的视频来说,ctts就需要存在了。当PTS、DTS不相等时,就需要ctts了,公式为 CT(n) = DT(n) + CTTS(n) 。

结构定义:

aligned(8) class CompositionOffsetBox extends FullBox(‘ctts’, 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_offset;
   }
}

字段含义:

entry_count:stts 中包含的entry条目数;

sample_count:单个entry中,具有相同offet的连续sample的个数。

sample_offset:CT和DT之间的offset。

stsc

每个thunk中包含几个sample。

sample 以 chunk 为单位分成多个组。chunk的size可以是不同的,chunk里面的sample的size也可以是不同的。

结构定义:

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;
    }
}

字段含义:

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小节);

stsz

每个sample的size(单位是字节)。

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

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

结构定义:

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 u sample_count; i++) {
            unsigned int(32)  entry_size;
        }
    }
}

字段含义:

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的话);

stz2

每个sample的size(单位是字节)。

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

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

结构定义:

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 u sample_count; i++) {
        unsigned int(field_size) entry_size;
    }
}

字段含义:

field_size:entry表中,每个entry_size占据的位数(bit),可选的值为4、8、16。4比较特殊,当field_size等于4时,一个字节上包含两个entry,高4位为entry[i],低4位为entry[i+1];

sample_count:等于下面entry的条目;

entry_size:sample的大小。

stco

thunk在文件中的偏移。

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

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

结构定义:

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;
    }
}

字段含义:

entry_count:有多少个项;

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

co64

跟stco一样,thunk在文件中的偏移。

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

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

结构定义:

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;
    }
}

字段含义:

entry_count:有多少个项;

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

mdat

mdat也是一个containerBox,MP4文件的媒体数据在mdat box里,根据moov中包含的这些媒体数据的索引以及时间信息等定位媒体数据的具体位置。

free

占坑用,内容可被忽略,似乎也没法用于拓展,ffmpeg最多只读取了其中的16个字节。

你可能感兴趣的:(Mp4格式解析)