当前位置: 首页 > article >正文

ExoPlayer架构详解与源码分析(10)——H264Reader

系列文章目录

ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor
ExoPlayer架构详解与源码分析(10)——H264Reader


文章目录

  • 系列文章目录
  • 前言
  • H264结构
  • H264Reader
    • SPS的解析
    • PPS的解析
    • SEI的解析
    • Slice的解析
  • 总结


前言

TsExtractor解封完TS数据后,会根据payload中的视频类型使用指定Reader继续解析,如果payload是H.264格式,就会使用H264Reader来继续解析PES payload部分视频数据流。先上下ProgressiveMediaPeriod的万年老图:
在这里插入图片描述
这部分已经可以和SampQueue关联起来了,也就是说图中sampleData的地方就发生在H264Reader中。

H264结构

在看代码前老规矩,先简单了解下H264的码流结构
H264都是由一个个的NAL基本单元组成的,每个NAL由包含一个HEADER和一个DATA,如下图
在这里插入图片描述
这些基本的NAL可能为多种类型如上图的SPS,PPS,SLICE,这些类型就定义在NAL的Header之中,Header的结构很简单就一个字节,如下表

名称大小(b)说明
forbidden_zero_bit1禁止位,占用NAL头的第一个位,当禁止位值为1时表示语法错误,告诉接收方丢掉该单元,否则为0
nal_ref_idc2指示当前NALU的优先级,或者说重要性,数值越大表明越重要
nal_unit_type5表示NALU的类型

那么nal_unit_type不同值对应什么类型呢看下表

nal_unit_typeNAL类型
0未使用
1不分区、非 IDR 图像的片
2SLICE A 片分区 A
3SLICE B 片分区 B
4SLICE C 片分区 C
5IDR 图像中的片
6Supplemental Enhancement Information(SEI ) 补充增强信息单元
7Sequence Paramater Set(SPS) 序列参数集
8Picture Paramater Set(PPS) 图像参数集
9Access Unit Delimiter(AUD) 分界符
10End Of Seq 序列结束
11End Of Stream 码流结束
12Filler Data 填充
13…23保留
24…31未使用

