要讀懂這部分代碼需要知道RTP打包協議相關的知識,具體可以閱讀相關RFC文檔-RFC6184 RTP Payload Format for H.264 Video
RFC6184協議
由於受到MTU限制,爲了保證我們的數據包不會被路由器分段傳輸,所以我們單次發送的包大小不能超過1500字節。
想要知道這個協議存在的意義先思考三個問題:
- 當一幀(包含IP頭和RTP頭)的數據量大於1500字節的時候我們該如何打包: frame_size > 1500
- 當多個幀(包含IP頭和RTP頭)總數據量小於1500字節的時候我們該如何打包: frame1_size + frame2_size + … < 1500
- 當一幀(包含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;
}