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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章