下面看下几个重要的unitType结构

  • Sequence Paramater Set(SPS) 序列参数集
    SPS结构比较复杂这里挑几个用到的

    名称大小(b)说明
    profile_idc8本视频编码时遵循的profile,profile分为Baseline,Main,Extended等,主要用来规定编码时是否采用某些特性,比如说Baseline profile就规定了只能使用I、P slice进行编码,关于profile的说明可以去查看标准的Annex A。
    constraint_set0_flag1强制使用Baseline profile进行编码
    constraint_set1_flag1强制使用Main profile进行编码
    constraint_set2_flag1强制使用Extended profile进行编码
    level_idc8本视频遵循的level,level主要规定了每秒最多能处理多少个宏块,最大的帧大小,最大的解码缓存,最大比特率等这些性能相关的东西,如果是硬解码,则比较容易出现由于视频level太高而不能解码的情况。
    seq_parameter_set_idue(v)本SPS的ID,这个ID主要是给PPS用的
    separate_colour_plane_flag1separate_colour_plane_flag 等于 1 表示对 4:4:4 色度格式中的三个色彩分量分别进行编码。 如果 separate_colour_plane_flag 的值为 0,则表示不对色彩成分进行单独编码,separate_colour_plane_flag 等于 1 时,主编码图像由三个独立的分量组成,每个分量由一个颜色平面(Y、Cb 或 Cr)的编码采样组成,每个采样使用单色编码语法。在这种情况下,每个色彩平面都与特定的 color_plane_id 值相关联
    log2_max_frame_num_minus4ue(v)指定了变量 MaxFrameNum 的值,值范围应为 0 至 12(含 12), M a x F r a m e N u m = 2 ( l o g 2 m a x f r a m e n u m m i n u s 4 + 4 ) MaxFrameNum = 2^{(log2maxframenumminus4 +4)} MaxFrameNum=2(log2maxframenumminus4+4)
    pic_order_cnt_typeue(v)指定解码图片顺序计数的方法,pic_order_cnt_type 的值范围应为 0 至 2(含 2)
    pic_width_in_mbs_minus1ue(v)图片宽度
    pic_height_in_map_units_minus1ue(v)图片高度
    frame_mbs_only_flag1是否只进行帧编码
    vui_parameters_present_flag1SPS是否包含vui参数, video usability information,在标准的Annex E中有描述,主要包含了视频的比例调整,overscan,视频格式,timing,比特率等信息
    aspect_ratio_info_present_flag1等于 1 表示存在 aspect_ratio_idc,等于 0 表示不存在 aspect_ratio_idc
    aspect_ratio_idc8指定样本的采样纵横比值。当 aspect_ratio_idc 表示 Extended_SAR(扩展 SAR)时,采样纵横比用 sar_width : sar_height 表示,当没有 aspect_ratio_idc 语法元素时,aspect_ratio_idc 值为 0
    sar_width16表示样本纵横比的水平尺寸
    sar_height16表示样本纵横比的垂直尺寸(单位与 sar_width 相同)

    ue(v)、se(v)表示以哥伦布编码的一种变长压缩算法

  • Picture Paramater Set(PPS) 图像参数集
    这里也挑几个用到的讲下

    名称大小(b)说明
    pic_parameter_set_idue(v)当前PPS的ID,供slice RBSP使用
    seq_parameter_set_idue(v)当前PPS所属的SPS的ID
    bottom_field_pic_order_in_frame_present_flag1用于POC计算,请参考h.264的POC计算中的bottom_field_flag
  • Supplemental Enhancement Information(SEI ) 补充增强信息单元
    集成在音视频码流中,用于在音视频内部传递消息,可以保证信息与直播音视频数据的同步,SEI并不是解码过程的必须项,有可能对解码过程(容错、纠错)有帮助,视频传输过程、解封装、解码环节,都可能因为某种原因丢弃SEI ,在视频内容的生成端、传输过程中,都可以插入SEI 信息。插入的信息,和其他视频内容一起经过传输链路到达了消费端。那么在SEI 中可以添加哪些信息呢?传递编码器参数、传递视频版权信息、传递摄像头参数、当然也可以传输字幕信息,后面我们会看到。

  • Slice
    视频中的一帧图像可以理解成由一个或多个Slice组成,每一个Slice总体来看都由两部分组成

    • Slice header,包含着分片类型、分片中的宏块类型、分片帧的数量以及对应的帧的设置和参数等信息,slice body中的宏块在进行解码时需依赖这些信息
      来看下Header 的结构

      名称大小(b)说明
      first_mb_in_sliceue(v)当前slice中包含的第一个宏块在整帧中的位置
      slice_typeue(v)当前slice的类型参照下表
      pic_parameter_set_idue(v)当前slice所依赖的pps的id;范围 0 到 255
      colour_plane_id2当标识位separate_colour_plane_flag为true时,colour_plane_id表示当前的颜色分量,0、1、2分别表示Y、U、V分量
      frame_numue(v)表示当前帧序号,数据长度参考上面的log2_max_frame_num_minus4
      field_pic_flag1场编码标识位。当该标识位为1时表示当前slice按照场进行编码;该标识位为0时表示当前 slice按照帧进行编码
      bottom_field_flag1底场标识位。该标志位为1表示当前slice是某一帧的底场;为0表示当前slice为某一帧的顶场
      idr_pic_idue(v)表示IDR帧的序号。某一个IDR帧所属的所有slice,其idr_pic_id应保持一致。该值的取值范围为[0,65535]。
      pic_order_cnt_lsbue(v)表示当前帧序号的另一种计量方式
      delta_pic_order_cnt_bottomse(v)表示顶场与底场POC差值的计算方法,不存在则默认为0
      delta_pic_order_cnt[0]se(v)指定编码帧顶部字段的图片顺序计数与预期图片顺序计数的差值
      delta_pic_order_cnt[1]se(v)指定图像顺序计数与编码帧底层字段的预期图像顺序计数的差值
      slice_typeName of slice_type
      0P (P slice)
      1B (B slice)
      2I (I slice)
      3SP (SP slice)
      4SI (SI slice)
      5P (P slice)
      6B (B slice)
      7I (I slice)
      8SP (SP slice)
      9SI (SI slice)
    • Slice body,通常是一组连续的宏块结构(参照上图),这里就是最终存储像素数据的地方了。宏块中还包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。具体结构这个里不是重点不展开。
      在这里插入图片描述
      一个视频由多个帧组成,一帧由多个Slice(片)组成,一个Slice由多个宏块组成,一个宏块又由多个(如4X4)的YUV像素数据组成。

看完了这些SPS、PPS、SLICE他们之间关系是怎么样的呢
在这里插入图片描述
Slice里的pic_parameter_set_id指向了PPS里的pic_parameter_set_id,而PPS里的seq_parameter_set_id又指向了SPS(序列参数集)里的seq_parameter_set_id,这样一个SPS关联多个PPS,而一个PPS又关联了多个Slice;解码器解码Slice时就通过这些ID查询相关的PPS、SPS获取解码所需的必要信息。

