RTP Header
RTP协议中,RTP Header(报头)包括固定报头(Fixed Header)与报头扩展(Header extension,可选)。
RTP Fixed Header结构如下,其中前12字节内容必须包含的。
1 2 3 4 5 6 7 8 9 10 11 12 |
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | contributing source (CSRC) identifiers | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
但是这Fixed Header携带的信息满足不了更复杂的需求。所以引入了RTP Header Extension,可以携带更多的信息,同时方便各种扩展。
RTP Header Extension
如果RTP Fixed Header中,X
字段为1,说明后面跟着RTP Header Extension。RTP Header Extension结构如下:
1 2 3 4 5 6 7 |
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | defined by profile | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | header extension | | .... | |
- defined by profile:决定使用哪种Header Extension:one-byte或者two-byte header
- length:表示Header Extension的长度:length x 4字节
根据Header Extension中ID+len字段所占字节,RTP Header Extension分为One-Byte Header和Two-Byte Header类型。
One-Byte Header
对于One-Byte Header,"defined by profile"字段为固定的0xBEDE。接着后面的结构如下,占用1个byte:
1 2 3 4 5 |
0 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | ID | len | +-+-+-+-+-+-+-+-+ |
- ID:4-bit长度的ID表示本地标识符
- len:表示extension data长度,范围:0~15,为0表示长度为1字节,15表示16字节
所以叫做One-Byte Header。
如下是一个One-Byte Header的示例:
1 2 3 4 5 6 7 8 9 10 11 |
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0xBE | 0xDE | length=3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ID | L=0 | data | ID | L=1 | data... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ...data | 0 (pad) | 0 (pad) | ID | L=3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
首先是0xBEDE固定字段开头,接着length长度为3,说明后面跟着3x4
字节长度的header extension 。对于第一个header extension:L=0
,表示data长度为1字节。对于第二个header extension:L=1
,表示data长度为2字节。由于按4字节对齐,所以接着是值为0的填充数据。最后一个header extension:L=3
,表示data长度为4字节。
WireShark抓包分析
如下是WireShark抓包WebRTC视频流解析的一个RTP包结构:
Defined by profile
字段为0xBEDE,表示One-Byte Header,Extension length
为1,表示Header Extension长度为1x4
字节,对于Header Extension:ID为3,Lengh为2。
相关代码Review
- 构造相关代码位于
RtpPacket::AllocateRawExtension
中 - 解析相关代码位于
RtpPacket::ParseBuffer
中
大家可以对着WebRTC相关代码熟悉下协议。
Two-Byte Header
"defined by profile"字段结构如下:
1 2 3 4 5 |
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0x100 |appbits| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
接着后面跟着的每个扩展元素结构如下,占用2个byte:
1 2 3 4 5 |
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ID | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
- ID:本地标识符
- length:表示extension data长度,范围1~255
如下是一个Two-Byte Header示例:
1 2 3 4 5 6 7 8 9 10 11 |
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0x10 | 0x00 | length=3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ID | L=0 | ID | L=1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | 0 (pad) | ID | L=4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
首先"defined by profile"字段为0x1000,length为3,后面跟着3x4
字节长度扩展,对于第一个header extension:L=0
,数据长度为0,对于第二个header extension:L=1
,data长度为1,接着是填充数据,对于第三个header extension:L=4
,后面跟着4字节长度数据。
由于WebRTC中默认都是One-Byte Header,所以就不抓包分析了,具体构造解析代码跟One-Byte Header位于同一地方。
常见RTP Header Extension
在WebRTC中定义了很多RTP Header Extension,最常见的要数用于带宽估计的Transport-CC扩展,记录传输层序列号:TransportSequenceNumber
,默认每个RTP包都带有此扩展。
还有记录音频能量大小的AudioLevel扩展,记录发送时间的AbsoluteSendTime扩展等等。
看下目前WebRTC支持的几种Header Extension:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
constexpr ExtensionInfo kExtensions[] = { CreateExtensionInfo<TransmissionOffset>(), CreateExtensionInfo<AudioLevel>(), CreateExtensionInfo<CsrcAudioLevel>(), CreateExtensionInfo<AbsoluteSendTime>(), CreateExtensionInfo<AbsoluteCaptureTimeExtension>(), CreateExtensionInfo<VideoOrientation>(), CreateExtensionInfo<TransportSequenceNumber>(), CreateExtensionInfo<TransportSequenceNumberV2>(), CreateExtensionInfo<PlayoutDelayLimits>(), CreateExtensionInfo<VideoContentTypeExtension>(), CreateExtensionInfo<RtpVideoLayersAllocationExtension>(), CreateExtensionInfo<VideoTimingExtension>(), CreateExtensionInfo<RtpStreamId>(), CreateExtensionInfo<RepairedRtpStreamId>(), CreateExtensionInfo<RtpMid>(), CreateExtensionInfo<RtpGenericFrameDescriptorExtension00>(), CreateExtensionInfo<RtpDependencyDescriptorExtension>(), CreateExtensionInfo<ColorSpaceExtension>(), CreateExtensionInfo<InbandComfortNoiseExtension>(), CreateExtensionInfo<VideoFrameTrackingIdExtension>(), }; }; |
SDP
如下是某个示例SDP内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 102 0 8 106 105 13 110 112 113 126 a=mid:0 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 119 125 118 124 107 108 109 117 116 123 a=mid:1 a=extmap:14 urn:ietf:params:rtp-hdrext:toffset a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:13 urn:3gpp:video-orientation a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id |
SDP中,a=extmap开头即为RTP Header Extension,
对于RFC中定义的RTP Header Extension,SDP格式如下:
1 |
a=extmap:<value> urn:ietf:params:rtp-hdrext:<extensionattributes> |
对于WebRTC中自定义的RTP Header Extension,SDP格式如下:
1 |
a=extmap:<value> <URI> |
value
值就是Header extension的本地标识符(ID值),ID值中0是预留位,所以我们可以看到从1开始递增,对于One-Byte Header来说,15也是预留位。
下面结合该SDP看下某个视频包的Wireshark解析:
可以看到该RTP包共有4个Header extension,这四个Header extension ID分别为:2,3,4,10。根据示例SDP,可知分别是:abs-send-time,transport-wide-cc-extensions,mid,rtp-stream-id扩展。
相关代码
RTP header extension的解析以及构造代码位于rtp_header_extensions.cc中。这里以TransportSequenceNumber扩展为例,看下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// TransportSequenceNumber // // 0 1 2 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=1 |transport-wide sequence number | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ constexpr RTPExtensionType TransportSequenceNumber::kId; constexpr uint8_t TransportSequenceNumber::kValueSizeBytes; bool TransportSequenceNumber::Parse(rtc::ArrayView<const uint8_t> data, uint16_t* transport_sequence_number) { if (data.size() != kValueSizeBytes) return false; *transport_sequence_number = ByteReader<uint16_t>::ReadBigEndian(data.data()); return true; } bool TransportSequenceNumber::Write(rtc::ArrayView<uint8_t> data, uint16_t transport_sequence_number) { ByteWriter<uint16_t>::WriteBigEndian(data.data(), transport_sequence_number); return true; } |
前面SDP章节提到了,Header extensin ID跟Header extensin类型的对应。它们的对应关系处理在RtpHeaderExtensionMap
类中,当进行SetLocalSdp
操作时候,会调用:
1 2 3 4 5 |
RtpHeaderExtensionMap::Register(int id, RTPExtensionType type, absl::string_view uri) { ids_[type] = static_cast<uint8_t>(id); } |
绑定它们的关系。
总结
本文介绍了RTP包中的Header Extension,通过本文可以了解下Header Extension的结构,以及如果根据SDP以及Wireshark分析。
参考
[1] RTP: A Transport Protocol for Real-Time Applications.https://tools.ietf.org/html/rfc3550.
[2] A General Mechanism for RTP Header Extensions.https://tools.ietf.org/html/rfc5285.
文章评论
ID的取值是怎么来的?
@觉烨 看SDP中的取值,也就是a=extmap:后面的值,一般从1开始递增,比如本文Wireshark截图中,ID值为3,结合我给出的示例SDP,可知为TransportSequenceNumber扩展
@Jeff 请教博主,SDP中的取值,也就是a=extmap:后面的值是怎么来的,会和RTPExtensionType中的定义产生关联吗?如何产生关联的?看sdp中transportcc是3,貌似和RTPExtensionType对应不上
@嗑瓜子 a=extmap:后面的值也就是ID值,根据SDP定义来的。有个RtpHeaderExtensionMap类,会绑定header extension id跟RTPExtensionType,在setlocalsdp操作中绑定的。sdp中transportcc是3,这个3是ID值,RTPExtensionType中的值是枚举的值,不一样。
首先是0xBEDE固定字段开头,接着length长度为3,说明后面跟着3x4字节长度的header extension 。对于第一个header extension:L=0,表示data长度为1字节。对于第二个header extension:L=1,表示data长度为2字节。由于按4字节对齐,所以接着是值为0的填充数据。最后一个header extension:L=3,表示data长度为4字节。
---L=0,表示data长度为1字节 为什么不需要补齐
@觉烨 按RFC定义,这个padding数据可以放在extension间或者最后一个extension后,你要放第一个后也可以
@Jeff 假如放中间是否有歧义了,例如我中间说2个字节,解析完2个字节后,究竟是ID为0,长度为1的,还是填充字段