webrtc之rtp协议

本文介绍一下rtp协议和rtcp协议。因为ffmpeg的rtp/rtcp协议实现比较简单,这里改用其他开源代码(mediasoup)来介绍。

作者:音视频小话
原文:https://mp.weixin.qq.com/s/pdw9OLFWfg8uJdH-xUqcjg

本文主要两部分:

  • rtp协议详解rtp固定头部分;rtp扩展头部分;
  • rtp协议代码实现这里以mediasoup的开源为例进行介绍。
https://github.com/versatica/mediasoup

文章有点长,可以先点赞收藏,有空当资料看。

本文先介绍Rtp报文结构,下一篇文章再介绍H264的Rtp封装,和Vp8/Vp9的Rtp封装。

1 Rtp协议

1.1 Rtp协议公共Header

RTP公共Header格式如下:

  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             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

每个rtp报文头有固定的12个字节。很多字段的值是在sdp交互阶段定义的,这些字段的含义如下:

  • version (V): 2 bits该字段是RTP的版本,含2bits。(值1表示rtp第一个rtp草案版本,值0表示”vat”音频工具)
  • padding (P): 1 bit如果padding字段有被设置1,报文尾部含有一个或多个额外补齐的字节,但是这些补齐字节不是有效载荷。补齐字节的最后一个字节标识多少个补齐字节,包含其本身。补齐常用于加密算法中必须完整size大小,或者底层协议承载多个RTP报文。
  • extension (X): 1 bit如果extension被设置1,固定的rtp头必须跟着一个扩展头,其格式定义在后面的rtp扩展字段有介绍
  • CSRC count (CC): 4 bitsCSRC字段是CSRC列表集合,其紧跟着固定rtp头。CSRC list: 每个32bits,0~15个。这个由cc标志位使能,代表为SSRC的媒体提供贡献的源列表。但在webrtc中,该位长期为0
  • marker (M): 1 bitmarker字段定义rtp载荷分片是否结束。
  • payload type (PT): 7 bits这个字段表示RTP载荷格式类型,由上层应用来决定其含义。在webrtc中payloadtype是在sdp中定义好的。Sdp中定义payload:m=audio 7 UDP/TLS/RTP/SAVPF 111a=rtpmap:111 opus/48000/2m=video 7 UDP/TLS/RTP/SAVPF 96 97a=rtpmap:96 VP8/90000a=rtpmap:97 rtx/90000Opus payloadtype: 111; Vp8 payloadtype: 96; Rtx重传payloadtype: 97, 用于Vp8的重传payloadtype
  • sequence number 16: 是RTP数据报文的增长计数,接受者可以用其来检测丢包和重建报文序列。 sequence number开始数字应该是随机的,这样可以让已知明文攻击变得困难些。
  • timestamp: 32 bits时间戳是RTP报文第一个字节的采样时间点。采样时间必须由一个单调线性增长的时钟,其可以完成时间同步和jitter的计算。timestamp应该是个随机值,就像sequence number一样。如果几个连续的RTP报文是同一时刻产生的,它们应该有相等的时间戳,如同一个视频帧。常规视频vp9/vp8/h264的timestamp单位: 1/90000秒。常规音频opus的timestamp单位: 1/48000秒
  • SSRC: 32bits唯一标识一个媒体流。m=video 7 UDP/TLS/RTP/SAVPF 96 97a=ssrc-group:FID 1575745693 271424139a=ssrc:1575745693 cname:NpOm4i8SHMDg1rzGa=ssrc:271424139 cname:NpOm4i8SHMDg1rzGm=audio 7 UDP/TLS/RTP/SAVPF 111a=ssrc:1030411845 cname:NpOm4i8SHMDg1rzG如上,video有两个ssrc:1575745693(主)271424139(rtx丢包重传)audio也有一个ssrc:1030411845 

1.2 Rtp Extesion Header

Rtp扩展头是紧跟着Rtp公共头。如果公共头中的x位被使能,一个可变长度的扩展头就会追加在rtp header里面,其放在SSRC后面。其包含16bit的长度字段,表示有几个word(32bits)的扩展头,这个长度不包括长度在内的4个字节。头16bits表示不同扩展头的profile信息。

 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                       |
|                             ....                              |

如上,相关rfc文档rfc5285。

  • 单字节扩展Header一个字节的扩展格式,16bits的profile定义为0xBEDE,那么后面的扩展项目格式就如下
 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|  ID   |  len  |
+-+-+-+-+-+-+-+-+
  • 双字节扩展Header

profile信息如下

 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         0x100         |appbits|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.2.1 单字节扩展Header