在网络传输流的过程中编码器可能会将每个NAL单元放入到单个独立的网络传输块中,如TS中可能一个包中之包含一个NAL,解码器可以很容易的检测出NAL的分界,然后依次取出NAL来解码,但是实际可能一个包里会包含一个PES头这个头后面跟随了多个NAL单元这种情况该如何找到这些NAL单元的分界呢?

很简单,给NAL前添加0x000001头3个字节,某些情况下会要求NAL长度对齐不足的部分填充0,所以H.264规定当检测到0x000000这3个字节的时候也表示当前NAL结束,这样感觉已经可以解决分界问题了。

但是如果NAL内部数据出现0x000001或者0x000000字段怎么办呢,解码器会误以为这里是新的NAL的开始,导致数据解码出错,于是H.264规定了另一个规则 emulation prevention,在编码器编码完一个NAL时,会再去检测当前NAL中是否包含上述2种字节序列,如果检测出则在最后一个字节前插入一个新字节0x03,当解码器在NAL内部检测到有0x000003 字节序列时,就会把0x03丢弃,恢复数据。

如0x000001 最后一个字节添加0x03 变成0x00000301,解码器丢弃后又变成0x000001。
源码里的ParsableNalUnitBitArray 和NalUnitUtil.unescapeStream方法就是用来丢弃0x03的。

H264Reader

了解了上面的知识,基本就可以开始看代码实现了,这部分最好联系上文ExoPlayer架构详解与源码分析(7)——SampleQueue一起看。

