RTP:Audio and video for the Internet 中文版阅读笔记

Posted by Piasy on May 23, 2020
本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2020/05/23/RTP/

之前发现连接 OWT Server 4.3.x 时,无法获取到 audio sending 相关的 stats,最终定位到是没有收到 Server 的音频 RTCP RR 报文,为了分析这个问题,我决定梳理一下 OWT Server 的 RTCP 流程,于是便有了 VSCode 远程调试 Docker 里的 OWT Server。不过在扎进代码之前,我决定先系统学习一下 RTP 协议,正好今年二月份 51talk 技术团队翻译了《RTP: Audio and video for the Internet》这一经典著作,正好帮了大忙。

今天我就把阅读笔记分享给大家,仅供参考。

我加了评论标注的版本可以从这里下载

概述

RTP 由主协议规范 rfc3550 和一系列 Profile、Payload Format 规范组成,RTP 主协议被设计为可以传输任意类型的数据,结合了具体的 Profile 和 Payload Format 规范后,我们才能知道完整的报文类型和格式、一些字段的使用规则,以及封包解包规则。

RTP/AVP rfc3551 定义了基本的多方音视频会议的 Profile;RTP/SAVP 定义了各种安全机制,它定义在 SRTP rfc3711 中;RTP/AVP 只包含了中期的(mid-term)反馈机制,RTP/AVPF rfc4585 则在其基础上补充了更即时的反馈机制;RTP/SAVPF rfc5124 则把 RTP/SAVP 和 RTP/AVPF 结合了起来,实现了安全的、带有即时反馈机制的音视频 RTP 传输,WebRTC 使用的通常都是 RTP/SAVPF。

Payload Format 有很多,比如 rfc7587 定义了 Opus 的 Payload Format,rfc6184 则定义了 H.264 的 Payload Format。

在 51talk 的 RTP 中文版中,Profile 被翻译为「配置文件」,Payload Format 被翻译为「有效负载格式」

RTP 发送方的行为:

  • 未压缩的媒体数据被采集到缓冲区中,从中产生压缩帧;
  • 压缩帧被装入 RTP 包中,准备发送;如果帧很大,它们可能被分成几个 RTP 包;如果它们很小,可以将几个帧绑定到一个 RTP 包中;
  • 根据使用中的错误恢复方案,可以使用信道编码器来生成错误恢复包或在传输之前重新对包进行排序;
  • 发送方负责生成它所生成的媒体流的定期状态报告,包括唇音同步所需的信息;它还从其他参与者那里收到接收质量反馈,并可能利用这些信息来调整其传输;

RTP 接收方的行为:

  • 接收方负责从网络中收集 RTP 数据包、恢复丢失的数据、纠正时序、播放缓冲区、自适应播放点、解压媒体,并将结果显示给用户;
  • 它还发送接收质量反馈,允许发送方调整到接收方的传输,并维护会话中参与者的数据库;

RTP 接收方的操作很复杂,它比发送方的操作更加复杂。这种复杂性的增加主要是由于 IP 网络的可变性:大部分复杂性来自于补偿丢失的包的需要,以及恢复受抖动影响的流的时序。

分组网络上的语音和视频通信

IP 网络的性能特征:获取网络性能的唯一方法是观察和测量。

  • 平均丢包率,丢包模式;
  • 网络传输时间,传输时间变化(jitter);jitter 的粗略度量是包的到达速率;
  • 可用带宽;

设计可在 IP 上运行的音视频应用程序的挑战是使它们在面对网络问题和意外情况时仍旧可靠。

测量、预测和建模网络行为是有许多微妙之处的复杂的问题:

  • 网络可以而且经常表现得很糟糕;
  • 网络中的异构性;

丢包和丢包模式的几个“典型”特征:

  • 虽然有些网络路径可能不会丢包,但这些路径在公共网络中并不常见;一个应用程序应该被设计来处理少量的数据包丢失,比如达到 5%;
  • 孤立的丢包组成了大多数观察到的丢包事件;
  • 丢包的概率不是均匀的:即使大多数丢包是孤立的包,连续丢包的突发概率也比随机事件更常见;丢包的突发通常是短暂的;一个应用程序,处理两到三个连续丢失的包将足以满足大多数突发丢包;
  • 很少出现长时间的突发丢包;一秒甚至更长的故障时间并不是未知的(?);
  • 包重复很少见,但也可能发生;
  • 类似地,在极少数情况下,数据包可能被破坏;其中绝大多数是由 TCP 或 UDP 校验码(如果启用)检测到的,包在到达应用程序之前会被丢弃;

