WebRTC之H.264打包RtpPacketizerH264


要读懂这部分代码需要知道RTP打包协议相关的知识,具体可以阅读相关RFC文档-RFC6184 RTP Payload Format for H.264 Video

RFC6184协议

由于受到MTU限制,为了保证我们的数据包不会被路由器分段传输,所以我们单次发送的包大小不能超过1500字节。
想要知道这个协议存在的意义先思考三个问题:

  1. 当一帧(包含IP头和RTP头)的数据量大于1500字节的时候我们该如何打包: frame_size > 1500
  2. 当多个帧(包含IP头和RTP头)总数据量小于1500字节的时候我们该如何打包: frame1_size + frame2_size + … < 1500
  3. 当一帧(包含IP头和RTP头)的数据量小于1500字节,而且和下一帧的数据之和大于1500字节时,我们改如何打包; frame1_size < 1500 && frame1_size + frame2_size > 1500

对于这三种情况我们的做法应该都是拆帧/组帧/单帧发送,但是每家的协议可能都不一样,所以它的目的就很明显了,要不大家都只能自己玩自己的。

协议中的缩写

缩写 全拼
DON Decoding Order Number
DONB Decoding Order Number Base
DOND Decoding Order Number Difference
FU Fragmentation Unit
IDR Instantaneous Decoding Refresh
MTAP Multi-Time Aggregation Packet
MTAP16 MTAP with 16-bit timestamp offset
MTAP24 MTAP with 24-bit timestamp offset
NAL Network Abstraction Layer
NALU Network Abstraction Layer Unit
SAR Sample Aspect Ratio
SEI Supplemental Enhancement Information
STAP Single-Time Aggregation Packet
STAP-A STAP type A
STAP-B STAP type B
VCL Video Coding Layer
VUI Video Usability Information

打包模式和负载结构介绍

协议定义了3中打包模式(Single NAL Unit Mode/Non-Interleaved Mode/Non-Interleaved Mode)和7种负载结构(NAL unit/STAP-A/STAP-B/MTAP16/MTAP24/FU-A/FU-B),下面表格指明了不同的打包模式可以使用那几种负载结构。我们只会简单介绍几种WebRTC中用到的负载结构。
Summary of allowed NAL unit types for each packetization mode (yes = allowed, no = disallowed, ig = ignore)

Payload Type Packet Type Single NAL Unit Mode Non-Interleaved Mode Interleaved Mode
0 reserved ig ig ig
1-23 NAL unit yes yes no
24 STAP-A no yes no
25 STAP-B no no yes
26 MTAP16 no no yes
27 MTAP24 no no yes
28 FU-A no yes yes
29 FU-B no no yes
30-31 reserved ig ig ig
  • 打包模式简单介绍

    • Single NAL Unit Mode,单个NAL单元模式(NAL unit)。这种模式用於单个一个RTP包封装一个NAL unit的情况
    • Non-Interleaved Mode,非交错模式(NAL unit/STAP-A/FU-A)。这种模式用于非B帧类型的NAL单元封装
    • Interleaved Mode,交错模式(STAP-B/MTAP16/MTAP24/FU-A/FU-B),这种模式可以用于带B帧类型的NAL单元封装
  • 负载结构简单介绍

    • NAL unit,这种结构就是直接把编码器输出的编码后的数据封装为一个RTP包
    • STAP-A,STAP-B,MTAP16和MTAP24,这几种结构用于多个NAL单元封装到一个RTP包中。STAP类型用于多个具有相同时间戳的NAL单元,MTAP类型用于多个具有不同时间戳的NAL单元
    • FU-A和FU-B,这种结构用于一个NAL单元拆分为多个RTP包的情况,封装同一个NAL单元的多个RTP包拥有相同的时间戳和不同的序号

NALU HDR(NAL单元头部)

每一个NAL单元的第一个字节是NAL单元的头部,头部格式如下:

    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |F|NRI|  Type   |
    +---------------+
标志位 说明
F forbidden_zero_bit,禁止为0
NRI 不同的负载类型对应不同的值,具体看下面的表格
Type NAL Unit的负载类型,具体看下面的表格
NAL Unit Type Content of NAL Unit NRI(binary)
0 Unspecified -
1 Coded slice of a non-IDR picture 10
2 Coded slice data partition A 10
3 Coded slice data partition B 01
4 Coded slice data partition C 01
5 Coded slice of an IDR picture 11
6 Supplemental enhancement information (SEI) 00
7 Sequence parameter set (SPS) 11
8 Picture parameter set (PPS) 11
9 Access unit delimiter 00
10 End of sequence 00
11 End of stream 00
12 Filler data 00
13-23 Reserved -
24-31 Unspecified -