@Override
  public void consume(ParsableByteArray data) {
    assertTracksCreated();

    int offset = data.getPosition();
    int limit = data.limit();
    byte[] dataArray = data.getData();

    // 将当前数据长度计入总长度,此时总数据的尾部和当前数据的尾部就是对齐的
    totalBytesWritten += data.bytesLeft();
    //到这里已经是解复用后的数据了,将数据发给SampleQueue
    output.sampleData(data, data.bytesLeft());

    // 循环读取到NAL单元结束
    while (true) {
      //通过判断是否为0x000001 3字节,确定NAL开始位置,prefixFlags用于保存上一次循环里的3字节信息,防止目标字节被循环分割
      int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);

      if (nalUnitOffset == limit) {
        // 读取到最后一个字节,循环结束
        nalUnitData(dataArray, offset, limit);
        return;
      }

      // 知道起始位置后,获取第四个字节后5位就是NAL的类型
      int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset);

      //获取NAL开始位置到当前位置的偏移量,当NAL单元开始位置在上一段数据中时,这个值为负值
      int lengthToNalUnit = nalUnitOffset - offset;
      if (lengthToNalUnit > 0) {
        //将当前位置到下一个NAL开始位置的数据输入
        nalUnitData(dataArray, offset, nalUnitOffset);
      }
      //用当前数据的结束位置-相对于当前数据的NAL开始位置,得到就是当前NAL开始位置到当前数据的结束距离
      int bytesWrittenPastPosition = limit - nalUnitOffset;
      //由于当前的结束位置和整个的结束位置是对齐的,用整个数据的长度减轻到结尾的距离,就是这个NAL相对于整个数据的绝对位置
      long absolutePosition = totalBytesWritten - bytesWrittenPastPosition;
      // 如果到下一个单元开始的长度为负,那么我们向 NAL 缓冲区写入了过多字节。当通知NAL结束时丢弃多余的字节。
      endNalUnit(
          absolutePosition,
          bytesWrittenPastPosition,
          lengthToNalUnit < 0 ? -lengthToNalUnit : 0,
          pesTimeUs);
      // 下个NAL单元开始
      startNalUnit(absolutePosition, nalUnitType, pesTimeUs);
      // 从NAL单元开始位置读取3个字节
      offset = nalUnitOffset + 3;
    }
  }
  
  //结束NAL单元
  private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
    if (!hasOutputFormat || sampleReader.needsSpsPps()) {
      sps.endNalUnit(discardPadding);
      pps.endNalUnit(discardPadding);
      if (!hasOutputFormat) {//保证只执行一次
        if (sps.isCompleted() && pps.isCompleted()) {//sps和pps都已经endNalUnit
          List<byte[]> initializationData = new ArrayList<>();
          initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
          initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
          //解析出SPS数据
          NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
          //解析出PPS数据
          NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
          //构建codecs 参数,最终用于 MediaCodec 的 configure,确定解码器
          String codecs =
              CodecSpecificDataUtil.buildAvcCodecString(
                  spsData.profileIdc,
                  spsData.constraintsFlagsAndReservedZero2Bits,
                  spsData.levelIdc);
          //通过SPS和PPS构建Format输出给SampleQueue
          output.format(
              new Format.Builder()
                  .setId(formatId)
                  .setSampleMimeType(MimeTypes.VIDEO_H264)
                  .setCodecs(codecs)
                  .setWidth(spsData.width)
                  .setHeight(spsData.height)
                  .setPixelWidthHeightRatio(spsData.pixelWidthHeightRatio)
                  .setInitializationData(initializationData)
                  .build());
          hasOutputFormat = true;
          sampleReader.putSps(spsData);
          sampleReader.putPps(ppsData);
          sps.reset();
          pps.reset();
        }
      } else if (sps.isCompleted()) {
        NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
        sampleReader.putSps(spsData);
        sps.reset();
      } else if (pps.isCompleted()) {
        NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
        sampleReader.putPps(ppsData);
        pps.reset();
      }
    }
    if (sei.endNalUnit(discardPadding)) {
      //丢弃0x03字节
      int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength);
      seiWrapper.reset(sei.nalData, unescapedLength);
      seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
      //解析SEI,解析这部分主演是防止SEI中包含字幕信息,将SEI中的字幕轨道提取出来
      seiReader.consume(pesTimeUs, seiWrapper);
    }
    boolean sampleIsKeyFrame =
        sampleReader.endNalUnit(position, offset, hasOutputFormat, randomAccessIndicator);
    if (sampleIsKeyFrame) {
      //这要么是 IDR 帧,要么是自随机访问指示符以来的第一个 I 帧,因此将其标记为关键帧。清除该标志,以便后续的非 IDR I 帧不会被标记为关键帧,直到我们看到另一个随机访问指示符。
      randomAccessIndicator = false;
    }
  }
  //sampleReader.endNalUnit
 public boolean endNalUnit(
        long position, int offset, boolean hasOutputFormat, boolean randomAccessIndicator) {
      if (nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_AUD//遇到一个AUD就sample一次Metadata
          || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {
        // If the NAL unit ending is the start of a new sample, output the previous one.
        if (hasOutputFormat && readingSample) {//Fromat未解析出也就是SPS PPS未解析完成,跳过
          //position为当前AUD结束位置,nalUnitLength 就是AUD长度,一般都是5
          int nalUnitLength = (int) (position - nalUnitStartPosition);
          //这里的offset 为当前AUD结尾到sampleData结尾的距离
          //offset +nalUnitLength后相当于AUD开始位置到sampleData结尾的距离,相当于当前Metadata 结束位置到SampleData结尾的距离
          outputSample(offset + nalUnitLength);
        }
        samplePosition = nalUnitStartPosition;//标记当前Metadata 开始位置
        sampleTimeUs = nalUnitTimeUs;//标记当前Metadata 开始时间
        sampleIsKeyframe = false;
        readingSample = true;//标记当前Metadata 开始
      }
      boolean treatIFrameAsKeyframe =
          allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator;
      sampleIsKeyframe |=
          nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR
              || (treatIFrameAsKeyframe && nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_NON_IDR);
      return sampleIsKeyframe;
    }

    private void outputSample(int offset) {
      if (sampleTimeUs == C.TIME_UNSET) {
        return;
      }
      @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
      //计算当前Metadata 的有效长度,从第一个AUD开始到下一个AUD的开始位置长度
      int size = (int) (nalUnitStartPosition - samplePosition);
      //将Metadata Sample,计算Metadata 在SampleData中起始位置时,就可以用SampleData总长度-Metadata 的长度-Metadata 结束位置到SampleData结尾的距离
      output.sampleMetadata(sampleTimeUs, flags, size, offset, null);
    }

SPS的解析

public static SpsData parseSpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {
    ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
    int profileIdc = data.readBits(8);//获取profileIdc 主要用于Codec的构建
    int constraintsFlagsAndReservedZero2Bits = data.readBits(8);//获取后面几个Flag主要用于Codec的构建
    int levelIdc = data.readBits(8);//获取levelIdc 主要用于Codec的构建
    int seqParameterSetId = data.readUnsignedExpGolombCodedInt();//获取SPS的ID

    int chromaFormatIdc = 1; // Default is 4:2:0
    boolean separateColorPlaneFlag = false;
    if (profileIdc == 100
        || profileIdc == 110
        || profileIdc == 122
        || profileIdc == 244
        || profileIdc == 44
        || profileIdc == 83
        || profileIdc == 86
        || profileIdc == 118
        || profileIdc == 128
        || profileIdc == 138) {
      chromaFormatIdc = data.readUnsignedExpGolombCodedInt();
      if (chromaFormatIdc == 3) {
        separateColorPlaneFlag = data.readBit();//获取separate_colour_plane_flag
      }
      data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
      data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
      data.skipBit(); // qpprime_y_zero_transform_bypass_flag
      boolean seqScalingMatrixPresentFlag = data.readBit();
      if (seqScalingMatrixPresentFlag) {
        int limit = (chromaFormatIdc != 3) ? 8 : 12;
        for (int i = 0; i < limit; i++) {
          boolean seqScalingListPresentFlag = data.readBit();
          if (seqScalingListPresentFlag) {
            skipScalingList(data, i < 6 ? 16 : 64);
          }
        }
      }
    }

    int frameNumLength = data.readUnsignedExpGolombCodedInt() + 4; // log2_max_frame_num_minus4 + 4
    int picOrderCntType = data.readUnsignedExpGolombCodedInt();//pic_order_cnt_type
    int picOrderCntLsbLength = 0;
    boolean deltaPicOrderAlwaysZeroFlag = false;
    if (picOrderCntType == 0) {
      // log2_max_pic_order_cnt_lsb_minus4 + 4
      picOrderCntLsbLength = data.readUnsignedExpGolombCodedInt() + 4;
    } else if (picOrderCntType == 1) {
      deltaPicOrderAlwaysZeroFlag = data.readBit(); // delta_pic_order_always_zero_flag
      data.readSignedExpGolombCodedInt(); // offset_for_non_ref_pic
      data.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_field
      long numRefFramesInPicOrderCntCycle = data.readUnsignedExpGolombCodedInt();
      for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
        data.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]
      }
    }
    int maxNumRefFrames = data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
    data.skipBit(); // gaps_in_frame_num_value_allowed_flag

    int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;//pic_width_in_mbs_minus1
    int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1;//pic_height_in_map_units_minus1
    boolean frameMbsOnlyFlag = data.readBit();//frame_mbs_only_flag
    int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
    if (!frameMbsOnlyFlag) {
      data.skipBit(); // mb_adaptive_frame_field_flag
    }

    data.skipBit(); // direct_8x8_inference_flag
    //下面确定视频帧的高宽
    int frameWidth = picWidthInMbs * 16;
    int frameHeight = frameHeightInMbs * 16;
    boolean frameCroppingFlag = data.readBit();
    if (frameCroppingFlag) {//获取裁剪后的高宽
      int frameCropLeftOffset = data.readUnsignedExpGolombCodedInt();
      int frameCropRightOffset = data.readUnsignedExpGolombCodedInt();
      int frameCropTopOffset = data.readUnsignedExpGolombCodedInt();
      int frameCropBottomOffset = data.readUnsignedExpGolombCodedInt();
      int cropUnitX;
      int cropUnitY;
      if (chromaFormatIdc == 0) {
        cropUnitX = 1;
        cropUnitY = 2 - (frameMbsOnlyFlag ? 1 : 0);
      } else {
        int subWidthC = (chromaFormatIdc == 3) ? 1 : 2;
        int subHeightC = (chromaFormatIdc == 1) ? 2 : 1;
        cropUnitX = subWidthC;
        cropUnitY = subHeightC * (2 - (frameMbsOnlyFlag ? 1 : 0));
      }
      frameWidth -= (frameCropLeftOffset + frameCropRightOffset) * cropUnitX;
      frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY;
    }

    @C.ColorSpace int colorSpace = Format.NO_VALUE;
    @C.ColorRange int colorRange = Format.NO_VALUE;
    @C.ColorTransfer int colorTransfer = Format.NO_VALUE;
    //确定宽高比
    float pixelWidthHeightRatio = 1;
    boolean vuiParametersPresentFlag = data.readBit();
    if (vuiParametersPresentFlag) {//vui_parameters_present_flag包含VUI数据
      boolean aspectRatioInfoPresentFlag = data.readBit();
      if (aspectRatioInfoPresentFlag) {//aspect_ratio_info_present_flag
        int aspectRatioIdc = data.readBits(8);//aspect_ratio_idc
        if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) {//自定义了宽高比
          int sarWidth = data.readBits(16);//sar_width
          int sarHeight = data.readBits(16);//sar_height
          if (sarWidth != 0 && sarHeight != 0) {
            pixelWidthHeightRatio = (float) sarWidth / sarHeight;
          }
        } else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {//无自定义获取已定义的宽高比
          pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
        } else {
          Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
        }
      }
      if (data.readBit()) { // overscan_info_present_flag
        data.skipBit(); // overscan_appropriate_flag
      }
      if (data.readBit()) { // video_signal_type_present_flag
        data.skipBits(3); // video_format
        colorRange =
            data.readBit() ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; // video_full_range_flag
        if (data.readBit()) { // colour_description_present_flag
          int colorPrimaries = data.readBits(8); // colour_primaries
          int transferCharacteristics = data.readBits(8); // transfer_characteristics
          data.skipBits(8); // matrix_coeffs

          colorSpace = ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries);
          colorTransfer =
              ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics);
        }
      }
    }

    return new SpsData(
        profileIdc,
        constraintsFlagsAndReservedZero2Bits,
        levelIdc,
        seqParameterSetId,
        maxNumRefFrames,
        frameWidth,
        frameHeight,
        pixelWidthHeightRatio,
        separateColorPlaneFlag,
        frameMbsOnlyFlag,
        frameNumLength,
        picOrderCntType,
        picOrderCntLsbLength,
        deltaPicOrderAlwaysZeroFlag,
        colorSpace,
        colorRange,
        colorTransfer);
  }