传输时间变化的特征:

  • 网络上的传输时间不是均匀的,而且会观察到抖动;
  • 绝大多数抖动是合理有界的,但分布的长尾效应比较明显;
  • 虽然重新排序相对较少,但在传输过程中可能会重新排序数据包;应用程序不应该假定接收数据包的顺序与发送数据包的顺序一致;

对于我们的目的来说,重要的是注意 TCP 和 UDP 之间的根本区别:可靠性(TCP)和及时性(UDP)之间的权衡。

实时传输协议

RTP 的设计者面临的挑战是在一个不可靠的传输层之上构建一个健壮的、实时的媒体传输机制。他们实现了这个目标,设计遵循了「应用级框架」和「端到端原则」的双重哲学。

应用程序级框架源于这样一种认识,即应用程序可以通过多种方式从网络问题中恢复,正确的方法取决于应用程序和使用它的场景。这些选择只有在应用程序与传输紧密交互时才有可能。

设计必须通过网络进行可靠通信的系统的两种方法:系统可以将正确传输数据的责任与数据一起往下传递,从而确保每一跳的可靠性;数据的责任由端点承担,即使单个跃点不可靠,也可以确保端到端的可靠性。第二种端到端方法渗透到互联网的设计中,TCP 和 RTP 都遵循端到端原则。端到端原则意味着智能是在端点上,而不是在网络中。

RTP 的优点是它提供了一个统一的实时音频/视频传输框架,可以直接满足大多数应用程序,但对于那些超出其限制的应用程序来说,它是可塑的。

RTP 主协议分为两部分:数据传输协议(RTP)和相关的控制协议(RTCP)。

RTP 数据传输协议管理终端系统之间的实时数据传输,如音频和视频。它为媒体有效负载定义了额外的帧级别,包括用于丢包检测的序列号、用于恢复时序的时间戳、有效负载类型和源标识符,以及媒体流中重要事件的标记。还指定了使用时间戳和序列号的规则,尽管这些规则在某种程度上依赖于使用的配置文件和有效负载格式,以及在一个会话中多路复用多个流。

RTCP 用于定期报告接收到数据的质量、参与者标识、源的其他描述信息、会话成员改变的通知以及同步媒体流所需要的信息。RTP 包通常每隔几毫秒发送一次,RTCP 包则以秒为单位进行操作。

RTP 数据传输协议

一个会话(session)包含一组使用 RTP 通讯的参与者。

RTP 报文分为四部分:固定头(Fixed Header), 可选的头扩展(Header Extension),可选的负载头(Payload Header),负载数据,如下图所示:

 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             |
|                             ....                              |
+---------------------------------------------------------------+
|                                                               |
|                   Header extension (optional)                 |
|                                                               |
+---------------------------------------------------------------+
|            Payload header (payload format dependent)          |
+---------------------------------------------------------------+
|                                                               |
|                                                               |
|                          Payload data                         |
|                                                               |
|                                       +-----------------------+
|                                       |    Padding if P = 1   |
+------------------------------------------------+--------------+

固定头字段

  • 版本号,固定为 2;
  • 填充(Padding)标志位,1 表示有填充;
  • 头扩展标志位,1 表示有头扩展;
  • 贡献源(contributing source)数量,可以为 0;
  • 对感兴趣事件的标记,它的确切含义由所使用的 RTP Profile 和媒体类型定义;
  • 负载类型(Payload Type,简称 PT);
  • 序列号;
  • 时间戳;
  • 同步源(synchronization source)标识符;
  • 贡献源标识符列表,如果 CC 为 0,则不存在;

负载类型

RTP/AVP 为一些编码格式定义了固定的 PT 映射,比如 PCMU 为 0,JPEG 为 26,还有一些其他的 Profile 也定义了固定映射,96~127 则预留给了动态映射。WebRTC 使用的大部分编码格式都是动态映射的,所以我们看 WebRTC 的 SDP 时,PT 基本都是从 96 开始的。

固定映射编码格式的参数,在 Profile 里进行了定义,动态映射的编码格式,(SDP)则需要通过 a=rtpmap: 行指定参数。