STAP

  • STAP结构
    STAP的通用结构如下,它包含了一个STAP HDR(Type为STAP-A/B,F和NRI是聚合帧的第一个NAL的值)和多个single NAL unit
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |F|NRI|  Type   |                                               |
    +-+-+-+-+-+-+-+-+                                               |
    |                                                               |
    |               Bytes 2..n of a single NAL unit                 |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • STAP-A和STAP-B结构
    B结构比A结构多了一个DON,当存在B帧的时候才会用到B结构,解码顺序和包顺序不同。实时视频会议中一般只会用到Baseline模式,Baseline模式不存在B帧,所以WebRTC也仅仅支持STAP-A模式。
    Payload format for STAP-A
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    :                                               |
    +-+-+-+-+-+-+-+-+                                               |
    |                                                               |
    |                single-time aggregation units                  |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    Payload format for STAP-B
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    :  decoding order number (DON)  |               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |
    |                                                               |
    |                single-time aggregation units                  |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 单个single-time aggregation unit结构
    由两部分组成:NAL unit size(占16位)和NAL unit,这里的NAL是H.264的NAL的概念,可以是SPS/PPS/IDR/Slice等
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    :        NAL unit size          |               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |
    |                                                               |
    |                           NAL unit                            |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 一个RTP包包含两个STAP-A结构的例子
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          RTP Header                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         NALU 1 Data                           |
    :                                                               :
    +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |               | NALU 2 Size                   | NALU 2 HDR    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         NALU 2 Data                           |
    :                                                               :
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

MTAP

  • MTAP结构
    它由两部分组成:decoding order number base和multi-time aggregation units(是一个大的结构,后面会说)
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    :  decoding order number base   |               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |
    |                                                               |
    |                 multi-time aggregation units                  |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • MTAP16和MTAP24
    MTAP24比MTAP16多出8位时间戳偏移(TS offset),其他都是一样的
    一个multi-time aggregation units由四部分组成:NAL unit size,DOND,TS offset和NAL unit
    Multi-time aggregation unit for MTAP16
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    :        NAL unit size          |      DOND     |  TS offset    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |  TS offset    |                                               |
    +-+-+-+-+-+-+-+-+              NAL unit                         |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    Multi-time aggregation unit for MTAP24
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    :        NAL unit size         |      DOND     |  TS offset    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |         TS offset             |                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
    |                              NAL unit                         |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 一个RTP包包含两个MTAP16结构的例子
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          RTP Header                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |MTAP16 NAL HDR |  decoding order number base   | NALU 1 Size   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |  NALU 1 Size  |  NALU 1 DOND  |       NALU 1 TS offset        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |  NALU 1 HDR   |  NALU 1 DATA                                  |
    +-+-+-+-+-+-+-+-+                                               +
    :                                                               :
    +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |               | NALU 2 SIZE                   |  NALU 2 DOND  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |       NALU 2 TS offset        |  NALU 2 HDR   |  NALU 2 DATA  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |
    :                                                               :
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

FU

FU适用于一个NAL unit过大,需要拆分为多个RTP包的情况,这种情况下多个RTP包的时间戳相同,序号不同,一帧最后一个RTP包的Mark位为1

  • FU-A和FU-B结构
    FU-B比FU-A多了一个DON。
     FU-A
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | FU indicator  |   FU header   |                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
    |                                                               |
    |                         FU payload                            |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    FU-B
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | FU indicator  |   FU header   |               DON             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
    |                                                               |
    |                         FU payload                            |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • FU indicator
    Type为FU-A,其他参考NAL HDR
    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |F|NRI|  Type   |
    +---------------+
  • FU header
    这个Type是视频帧原来的Type(看NAL HDR关于Type部分的介绍)
    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |S|E|R|  Type   |
    +---------------+
标志位 说明
S 表示一帧的开始部分
E 表示一帧的结束部分
R 保留位,必须是0
Type NAL unit的负载类型

RtpPacketizerH264代码分析

有了上面的基础以后再看WebRTC这部分代码感觉就很容易理解了,WebRTC的结构写得挺好的。

WebRTC中的打包模式和包结构

直接看这两个枚举类型就知道WebRTC支持的打包模式以及打包模式对应支持的打包结构了,注释都说得蛮清楚的。

  • 只能选择其中一种打包模式
  • 默认打包模式是SingleNalUnit,但是当设置编码器属性kH264FmtpPacketizationMode为1时,则选择NonInterleaved模式
  • 从代码上所有平台默认都会是NonInterleaved模式,因为看codec.cc代码就知道,当创建H.264编码器的时候就会自动设置这个属性为1
  • 理论上来说只有非常确定一个帧的数据不会超过1500-UDP header size - RTP header size才会选择SingleNalUnit,要不就没办法打包了,WebRTC的做法是直接奔溃给你看(具体看PacketizeSingleNalu函数)
