MP4文件中的H.264存储方式
搞了一天一宿,把码流数据都堆放好依旧不能播放,moov的长度和mdat的长度应该都没有问题,不明原因。看看264码流在mp4中如何存吧,先看看264的码流结构,然后再对照文档看看码流在mp4中如何存的。今天看了一小会儿《新一代视频编码压缩标准——H264/AVC》的第7章,H.264的句法和语义,一些基本概念:
- 264的码流是分层的,从上到下依次是序列层,图像层,片层
- 264取消了序列层和图像层,用序列参数集(sps)和图像参数集代替(pps)。
- 参数集是一个独立的部分,不依赖于参数集外其它句法。
- 参数集只有在片层句法需要的时候被引用,一个sps可以被多个pps引用,同一个pps也可以被多个图像引用。
- 参数集(sps+pps)和片层(slice)应该分开传输,参数集要用可靠信道传输,如tcp
- 参数集(sps+pps)和片层(slice)应该在解码前被解码器受到
slice中有句法能指出所引用的参数集编号,如下图
下面这个图我还没完全理解其含义
- 疑惑1:这个I,P代表的是什么?和slice里的I,P帧是一回事么?
I是IDR,当解码到IDR时,清空参考帧队列,将以解码的数据全部抛弃,重新查找参数集,开始一个新的队列
-
疑惑2:如果有一个264的码流,是每一个I,P(不知道这个是不是代表I,P帧的意思)都要有一个SPS+PPS,还是整个流只有一个SPS+PPS写在流的最前端
-
疑惑3:这个跟NAL怎么对应起来???
翻到前面第六章6.3.3.2 H.264的编码格式(P89)
264按功能来分,分成两层,VCL和NAL,VCL是编码后的码流序列,NAL是用来封装VCL的一种网络打包格式。如图
6.12.5 H.264传输(P146)
每个NAL可以独立的分组传输(放到一个包里),在解码前重新排序。
RBSP的格式如下:
;
- 疑惑1:这里面的I,P帧和图1的I,P有什么区别和联系啊?还有图3和图1有什么联系啊?
7.3 语义
数据流在存储在介质上时(理解是存成文件形式,按照ISO的文件格式,例如mp4是ISO xxxx-12)
NAL的起始码为: 0x 00 00 01
但是如果NAL内部出现了起始码,怎么办?答案是在最后一个字节前加入0x03
下面分析一段自己拼凑的不能播放的码流,和能播放的264码流
264的sps和pps存在了avc box里面,只要把它提取出来即可
ISO的mp4文件封装格式如下:
class AVCDecoderConfigurationRecord
{
unsigned int(8) configurationVersion = 1 ; //4字节
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = '111111'b; //1字节
unsigned int(2) lengthSizeMinusOne;
bit(3) reserved = '111'b;
unsigned int(5) numOfSequenceParameterSets; //定义有多少个SPS,一般为1
for(int i = 0; i < numOfSequenceParameterSets;i++)
{
unsigned int(16) sequenceParametersSets ; //SPS的数量,2字节
bit(8*sequenceParametersSets) sequenceParameterSetNALUUnit; //这里就是SPS的内容,依次读取上面指定的数量就OK
}
unsigned int(8) numOfPictureParameterSets; //PPS的数量
for(int i = 0; i < numOfPictureParameterSets;i++)
{
unsigned int(16) pictureParameterSetlength; //PPS的长度
bit(8*picuureParameterSetLength) pictureParameterSetNALUnit; //pps的内容
}
}
SPS和PPS搞明白了,下面再研究下码流,刚才跟师姐探讨,收获不小。
这是264 码流的封装结构,一个NAL头+RBSP形成一个NALU,一个很重要的概念是264是按照NAL单元进行封装的,也就是说解码器只要收到NAL单元就能解码,各个单元间都是独立的。
那如何确定时序呢?
这个也是封装在NAL单元里的,解码器收到后自然会按照时序进行重新排列
那前面说的SPS和PPS呢?解码器如何收到呢?
这个要说NAL单元的类型了,NAL单元是分类型的,比如说有一个NAL单元可以单独传输SPS和SSP,这个单元在NAL头部里面,在RBSP中的数据就是相关数据,同样NAL类型也可以是slice数据
;
也就是说在mdat后面的数据是这样:帧长度+NALU。。。帧长度+NALU。。。
代码人生那片帖子中的代码意思是这样:
- 在mp4文件中找到SPS和PPS
- 把他们写成NALU先放到264流的最前面好让解码器先收到具体做法是0X001+sps数据+0X001+pps数据
- 然后讲264数据打包成NALU接着写入流里面,做法是,0X001+mdat后的每帧数据NALU,不断写入。这样就形成了RAW格式的264码流~
下面再看一个例子,这是我用工具共.MP4文件中抽取的264码流,各式如下
绿色和蓝色是SPS+PPS,然后视频数据是如何分帧的呢?
这是mp4文件中的码流存放格式,先用4字节00 00 02 E1存放该帧长度,然后是该帧类型,从65开始往后的737字节都是这帧的内容
到7C结束,正好737字节,下一帧从61开始,61代表P帧,以此类推。。。。
下一步的工作是按照这个思路,整合一段264码流看能不能播放