TODO: RTP clock rate?

序列号

序列号用于标识数据包,主要用途是丢包检测。尽管它可以让接收者按发送顺序重新组织数据包,但它不用于安排数据包的播放顺序(这是时间戳的作用)。序列号应始终是连续的序列,对于每个发送的数据包,序列号应增加一,序列号的初始值则应该随机,而不是固定从零开始(出于安全的考虑)。和序列号相关的处理算法,比如回绕处理、丢包检测等,可以阅读 rfc3550 的附录部分。

时间戳

时间戳表示数据包中媒体数据的第一个字节的采样时刻,用于编排媒体数据的输出。时间戳的初始值应该随机选择,而不是从零开始。时间戳回绕是 RTP 正常操作,应由应用程序处理。时间戳是从媒体时钟派生而来的,该媒体时钟必须以线性和单调的方式增加(当然,回绕除外),从而为每个 RTP 会话生成单调的时间轴。时间戳顺序和序列号顺序可能不一致,比如 B 帧。可能多个 RTP 包的时间戳一样,比如一个大的视频帧被分割为多个 RTP 包,这些包的序列号不同,但时间戳相同。

同步源 SSRC

SSRC 用来标识 RTP 会话中的参与者。它是临时的,会通过 RTCP 映射到一个长期的规范名称 CNAME。SSRC 由参与者加入会话时随机选择,使用高质量的随机源来生成 SSRC,并实现冲突检测。

接收方必须按 SSRC 对数据包进行分组才能进行播放。如果参加者在一个 RTP 会话中生成多个流(例如,来自不同的摄像机),每个流都必须标识为不同的 SSRC,以便接收方可以区分哪些数据包属于哪个流。

贡献源 CSRC

在正常情况下,RTP 数据由单个源生成,但是当多个 RTP 流通过混流器或转换器时,多个数据源可能对 RTP 数据包有所贡献。 贡献源(CSRC)列表标识了对 RTP 数据包做出了贡献的参与者,但它不负责其时序和同步。

头扩展

WebRTC 使用了很多 RTP 头扩展,头扩展的格式为:

 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                       |
|                             ....                              |
  • 一个 RTP 报文里最多允许一个头扩展,「defined by profile」的取值用来标识后面「header extension」部分的含义;
  • 长度的单位为四字节,表明「header extension」部分的长度,允许为 0;
  • 「header extension」可以包含多个元素(头),其格式都是 ID + len + data 的形式;

头扩展 rfc8285 定义了两种:单字节头和双字节头。

单字节头

单字节头的「defined by profile」字段固定为 0xBEDE。

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


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

单字节头的 L (len) 字段是 data 字节数减一。

双字节头

单字节头的「defined by profile」字段固定为 0x100X,最后四个 bit 为 app bits。

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


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


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

双字节头的 L (len) 字段是 data 字节数,可以为 0。


负载头和负载数据的格式,由 Payload Format 规范定义,这里就不展开了。

RTP 控制协议

RTCP 的实现分为三部分,数据包格式、时序规则以及参与者数据库。

RTCP 包格式为:

 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|   IC    |       PT      |             Length            |
+---------------+---------------+-------------------------------+
|                                                               |
|                  Format-specific information                  |
|                                                               |
|                                       +-----------------------+
|                                       |    Padding if P = 1   |
+---------------------------------------+-----------------------+

各个字段的含义为:

  • 版本号,固定为 2;
  • 填充(Padding)标志位,1 表示有填充;
  • 条目数量(Item Count,简称 IC),在 RTCP 包的内容是条目列表时,用来表明条目数量;其他情况可作为别的含义;
  • 包类型(Packet Type,也简称 PT),rfc3550 定义了五种标准包类型,分别是 sender report (SR), receiver report (RR), source description (SDES), goodbye (BYE), application-specific message (APP);
  • 长度,包头之后的内容总长度,单位是四字节,允许为 0;

RTCP 包不会单独的被传输,它需要打包在一起形成复合包(compound packets)进行传输。每一个复合包都会被一个底层的包封装(通常是 UDP/IP 包)用来传输。如果要对复合包进行加密,那么 RTCP 的包组的前缀通常是一个 32 位的随机数。复合包的结构如下图所示:

