提取MP4文件中的h264/h265视频流
这是一个非常细节的问题,毫无疑问,没有多少人会真的关心这个问题。所以这篇就是写给极少数人看的。
这个问题,以及其逆问题“封装h264/h265视频流到mp4文件中”,定义在国际标准ISO-14496的第15部分。
这个国际标准,我看到了两个版本,2004年,仅仅有h264部分。2013年版本,这个版本则包含了更多的视频流格式h265,以及其他的扩展视频格式。
背景
到达这个问题之前,其实很有很多的其他细节问题:
MP4文件层级与组织
这个问题,不是本文讨论的重点,仅仅简单介绍下。
MP4文件,由层级的box/atom组成。box的头信息包含固定4个字节的长度信息和4个字节的类型信息。
类型一般都是类似FourCC的形式。box也有层级结构的,这样box可以嵌套或者包含多个box。一般真正的视频数据都在“mdat”类型的box中。
一般MP4文件中有音视频track,每个track则有很多chunk,chunk内有多个sample。从track一直到sample,都是按照层级/表进行组织的。
所以,从MP4文件中找到相应的sample,就可以把数据提取出来。
h264/h265视频流
这种视频流中的每个单元为NALU,NALU之间以start code(0x00000001)进行分割。
由上面的两点基础信息,就可回到问题本身了。
提取MP4文件中的h264/h265视频流
这个问题其实非常简单,核心问题其实只需要一句话。摘自ISO-IEC-14496-15 5.2.2.
|
|
这部分的意思是
ES数据不包含start code,而是存储上NALU的长度。反向进行时候,需要将start code添加回来。
NALU的长度的长度信息则存储在MP4文件中的“avcC”中。具体的定义如下
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1;
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = ‘111111’b;
unsigned int(2) lengthSizeMinusOne; // offset 4
bit(3) reserved = ‘111’b;
unsigned int(5) numOfSequenceParameterSets;
for (i=0; i< numOfSequenceParameterSets; i++) {
unsigned int(16) sequenceParameterSetLength ;
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
}
unsigned int(8) numOfPictureParameterSets;
for (i=0; i< numOfPictureParameterSets; i++) {
unsigned int(16) pictureParameterSetLength;
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
}
}
一个典型的“avcC”内容如下:
|
|
解析的结果(内部的数值都是十六进制的)
|
|
根据里面的lengthSize就可以得到“NALU的长度”这部分数据的长度。
+---+-------+---+-------+--+-------+---
|len| NAL |len| NAL | | |
+---+-------+---+-------+--+-------+---
注意,这里的顺序是大端(网络序)的。一般一个sample有一个NALU(当然不是绝对的)。
SPS和PPS是h264流中的元信息,在MP4文件中单独存放在“avcC”中。(如上所示),转换的时候,还需要将SPS和PPS提取出来,添加上0x00000001,放在h264视频流的开始位置。
h265的处理方法
其实几乎是一样的,对于h265,其元信息在”hvcC”类型box中。具体的语法结构就不贴了。其中lengthSize的偏移地址为21.
code
很多开源代码珠玉在前,我就不献丑了。
ffmpeg中,可以参考libavcodec/h264_mp4toannexb_bsf.c. 这个文件不长,只有300行左右。其功能完成就是本文讨论的内容,完全可以对比着一一参考。
- h264_extradata_to_annexb 处理avcC中的数据,并且提取出sps和pps信息
- h264_mp4toannexb_filter 从sample中提取nalu数据
Android的代码中可以参考libstagefright中的MPEG4Extractor.cpp文件。这个代码展示了完整的如何解析MP4文件的过程。关于本文讨论的内容,在read函数中3883行附近。在代码中搜索lengthsize也可以比较容易定位到。