SPS这主要获取的SPS 的ID,编码的profile,帧宽高,以及宽高比,到这里基本可以确定解码器,确定出视频的宽高等全局参数。

PPS的解析

  public static PpsData parsePpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {
    ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
    int picParameterSetId = data.readUnsignedExpGolombCodedInt();//pic_parameter_set_id PPS的ID
    int seqParameterSetId = data.readUnsignedExpGolombCodedInt();//seq_parameter_set_id SPS的ID
    data.skipBit(); // entropy_coding_mode_flag
    boolean bottomFieldPicOrderInFramePresentFlag = data.readBit();//bottom_field_pic_order_in_frame_present_flag
    return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);
  }

PPS的解析就简单多了主要或bottom_field_pic_order_in_frame_present_flag这一个值。

SEI的解析

public static void consume(
      long presentationTimeUs, ParsableByteArray seiBuffer, TrackOutput[] outputs) {
    while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
      int payloadType = readNon255TerminatedValue(seiBuffer);//SEI的类型
      int payloadSize = readNon255TerminatedValue(seiBuffer);//SEI大小
      int nextPayloadPosition = seiBuffer.getPosition() + payloadSize;
      // Process the payload.
      if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) {
        // This might occur if we're trying to read an encrypted SEI NAL unit.
        Log.w(TAG, "Skipping remainder of malformed SEI NAL unit.");
        nextPayloadPosition = seiBuffer.limit();
      } else if (payloadType == PAYLOAD_TYPE_CC && payloadSize >= 8) {//字幕类型的数据
        int countryCode = seiBuffer.readUnsignedByte();//获取国家
        int providerCode = seiBuffer.readUnsignedShort();//获取地区
        int userIdentifier = 0;
        if (providerCode == PROVIDER_CODE_ATSC) {
          userIdentifier = seiBuffer.readInt();
        }
        int userDataTypeCode = seiBuffer.readUnsignedByte();
        if (providerCode == PROVIDER_CODE_DIRECTV) {
          seiBuffer.skipBytes(1); // user_data_length.
        }
        boolean messageIsSupportedCeaCaption =
            countryCode == COUNTRY_CODE
                && (providerCode == PROVIDER_CODE_ATSC || providerCode == PROVIDER_CODE_DIRECTV)
                && userDataTypeCode == USER_DATA_TYPE_CODE_MPEG_CC;
        if (providerCode == PROVIDER_CODE_ATSC) {
          messageIsSupportedCeaCaption &= userIdentifier == USER_DATA_IDENTIFIER_GA94;
        }
        if (messageIsSupportedCeaCaption) {//开始解析字幕
          consumeCcData(presentationTimeUs, seiBuffer, outputs);
        }
      }
      seiBuffer.setPosition(nextPayloadPosition);
    }
  }

  public static void consumeCcData(
      long presentationTimeUs, ParsableByteArray ccDataBuffer, TrackOutput[] outputs) {
    // First byte contains: reserved (1), process_cc_data_flag (1), zero_bit (1), cc_count (5).
    int firstByte = ccDataBuffer.readUnsignedByte();
    boolean processCcDataFlag = (firstByte & 0x40) != 0;
    if (!processCcDataFlag) {
      // No need to process.
      return;
    }
    int ccCount = firstByte & 0x1F;
    ccDataBuffer.skipBytes(1); // Ignore em_data
    // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
    // + cc_data_1 (8) + cc_data_2 (8).
    int sampleLength = ccCount * 3;
    int sampleStartPosition = ccDataBuffer.getPosition();
    for (TrackOutput output : outputs) {
      ccDataBuffer.setPosition(sampleStartPosition);
      output.sampleData(ccDataBuffer, sampleLength);//发送数据到SamleQueue
      if (presentationTimeUs != C.TIME_UNSET) {
        output.sampleMetadata(//字幕轨道sampleMetadata
            presentationTimeUs,
            C.BUFFER_FLAG_KEY_FRAME,
            sampleLength,
            /* offset= */ 0,
            /* cryptoData= */ null);
      }
    }
  }

