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中有句法能指出所引用的参数集编号,如下图

image

下面这个图我还没完全理解其含义

image

  • 疑惑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的一种网络打包格式。如图

image

6.12.5 H.264传输(P146)

每个NAL可以独立的分组传输(放到一个包里),在解码前重新排序。

RBSP的格式如下:

image;

  • 疑惑1:这里面的I,P帧和图1的I,P有什么区别和联系啊?还有图3和图1有什么联系啊?

7.3 语义

数据流在存储在介质上时(理解是存成文件形式,按照ISO的文件格式,例如mp4是ISO xxxx-12)

NAL的起始码为: 0x 00 00 01

但是如果NAL内部出现了起始码,怎么办?答案是在最后一个字节前加入0x03

下面分析一段自己拼凑的不能播放的码流,和能播放的264码流

image

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数据

image;

也就是说在mdat后面的数据是这样:帧长度+NALU。。。帧长度+NALU。。。

代码人生那片帖子中的代码意思是这样:

  • 在mp4文件中找到SPS和PPS
  • 把他们写成NALU先放到264流的最前面好让解码器先收到具体做法是0X001+sps数据+0X001+pps数据
  • 然后讲264数据打包成NALU接着写入流里面,做法是,0X001+mdat后的每帧数据NALU,不断写入。这样就形成了RAW格式的264码流~

下面再看一个例子,这是我用工具共.MP4文件中抽取的264码流,各式如下

image

绿色和蓝色是SPS+PPS,然后视频数据是如何分帧的呢?

image

这是mp4文件中的码流存放格式,先用4字节00 00 02 E1存放该帧长度,然后是该帧类型,从65开始往后的737字节都是这帧的内容

image

到7C结束,正好737字节,下一帧从61开始,61代表P帧,以此类推。。。。

下一步的工作是按照这个思路,整合一段264码流看能不能播放