+---------------------------------------------------------------+
|                                                               |
|                                                               |
|                          IP header                            |
|                                                               |
|                                                               |
+---------------------------------------------------------------+
|                                                               |
|                          UDP header                           |
|                                                               |
+---------------------------------------------------------------+
|                  Random prefix (if encrypted)                 |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|V=2|P|   IC    |       PT      |             Length            |
+---------------+---------------+-------------------------------+
|                                                               | first
|                                                               | RTCP
|                  Format-specific information                  | packet
|                                                               |
|                                                               |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|V=2|P|   IC    |       PT      |             Length            |
+---------------+---------------+-------------------------------+
|                                                               | second
|                                                               | RTCP
|                  Format-specific information                  | packet
|                                                               |
|                                                               |
+---------------------------------------------------------------+

RR

RTCP 的主要用途之一就是接收质量报告,它通过接收数据的所有参与者发送的 RR 包来完成,RR 包的类型取值为 201,包格式为:

 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|    RC   |   PT=RR=201   |             Length            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of packet sender                     |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|                 SSRC_1 (SSRC of first source)                 | report
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
| Lost fraction |       Cumulative number of packets lost       |   1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Extended highest sequence number received           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Interarrival jitter                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Timestamp of last sender report received (LSR)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Delay since last sender report received (DLSR)         |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|                 SSRC_2 (SSRC of second source)                | report
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
:                               ...                             :   2
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|                  profile-specific extensions                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中报告块(report block)的数量,用 RC 字段表示,RC 字段可以为 0。

许多 RTCP 的包类型在固定部分的后面有一个条目列表(Item List),这个结构类似于 RR。所以此处需要注意,即使条目列表是空的,数据包的固定部分仍然保持不变,这也就是说,如果 RR 中没有报告块,那么要将数据包中的 RC 设置为 0、长度设置为 1,对应四个字节的固定 RTCP 包头,外加四个字节的报告者 SSRC(SSRC of packet sender)。

每个 RR 包最多包含 31 个条目(744 个字节),如有更多条目,则应在复合包里发送多个 RR 包。

RR 报告块各字段的具体含义、使用场景(比如计算 RTT、丢包情况、jitter 等),可以阅读原书章节,或 RFC 文档。

SR

SR 主要是为了使接收方能够同步多个媒体流(比如音视频同步),SR 包的类型取值为 200,包格式为:

 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|    RC   |   PT=SR=200   |             Length            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of packet sender                     |
+---------------------------------------------------------------+
|                        NTP timestamp                          |
|                                                               |
+---------------------------------------------------------------+
|                        RTP timestamp                          |
+---------------------------------------------------------------+
|                    Sender's packet count                      |
+---------------------------------------------------------------+
|                    Sender's octet count                       |
+---------------------------------------------------------------+
|                   Receiver report block(s)                    |
|                                                               |

SR 的有效负载包含一个 24 字节的报告块,后面跟着零个或多个接收方报告块,由 RC 字段标识,类似于 RR 报告报。当发送者也是接受方的时候,RR 报告块就出现了。

SR 包各字段的具体含义、使用场景(比如计算吞吐率等),可以阅读原书章节,或 RFC 文档。

SDES

SDES 主要是为了提供参与者认证和补充性细节,如位置、电子邮件地址和电话号码等,SDES 包的类型取值为 202。

SDES 的包格式、各字段的具体含义、使用场景,可以阅读原书章节,或 RFC 文档。

BYE

RTCP 通过 BYE 包为成员提供松散的控制,如果收到 BYE,则表明某些参与者已经离开了会话。BYE 包是在参与者离开会话的时候,或者当其他参与者因为冲突而改变 SSRC 的时候生成的。BYE 包的类型取值为 203。

BYE 的包格式、各字段的具体含义、使用场景,可以阅读原书章节,或 RFC 文档。

如果想利用 BYE 包,需要实现校验机制,以避免恶意「被离开」

APP

APP 包允许应用程序来自定义扩展,包的类型取值为 204。

APP 的包格式、各字段的具体含义、使用场景,可以阅读原书章节,或 RFC 文档。

组包

RTCP 包不会单独发送,而是组包成一个复合数据包进行传输。如果生成复合 RTCP 包的参与者是活跃的数据发送方,那么该复合包必须以 SR 包开始,否则必须从 RR 包开始。如果 RR 报告块太多,无法放入一个 RR 包,则复合包应以一个 SR/RR 包开头,后面跟着多个 RR 包(未必紧跟)。如果 RR 报告块多得导致复合包大小超过 MTU 了,那应该分多次(多个周期,而非同一周期多个包)发送。