可以看到这里解析SEI主要是为了获取其中的字幕信息,如果没有字幕信息,SEI可以直接忽略

Slice的解析

定义在SampleReader中。

public void appendToNalUnit(byte[] data, int offset, int limit) {
      if (!isFilling) {//数据还没有填充足够,返回继续填充
        return;
      }
      int readLength = limit - offset;
      if (buffer.length < bufferLength + readLength) {
        buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2);
      }
      System.arraycopy(data, offset, buffer, bufferLength, readLength);
      bufferLength += readLength;

      bitArray.reset(buffer, 0, bufferLength);
      if (!bitArray.canReadBits(8)) {
        return;
      }
      bitArray.skipBit(); // forbidden_zero_bit
      int nalRefIdc = bitArray.readBits(2);//nal_ref_idc优先级
      bitArray.skipBits(5); // nal_unit_type

      // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013)
      // subsection 7.3.3.
      if (!bitArray.canReadExpGolombCodedNum()) {
        return;
      }
      bitArray.readUnsignedExpGolombCodedInt(); // first_mb_in_slice
      if (!bitArray.canReadExpGolombCodedNum()) {
        return;
      }
      int sliceType = bitArray.readUnsignedExpGolombCodedInt();//slice_type
      if (!detectAccessUnits) {
        // There are AUDs in the stream so the rest of the header can be ignored.
        isFilling = false;
        sliceHeader.setSliceType(sliceType);
        return;
      }
      if (!bitArray.canReadExpGolombCodedNum()) {
        return;
      }
      int picParameterSetId = bitArray.readUnsignedExpGolombCodedInt();//获取 PPS ID
      if (pps.indexOfKey(picParameterSetId) < 0) {
        // We have not seen the PPS yet, so don't try to decode the slice header.
        isFilling = false;
        return;
      }
      NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId);//首先通过当前Slice的PPS ID 获取到PPS
      NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId);//再通过PPS的ID获取到SPS数据
      if (spsData.separateColorPlaneFlag) {//separate_colour_plane_flag为1说明存在colour_plane_id
        if (!bitArray.canReadBits(2)) {
          return;
        }
        bitArray.skipBits(2); // 跳过colour_plane_id
      }
      if (!bitArray.canReadBits(spsData.frameNumLength)) {
        return;
      }
      boolean fieldPicFlag = false;
      boolean bottomFieldFlagPresent = false;
      boolean bottomFieldFlag = false;
      //通过SPS 获取到的帧序号长度读取帧序号
      int frameNum = bitArray.readBits(spsData.frameNumLength);
      if (!spsData.frameMbsOnlyFlag) {//frame_mbs_only_flag 不只进行帧编码
        if (!bitArray.canReadBits(1)) {
          return;
        }
        fieldPicFlag = bitArray.readBit();
        if (fieldPicFlag) {//field_pic_flag 还存在场编码
          if (!bitArray.canReadBits(1)) {
            return;
          }
          bottomFieldFlag = bitArray.readBit();//bottom_field_flag 底场标识位
          bottomFieldFlagPresent = true;
        }
      }
      boolean idrPicFlag = nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR;//IDR 类型的NAL
      int idrPicId = 0;
      if (idrPicFlag) {
        if (!bitArray.canReadExpGolombCodedNum()) {
          return;
        }
        idrPicId = bitArray.readUnsignedExpGolombCodedInt();//idr_pic_id IDR帧的序号
      }
      int picOrderCntLsb = 0;
      int deltaPicOrderCntBottom = 0;
      int deltaPicOrderCnt0 = 0;
      int deltaPicOrderCnt1 = 0;
      if (spsData.picOrderCountType == 0) {
        if (!bitArray.canReadBits(spsData.picOrderCntLsbLength)) {
          return;
        }
        picOrderCntLsb = bitArray.readBits(spsData.picOrderCntLsbLength);//pic_order_cnt_lsb
        if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
          if (!bitArray.canReadExpGolombCodedNum()) {
            return;
          }
          deltaPicOrderCntBottom = bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt_bottom
        }
      } else if (spsData.picOrderCountType == 1 && !spsData.deltaPicOrderAlwaysZeroFlag) {
        if (!bitArray.canReadExpGolombCodedNum()) {
          return;
        }
        deltaPicOrderCnt0 = bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt[0]
        if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) {
          if (!bitArray.canReadExpGolombCodedNum()) {
            return;
          }
          deltaPicOrderCnt1 = bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt[1]
        }
      }
      sliceHeader.setAll(
          spsData,
          nalRefIdc,
          sliceType,
          frameNum,
          picParameterSetId,
          fieldPicFlag,
          bottomFieldFlagPresent,
          bottomFieldFlag,
          idrPicFlag,
          idrPicId,
          picOrderCntLsb,
          deltaPicOrderCntBottom,
          deltaPicOrderCnt0,
          deltaPicOrderCnt1);
      isFilling = false;
    }

   

