ISO/IEC 14496
ISO/IEC 14496 是MPEG专家组制定的MPEG-4标准,于2000年年初正式成为国际标准。MPEG-4与MPEG-1和MPEG-2有很大的不同。MPEG-4不只是具体压缩算法,它是针对数字电视、交互式绘图应用(影音合成内容)、交互式多媒体(WWW、资料撷取与分散)等整合及压缩技术的需求而制定的国际标准。
MPEG-4由一系列的字标准组成,包含以下的部分:
- ISO/IEC 14496-1:系统 - 描述视频和音频数据流的控制、同步以及混合方式(即混流Multiplexing,简写为MUX)。
- ISO/IEC 14496-2:视频 - 定义一个对各种视觉信息(包括自然视频、静止纹理、计算机合成图形等等)的编解码器。例如,XviD编码就属于MPEG -4 Part 2。
- ISO/IEC 14496-3:音频 - 定义一个对各种音频信号进行编码的编解码器的集合,包括高级音频编码(Advanced Audio Coding,缩写为AAC)的若干变形和其他一些音频/语音编码工具。
- ISO/IEC 14496-4:一致性 - 定义对本标准其他的部分进行一致性测试的程序。
- ISO/IEC 14496-5:参考软件 - 提供用于演示功能和说明本标准其他部分功能的软件。
- ISO/IEC 14496-6:多媒体传输集成框架 - DMIF for Delivery Multimedia Integration Framework。
- ISO/IEC 14496-7:优化的参考软件 - 提供对实现(ISO/IEC 14496-5)进行优化的例子。
- ISO/IEC 14496-8:在IP网络上传输 - 定义在IP网络上传输MPEG-4内容的方式。
- ISO/IEC 14496-9:参考硬件 - 提供用于演示怎样在硬件上实现本标准其他部分功能的硬件设计方案。
- ISO/IEC 14496-10:高级视频编码 - Advanced Video Coding,缩写为AVC,定义一个视频编解码器codec。AVC和XviD都属于MPEG-4编码,但由于AVC属于MPEG-4 Part 10,在技术特性上比属于MPEG-4 Part 2的XviD要先进。另外,它和ITU-T H.264标准是一致的,故又称为H.264。
- ISO/IEC 14496-11:场景描述和应用引擎 - 被称作 BIFS(Binary Format for Scene),XMT,MPEG-J。它被设计的用处是实现动态显示和交互显示的有效展现,构建2D和3D的图像、画面、文本,以及试听材料。这种显示的展现包括对不同场景组件(scene component)的时空组织性,以及用户交互性和动画进行描述。
- ISO/IEC 14496-12:基于ISO的媒体文件格式 - 定义一个存储媒体内容的文件格式。mp4文件的解析参考的就是该部分。
- ISO/IEC 14496-13:知识产权管理和保护拓展 - IPMP for Intellectual Property Management and Protection。
- ISO/IEC 14496-14:MPEG-4文件格式 - 定义基于 ISO/IEC 14496-12 的用于存储MPEG-4内容的视频文件格式。
- ISO/IEC 14496-15:AVC文件格式 - 定义基于 ISO/IEC 14496-12 的用于存储 ISO/IEC 14496-10 的视频内容的文件格式。
- ISO/IEC 14496-16:动画扩展框架 - AFX,Animation Framework eXtension。
- ISO/IEC 14496-17:同步文本字幕格式。
- ISO/IEC 14496-18:字体压缩和流式传输 - 针对开放字体格式Open Font Format。
- ISO/IEC 14496-19:合成材质流 - Synthesized Texture Strean。
- ISO/IEC 14496-20:简单场景表示 - LASeR for Lightweight Scene Representation。
- ISO/IEC 14496-21:用于描绘(Rendering)的MPEG-J拓展。
- ISO/IEC 14496-22:开放字体格式 - Open Font Format。
- ISO/IEC 14496-23:符号化音乐表示 - Symbolic Music Representation。
- ISO/IEC 14496-24:音频与系统交互作用 - Audio and systems interaction。
- ISO/IEC 14496-25:3D图形压缩模型 - 3D Graphics Compression Model。
- ISO/IEC 14496-26:音频一致性检查 - 定义测试音频数据与 ISO/IEC 14496-3 是否一致的方法,Audio comformance。
- ISO/IEC 14496-27:3D图形一致性检查 - 定义测试3D图形数据与 ISO/IEC 14496-11:2005,ISO/IEC 14496-16:2006,ISO/IEC 14496-21:2006 和 ISO/IEC 14496-25:2009 是否一致的方法,3D Graphics conformance。
- ISO/IEC 14496-28:复合字体表示法 - Composite font representation。
- ISO/IEC 14496-29:网络视频编码 - Web video coding。
- ISO/IEC 14496-30:ISO基本媒体文件格式中的定时文本和其他可视覆盖层 - Timed text and other visual overlays in ISO base media file format。
- ISO/IEC 14496-31:浏览器视频编码 - Video coding for browsers。
- ISO/IEC 14496-33:互联网视频编码 - Internet video coding。
mp4文件格式
MP4是在 ISO/IEC 14496-14 标准文件中定义的一种多媒体容器格式,是 ISO/IEC 14496-12 标准中所定义的媒体格式的一种实现。
参考标准
术语和定义
- box:由唯一类型标识符和长度定义的面向对象构建块。
- container box:用于包含和分组一组相关box的box。
- sample:一个时间戳的所有数据。一个音轨中的sample的时间戳不能重复。在非hint track中,一个sample是一个单独的视频帧,或一组连续的视频帧,或是一段连续的压缩音频;在hint track中,一个sample定义一个或多个流数据包的形成。
- chunk:一个音轨的连续sample集合。
- track:相关sample的时序。对于media data,track对应于一系列图像或采样的音频;对于hint track,一个轨道对应于一个流信道。
- hint track:一种特殊的音轨,它不包含媒体数据,而是包含将一个或多个音轨打包成一个流媒体信道的方法。
- presentation:一个或多个运动序列,可能与音频结合。表示一个视频文件。
- media data box:为一个presentation保存实际媒体数据的box,类型标识符为mdat。
- movie box:一个container box,其子box定义了一个presentation的metadata,类型标识符为moov。
- sample description:定义并描述一个track中多个sample格式的box,类型标识符为stsd。
- sample table:一个track中sample的时序和物理布局,类型标识符为stbl。
- sync sample:描述关键帧,类型标识符为stss。
文件组成
MP4文件由一系列的box构成,box中可以套box,所有的数据都保存在box中。每个文件主要由以下部分构成:
- File Type Box:ftyp,文件类型
- Media Data Box:mdat,实际媒体数据
- Free Space Box:free或skip,无关紧要的内容,可忽略
- Meta Box:meta,包含描述或注释信息
- Movie Box:moov,实际媒体数据的metadata
- Movie Header Box:mvhd,文件总体信息,如时长和创建时间等
- Track Box:trak,一个视频或音频序列
- Track Header Box:tkhd,track的特性和总体信息,如时长和宽高等
- Media Box:mdia,包含整个track的媒体信息,比如媒体类型和sample信息
- Media Header Box:mdhd,包含track总体信息,内容和track header大致一样
- Handler Reference Box:hdlr,解释媒体的播放过程信息
- Media Information Box:minf,包含所有描述该track的媒体信息的对象,信息存储在子box中
- Video Media Header Box / Sound Media Header Box:vmhd/smhd,包含当前track的视频/音频描述信息
- Data Infomation Box:dinf,解释如何定位媒体信息
- Data Reference Box:dref,用来设置当前box描述信息的data entry
- Data Entry Url Box:url,用来定位track数据
- Data Entry Urn Box:urn,用来定位track数据
- Data Reference Box:dref,用来设置当前box描述信息的data entry
- Sample Table Box:stbl,sample时序和物理布局表
- Sample Description Box:stsd,定义和描述轨中的采样格式结构
- Time To Sample Box:stts,存储sample的duration,描述sample时序的映射方法
- Sync Sample Box:stss,确定media中的关键帧
- Sample Size Box:stsz或stz2,定义每个sample的大小
- Sample To Chunk Box:stsc,描述sample与chunk的映射关系
- Chunk Offset Box:stco(32位)或co64(64位),定义每个chunk在媒体流中的位置
- Composition Time To Sample Box:ctts,解码时间和合成时间的偏移量
C#实现
本文使用c#解析MP4文件,后面将展示部分box的结构,逐步补充。
Mp4File
public class Mp4File
{
private string filePath;
private string fileName;
/// <summary>
/// File Type
/// </summary>
public FileTypeBox Ftyp = new FileTypeBox();
/// <summary>
/// Media Data
/// </summary>
public List<MediaDataBox> Mdats = new List<MediaDataBox>();
/// <summary>
/// Movie
/// </summary>
public MovieBox Moov = new MovieBox();
/// <summary>
/// Free
/// </summary>
public FreeSpaceBox Free;
/// <summary>
/// Meta
/// </summary>
public MetaBox Meta;
/// <summary>
/// 其他
/// </summary>
public List<Box> Boxs = new List<Box>();
public bool Open(string file)
{
this.filePath = file;
this.fileName = Path.GetFileNameWithoutExtension(fileName);
FileStream fs = new FileStream(this.filePath, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
br.BaseStream.Seek(0, SeekOrigin.Begin);
while (br.PeekChar() > -1)
{
Box box = new Box();
box.ReadHeader(br);
switch (box.Type)
{
case "ftyp":
Ftyp.Copy(box);
Ftyp.ReadContent(br);
break;
case "mdat":
MediaDataBox mdat = new MediaDataBox();
mdat.Copy(box);
mdat.ReadContent(br);
Mdats.Add(mdat);
break;
case "free":
case "skip":
Free = new FreeSpaceBox();
Free.Copy(box);
Free.ReadContent(br);
break;
case "meta":
Meta = new MetaBox();
Meta.Copy(box);
Meta.ReadFullHeader(br);
Meta.ReadContent(br);
break;
case "moov":
Moov.Copy(box);
Moov.ReadContent(br);
break;
default:
box.ReadContent(br);
Boxs.Add(box);
break;
}
}
br.Close();
fs.Close();
return true;
}
public override string ToString()
{
StringBuilder str = new StringBuilder();
str.Append(Ftyp.ToString());
for (int i = 0; i < Mdats.Count; i++)
{
str.Append(Mdats[i].ToString());
}
if (Free != null)
{
str.Append(Free.ToString());
}
if (Meta != null)
{
str.Append(Meta.ToString());
}
str.Append(Moov.ToString());
// 其他
for (int i = 0; i < Boxs.Count; i++)
{
str.Append(Boxs[i].ToString());
}
return str.ToString();
}
}
Box
box以box header开头,header中包含box的大小和类型。box中的字节序为网络字节序,也就是大端字节序(Big-Endian).
/// <summary>
/// 由唯一类型标识符和长度定义的面向对象的构建;
/// box中的字节序为网络字节序,也就是大端字节序(Big-Endian);
/// </summary>
public class Box
{
/// <summary>
/// box前四个字节,uint32类型,标识整个box所占用的大小,包括header部分;
/// 如果box很大(例如存放具体视频数据的mdat box),超过了uint32的最大数值,size就被设置为1,并用type后面的8位uint64的largesize来存放大小;
/// </summary>
public ulong Size;
/// <summary>
/// box类型,uint类型,占四个字节
/// </summary>
public string Type;
/// <summary>
/// 若Type为uuid,则header中包含该字段,类型为unsigned int(8)[16],占16个字节
/// </summary>
public string UserType;
/// <summary>
/// box header长度
/// </summary>
protected int headerLength;
/// <summary>
/// 设置父级路径
/// </summary>
protected string parentPath = string.Empty;
/// <summary>
/// 设置父级路径
/// </summary>
/// <returns></returns>
public string SetParentPath(string value)
{
return parentPath = value;
}
/// <summary>
/// 获取路径
/// </summary>
/// <returns></returns>
public string GetPath()
{
return Path.Combine(parentPath, this.GetType().Name);
}
/// <summary>
/// 读取box header,获取大小和类型
/// </summary>
/// <param name="br"></param>
/// <returns>box header的长度</returns>
public virtual void ReadHeader(BinaryReader br)
{
Size = GetUint32(br);
Type = GetString(br, 4).Trim();
if (Size == 1)
{
Size = GetUint64(br);
headerLength = 16;
}
else
{
headerLength = 8;
}
if (Type.Equals("uuid"))
{
UserType = GetString(br, 16).Trim();
headerLength += 16;
}
}
/// <summary>
/// 根据长度跳过box内容
/// </summary>
/// <param name="br"></param>
public virtual void ReadContent(BinaryReader br)
{
ulong contentLength = Size - (ulong)headerLength;
ulong i = 0;
while (i < contentLength)
{
try
{
br.ReadByte();
}
catch (Exception e)
{
break;
}
i++;
}
}
public override string ToString()
{
StringBuilder str = new StringBuilder();
str.AppendLine(GetPath());
if (Size > uint.MaxValue)
{
str.AppendLine(" Size : " + 1);
str.AppendLine(" Type : " + Type);
str.AppendLine(" Largesize : " + Size);
}
else
{
str.AppendLine(" Size : " + Size);
str.AppendLine(" Type : " + Type);
}
//str.AppendLine(" ContentLength : " + (Size - (ulong)headerLength));
return str.ToString();
}
public virtual void Copy(Box box)
{
Size = box.Size;
Type = box.Type;
UserType = box.UserType;
headerLength = box.headerLength;
parentPath = box.parentPath;
}
public static short GetInt16(BinaryReader br)
{
byte[] bytes = br.ReadBytes(2);
Array.Reverse(bytes);
return BitConverter.ToInt16(bytes, 0);
}
public static ushort GetUint16(BinaryReader br)
{
byte[] bytes = br.ReadBytes(2);
Array.Reverse(bytes);
return BitConverter.ToUInt16(bytes, 0);
}
public static int GetInt32(BinaryReader br)
{
byte[] bytes = br.ReadBytes(4);
Array.Reverse(bytes);
return BitConverter.ToInt32(bytes, 0);
}
public static uint GetUint32(BinaryReader br)
{
byte[] bytes = br.ReadBytes(4);
Array.Reverse(bytes);
return BitConverter.ToUInt32(bytes, 0);
}
public static ulong GetUint64(BinaryReader br)
{
byte[] bytes = br.ReadBytes(8);
Array.Reverse(bytes);
return BitConverter.ToUInt64(bytes, 0);
}
public static int[] GetInt32Array(BinaryReader br, int length)
{
int[] array = new int[length];
for (int i = 0; i < length; i++)
{
array[i] = GetInt32(br);
}
return array;
}
public static uint[] GetUint32Array(BinaryReader br, int length)
{
uint[] array = new uint[length];
for (int i = 0; i < length; i++)
{
array[i] = GetUint32(br);
}
return array;
}
public static ushort[] GetUint16Array(BinaryReader br, int length)
{
ushort[] array = new ushort[length];
for (int i = 0; i < length; i++)
{
array[i] = GetUint16(br);
}
return array;
}
public static string GetString(BinaryReader br, int length)
{
byte[] bytes = br.ReadBytes(length);
return Encoding.ASCII.GetString(bytes).Trim('\0');
}
public static byte GetByte(BitArray bitArray, int startIndex, int length)
{
byte data = 0;
for (int i = 0; i < length; i++)
{
if (bitArray[startIndex + i])
{
data |= (byte)(1 << 8 - (startIndex + i));
}
}
return data;
}
}
Full Box
full box的header中除了size、type外,还包含version和flags字段。
/// <summary>
/// box header相对box多了version和flags字段
/// </summary>
public class FullBox : Box
{
/// <summary>
/// 用来指定该box的文件的格式
/// full box的特有字段
/// </summary>
public byte Version;
/// <summary>
/// 标志图
/// full box的特有字段
/// </summary>
public byte[] Flags = new byte[3];
public override void ReadHeader(BinaryReader br)
{
base.ReadHeader(br);
ReadFullHeader(br);
}
/// <summary>
/// 读取fullbox相对box额外的信息
/// </summary>
/// <param name="br"></param>
public void ReadFullHeader(BinaryReader br)
{
Version = br.ReadByte();
Flags = br.ReadBytes(3);
headerLength += 4;
}
public override string ToString()
{
StringBuilder str = new StringBuilder();
str.Append(base.ToString());
str.AppendLine(" Version : " + Version);
str.AppendLine(" Flags : " + BitConverter.ToString(Flags));
return str.ToString();
}
public virtual void Copy(FullBox box)
{
base.Copy(box);
Version = box.Version;
Flags = box.Flags;
}
}
File Type Box
文件类型,包含在文件中,有且仅有一个。
/// <summary>
/// 文件类型,占24个字节,type域为ftyp
/// </summary>
public class FileTypeBox : Box
{
/// <summary>
/// 文件类型标识符,例如mp42,占四个字节(int)
/// </summary>
public string MajorBrand;
/// <summary>
/// major brand的次版本标识,占四个字节(int)
/// </summary>
public string MinorVersion;
/// <summary>
/// 兼容类型,占八个字节
/// </summary>
public string CompatibleBrands;
public override void ReadContent(BinaryReader br)
{
MajorBrand = GetString(br, 4);
MinorVersion = GetString(br, 4);
CompatibleBrands = GetString(br, 8);
}
public override string ToString()
{
StringBuilder str = new StringBuilder();
str.Append(base.