一个字节的扩展格式,16bits的profile定义为0xBEDE。

每一个项由4bits的ID和4bits的len组成:

 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|  ID   |  len  |
+-+-+-+-+-+-+-+-+

4bits的ID常用值:1-14,如果是0,表示pad;而15是保留为未来协议定义使用。如果遇到id是15,后面就不用解析len,直接结束扩展头的解析。

4bit长度表示扩展数据长度减一。如len=0表示扩展数据为1byte,如果len=15(最大值)表示扩展数据长度为16bytes。

举例如下,有三个扩展项,内含padding:

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                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.2.2 双字节扩展Header

在双字节的头中,会有16bits的字段描述扩展信息。

其定义的profile信息如下:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         0x100         |appbits|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

appbits有4bits,应该有应用定义,如果没有特殊的定义需要,appbits为0。每个扩展项开始有一个字节的ID和一个字节的长度:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       ID      |     length    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

8bits的ID有效范围1-255,0表示pad,而256表示上面的”appbits“的profile开头。8bits的长度表示真实的byte大小,值0就是没有数据。

举例如下,3个扩展项,一些padding:

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                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.2.3 webrtc中的常用rtp extension

webrtc中的常用rtp extension。

Audio的sdp中对header的常用信息:

a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
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:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level

Video的sdp中对header的常用信息:

a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
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:13 urn:3gpp:video-orientation
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset

这里给出两个举例: 

  • abs-send-time格式描述:1字节的扩展,3字节的数据,每个报文4个字节。编码方式:timestamp是24bits,64秒一次翻转,3.8us一个单位精度。NTP timestamp是高32bits为秒,低32bits为秒的小数。
abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff

这里用于计算tcc的rtp packet的ntp时间戳,之前有一篇号内介绍Tcc的博文: webrtc tcc详解

 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=1            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ID   | L=2   |            abs_send_time(24bits)              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • transport-wide-cc-extensions

格式如下:

 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=1            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ID   | L=1   |transport-wide sequence number | zero padding  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

.RTP头扩展中有一个全局的sequence number(包含音视频的增量),只要是用同一个socket发出的,也就是同一个PeerConnection发出的。也是用于Tcc拥塞算法使用。

2 Rtp代码实现

这里以开源mediasoup中rtp/rtcp代码为例子

https://github.com/versatica/mediasoup

关注文件

worker\include\RTC\RtpPacket.hpp
worker\src\RTC\RtpPacket.cpp

Rtp::Parse函数解析Rtp数据,见代码和注释:

RtpPacket* RtpPacket::Parse(const uint8_t* data, size_t len){ 
   auto* ptr = const_cast<uint8_t*>(data);
    //先获取Rtp公共头
    //Rtp公共头,就是包含SSRC的公共头部分.
    auto* header = reinterpret_cast<Header*>(ptr);
    // 12 bytes的Rtp公共头.
    ptr += HeaderSize;
    // 如果Rtp公共头中的extension==1,即使能.
    HeaderExtension* headerExtension{ nullptr };
    size_t extensionValueSize{ 0u };
    if (header->extension == 1u)    {
      //获取Rtp扩展头
      //16bits的appbits,16bits的len
      headerExtension = reinterpret_cast<HeaderExtension*>(ptr);
      // 获取extersion的总长度(不包括:16bits的appbits,16bits的len)
      extensionValueSize = static_cast<size_t>(ntohs(headerExtension->length) * 4);
      //指针跳到Rtp extension数据块后面
      ptr += 4 + extensionValueSize;    }
    // Get payload.
    uint8_t* payload     = ptr;    size_t payloadLength = len - (ptr - data);
    return new RtpPacket(header, headerExtension, payload, payloadLength, payloadPadding, len);
  }

再Parse函数后,得到Rtp公共头的指针,Rtp extension头的指针和长度,Payload的指针和长度。

通过以上数据,就可以得到一个RtpPacket类指针。

  RtpPacket::RtpPacket( 
   Header* header,
   HeaderExtension* headerExtension,
   const uint8_t* payload,
   size_t payloadLength,
   uint8_t payloadPadding, 
   size_t size)
    : header(header), headerExtension(headerExtension), payload(const_cast<uint8_t*>(payload)),      payloadLength(payloadLength), payloadPadding(payloadPadding), size(size)
  {
    ParseExtensions();
  }

这样一个RtpPacket数据对象就能ready,并能被传递和使用。

3 总结

本文介绍Rtp报文结构,包括:

  • Rtp公共头结构
  • Rtp扩展头结构,及其在Sdp中的定义

下一篇文章介绍H264的Rtp封装,和Vp8/Vp9的Rtp封装。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论