Slice解析主要是解析了Slice的Header,用于判断是否为I帧以及判断当前NAL是否为图像的第一个VCL类的NAL,这些数据主要用于没有AUD时确定SampleMetadata的时机。
下面我们来动态看下SampleMetadata基于流的时序关系图:
在这里插入图片描述
对照上图可以看出,H264Reader 一开始就会将所有数据Sample到SampleQueue,接下来会查找第一个NAL开始位置,如果读取到第一个AUD,记录AUD开始位置为samplePosition,作为这段SampleData的有效开始位置,下面数据序列首先会将SPS,PPS这2个索引的NAL放在前面,等解码器获取了SPS和PPS基本上就能确定解码器的具体配置,这个时候会调用SampleQueue的format方法将解码器格式输出,用于解码器的初始化等。当读取到下一个AUD的时候将这个AUD的开始位置作为有效SampleData的结束位置,通过有效结束位置(absolutePosition)-有效开始位置(samplePosition)获得当前有效数据长度(size),同时通过当前AUD的长度(nalUnitLength)+下一个NAL头到数据段末尾的距离(bytesWrittenPastPosition)得到offset,将size和offset传给SampleQueue的sampleMetadata方法,sampleMetadata里通过数据总长度-size-offset确定当前SampleData有效数据的开始位置,这样就记录了每个Sample的开始位置和长度,当Rendere读取数据用于解码时,就可以查询这个开始位置和长度读取有效的视频数据给解码器。