SR/RR 包后面紧跟着一个 SDES 包,这个包必须包含一个 CNAME 条目,可能包含其他条目。

RTCP 复合包发送频率、各类包的复合频率,由 Profile 定义。


RTCP 包格式和生成复合包的规则,只是 RTCP 的一部分,另外两部分「参与者数据库」、「RTCP 包发送的时序规则」,可以阅读原书章节,或 RFC 文档。

媒体采集、播放和时序

生成 RTP 包

RTP 包生成过程的关键部分是为帧分配时间戳,分割过大的帧以及生成有效负载包头。

时间戳:

  • RTP 时间戳表示帧中数据的第一个字节的采样时刻,它从一个随机初始值开始,并以与媒体相关的速率递增;
  • RTP 时间戳的时钟必须以连续和单调的方式增加,而不管查找操作或播放中的暂停,这意味着时间戳并不总是对应于帧从文件开始的时间偏移,而是以自播放开始以来的时间轴为测量基准;
  • 时间戳是按帧分配的,如果将一个帧分成多个 RTP 数据包,则组成该帧的每个数据包都将具有相同的时间戳;
  • RTP 数据包和 RTCP SR 包的时间戳表示发送方媒体的时序;
  • 尽管 RTP 很好地定义了时序模型,但该规范并未提及用于重建接收方时序的算法,这是有意为之的:播放算法的设计取决于应用程序的需求,这是供应商可以区分其产品的领域;

接收方行为

RTP 接收方的操作是一个复杂的过程,比发送方的操作更复杂。这种复杂性的增加,在很大程度上是由于 IP 网络的固有变化造成的,大部分的复杂性,来自丢包补偿和时序恢复。

播放缓冲区、自适应播放点

接收方收到数据包后,根据 SSRC 找到对应的缓冲区放入。缓冲区里的包会根据 RTP 时间戳排序,并延迟一段时间后再播放,这段缓冲可以平滑网络抖动。在这段缓冲时间里,接收方会等待未收到的包,或使用错误隐藏/纠正技术,尽最大努力解码帧。对播放缓冲区的操作需要在保真度和延迟之间做权衡。

设计 RTP 播放缓冲区的主要困难是确定播放延迟,它取决于多种因素:

  • 接收帧的第一个和最后一个数据包之间的延迟;
  • 接收任何纠错数据包之前的延迟;
  • 由于网络抖动和路由更改导致的数据包间的时序变化;
  • 发送方和接收方之间的相对时钟偏移(skew);
  • 应用程序的端到端之间延迟预测,以及接收质量和延迟的相对重要性;

在决定每一帧的播放时间的时候,接收方对播放的计算步骤如下:

  • 把发送者的时间轴映射到本地播放时间轴,以补偿发送方和接收方时钟之间的相对偏移,并导出播放时间计算的基准时间;
  • 如果有必要,接受者可以通过添加一个偏移补偿偏移(skew compensation offset)来补偿相对发送者的时钟,该偏移会定期校准基准时间;
  • 本地时间轴上的播放延迟是根据播放延迟中与发送者相关的部分和与抖动相关的部分计算的;
  • 如果路由发生了变化,或包被重新排序,或选择的播放延迟导致帧重叠,或相应媒体中其他的变化,则会调整播放延迟;
  • 最后,将播放延迟添加到基准时间中,以获得帧的实际播放时间;

调整播放点(减小或增大播放延迟)可以有两种策略:每帧都稍微调整,或少量的大调整。音频可以在静音期间调整,视频则可以在帧间隔期间调整。

唇音同步

  • 要同步的多个 SSRC 需要使用相同的 CNAME,并利用 RTCP SDES 包告知接收方;
  • 发送方利用 RTCP SR 包,提供每个 SSRC 的 RTP 时间戳与公共参考时间戳(NTP 时间戳)的对应关系;

错误隐藏

