要读懂这部分代码需要知道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;
}