总结

到这里ProgressiveMediaPeriod的数据解析部分终于讲完,那么这些解析的数据是如何加载的呢,这就是ProgressiveMediaPeriod整体架构右半部分的最后一块拼图——DataSource,这也是我们后面要讲的内容了。


版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持


http://www.kler.cn/a/159302.html

相关文章:

  • 20.UE5UI预构造,开始菜单,事件分发器
  • RabbitMQ实战启程:从原理到部署的全方位探索(上)
  • 怎样选择合适的服务器租用呢?
  • JDBC-Mysql 时区问题详解
  • 基于SpringBoot+RabbitMQ完成应⽤通信
  • 【Qt聊天室客户端】消息功能--发布程序
  • Linux4.9、进程地址空间
  • 要地防控反无人机系统及其关键技术
  • 【JSD1209考试】题目与解答
  • 【交叉编译】
  • 数据结构:链表应用:第6关:链表的分解
  • 记录 | CUDA编程中使用#ifdef指令控制生成CPU和GPU代码
  • 【无标题】从0到1 搭建一个vue3+Django项目
  • 【数据库】MSSQL 注入入门级的讲解
  • 【C语言】指针与数组的潜在联系
  • RNN、Seq2Seq、Transformers:NLP 中常用的神经架构简介
  • 计算机操作系统2
  • 三轴加速度计LIS2DW12开发(2)----基于中断信号获取加速度数据
  • 统信UOS_麒麟KYLINOS上安装特定版本python
  • MVC、MVP、MVVM模式的区别
  • std::move
  • C++h弧度转成角度
  • 备战春招——12.05算法
  • IntelRealSense深度相机D455在ROS1运行中的消息内容
  • Linux定时循环备份指定文件或文件夹,每月永久备份留1份
  • Linux基础命令(测试相关)