音频丢包隐藏技术:

  • 人类对声音的感知是一个复杂的过程,很难对声音质量做出客观测量,不过可以通过 MOS 评分进行主观评价;
  • 静音替换,用静音数据包代替一个丢失的语音包,最简单,但效果也最差,静音替换仅对短时间(<16 毫秒)和较小丢包率(<2%)有效;
  • 噪声替换,用静音数据包代替一个丢失的语音包,研究表明,语音的恢复,即人类大脑 用正确的声音潜意识地修复缺失的语音片段的能力,发生在用噪音修复语音的时候,而不是用静音来修复的时候;
  • 重复,用前面接收到的数据包(而非修复的)代替一个丢失的语音包,当间隙很小时,重复效果最好,因为在间隙小时信号的特征很可能是相似的,通过逐渐减弱重复信号,可以提高具有较长时间间隙的表现;
  • 对语音来说重复比噪声替换效果更好;重复对语音的作用比对音乐的效果好,是因为音乐的特点更加多样,与信号频谱相匹配的噪声可能是音乐信号更好的选择;
  • 波形替换,根据丢失包周围语音信号的特征生成合适的替换包,它们可以看作是包重复的扩展,可以避免间隙边缘的不连续,并更好地匹配信号的特性;
  • 时间尺度修正,将丢失包两侧的音频延伸到整个间隙;
  • 再生修复,利用音频压缩算法的知识,推导出适当的解码器参数,以恢复丢失的包;
  • 编码器状态的插值,基于线性预测(例如,G.723.1)的编解码器通过在丢失包的任一侧插值帧来导出预测器参数;
  • 基于模型的修复,尝试将丢失包两侧的信号拟合到声道/编解码器模型,并使用该模型预测正确的填充;
  • 更复杂的方案,收益随复杂度的增速会变缓;

视频丢包隐藏技术:

  • 复制运动补偿,时域重复,受丢包影响的部分被替换为前一帧的重复;帧内运动补偿、前一帧运动补偿;
  • 空间域的修复,在周围数据的基础上对缺失块进行插值;
  • 频域的修复,对周围块的低阶 DCT 系数求平均值,以生成缺失块的填充;
  • 减少依赖,压缩效率和丢包容忍度之间存在根本的权衡,发送方必须意识到,使用预测编码将数据压缩到非常低 的码率并不能有效防止丢包;
  • 交织,在传输之前对数据进行重新排序,以便在传输期间将原来相邻的数据按保证的距离分开,使得传输流中连续爆发丢包在恢复原始顺序时看起来是孤立的丢包;会给传输过程增加相当大的延迟,对于流媒体非常有用;

错误恢复

用于纠正传输错误的技术分为两类:前向纠错和重传。前向纠错依赖于发送方向媒体流中添加的额外数据,然后接收方可以使用这些数据以一定的概率纠正错误。另一方面,重传依赖于对特定包的附加副本的显式请求。

  • 奇偶校验 FEC:发 A, B, A XOR B,收到任意两个,即可得到 A 和 B;rfc2733,SSRC 相同,PT 不同;
  • 非均匀错误保护:不均匀分层保护(unequal layered protection, ULP)码;
  • 里德-所罗门码;
  • 音频冗余编码;
  • 信道编码,部分校验和,AMR 音频编码;
  • 重新选择参考帧;
  • 重传,增加了 RTP FB 类型的 RTCP 包,类型取值为 205,包含 NACK、PLI 等子类型,具体见 rfc4585;限制重传适用性的主要因素是反馈延迟;

如果使用纠错,RTP 实现可以显著增强对 IP 网络的不利影响的鲁棒性。但是,这些技术是有代价的:实现变得有些复杂,接收方需要更复杂的播放缓冲区算法,发送方需要逻辑来决定包含多少恢复数据以及何时丢弃这些数据。

拥塞控制

TCP:接收窗口 + 拥塞窗口,慢启动 + 拥塞避免,加性增加、乘性降低。

TCP 中存在一种系统的不公平性:因为该算法响应反馈,所以往返时间较短的连接可以更快地返回反馈,因此效果更好。因此,具有较长 RTT 的连接只能获得较低的平均份额。

类 TCP 码率控制:应用程序必须快速调整其发送码率,编码器有可能不支持,即便支持也会导致媒体质量的明显变化。

TCP 友好码率控制:对 TCP 平均吞吐量建模,测量几个关键指标后,估计 TCP 在类似网络路径上的平均吞吐量,以此作为参考。

分层编码:加入实验的困难在于试图实现共享学习。


欢迎大家加入 Hack WebRTC 星球,和我一起钻研 WebRTC。

piasy-knowladge-planet