enum H264PacketizationTypes {
  kH264SingleNalu, // This packet contains a single NAL unit.
  kH264StapA, // This packet contains STAP-A (single time aggregation) packets. If this packet has an associated NAL unit type, it'll be for the first such aggregated packet.
  kH264FuA, // This packet contains a FU-A (fragmentation unit) packet, meaning it is a part of a frame that was too large to fit into a single packet.
};
enum class H264PacketizationMode {
  NonInterleaved = 0, // Mode 1 - STAP-A, FU-A is allowed
  SingleNalUnit // Mode 0 - only single NALU allowed
};

代码分析

// payload_data编码后的裸数据;payload_size裸数据大小;fragmentation裸数据有几个片,偏移和大小分别是多少,有些编码器每个I帧都会带sps和pps,也就是这种样子的sps|pps|nal unit
size_t RtpPacketizerH264::SetPayloadData(const uint8_t* payload_data, size_t payload_size, const RTPFragmentationHeader* fragmentation) {
  for (int i = 0; i < fragmentation->fragmentationVectorSize; ++i) {
    // 依次取出NAL单元
    const uint8_t* buffer = &payload_data[fragmentation->fragmentationOffset[i]];
    size_t length = fragmentation->fragmentationLength[i];

    bool updated_sps = false;
    H264::NaluType nalu_type = H264::ParseNaluType(buffer[0]);
    // 当判断是sps的时候,判断sps是否需要重写,具体目的看代码,不影响我们理解打包
    if (nalu_type == H264::NaluType::kSps) {
      rtc::Optional<webrtc::SpsParser::SpsState> sps;
      std::unique_ptr<rtc::Buffer> output_buffer(new rtc::Buffer());
      output_buffer->AppendData(buffer[0]);
      webrtc::SpsVuiRewriter::ParseResult result = webrtc::SpsVuiRewriter::ParseAndRewriteSps(buffer + webrtc::H264::kNaluTypeSize, length - H264::kNaluTypeSize, &sps, output_buffer.get());

      switch (result) {
        case SpsVuiRewriter::ParseResult::kVuiRewritten:
          input_fragments_.push_back(Fragment(output_buffer->data(), output_buffer->size()));
          input_fragments_.rbegin()->tmp_buffer = std::move(output_buffer);
          updated_sps = true;
          break;
      }
    } // end for

   // 用Fragment结构封装每一个NAL单元
    if (!updated_sps)
      input_fragments_.push_back(Fragment(buffer, length));
  }
  // Fragment结构转为PacketUnit结构
  GeneratePackets();
  return num_packets_left_;
}

void RtpPacketizerH264::GeneratePackets() {
  for (size_t i = 0; i < input_fragments_.size();) {
    // 选择打包模式
    switch (packetization_mode_) {
      case webrtc::H264PacketizationMode::SingleNalUnit:
        PacketizeSingleNalu(i);
        ++i;
        break;
      case webrtc::H264PacketizationMode::NonInterleaved:
        size_t fragment_len = input_fragments_[i].length;
        if (i + 1 == input_fragments_.size()) {
          fragment_len += last_packet_reduction_len_;
        }
        // 当NAL单元过大的时候选择FU-A结构,否则选择STAP-A结构
        if (fragment_len > max_payload_len_) {
          PacketizeFuA(i);
          ++i;
        } else {
          i = PacketizeStapA(i);
        }
        break;
    }
  }
}

// 填充RtpPacketToSend
bool RtpPacketizerH264::NextPacket(RtpPacketToSend* rtp_packet) {
  if (packets_.empty()) {
    return false;
  }

  webrtc::RtpPacketizerH264::PacketUnit packet = packets_.front();
  if (packet.first_fragment && packet.last_fragment) {
    // Single NAL unit packet.
    size_t bytes_to_send = packet.source_fragment.length;
    uint8_t* buffer = rtp_packet->AllocatePayload(bytes_to_send);
    memcpy(buffer, packet.source_fragment.buffer, bytes_to_send);
    packets_.pop();
    input_fragments_.pop_front();
  } else if (packet.aggregated) {
    RTC_CHECK_EQ(H264PacketizationMode::NonInterleaved, packetization_mode_);
    bool is_last_packet = num_packets_left_ == 1;
    NextAggregatePacket(rtp_packet, is_last_packet);
  } else {
    RTC_CHECK_EQ(H264PacketizationMode::NonInterleaved, packetization_mode_);
    NextFragmentPacket(rtp_packet);
  }
  RTC_DCHECK_LE(rtp_packet->payload_size(), max_payload_len_);
  if (packets_.empty()) {
    RTC_DCHECK_LE(rtp_packet->payload_size(), max_payload_len_ - last_packet_reduction_len_);
  }
  rtp_packet->SetMarker(packets_.empty());
  --num_packets_left_;
  return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章