阿里云视频云

阿里云视频云 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

个人动态

阿里云视频云 发布了文章 · 2月24日

你真的懂 MP4 格式吗?

MP4 文件格式又被称为 MPEG-4 Part 14,出自 MPEG-4 标准第 14 部分 。它是一种多媒体格式容器,广泛用于包装视频和音频数据流、海报、字幕和元数据等。(顺便一提,目前流行的视频编码格式 AVC/H264 定义在 MPEG-4 Part 10)。MP4 文件格式基于 Apple 公司的 QuickTime 格式,因此,QuickTime File Format Specification 也可以作为我们研究 MP4 的重要参考。

作者:张武星
审核:泰一

Overview

MP4 文件由 box 组成,每个 box 分为 Header 和 Data。其中 Header 部分包含了 box 的类型和大小,Data 包含了子 box 或者数据,box 可以嵌套子 box。

下图是一个典型 MP4 文件的基本结构:
MP4 文件结构

图中看到 MP4 文件有几个主要组成部分:

fytp

File Type Box,一般在文件的开始位置,描述的文件的版本、兼容协议等。
ftyp 内容

moov

Movie Box,包含本文件中所有媒体数据的宏观描述信息以及每路媒体轨道的具体信息。一般位于 ftyp 之后,也有的视频放在文件末尾。注意,当改变 moov 位置时,内部一些值需要重新计算。
moov 内容

mdat

Media Data Box,存放具体的媒体数据。
mdat 内容

Moov Insider

MP4 的媒体数据信息主要存放在 Moov Box 中,是我们需要分析的重点。moov 的主要组成部分如下:

mvhd

Movie Header Box,记录整个媒体文件的描述信息,如创建时间、修改时间、时间度量标尺、可播放时长等。

下图示例中,可以获取文件信息如时长为 3.637 秒。
mvhd 内容

udta

User Data Box,自定义数据。

track

Track Box,记录媒体流信息,文件中可以存在一个或多个 track,它们之间是相互独立的。每个 track 包含以下几个组成部分:

tkhd

Track Header Box,包含关于媒体流的头信息。

下图示例中,可以看到流信息如视频流宽度 720,长度 1280。
tkhd 内容

mdia

Media Box,这是一个包含 track 媒体数据信息的 container box。子 box 包括:

  • mdhd:Media Header Box,存放视频流创建时间,长度等信息。
  • hdlr:Handler Reference Box,媒体的播放过程信息。
  • minf:Media Information Box,解释 track 媒体数据的 handler-specific 信息。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。

stbl 包含了媒体流每一个 sample 在文件中的 offset,pts,duration 等信息。想要播放一个 MP4 文件,必须根据 stbl 正确找到每个 sample 并送给解码器。

mdia 展开如下图所示:
mdia 内容

Stbl Insider

Sample Table Box,上文提到 mdia 中最主要的部分是存放文件中每个 Sample 信息的 stbl。在解析 stbl 前,我们需要区分 Chunk 和 Sample 这两个概念。

在 MP4 文件中,Sample 是一个媒体流的基本单元,例如视频流的一个 Sample 代表实际的 nal 数据。Chunk 是数据存储的基本单位,它是一系列 Sample 数据的集合,一个 Chunk 中可以包含一个或多的 Sample。
一个 chunk 包含一个或多个 sample

stbl 用来描述每个 Sample 的信息,包含以下几个主要的子 box:

stsd

Sample Description Box,存放解码必须的描述信息。

下图示例中,对于 h264 的视频流,其具体类型为 avc1,extensions 中存放有 sps,pps 等解码必要信息。
stsd 内容

stts

Time-to-Sample Box,定义每个 Sample 时长。Time To Sample 的 table entry 布局如下:
stts table entry 布局

  • Sample count:sample 个数
  • Sample duration:sample 持续时间

持续时间相同的连续的 Sample 可以放到一个 entry 里面,以达到节省空间的目的。

下图示例中,第 1 个 Sample 时间为 33362 微秒,第 2-11 个 Sample 时间为 33363 微秒:
stts 内容

stss

Sync Sample Box,同步 Sample 表,存放关键帧列表,关键帧是为了支持随机访问。
stss 的 table entry 布局如下:
stss table entry 布局

下图示例中,该视频 track 只有一个关键帧即第 1 帧:
stss 内容

stsc

Sample-To-Chunk Box,Sample-Chunk 映射表。上文提到 MP4 通常把 Sample 封装到 Chunk 中,一个 Chunk 可能会包含一个或者几个 Sample。Sample-To-Chunk Atom 的 table entry 布局如下图所示:
stsc table entry 布局

  • First chunk:使用该表项的第一个 chunk 序号。
  • Samples per chunk:使用该表项的 chunk 中包含有几个 sample。
  • Sample description ID:使用该表项的 chunk 参考的 stsd 表项序号。

下图示例中,可以看到该视频 track 一共有两个 stsc 表项,Chunk 序列 1-108,每个 Chunk 包含一个 sample,Chunk 序列 109 开始,每个 Chunk 包含两个 Sample。
stsc 内容

stsz

Sample Size Box,指定了每个 Sample 的 size。Sample Size Atom 包含两 Sample 总数和一张包含了每个 Sample Size 的表。

Sample Size 表的 entry 布局如下图:
stsz table entry 布局

下图示例中,该视频流一共有 110 个 Sample,第 1 个 Sample 大小为 42072 字节,第 2 个 Sample 大小为 7354 个字节。
stsz 内容

stco

Chunk Offset Box,指定了每个 Chunk 在文件中的位置,这个表是确定每个 Sample 在文件中位置的关键。该表包含了 Chunk 个数和一个包含每个 Chunk 在文件中偏移位置的表。每个表项的内存布局如下:
stco table entry 布局

需要注意,这里 stco 只是指定的每个 Chunk 在文件中的偏移位置,并没有给出每个 Sample 在文件中的偏移。想要获得每个 Sample 的偏移位置,需要结合 Sample Size box 和 Sample-To-Chunk 计算后取得。

下图示例中,该视频流第 1 个 Chunk 在文件中的偏移为 4750,第 1 个 Chunk 在文件中的偏移为 47007。
stco 内容

如何计算 Sample 偏移位置

上文提到通过 stco 并不能直接获取某个 Sample 的偏移位置,下面举例说明如何获取某一个 pts 对应的 Sample 在文件中的位置。大体需要以下步骤:

  1. 将 pts 转换到媒体对应的时间坐标系。
  2. 根据 stts 计算某个 pts 对应的 Sample 序号。
  3. 根据 stsc 计算 Sample 序号存放在哪个 Chunk 中。
  4. 根据 stco 获取对应 Chunk 在文件中的偏移位置。
  5. 根据 stsz 获取 Sample 在 Chunk 内的偏移位置并加上第 4 步获取的偏移,计算出 Sample 在文件中的偏移。

例如,想要获取 3.64 秒视频 Sample 数据在文件中的位置:

  1. 根据 time scale 参数,将 3.64 秒转换为视频时间轴对应的 3640000。
  2. 遍历累加下表所示 stts 所有项目,计算得到 3640000 位于第 110 个 Sample。
type    stts
size    224
flags   0
version 0
sample_counts   1,10,1,1,11,1,1,2,1,25,1,1,1,17,1,10,1,1,1,7,1,1,1,1,10,1
sample_deltas   33362,33363,33362,33364,33363,33362,33364,33363,33362,33363,33362,33364,33362,33363,33362,33363,33362,33364,33362,33363,33362,33364,33363,33362,33363,0
  1. 查询下表所示 stsc 所有项目,计算得到第 110 个 Sample 位于第 109 个 Chunk,并且在该 Chunk 中位于第 2 个 Sample。
type    stsc
size    40
flags   0
version 0
first_chunk 1,109
samples_per_chunk   1,2
sample_description_index    1,1
  1. 查询下表所示 stco 所有项目,得到第 109 个 Chunk 在文件中偏移位置为 1710064。
Property name   Property value
type    stco
size    452
flags   0
version 0
chunk_offsets   4750,47007,54865,61967,75519,88424,105222,117892,133730,149529,165568,182034,194595,210776,225470,240756,255358,270711,285459,300135,315217,330899,347372,363196,376409,394509,407767,424615,438037,455603,469784,487287,505197,519638,536714,553893,567187,584744,599907,615298,630669,645918,662605,678655,693510,708980,724061,738946,754170,771520,787233,800847,816997,832490,847814,862559,877929,898379,911054,925810,943883,956497,974403,991527,1009478,1025198,1041806,1062609,1078401,1091360,1105142,1118748,1132815,1145281,1156966,1171871,1186742,1202760,1218235,1236688,1249330,1263163,1280880,1297903,1313162,1332885,1345726,1359017,1376283,1391401,1405512,1419550,1433644,1452103,1475241,1492689,1511291,1522606,1535368,1559413,1575331,1588853,1609829,1626623,1642798,1658640,1674160,1693972,1710064
  1. 查询下表所示 stsz 所有项目,得到第 109 个 Sample 的 size 为 14808。计算得到 3.64 秒视频 Sample 数据在文件中:

offset:1710064 + 14808 = 1724872
size:17930

type    stsz
size    460
flags   0
version 0
sample_sizes    42072,7354,6858,13110,12684,16416,12490,15497,15630,15865,16116,12387,15775,14519,14929,14433,15181,14390,14496,14717,15507,16101,15643,12843,17911,13070,16455,13221,17186,14002,17139,17737,14251,16708,16999,12911,17356,14801,15213,15016,15062,16505,15689,14657,15053,14907,14527,15048,17161,15308,13432,15777,15307,14971,14568,14987,20264,12494,14382,17873,12235,17718,16770,17766,15366,16420,20623,15403,12761,13394,13390,13714,12295,11505,14541,14689,15635,15291,18091,12458,13645,17346,16847,14902,19530,12446,13105,16872,14937,13944,13657,13908,18092,22959,17080,18421,11129,12400,23844,15564,13340,20603,16609,15984,15474,15339,19451,15719,14808,17930
sample_size 0
sample_count    110
  • 验证:用编辑器打开 MP4 文件,定位到文件偏移 offset = 1724872 的位置,前 4 字节值为 0x00004606。在 avcc 中一个 Sample 的前 4 个字节代表这个包的大小,转换为十进制是 17926,该值正好等于 size = 17930 减去表示长度的四个字节。

参考资料

在线 MP4 解析工具
QuickTime File Format Specification

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 1 收藏 0 评论 0

阿里云视频云 发布了文章 · 2月22日

流媒体传输协议之 RTMP

作者:逸殊
审核:泰一

简介

RTMP 在可靠流式传输(TCP)的基础上提供了双向的消息多路复用服务,在通讯双方之间传输与时间相关的并行流数据,如音频,视频和数据消息。协议实现方通常为不同的消息类型指定不同的优先级,这样在网络带宽受限时能改变底层传输顺序。

定义

  • 负载:包中所承载的数据。例如音频或视频数据。
  • 包:一个数据包由固定头部和所承载的数据组成。一些底层协议可能需要定义数据包的封装格式。
  • 端口:在一个计算机中用于区分不同目标的抽象定义。在 TCP/IP 协议中用一个小的正整数来表示端口。OSI 传输层的传输选择器就相当于端口。
  • 传输地址:标识一个传输终端的网络地址和端口的组合,例如 IP 地址和 TCP 端口的组合。
  • 消息流:允许消息传播的逻辑通道。
  • 消息流 ID:每个消息都会有一个对应的 ID,用于标识其所在的消息流。
  • 块:消息的一个片段。消息在传输之前会被分割成更小的片段,因为每一块都很小,以至于可以给不同的块指定各自的优先级,通过这种方式保证多个流中数据可以按照时间戳的顺序传输。
  • 块流:块向某一确定方向传播的逻辑通道。可以是客户端到服务端,也可以是服务端到客户端。
  • 块流 ID:每个块都会有一个对应的 ID,用于标识其所在的块流。
  • 复用:将独立的音频 / 视频数据整合为统一的音视频流,可以使多个音视频流同步传输。
  • 复用分离:复用的逆向过程。将合并的音视频数据分离为原始的音频和视频数据。
  • 远程过程调用:客户端或服务端调用另一端的功能。
  • 元数据:媒体数据的描述信息。
  • 应用实例:服务器上可以和 Client 建立连接的应用。
  • 动作消息格式:一个可用于序列化 ActionScript 对象图的紧凑的二进制格式。
  • 字节序:字节的顺序,即多字节类型的数据在内存中的存放顺序。TCP/IP 各层协议将字节序定义为大端字节序,因此 TCP/IP 协议中使用的字节序通常称之为网络字节序。
  • 大字节序:高位字节排放在内存的低地址,低位字节排放在内存的高地址。
  • 小字节序:低位字节排放在内存的低地址,高位字节排放在内存的高地址。

字节序,校准,时间格式

所有整数都是以网络字节序来表示的。除非另行说明,本文中的所有数字都是十进制数。
在没有特殊说明的情况下,RTMP 中的数据都是字节对齐的。如果有填充的话,填充字节应该用 0。
RTMP 中的时间戳是用一个整数来表示的,代表相对于一个起始时间的毫秒数。通常每个流的时间戳都从 0 开始,但这不是必须的,只要通讯双方使用统一的起始时间就可以了。要注意的是,跨流的时间同步(不同主机之间)需要额外的机制来实现。
由于时间戳的长度只有 32 位,所以只能在 50 天内循环(49 天 17 小时 2 分钟 47.296 秒)。而流是可以不断运行的,可能多年才会结束。所以 RTMP 应用在处理时间戳是应该使用连续的数字算法,并且应该支持回环处理。例如:一个应用可以假设所有相邻的时间戳间隔不超过 2^31-1 毫秒,在此基础上,10000 在 4000000000 之后,3000000000 在 4000000000 之前。
时间戳增量也是以毫秒为单位的无符号整数。时间戳增量可能会是 24 位长度也可能是 32 位长度。

RTMP 块流

块流为上层流媒体协议提供复用和分包的功能。RTMP 块流是为配合 RTMP 协议而设计,但它可以使用在任何发送消息流的协议中。每个消息包含时间戳和负载类型信息。RTMP 块流和 RTMP 协议组合可以适用于多种音视频应用,从一对一或一对多直播到视频会议都能很好的满足。
当使用可靠传输协议(如 TCP)时,RTMP 块流为所有消息提供了可靠的跨流端对端按时间戳顺序发送的机制。RTMP 块流不提供优先级控制,但是可以由上层协议提供这样的优先级。例如:当某个客户端网络比较慢时,可能会选择抛弃一些视频消息来保证声音消息能够及时接收。
RTMP 块流除自身内置的协议控制消息外,还为上层协议提供了用户控制消息的机制。

消息格式

消息格式由上层协议定义,消息可以被分成多个块以支持多路复用。消息应该包含分块功能所需的所有字段,具体内容如下:

  • 时间戳(4-byte):消息的时间戳。
  • 长度(3-byte):消息有效负载的长度,如果消息头不能被省略,则消息头的长度也应该包含在长度中。
  • 类型 ID(1-byte):消息类型 ID。一些类型 ID 是为协议控制消息保留的,这些消息所表示的信息同时供 RTMP 块流协议和上层协议使用。所有其他类型 ID 都用于上层协议,RTMP 块流对这些 ID 做不透明处理。实际上,RTMP 块流不需要用这些值来区分类型,所有消息都可以是相同的类型,应用也可以用本字段来区分同步轨道而不是区分类型。
  • 消息流 ID(4-byte):消息流 ID 可以是任意值。被复合到同一个块流的消息流,依据消息流 ID 进行分离。另外,就相关的块流而言,这个值是不透明的。这个字段使用小字节序。

握手

RTMP 连接以握手开始,它的握手过程可能和其他协议不同,这里的握手由 3 个固定大小的块组成,而不是可变大小的块加上固定大小的头。

握手流程

握手由客户端发送 C0 和 C1 块开始。
客户端必须等接收到 S1 之后才可以发送 C2。客户端必须等接收到 S2 之后才可以发送其他数据。
服务器必须等接收到 C0 之后才可以发送 S0 和 S1,也可能接收到 C1 之后发送。服务器必须等接收到 C1 之后才可以发送 S2。服务器必须等接收到 C2 之后才可以发送其他数据。

C0 和 S0 格式

C0 和 S0 是单独的一个字节,可以当做一个 8bit 的整数字段来对待。
1.png

以下是 C0 和 S0 包的字段解释:

  • 版本号(8 位): 在 C0 包中,该字段表示客户端请求的 RTMP 版本。在 S0 中,该字段表示服务器选择的 RTMP 版本。本规范所定义的版本是 3。可选值中,0-2 是早期版本所用的,已被丢弃,4-31 保留在未来使用,32-255 不允许使用(为了区分其他以某一可见字符开始的文本协议)。如果服务器不能识别客户端请求的版本,应该返回 3,客户端可能选择降级到版本 3,也可能放弃握手。

C1 和 S1 格式

C1 和 S1 包固定为 1536 字节,包含以下字段:
2.png

  • 时间戳(4 字节):该字段承载一个时间戳,该时间戳应该作为发送端点所有后续块的时间戳起始时间。可以是 0,也可以是其他的任意值。为了同步多个块流,端点可能会发送其他块流的当前时间戳。
  • 零值(4 字节):该字段所有值都必须为 0。
  • 随机数据(1528 字节):该字段可以是任意值。通过这个字段来区分自己和连接的另一方,所以此数据应该有充分的随机性,但是没必要使用加密安全的随机值或动态值。

C2 和 S2 格式

C2 和 S2 包的长度也为 1536 字节,基本上是 S1 和 C1 的回传,包含以下字段:
3.png

  • 时间戳(4 字节):该字段必须包含对端发来的时间戳(对 C2 来说是 S1, 对 S2 来说是 C1)。
  • 时间 2(4 字节):该字段必须包含先前发送的并被对端读取的包的时间戳。(对 C2 来说是 C1,对 S2 来说是 S1)。
  • 随机数据回显(1528 字节):该字段必须包含对端发送过来的随机数据字段值(对 C2 来说是 S1, 对 S2 来说是 C1)。任何一端都可以用时间戳和时间戳 2 两个字段值和当前时间戳来快速的估算带宽和延迟,但这样可能并不实用。

握手流程示意图

4.png

上图提到的状态的解释如下:

  • Uninitialized:未初始化状态。在该阶段发送协议版本。客户端在 C0 包中发送 RTMP 协议版本,如果服务器支持此版本,服务器将在响应中发送 S0 和 S1。如果不支持,服务器采用适当的行为作为响应,在 RTMP 规范中是终止连接。
  • Version Send:版本已发送状态。在未初始化状态之后客户端和服务端都进入版本已发送状态。客户端等待接收 S1 包,服务端等待接收 C1 包。收到所等待的包后,客户端发送 C2 包,服务端发送 S2 包。之后状态进入发送确认状态。
  • Ack Send:客户端和服务端等待接收 S2 和 C2 包,收到后进入握手完成状态。
  • Handshake Done:握手完成, 客户端和服务端开始交换消息。

分块

握手完成后,一个或多个块流可能会复用同一个连接,每个块流承载来自同一个消息流的同一类消息。每个块都有一个唯一的块流 ID,这些块通过网络进行传输。在传输过程中,必须一个块发送完毕之后再发送下一个块。在接收端,将所有块根据块中的块流 ID 组装成消息。
分块将上层协议的大消息分割成小的消息,保证大的低优先级消息(比如视频)不阻塞小的高优先级消息(比如音频或控制消息)。
分块还能降低消息发送的开销,它在块头中包含了压缩的原本需要在消息中所包含的信息。
块大小是可配置的,这个可以通过一个设置块大小控制消息进行设定修改。越大的块 CPU 使用率越低,但是在低带宽的情况下,大的写入会阻塞其他内容的写入。而小一些的块不适合高比特率的流。

块格式

每个块由块头和数据组成,块头包含 3 部分:基本头、消息头和扩展时间戳。
5.png

  • 基本头 (1-3 字节):块流 ID 和块类型,块类型决定了之后消息头的编码格式。基本头的长度取决于块流 ID,当块流 ID 越大时所需要的字节数越多。
  • 消息头 (0,3,7 或 11 字节):所发送消息的描述信息。该部分的长度取决于基本头中指定的块类型。
  • 扩展时间戳 (0 或 4 字节):该部分只有在某些特殊情况下才会使用,是否使用取决于时间戳或时间戳增量是否超出了块消息头中相应字段的描述范围。
  • 块数据 (变长):块承载的有效数据,最大长度为配置的块大小。
基本头

基本头包含块流 ID 和块类型(在下图中用 fmt 字段表示),块类型决定了消息头的编码格式,基本头长度可能是 1,2 或 3 字节,这取决于块流 ID 的长度。
协议实现方应该用能够用最短表示法来表示块流 ID。
RTMP 最多支持 65597 个流,ID 在 3-65599 范围内,0,1,2 为保留值。如果 2~7 位代表的值为 0 表示块基本头占 2 个字节,并且块流 ID 范围在 64-319 之间(第二个字节 + 64),如果 2~7 位代表的值为 1 表示块基本头占 3 个字节,并且 ID 范围在 64-65599 之间(第三个字节 * 256 + 第二个字节 + 64),当 ID 在 3-63 之间时直接使用 2~7 位的值来表示流 ID。
2-63 范围内的块流 ID 用 1 个字节来编码:
6.png

64-319 范围内的块流 ID 用 2 个字节来编码,块流 ID 为计算所得,公式为:第二个字节值 + 64:
7.png

64-65599 范围内的块流 ID 用 3 个字节来编码,块流 ID 为计算所得,公式为:第三个字节值 * 255 + 第二个字节值 + 64
8.png

上述图中各个部分的含义如下:

  • cs id (6 位):该字段表示完整的块流 ID,取值在 2-63 之间。0,1 两个值是保留值,用来表示基本头是 2 字节还是 3 字节长度。
  • fmt:该字段表明了消息头使用的格式。
  • cs id - 64 (8 位或 16 位):该字段表示块流 ID,取值在 64-63399 之间。

64-319 范围内的块流 ID 可以用 2 字节来表示,也可以用 3 字节表示。

消息头

消息头共有 4 种不同的格式,根据基本头中的 "fmt" 字段值来确定。协议实现方应该用最紧凑的格式来表示块消息头。

类型 0

0 类型的块消息头占 11 个字节长度,该类型必须用在一个块流的开头,和每当块流时间戳回退的时候(例如视频回退的操作)。
9.png

  • timestamp (3 字节):对于 0 类型的消息块,消息的绝对时间戳在这里发送。 如果时间戳大于或等于 16777215 (0xFFFFFF),改字段值必须为 16777215,并且必须设置扩展时间戳来共同编码 32 位的时间戳。否则该字段就是完整的时间戳。
  • message length (3 字节): 消息长度,类型 0 和类型 1 的块包含此字段,表示消息的长度。要注意的是,通常消息长度与块长度并不相同。块长度除了最后一个块之外,都与块最大长度相同。
  • message type id (3 字节): 消息类型 id,类型 0 和类型 1 的块包含此字段,表示消息的类型。
  • message stream id (4 字节): 消息流 ID,类型 0 的块包含此字段,表示消息流 ID。消息流 ID 以小字节序存储。通常,相同块流中的消息属于用一个消息流。虽然,不同的消息流复用相同的块流会导致消息头无法有效压缩,但是当一个消息流已关闭,准备打开另外一个消息流时,就可以通过发送一个新的 0 类型块来实现复用。
类型 1

1 类型的块消息头占用 7 个字节长度,不包含消息流 ID,该块沿用上一个消息的消息流 ID。对于传输大小可变消息的流(如多数视频格式),在发送第一个消息之后的每个消息都应该使用该类型格式。
10.png

  • timestamp delta (3 字节): 时间戳增量。类型 1 和类型 2 的块包含此字段,表示前一个块的 timestamp 字段和当前块 timestamp 间的差值。 如果时间戳增量大于或等于 16777215 (0xFFFFFF),该字段必须为 16777215,并且必须设置扩展时间戳,来共同表示 32 位的时间戳增量,否则该字段值就是实际的时间戳增量。
类型 2

2 类型的块消息头占用 3 个字节长度,不包含消息流 ID 和消息长度,沿用上一个块的消息流 ID 和消息长度。对于传输固定大小消息的流(如音频和数据格式),在发送第一个消息之后的每一个消息都应该使用该类型格式。
12.png

类型 3

3 类型的块没有消息头,消息流 ID、消息长度和时间戳增量,该类型的块使用和上一个块相同的头数据。当一个消息被分割成块时,除了第一个块,其他块都应该使用该类型。由相同大小、消息流 ID 和时间间隔的消息组成的流,在类型 2 的块之后所有块都应该使用该类型格式。如果第一个消息和第二消息之间的时间增量与第一个消息的时间戳相同,则 0 类型的块之后可以马上发送 3 类型的块,而不必使用 2 类型的块来注册时间增量。如果类型 3 的块跟在类型 0 的块后面,那么 3 类型块的时间戳增量与 0 类型块的时间戳相同。

扩展时间戳

扩展时间戳用来辅助编码超过 16777215 (0xFFFFFF) 的时间戳或时间戳增量,也就说消息头无法用 24 位数字来表示时间戳或时间戳增量时,既 0 类型块的时间戳字段或 1,2 类型的时间戳增量字段值为 16777215 (0xFFFFFF)。当最近的属于相同块流 ID 的 0 类型块、1 类型块或 2 类型块有此字段时有此字段时,3 类型块也应该有此字段。

示例

示例 1

这是一个简单的音频流消息,这是示例示范了信息冗余。
13.png

下图展示该消息流以块流形式发送。从 3 类型块开始了数据传输优化,之后的块只附加了一个字节。
14.png

示例 2

该示例展示了一个超过 128 字节长度的消息,消息被分割成了数个块。
15.png

下图是被分割成的块:
16.png

第一个块的头信息指明了消息总大小为 307 字节。
注意这两个示例,3 类型块可以在两种情况下使用。第一种情况是消息拆分成多个块,另一种情况是新消息复用上一个消息的所有头部内容。

协议控制消息

RTMP 块流用消息类型 1,2,3,5 和 6 来作为协议控制消息,这些消息包含 RTMP 块流协议所需要的信息。
这些协议控制消息必须用 0 作为消息流 ID (控制流 ID),并在 ID 为 2 的块流中发送。协议控制消息收到后立即生效,它们的时间戳信息是被忽略的。

设置块大小

协议控制消息类型 1:设置块大小,用于通知另一端新的最大块大小。
最大块大小默认为 128 字节,客户端或服务端可以修改此值,并用该消息通知另一端。例如,假设一个客户端想要发送 131 字节的音频数据,而最大块大小为 128。在这种情况下,客户端可以向服务端发送该消息,通知它最大块大小被设置为了 131 字节。这样客户端只用一个块就可以发送这些音频数据。
最大块大小不能小于 1 字节,通常应该不低于 128 字节。每个方向上的最大块大小是独立的。
17.png

  • 0 (1 位): 该位必须为 0.
  • chunk size (31 位): 该字段以字节形式保存新的最大块大小,该值将用于后续的所有块的发送,直到收到新的通知。该值可取值范围为 1-2147483647 (0x7FFFFFFF),但是所有大于 1677215 (0xFFFFFF) 的值都是视作是 16777215,因为任何块不可能比消息大,而消息长度不能大于 16777215 字节。

终止消息

协议控制消息类型 2:终止消息,通知正在等待消息后续块的另一端,可以丢弃指定块流接收到的数据,块流 ID 为该消息的载荷。应用可能在关闭的时候发送该消息,用来表明后面的消息没有必要继续处理了。
18.png

  • chunk stream id (32 字节): 指定消息的块流 ID。

确认消息

客户端或服务器在收到数据总长和窗口大小相等时,通过它回复确认消息。在连接建立完成后,消息的发送方会通知接收方一个窗口的大小(指定一个长度),如果接收方收到指定长度的数据后没有发送回复消息,发送方就不会再发送任何内容了。
19.png

  • sequence number (32 字节): 到当前时刻为止接收到的字节总数。

确认窗口大小

客户端或服务端发送该消息来通知对端发送确认消息所使用的视窗大小,并等待接收端发送确认消息。接收端在接收到视窗大小后必须发送确认消息。
20.png

设置对方传输带宽

客户端或服务端发送该消息来限制对端的输出带宽。接收端收到消息后,可以直接使用消息中指定的窗口大小,而不需要等待收到确认消息之后。如果视窗大小与上一个视窗大小不同,则该消息的接收端应该向该消息的发送端发送新的窗口大小消息。这个消息和上一个消息都是调整窗口大小的,不同的地方是,这个消息是接收者请求发送者,让它调整窗口大小,而上一个消息是发送者主动设置了窗口大小,通知数据接收者。
21.png

Limit Type(限制类型)有以下值:

  • 0 - Hard: 应该将输出带宽限制为指定视窗大小。
  • 1 - Soft: 应该将输出带宽限制为指定视窗大小和当前视窗大小中较小的值。
  • 2 - Dynamic: 如果上一个消息的限制类型为 Hard,则该消息同样为 Hard,否则抛弃该消息。

RTMP 消息格式

虽然 RTMP 被设计成使用 RTMP 块流传输,但是它也可以使用其他传输协议来发送消息,在这种情况下 RTMP 消息的格式如下所示。值得一提的是,RTMP 块流协议和 RTMP 协议配合时,非常适合音视频应用,包括单播、一对多实时直播、视频点播和视频会议等。

格式

服务端和客户端通过在网络上发送 RTMP 消息实现之间的交互,消息包括音频、视频、数据等。
RTMP 消息包含两部分,消息头和有效负载。

RTMP 消息头

消息头包含以下信息:

  • Message Type: 消息类型,占用 1 个字节。1-6 的消息类型 ID 是为协议控制消息保留的。
  • Length: 有效负载的字节数,占用 3 个字节。该字段是用大端序表示的。
  • Timestamp: 时间戳,占用 4 个字节,用大端序表示。
  • Message Stream Id: 消息流 ID,标识消息所使用的流,用大端序表示。

22.png

消息有效负载

消息的另一部分就是有效负载,也是消息包含的实际数据,可以是音频样本或者压缩的视频数据。

用户控制消息

RTMP 协议将消息类型 4 作为用户控制消息 ID,这些消息包含 RTMP 流所需的必要信息。消息类型 1,2,3,5 和 6 由 RTMP 块流协议使用。
用户控制消息应该使用 ID 为 0 的消息流(控制流),并且通过 RTMP 块流传输时使用 ID 为 2 的块流。用户控制消息收到后立即生效,它们的时间戳信息会被忽略。
客户端或服务端通过发送该消息告知对方用户控制事件。该消息携带事件类型和事件数据两部分。
23.png

开头的 2 个字节用于指定事件类型,紧跟着是事件数据。事件数据字段长度可变,但是如果用 RTMP 块流传输,则消息总长度不能超过最大块大小,以使消息可以使用一个单独的块进行传输。

RTMP 指令消息

各种类型的消息在客户端和服务端之间进行交换,包括用于发送音频数据的音频消息,用于发送视频数据的视频消息,用于发送任意用户数据的数据消息,共享对象消息和指令消息等。共享对象消息的主要用途是管理客户端和相同服务器的共享数据。指令消息发送的是客户端与服务端之间的 AMF 编码指令,客户端或服务端也可以通过指令消息来实现远程过程调用(RPC)。

消息类型

客户端和服务端通过在网络上发送消息来实现交互,消息可以是任意类型,包括音频消息、视频消息、指令消息、共享对象消息、数据消息和用户控制消息等。

指令消息

指令消息在客户端和服务端之间传递 AMF 编码的指令,消息类型 20 代表 AMF0 编码,消息类型 17 代表 AMF3 编码。发送这些消息来完成连接、创建流、发布、播放、暂停等操作。像状态、结果这样的指令消息,用于通知发送方请求的指令状态。一条指令消息由指令名、事务 ID 和包含相关参数的指令对象组成。客户端或服务端还可以通过指令消息来实现远程过程调用 (RPC)。

数据消息

客户端或服务端通过该消息来发送元数据或其他用户数据。元数据包括数据 (音频、视频) 的创建时间、时长、主题等详细信息。消息类型 18 代表 AMF0 编码,消息类型 15 代表 AMF3 编码。

共享对象消息

共享对象是在多个客户端之间同步的 Flash 对象 (键值对集合)。消息类型 19 代表 AMF0 编码,消息类型 16 代表 AMF3 编码。每个消息都可以包含多个事件。
24.png

支持以下事件类型:

  • 创建(1):客户端向服务端发送,请求创建指定名称的共享对象。
  • 释放(2):客户端通知服务端,共享对象已在本地删除。
  • 请求更新(3):客户端请求修改共享对象的属性值。
  • 更新(4):通知服务端向除自己外的其他客户端发送共享数据消息,通知它们有属性的值发生了变化。
  • 成功(5):“请求更新” 事件被接受后,服务端向发送请求的客户端回复此事件。
  • 发送消息(6):客户端向服务端发送此事件,来广播一个消息。服务端收到此事件后向所有客户端广播一条消息,包括请求方客户端。
  • 状态(7):服务端发送此事件来通知客户端错误信息。
  • 清除(8):服务端向客户端发送此事件,通知客户端清除一个共享对象。服务端在回复客户端的 “创建” 事件时也会发送此事件。
  • 移除(9):服务端发送此事件,使客户端删除一个插槽。
  • 请求移除(10):客户端删除一个插槽时发送此事件。
  • 创建成功(11):当连接成功时服务端向客户端发送此事件。

音频消息

客户端或服务端通过发送此消息来发送音频数据给对方,消息类型 8 是为音频消息预留的。

视频消息

客户端或服务端通过发送此消息来发送视频数据给对方,消息类型 9 是为视频消息预留的。

组合消息

组合消息,是一个消息包含多个子 RTMP 消息,子消息符合 RTMP 消息格式。消息类型 22 用于组合消息。
25.png

组合消息的消息流 ID 会覆盖其中子消息的消息流 ID。
组合消息的时间戳和其中第一个子消息的时间戳的差值,是用来将所有子消息的时间戳重整为流时间的位移量。位移量会加到每一个子消息的时间戳上来换算出正常的流时间。第一个子消息的时间戳应该与组合消息的时间戳相同,所以位移量应该为 0。
Back Pointer (反向指针) 包含前一个消息的长度(包括消息头),这样符合 flv 文件格式,可用于进行后退操作。
使用组合消息有以下好处:

  • 块流协议中,一个块最多只能发送一个消息,这样就使用组合消息,加大块大小,从而降低发送的块数量。
  • 子消息在内存中连续存放,这样系统调用网络发送数据的性能更高。

用户控制消息事件

客户端或服务器通过该消息发送用户控制事件。
26.png

用户控制消息支持以下事件:

  • 流开始(0):服务端发送该事件,用来通知客户端一个流已经可以用来通讯了。默认情况下,该事件是在收到客户端连接指令并成功处理后发送的第一个事件。事件的数据使用 4 个字节来表示可用的流的 ID。
  • 流结束(1):服务端发送该事件,用来通知客户端其在流中请求的数据已经结束了。如果没有额外的指令,将不会再发送任何数据,而客户端会丢弃之后从该流接收到的消息。事件数据使用 4 个字节来表示回放完成的流的 ID。
  • 流枯竭(2):服务端发送该事件,用来通知客户端流中已经没有更多的数据了。如果服务端在一定时间后没有探测到更多数据,它就可以通知所有订阅该流的客户端,流已经枯竭。事件数据用 4 个字节来表示枯竭的流的 ID。
  • 设置缓冲区大小(3):客户端发送该事件,用来告知服务端用来缓存流中数据的缓冲区大小 (单位毫秒)。该事件在服务端开始处理流数据之前发送。事件数据中,前 4 个字节用来表示流 ID,之后的 4 个字节用来表示缓冲区大小(单位毫秒)。
  • 流已录制(4):服务端发送该事件,用来通知客户端指定流是一个录制流。事件数据用 4 个字节表示录制流的 ID。
  • ping 请求(5):服务端发送该事件,用来探测客户端是否处于可达状态。事件数据是一个 4 字节的时间戳,表示服务端分发该事件时的服务器本地时间。客户端收到后用 ping 响应回复服务端。
  • ping 响应(6):客户端用该事件回复服务端的 ping 请求,事件数据为收到的 ping 请求中携带的 4 字节的时间戳。

指令类型

客户端和服务器交换 AMF 编码的指令。发送端发送一条指令消息,其中包含了指令名称、处理 ID、以及含有相关参数的指令对象。例如,连接指令消息包含了’app' 参数,以告知服务器客户端希望连接的目标程序。接收端处理这条指令并回复含有同样处理 ID 的响应。回复的字符串可能为_result、_error 或方法名。如 verifyClient 或 contactExternalServer.
_result 或_error 的指令字符代表一条响应,处理 ID 则表明回复是针对哪条指令的,这在 IMAP 或其他协议中是完全相同的。指令字符串中的方法名表明发送端希望运行接收端上的一个方法。
指令消息可分为如下两类:

  • NetConnection:一个服务器和客户端之间连接的高层表现对象。
  • NetStream:一个音频流、视频流及其他相关数据传输流,我们会发送如播放、暂停等指令来控制数据流动。

NetConnection 指令

NetConnection 管理着一个客户端程序和服务器之间的双向连接,除此之外,它还提供了对异步远程方法调用的支持。
下列指令可通过 NetConnection 进行发送:

  • Connect
  • Call
  • Close
  • CreateStream
Connect

客户端发送 connect 指令至服务器端以请求连接至某一服务器程序实例。
指令结构如下:
27.png

Connect 指令中会用到的键值对:
28.png

音频编码:
29.png

视频编码:
30.png

视频功能:
31.png

对象编码:
32.png

由服务器发送至客户端的指令结构如下:
33.png

指令执行流程:
34.png

指令执行的消息流如下:

  • 客户端发送 connect 指令至服务器以请求连接至服务器端程序实例。
  • 在收到连接指令后,服务器端发送协议消息 'Window Acknowledgement Size' 给客户端。同时,服务器端还会连接 connect 指令中提到的应用。
  • 服务器端发送协议消息‘Set Peer Bandwidth’至客户端。
  • 客户端成功处理‘Set Peer Bandwidth’后发送协议消息‘Window Acknowledgement Size' 给服务器端。
  • 服务器端发送用户控制消息(StreamBegin)协议消息给客户端。
  • 服务器端发送指令消息以通知客户端连接状态(success/fail)。该指令中含有处理 ID (与 1 中收到相同),该消息同时还制定了部分属性,如 Flash Media Server 版本(string)。除此之外,它还指定了连接响应相关的信息如 level (string),code (string),description (string),object-encoding (number) 等。
Call

NetConnection 对象的 call 方法用于远程调用接收端上的程序。需要远程调用的程序名称通过一个参数传递给 call 指令。
发送指令结构如下:
35.png

响应指令结构如下:
36.png

CreateStream

客户端发送该指令至服务器端以创建一条用于传递消息的逻辑通道,从而可以利用已创建的流通道发布音频、视频和元数据。
NetConnection 是默认的通讯通道,流 ID 为 0。协议和一些指令消息,包括 createStream,使用默认通讯通道。
客户端发出的指令结构如下:
37.png

服务器发出的指令结构如下:
38.png

NetStream 指令

基于 NetConnection 的客户端至服务器间连接,NetStream 定义了一条可以传递音频流、视频流以及消息流的通道。NetConnection 对象支持多个 NetStreams 以传输多个数据流。
客户端可在 NetStream 中发送下列指令至服务器:

  • Play
  • Play2
  • DeleteStream
  • CloseStream
  • ReceiveAudio
  • ReceiveVideo
  • Publish
  • Seek
  • Pause

服务器端通过 “onStatus" 将 NetStream 的状态更新至客户端:
39.png

Play

客户端发送该指令值以播放一个流。多次调用该指令也可创建一个播放清单。
如果你希望创建一个在不同 live 或 recorded 流间切换的动态播放清单,需要多次调用 play 并传递 false 以避免每次 reset。相反地,如果你希望立即播放某一指定流,传递 true 以清除等待播放队列中的所有其他流。
客户端发送的指令结构如下:
40.png

流程图如下:
41.png

指令执行期间的消息流如下:

  • 客户端在接收到来自服务器的 createStream 指令的成功结果后发送 play 指令。
  • 在接收到 play 指令后,服务器发送协议数据来设置块大小。
  • 服务器发送一些另外一个协议数据 (用户控制),在这个消息里包含事件 “StreamIsRecord” 和流 ID。这个消息的前 2 个字节是事件类型随后的 4 字节是流 ID。
  • 服务器向客户端发送另外一个协议消息 (用户控制),这个消息指示了 “StreamBegin” 事件,表示流开始了。
  • 如果客户端向服务器发送的 play 指令成功执行了,服务器会发送 onStatus 指令消息包含 NetStream.Play.Start 或 NetStream.Play.Reset。仅当客户端发送的 play 指令中的设置了 reset 标志 NetStream.Play.Reset 才会被发送。如果播放的流不存在,服务器会在发送 onStatus 消息中包含 NetStream.Play.StreamNotFound。随后,服务器就发送客户端播放的音频和视频数据。
Play2

不同于 play 指令,play2 可以切换码率而不改变播放内容的时间轴。服务器为客户端可以在 play2 中请求的所有支持的码率维护多个字段。
客户端发送的指令结构如下:
42.png

该指令的消息流程如下图:
43.png

DeleteStream

当 NetStream 对象将要被销毁时,它发送该 deleteStream 指令。
客户端发送的指令结构如下:
44.png

服务器不需要发送任何应答。

ReceiveAudio

NetStream 发送 ReceiveAudio 消息通知服务器是否发送或不发送音频到客户端。
客户端发送的指令结构如下:
45.png

如果 receiveAudio 指令发送带有 flase 的 bool flag,服务器不发送任何响应。如果这个标志被设置为 true,服务器应答 NetStream.Seek.Notify 和 NetStream.Play.Start 的状态消息。

ReceiveVideo

NetStream 发送 ReceiveVideo 消息通知服务器是否发送或不发送视频到客户端。
客户端发送的指令结构如下:
46.png

如果 receiveVideo 指令发送带有 flase 的 bool flag,服务器不发送任何响应。如果这个标志被设置为 true,服务器应答 NetStream.Seek.Notify 和 NetStream.Play.Start 的状态消息。

Publish

客户端发送 publish 指令将已命名的流发布到服务器上。使用这个名称,任何客户端都可以播放此流,并接收已发布的音频、视频和数据消息。
客户端发送的指令结构如下:
47.png

服务器应答 onStatus 指令,以标记发布的开始。

Seek

客户端发送 seek 指令以定位媒体文件内或者播放列表的某个位置(以毫秒为单位)。
客户端发送的指令结构如下:
48.png

当定位成功,服务器发送 NetStream.Seek.Notify 的状态消息。失败的时候,它返回一个_error 的消息。

Pause

客户端发送 pause 指令以告诉服务器暂停或者开始播放。
客户端发送的指令结构如下:
49.png

当流被暂停,服务器发送一个 NetStream.Pause.Notify 的状态消息。当一个流变成未暂停状态,NetStream.Unpause.Notify 被发送。失败的时候,它返回一个_error 的消息。

消息交换例子

这里是一些样例,以解释使用 RTMP 的消息交换。

发布视频

这个例子说明了一个发布者如何发布一个流并将视频流推到服务器上。其他客户端可以订阅这个已发布的流,并播放视频。
50.png

广播一个共享对象消息

这个例子说明了在创建和更改共享对象时所交换的消息。它也说明了共享对象消息广播的过程。
51.png

发布媒体流元数据

这个例子描述了发布元数据的消息交换。
52.png

参考内容

[1] RTMP 规范
[2] RTMP 协议规范翻译工作
[3] RTMP 协议规范 1.0 中文版

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 4 收藏 2 评论 0

阿里云视频云 发布了文章 · 2月19日

我在春晚现场护航直播

撰文| 丹如
编辑| 猛哥
制作| 杭派工程师

每当微信群内聊到春晚,只要有一个人发出 “宫廷玉液酒,一百八一杯”,接下来你就会发现即便是平时最爱潜水的人也忍不住在后面接上 “这酒怎么样?”“这酒真是美!” 一直到整个群都热闹起来,开始一场春晚经典小品串演。

春晚对于中国人的意义早已不是一档歌舞小品串烧的晚会,它是除夕除了包饺子外最重要的事,即便放着是不看,它也除夕打麻将专属 BGM。

有人说在互联网的冲击下,春晚的影响力越来越小。可实际上春晚的观众每年都在递增,2020 年海内外观众总规模达 12.32 亿人,与其说互联网冲击了春晚,不如说互联网使得春晚的观看形式更加多样,去年新媒体端直播用户规模达到了 6.06 亿人。

杭派今天推送的故事就是关于除夕在央视大楼为春晚网络直播进行技术保障的阿里云技术服务工程师的自述:

董琪 阿里云技术服务工程师

关于春晚,我印象最深刻的节目莫过于 2001 年的《卖拐》,那年我还在上学,赵本山一出场,我就要赶紧端着碗坐得离电视机坐得更近一些,山东人喜欢一大家子聚在一起,但我们家的电视只有 32 寸,要是坐得远,错过了最精彩的段子,感觉一年都白过了。

谁也没想到过了 20 年,我能够成为央视春晚进行技术护航保障。消息传回老家,亲戚们都沸腾了。

春晚当天,我确实是在央视大楼,不过不是在总部大楼,而是在距离大裤衩 15 公里的央视旧址 “望海楼” 工作。春晚的网络直播由央视网负责,而阿里云则是负责为春晚网络直播进行技术保障的六家供应商之一,当观众们在电视或者电脑屏幕后收看春晚时,我们都在为直播内容顺利抵达千家万户而奋战。

你问我的工作到底是做什么?我来解释一下。

假设央视大楼是一个发射春晚直播内容的信号塔,全国即便是最偏僻的山村、边哨以及海外华人想要看到春晚的网络直播,一方面人们需要网络信号和客户端,另一方面春晚要准备充分的带宽资源,也就是传说中的 CDN(内容网络分发)。

2020 年,全球观看春晚网络直播的人有 6.06 亿人,直播规模堪称全球最大。春晚的直播要求比一般的晚会更为严格,比如卡顿率小于 1% 等、低带宽还要高清晰。面对春晚如此大的高并发、高突发的情况下,无论是对于央视网,还是阿里云,实时调动全球的资源进行匹配都是一次年底终极大考。

2017 年,阿里云开始为春晚网络直播进行技术护航,第一年我没有参与,但 2018 年至今的三年,我都在现场度过。第一次去做春晚护航时,女儿问我 “能不能给我一份 TFBOYS 的签名”,TF 我知道是传输功能的意思,但 TFBOYS 是谁我真的不知道。

能为春晚这样一档意义重大的联欢晚会进行技术保障,无论是对阿里云还是其他技术供应商而言,都是既荣幸又紧张的工作。春节期间,类似于抖音、快手等各种平台也都会进行大型的发红包活动,带宽资源非常紧张,所以提前两个月,我和我的同事们就要在公司内部为春晚护航申请更多的资源来保障春晚直播。

春晚前一周,差不多小年前后,我们就会到现场进行预演,央视网专门给我们在望海楼负一层开辟出一间 “春晚护航保障组” 的会议室,会议室正前方是一张用来观看春晚直播的大屏,旁边还有一张小屏幕实时呈现春晚直播流量的变化。

去年预演从晚上十点到晚上十二点开始,央视网的总指挥一声令下,模拟数千万观众观看直播的流量瞬间开始冲击我们提前预备好的带宽,这时阿里云和其他的技术供应商都会对各自的 CDN 进行监控,总指挥每下达一个指令,我们都会按照相关的预案进行操作。

预演虽然不如晚会当天那么紧张,但大家都不想在其他厂商面前掉链子,所以预演之前阿里云就会把所有可能出现的情况都进行排查。也因此,连续几年,阿里云的故障率都很低,这让我在现场的腰板挺得更直了。

除了故障率,我们也非常关注直播过程中的稳定性。阿里云为春晚提供的技术保障来自于我们自己的视频云,针对端到端全链路的各种指标,视频云建立了一整套包括监控告警、降级措施、应急预案演习的完整方案。

如果一旦春晚直播期间在主链路环节出了任何抖动、卡顿或者故障等问题,我们的主备双重机制也会立即启动,让屏幕后的全国人们感受不到任何抖动。

春晚当天,下午一点我们就进入了紧张的筹备,三四点钟最终的节目单就会给到我们,保障组的负责人会将他们预估的哪些节目会出现流量高峰,提前告知大家,就这样,我终于知道了很多当红的明星到底是谁,因为只要他们出现必定会带来一波又一波的流量高峰。

但我不能把节目单透露给任何人,即便是我女儿,连续几年不能陪她一起过除夕,我还是有些抱歉,所以晚上六点吃完饭后,我都会给她开一个视频,让她也能感受一下春晚备战的氛围。

八点,牛年的春晚终于开始,今年的新媒体直播用户规模 5.69 亿,幸好我们提前准备足够的带宽,撑住了春晚的流量巅峰。

除了现场的基本保障,今年我们还采用了视频云窄带高清技术,从人眼视觉模型出发,将视频的优化目标从经典的 “保真度最高” 调整为 “主观体验最好”,在提供更加清晰的观看体验同时节省带宽。

另外,视频云今年还与春晚节目组一起,将 AI 编辑部应用于春晚内容的生产和呈现,通过对 4K 横屏节目内容按照 AI 识别后的主体进行分割与智能裁剪,快速将横屏拍摄的视频裁剪为多个人物竖屏素材,专业剪辑师再编辑为可在央视文艺和央视频等短视频平台发布的视频内容,全国观众就可以在各大视频平台上看到 “专属” 视角的春晚。

新技术为春晚带来的变化越来越多,哪怕我们只是尽到其中一点微薄的力量,也仍旧觉得很开心。唯一遗憾的是,这一年的节目我又是只知 “数据”,不知 “内容”,只能等到初一再回家补看。
12 点的钟声响起时,我听到了不远处的长安街上响亮的烟花声,虽然无法眼见这些热闹,但听着烟花的声音,我心头的重担彻底放下 “这一年最重要的工作已经顺利结束”,再过七个小时,我就能飞回青岛和家人共度春节了。

晚上两点,走出央视大楼时,我打了一个网约车,我问司机 “除夕还在工作啊?”对方说:“您不也是吗”,然后我们相视笑了起来。

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2月10日

流媒体传输协议之 RTP(下篇)

本系列文章将整理各个流媒体传输协议,包括 RTP/RTCP,RTMP,希望通过深入梳理协议的设计细节,能够给流媒体领域的开发者带来一定的启发。

作者:逸殊
审核:泰一

接上篇:《 流媒体传输协议之 RTP(上篇)》

RTP 控制协议

Sender & Receiver 报告

RTP 使用 Sender 报告(SR)和 Receiver 报告(RR)来反馈数据的接收质量,如果是媒体数据的发送者那就会发送 SR,否则发送 RR。这两类报文是通过头部的报文类型识别码来做区分的。SR 相对于 RR 来说多了 20byte 的 Sender 相关信息,除此之外其他内容都是一样的。

SR 报文

SR 报文包含三个部分,第一个部分是头部,有 8 BYTE,各个字段的含义如下:

  • version (V): 2 bits,RTP 协议版本。
  • padding (P): 1 bit,是否包含填充,最后一个填充字节标识了总共需要忽略多少个填充字节(包括自己)。Padding 可能会被一些加密算法使用,因为有些加密算法需要定长的数据块。在复合包中,只有最后一个 RTCP 包需要添加填充。
  • reception report count (RC): 5 bits,有多少个接收报告。可以为 0。
  • packet type (PT): 8 bits,200 表示 SR 报文。
  • length: 16 bits,报文长度(按 32-bit 字统计),包含头部和填充字节。
  • SSRC: 32 bits,身份定位符。

第二部分是发送者信息,包含 20 BYTE 的数据,总结了这个发送的的传输统计,各个字段的含义如下:

  • NTP timestamp: 64 bits,Wallclock time,用于计算 RTT。
  • RTP timestamp: 32 bits,RTP 时间戳,基于 NTP 的某一随机偏移量。用于媒体数据内同步。
  • sender's packet count: 32 bits,这个 SSRC 总共发送了多少包。
  • sender's octet count: 32 bits,这个 SSRC 总共发送了多少 BYTE 的数据。

第三部分可能什么都没有,也可能有多个接收报告,这取决的上次报告以后收到了多少个 Sender 的数据。每个报告块统计了一个 SSRC 的包数。具体内容如下:

  • SSRC_n (source identifier): 32 bits,这个信息块对应的 SSRC。
  • fraction lost: 8 bits,上次 SR 或 RR 发送后到目前为止的丢包率。
  • cumulative number of packets lost: 24 bits,整体过程的丢包总数。
  • extended highest sequence number received: 32 bits,低 16-bit 是收到的最新的 RTP 报文序列号,高 16-bit 是序列号循环的次数。
  • interarrival jitter: 32 bits,RTP 数据报文抵达时间的抖动。如果 Si 代表 i 包中包含的 RTP 时间戳,Ri 代表 i 包被接收时的 RTP 时间戳,那两个包 i 和 j 的到达时间抖动算法如下::D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)。我们在计算这个抖动时,要结合每个包的抖动,来计算一个平均值,计算平均值的方案如下:J(i) = J(i-1) * (15 / 16) + (|D(i-1,i)|)/16。
  • last SR timestamp (LSR): 32 bits,该 SSRC 最后一个 RTCP 报文(SR)中带的 NTP 时间。
  • delay since last SR (DLSR): 32 bits,从该 SSSR 最后一个 RTCP 报文(SR)被收到以来经过的时间。

数据的发送者可以通过当前时间 A,接收到 RR 部分中的 LSR 和 DLSR 来计算 RTT,计算示意图如下:

RR 报文

接收报告的格式和发送报文格式一样,只不过它在头部中用 201 表示这是一个 RR 报文。此外 RR 报文中不含有上述 SR 报文中的第二部分。如果 RR 报文是空的那么需要在头部标明 RC=0。

发送 / 接收报文的拓展
一些预设可能根据自己的需求,要在接收报告和发送报告中附加一些信息。那么这些附加内容应该在 SR 或者 RR 的结尾之后。如果这些内容只有发送者相关,那么 RR 中就不包含这些信息。

分析发送报告和接收报告
这些接收质量的报告信息可能不光只有发送者要使用,接收者或者第三方监控器也会使用。发送者可能根据接收质量调整自己的传输策略。接收者可以根据这个信息来确定自己遇到的问题是本地网络的问题还是整个 Session 的问题。网络的管理者可以根据这些信息来评估整个网络环境的情况。

SDES 报文


SDES 是一个三级结构,它包含一个头和 0 个或多个数据块,每一个数据块对应了一个 SSRC 或 CSRC,它又由多个描述字段组成。头部的信息如下:

  • version (V),padding (P),length: 和上面一样。
  • packet type (PT): 8 bits,202 表示 SDES 类型。
  • source count (SC): 5 bits,SSRC/CSRC 块的数量。

每一个块中都包含多个描述内容,这些描述内容都是 32-bit 对齐的,其中前 8-bit 描述了类型,接着 8-bit 描述了信息长度(不包含前 16-bit),然后信息内容。注意信息部分不能超过 255 BYTE,这和前面的很多工作类似是为了约束 RTCP 的带宽。

描述的文本内容是 UTF-8 编码的。如果要使用多字节的编码,需要在醒目的地方表示用的什么的编码。

各个描述部分是没有中间分隔的,所以要用空字节来填充以达到对齐的效果。注意这里的填充和 RTCP 头部的 P 不是一个概念。

末端节点发送的 SDES 包含他自己的数据源标识。而 Mixer 发送的 SDES 包含多个 CSRC,如果 CSRC 的数量超过了 31 个,会拆分成多个 SDES 报文。

SDES 的所有类型会在后面一一介绍。其中只有 CNAME 是强制要有的。可能有一些类型的的描述只有部分预设才会使用。但是这些内容都是在一个共通的地方来记载,以防止不同的预设使用的描述类型发生冲突。如果要注册新的类型,需要通过 IANA 注册。

CNAME:权威的末端节点身份标识


CNAME 有如下特征:

  • 因为 SSRC 在许多意外情况下会重新生成,所以 CNAME 被用来绑定旧的 SSRC 和新的 SSRC,来保持数据源的连续。
  • 和 SSRC 一样,CNAME 也需要保证唯一性(同一个 Session 中)。
  • 为了让同一个参与者的多个 SSRC 绑定在一起,我们需要 CNAME 是固定的。
  • 为了让第三方监控用起来方便,CNAME 应该即方便程序使用,也要设计成可读的,可以根据它确认来源。

因此 CNAME 应该通过算法来生成而不是手动生成。为了满足如上需要,一般来说是按照如下的格式来描述 CNAME:

  • "user@host" eg: "doe@192.0.2.89" or "doe@2201:056D::112E:144A:1E24".
  • "host", 如果是单用户系统,获取不到 user 时只使用 host。eg: "sleepy.example.com","192.0.2.89" or "2201:056D::112E:144A:1E24".

有些人可能会发现,如果上述的 host 使用的是子网地址的话,就没办法保证整个 Session 的唯一性了,通常这类没有直接 IP 的使用者是通过一个 RTP 级别的 Translator 来访问公共网络。这个 Translator 会处理从私有地址到公网地址的转换工作。

NAME:用户名

这个是描述数据源的真实名字,eg:"John Doe, Bit Recycler"。整个 Session 过程中希望这个值不变。全 Session 不需要唯一。

EMAIL:电子邮箱地址

电子邮箱地址,eg: "John.Doe@example.com"。整个 Session 过程中希望这个值不变。

PHONE:电话号码

电话号码需要以国际访问码开头,eg: "+1 908 555 1212"。

LOC:用户地理地址

视应用不同,详细程度会各不相同。

TOOL:应用名或工具名

带版本号的应用名,可以用来 DEBUG。

NOTE:提醒 / 状态

用来发送暂时性的消息描述当前状态。eg: "on the phone, can't talk"。

PRIV:自定义拓展

上层应用自定义的格式。一般都是用过一个前缀描述消息类型,然后后面跟着消息正文。

BYE 报文

BYE 报文表示一个或多个流媒体源不再活跃。

  • version (V),padding (P),length: 同上。
  • packet type (PT): 8 bits,203 表示 BYE 报文。
  • source count (SC): 5 bits,退出 Session 的 SSRC 的数量。

如果 BYE 报文被 Mixer 收到了,Mixer 应该啥都不改动,就发给下一节点。如果 Mixer 关闭了,它要发送一个包含它管理的所有 SSRC 的 BYE 报文。BYE 报文中可能也会跟着带一些离开原因的描述。这些描述和 SDES 中带的描述类似,需要 32-bit,用空字节填补空缺。

APP:应用定义的 RTCP 报文

APP 报文一般用于实验性的功能和开发。如果识别到了不认识 NAME 那么上层应用一般都会忽略它。如果开发或者测试功能稳定了,一般是要通过 IANA 注册一个新的 RTCP 报文类型。

  • version (V),padding (P),length: 同上。
  • subtype: 5 bits,APP 报文子类型,一般是上层应用定义。
  • packet type (PT): 8 bits,204 表示 APP 类型的 RTCP 报文。
  • name: 4 octets 一般是应用名,防止 subtype 冲突。
  • application-dependent data: variable length 和上层应用相关的内容,需要 32-bit 对齐。

RTP Translator & Mixer

作为末端节点的补充,RTP 引入了 Translator 和 Mixer 的概念,它们是 RTP 层的中间件。虽然这多少增加了协议的复杂度,但是对音视频通话应用来说它们还是很关键的,因为它们能解决防火墙问题和低带宽连接的问题。

描述

一个 RTP Translator/Mixer 连接至少两个传输层的用户组。通常来说,这里提到的用户组是公共网络的概念,传输层协议会为其生成一个组播地址(ip:port)。网络层协议,像是 IPv4 和 IPv6 对 RTP 协议来说是隐藏的。一个系统可能会有多个 Translator 和 Mixer(多个 Session),它们中的每一个都可以看作是一个用户组的逻辑分割。

为了避免创建在创建 Translator 和 Mixer 造成了网络包循环,必须遵循下列规则:

  • 每个通过连接 Translator 和 Mixer 而加入 Session 的用户组,要么需要网络层隔离,要么最少互相知道这些参数(protocol,address,port)中的一个。
  • 由上一个规则推广的话,各个用户组绝对不能同时连接多个 Translator 或者 Mixer,除非有某种机制能保证他们之间数据被阻断。

Translator:在不改变 RTP 报文 SSRC 的条件下,向后传播该报文,正因为如此,报文的接收者才能识别到 Translator 转发后的报文到底是来自哪个人。有些 Translator 可能直接转发报文,不做任何改动,也有可能改变数据编码,payload 类型和时间戳。

如果多个数据报文被重新编码并合并到一起的话,Translator 必须为这类报文指定一个组新的序列号。这样,输入报文的丢失就会导致输出报文的断层。数据的接收者一般是不知道 Translator 的存在的,除非通过 payload 类型的不同或者传输层报文的源地址来判断。

Mixer:从一个或多个数据源那里接收数据,随后可能会改变数据的格式,然后将这些数据合并,并传递给下家。因为多个数据源的时序并不一定是同步的,所以 Mixer 需要整合各个数据源的时序关系,并将其映射到自己的一套时序上,所以 Mixer 也是一个 SSRC,所有通过 Mixer 的报文必须打上该 Mixer 的 SSRC。

为了表示这些数据的原始数据源,一般会通过 CSRC 列表来记录。有些 Mixer 可能自己也是一个原始数据源,所以他自己的 SSRC 也会出现在 CSRC 列表中。有些应用可能不希望 Mixer 的 SSRC 出现在 CSRC 中,但是这样可能就无法发现循环网络包。

上图是一个 Mixers 和 Translators 连接的例子。[] 代表末端节点,() 代表 Mixer,<> 代表 Translator,"M1:48 (1, 17)" 表示 Mixer1 的报文,48 是 Mixer1 的 SSRC,括号里的 1,17 是 CSRC,它合并了 E1:17 和 E2:1 这两个节点的数据。

Translator 处理 RTCP

除了要转发数据包,进行数据包的更改,Translator 和 Mixer 也要发送 RTCP 报文。在很多情况下,它会将收到的末端节点的 RTCP 报文合并到复合包中。当再次收到这些包时或者自己的 RTCP 周期到时,它会将复合包发送出去。

有的 Translator 可能对收到的 RTCP 报文不做任何改动,只是简单的转发这个包。如果这个 Translator 改变了报文数据的 payload,它必须对 SR 或者 RR 做相关的改动。通常来说,Translator 不能将多个数据源的 SR 和 RR 合并,因为这样会导致 RTT 的计算出现问题(RTT 根据 LSR 和 DLSR 计算)。

  • SR 中的发送者信息: Translator 不会创建自己的发送者信息,它会将收到 SR 传给下家。其中 SSRC 不会发生任何改动,但是发送者信息有必要的话一定要做适当的改动。如果 Translator 改变了数据编码,那 "byte count" 字段就要更改。如果他将多个数据报文合并,那它需要修改 "sender's packet count" 字段。如果它改变了时间频率,那就需要修改 "RTP timestamp"。
  • SR/RR 中的接收者信息:SSRC 不会发生任何改动,如果 Translator 改变了序列号,那就需要修改 "extended last sequence number",在某些极端情况下,它可能完全没有接收反馈,或者根据接收到的 SR/RR 来构建自己的接收报告。一般情况下 Translator 是不需要自己的 SSRC 的,但是如果是为了表示自己的数据接收情况,它可能也会生成自己的 SSRC,并将这些 RTCP 报文发送过所有的连接者。
  • SDES:一般 Translator 收到 SDES 后会什么都不改就发给下家,但是也有可能为了节约带宽筛掉 CNAME 之外的信息的,如果 Translator 要发送自己的 RR 信息,那它一定要发送一个自己的 SDES 给所有连接者。
  • BYE:无改动转发,如果 Translator 有自己的 SSRC 也要发送自己的 BYE。
  • APP:无改动转发。

Mixer 处理 RTCP

因为 Mixer 会生成自己的数据流,所以他不会转发经过他的 SR 和 RR 而是为连接双方发送自己的 SR 和 RR 报文。

  • SR 的发送者信息:Mixer 不转发数据来源的发送信息。它会生成自己的发送者信息并把它发送给下家。
  • SR/RR 中的接收者信息:Mixer 会生成自己的接收信息,然后发送给所有数据来源,它绝对不能做接收报告的转发工作,或者把自己的接收信息发给错误的对象。
  • SDES:Mixers 通常会不做任何改动就转发 SDES 信息,但是也有可能为了节约带宽过滤除了 CNAME 之外的其他信息。Mixer 必须发送自己的 SDES 报文。通常,Mixer 会将多个收到的 SDES 打包一起发送。
  • BYE:Mixer 必须转发 BYE 报文。如果 Mixer 要退出时,它会将所有数据来源的 SSRC 放进 BYE 报文,也包括自己的 SSRC。
  • APP:视上层应用。

瀑布型 Mixer

一个 RTP Session 可能包含多个 Mixer 和 Translator,就像上图一样。如果 Mixer 是瀑布型的,就像 M2 和 M3,一个 Mixer 收到的数据可能是已经合并过的,它有自己的 CSRC 列表。那么第二个 Mixer 需要将之前的 CSRC 和自己接收的所有 SSRC 合并。就像图中 M3 的输出是 M3:89 (64,45)。

SSRC 的分配和使用

前面已经说过 SSRC 是一个随机的 32-bit 数,它需要在整个 Session 内保证唯一性。所以同一个网络下的参与者在刚加入 Session 时使用不同的 SSRC 至关重要。

我们不能简单的用本地的网络地址,因为可能不唯一。也不能不考虑初始状态而简单地调一个随机数函数。

碰撞的可能性

因为 SSRC 是随机选择的,这就可能多个数据源选用了相同的 SSRC。如果大家是同时加入 Session 的话,这个碰撞的几率就更高。如果 SSRC 的数量是 N,L 是 SSRC 的数据长度(这里是 32),那么碰撞的可能性是 1 - exp(-N2 / 2(L+1)),当 N=1000 时,碰撞率大概是 10**-4。

通常来说,实际的碰撞率会比上述的最坏情况要低。通常一个新节点加入时,其他节点已经有了自己的唯一 SSRC,这时候碰撞的概率只是生成的新 SSRC 在这些现有 SSRC 之中的可能性。这时候碰撞率是 N/2**L。当 N=1000 时,碰撞率大约是 210*-7。

因为新加入的节点会先接收一段时间的报文然后才发送自己的第一个报文,所以在它生成 SSRC 时可以避开已知的 SSRC,这也有效的降低了碰撞的几率。

碰撞的解决方案和循环的发现

通常来说 SSRC 碰撞的可能性很小,所有的 RTP 实现必须有发现冲突的机制,并在发现冲突时作出适当的处理。如果数据源发现了任何一个别的数据源和自己使用同一个 SSRC,它必须用原来的 SSRC 发送一个 BYE 报文,然后选用一个新的 SSRC。如果一个数据的接收者发现了多个数据源的 SSRC 碰撞了(通过传输地址或者 CNAME),那么它会只接收其中一个人的报文,丢弃另一个人的所有报文。

因为整个 Session 中的 SSRC 是唯一的,所以它也可以被用来发现环型报文。环形报文会导致数据的重复以及控制信息的重复。

  • Translator 可能会错误地将报文发送回该报文来的地方。
  • 两个 Translator 错误地同时启动,它们两个都会转发同样的数据。
  • Mixer 可能会错误地将合并报文发送回这些报文来的地方。

一个数据源可能发现自己的或者别人的报文被循环发送了。无论是报文循环还是 SSRC 的碰撞都会导致同一个现象,即 SSRC 相同但是传输地址不同的报文。因此,如果数据源改变了自己的传输地址,那它就需要同时改变自己的 SSRC 来避免被检测成环形报文。有一个需要注意的内容是,如果一个 Translator 再重启的过程中改变了自己的传输地址,那么这个 Translator 转发的所有数据都会被检测成环。这类情况的解决方案一般有如下两个:

  • 重启的时候不改变传输地址。
  • 接收者的超时机制。

如果循环或者碰撞发生在离 Translator 和 Mixer 很远的地方,我们就不能通过传输地址来发现。但是我们仍然可以通过 CNAME 的不同来发现 SSRC 碰撞。

为了解决上述问题,RTP 的实现必须包含一个类似如下的算法。这个算法不包括多个数据源 SSRC 碰撞的情况,这类情况通常下都是先用原来的 SSRC 发送一个 BYE 然后重新选择一个新的 SSRC。

这个算法需要维护一个 SSRC 和传输地址的映射关系。因为 RTP 的数据和 RTCP 传输使用的是两个不同的端口,所以一个 SSRC 对应的是两个传输地址。

每次收到 RTP 报文和 RTCP 报文都会将其 SSRC 和 CSRC 在上述的表中进行比对。如果发现了传输地址对不上的情况,我们就可以说发现了一个循环或者碰撞。对于 RTCP 数据来说,可能每个数据块都有自己独立的 SSRC,比如 SDES 数据,对于这种情况就需要分别比对。如果没有在表中找到这个 SSRC 或者 CSRC,就需要新添加一项。当收到 BYE 报文时,需要先比对这个 BYE 的传输地址,如果传输地址匹配上了,就将这一项从表中删除。或者基于超时机制,将超时的数据从表中移除。

为了追踪自己的数据报文循环情况,必须维护另一个列表,这个表存储冲突报文的传输地址和收到该报文的时间。如果超过 10 个 RTCP 周期都没有收到这个传输地址的冲突报文,就将该项从表中删除。

下面的算法还假设参与者自己的 SSRC 和状态都包含在 SSRC 表中,它会先比对自己的 SSRC。

if (SSRC or CSRC identifier is not found in the source
             identifier table) {
             create a new entry storing the data or control source
                 transport address, the SSRC or CSRC and other state;
         }
   /* Identifier is found in the table */
   else if (table entry was created on receipt of a control packet
            and this is the first data packet or vice versa) {
       store the source transport address from this packet;
   }
   else if (source transport address from the packet does not match
            the one saved in the table entry for this identifier) {
       /* An identifier collision or a loop is indicated */
       if (source identifier is not the participant's own) {
           /* OPTIONAL error counter step */
           if (source identifier is from an RTCP SDES chunk
               containing a CNAME item that differs from the CNAME
               in the table entry) {
               count a third-party collision;
           } else {
               count a third-party loop;
           }
           abort processing of data packet or control element;
           /* MAY choose a different policy to keep new source */
       }
       /* A collision or loop of the participant's own packets */
       else if (source transport address is found in the list of
                conflicting data or control source transport
                addresses) {
           /* OPTIONAL error counter step */
           if (source identifier is not from an RTCP SDES chunk
               containing a CNAME item or CNAME is the
               participant's own) {
               count occurrence of own traffic looped;
           }
           mark current time in conflicting address list entry;
           abort processing of data packet or control element;
       }
       /* New collision, change SSRC identifier */
       else {
           log occurrence of a collision;
           create a new entry in the conflicting data or control
               source transport address list and mark current time;
           send an RTCP BYE packet with the old SSRC identifier;
           choose a new SSRC identifier;
           create a new entry in the source identifier table with
               the old SSRC plus the source transport address from
               the data or control packet being processed;
       }
   }

层级编码

对于不同 Session 的层级编码传输,一般都是所有层都使用同一个 SSRC,如果其中某一层发现了 SSRC 冲突,那么只改变这一层的 SSRC,而且他层的 SSRC 不做改变。

安全

下层协议可能会提供 RTP 应用所需要的所有安全服务,包括认证,数据完整性,数据保密性。这些服务在 IP 协议中都有解决方案。因为 Audio 和 Video 初始化过程中需要数据加密,而这时候 IP 协议这一层的安全服务还没有提供。所以,RTP 需要实现一个 RTP 专用的保密服务。这个保密服务是非常轻量级的,而且保密部分的服务向后兼容,以后可以随时进行更换。或者,某些预设会提供这部分加密服务,比如 SRTP(Secure Real-time Transport Protocol),SRTP 是基于 Advanced Encryption Standard (AES) 提供了一个比 RTP 默认加密服务更强大的实现。

保密性

保密性是指我们的报文只希望一些特定的接收者可以解码成明文,而其他人只能得到无用的信息,保密性是通过加密编码来提供的。

当需要为 RTP 和 RTCP 报文提供加密服务时,所有传输的内容都会在下层报文那里进行加密。对于 RTCP 来说,需要一个 32-bit 的随机数作为前缀。而 RTP 报文不需要前缀,取而代之的是随机序列号和时间戳偏移。因为随机部分很少,所以可以说这是一个非常弱的初始向量。此外,SSRC 也可被破解者修改,这是这个加密方案的另一个薄弱的环节。

对于 RTCP 来说,可能会将一个复合包分成两批,第一批加密,后一批明文发送。例如,SDES 部分的信息可能加密,而接收报告部分不加密就发送出去,因为只有这样那些第三方监控器才能在不知道密钥的情况下统计网络状况。如下图所示,SDES 信息必须跟在一个空的 RR 后,并且要有一个随机前缀。

RTP 协议使用的 Data Encryption Standard (DES) 算法,使用 cipher block chaining (CBC) 模式,这需要数据填充到 64-bit 对齐。密码算法使用零作为初始向量,因为 RTCP 报文中已经有一个随机前缀了。

RTP 之所以选择这个默认协议是因为它用起来很容易,但是因为 DES 太容易破解了。所以推荐预设中使用更健壮的加密算法来替换这个默认方案,例如 Triple-DES。这些算法普遍需要一个随机初始化块,RTCP 使用了 32-bit 的随机数作为前缀,RTP 使用了时间戳和序列号的随机偏移,可是相邻的 RTP 报文之间的随机性就很差。需要注意的是,无论是 RTCP 还是 RTP,它们的随机性都有限。加密型更好的应用,需要考虑更多的保密措施。例如 SRTP 配置文件,就基于 AES 来加密,它的加密方案就更完备,选择这个预设来使用 RTP 就挺不错的。

前面提到过也可以用 IP 级的加密方案或者 RTP 级的加密,一些预设可能会定义别的 payload 类型来加密。这种方案,可能只加密 payload 部分而头部分使用明文,因为只有 payload 部分才是应用真正需要的内容。这可能对硬件设备来说非常有用,它既处理解密过程,又处理解码过程。

身份认证和消息完整性

RTP 协议这一层没有身份认证和消息完整性服务,因为有些上层服务可能没有认证就能使用。而消息完整性服务依赖下层协议来实现。

RTP 下的网络层和传输层协议

RTP 需要下层协议提供多路复用机制。对于 UDP 这类应用,推荐 RTP 应该使用一个偶数端口传输数据,和它相关的 RTCP 流应该是用高一位的奇数端口。在单播模式下,每个参与者都需要一对端口来传输 RTP 和 RTCP 报文。两个参与者可能使用相同的端口。绝对不能以接收到的报文网络地址直接作为目标地址发送报文。

建议层编码模式是,使用相邻的端口,因此对于层 N 来说,数据端口是 P+2N,控制端口是 P+2N+1。对于 IP 组播来说,可能不会得到相邻的组播地址。

RTP 数据报文没有描述报文长度的信息。所以 RTP 报文依赖下层协议提供长度标识。所以一个 RTP 报文的最大长度由下层协议限制。

如果 RTP 报文使用的下层协议是流传输协议的话,必须定义一套数据帧分割机制。

参考

[1] rfc3550

阅读作者的更多文章,关注作者个人公众号:贝贝猫技术分享

作者的个人博客:https://www.beikejiedeliulang...

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2月9日

流媒体传输协议之 RTP (上篇)

本系列文章将整理各个流媒体传输协议,包括 RTP/RTCP,RTMP,希望通过深入梳理协议的设计细节,能够给流媒体领域的开发者带来一定的启发。

作者:逸殊
审核:泰一

介绍

RTP,即 real-time transport protocol(实时传输协议),为实时传输交互的音频和视频提供了端到端传输服务。其中包括载荷的类型确认,序列编码,时间戳和传输监控功能。一般应用都是基于 UDP 协议,来使用 RTP 的多路技术以及验和服务。然而,RTP 还可以与其它适合的协议并用,如果底层网络支持多路分发,RTP 还可以将数据传输给多个目标。

需要注意的是 RTP 不提供任何机制以保证数据的实时性和 QOS (quality-of-service),而是依赖底层的服务来提供这些功能,RTP 既不保证传输的可靠性也不保证无序传输,同时也不假定底层网络是可信任的和有序的。接收端可以利用 RTP 中的序列号排序收到的报文。

RTP 与 RTCP

  • 实时传输协议 (RTP),传输具有实时特性的数据
  • RTP 控制协议 (RTCP),监控 QOS 和传递会话中参与者的信息。它没有明确的成员控制功能和 Session 建立过程,但这些对一个相对宽松的 Session 控制来说已经足够了,它没有必要包含一个应用的所有控制功能。

RTP 代表了一种新型协议,它遵循 Application level framing 和 Integrated layer processing。即 RTP 可以比较容易的拓展以传递某些特定需要的内容,而且可以比较容易地集成进某个应用,而不是作为一个独立的补充层。RTP 协议被故意地设计成不完整的协议框架。

RTP 的使用场景

下面的例子描述了 RTP 的部分特性,选择的例子是用来阐明基于 RTP 的应用的基本操作,而不是说 RTP 仅能用于此类应用。

简单的多播音频会议

一个小组要通过网络开一个音频会议,他们用了 IP 多播服务。基于某种分配机制,小组得到了一个多播组地址和一对端口,其中一个端口是用来传输音频数据的,另一个是用来传输 RTCP 报文的。这个组播地址和端口发给了所有与会者。如果想要引入一些安全策略,可以对数据报文和控制报文加密,然后把加密时用到的密钥分发给与会者。

这个音频会议软件,可能会一直发送时长为 20ms 的音频数据包。每个实际音频数据包,都以 RTP 头数据开始,然后再以 UDP 协议封装并发送。RTP 包的头部标识了该包的数据类型,以便消息发送器来改变数据的编码。例如,针对低带宽的与会者进行一些调节,或者对网络拥堵作出反应。

像 UDP 这类包类型的网络,偶尔会丢包,乱序,延迟不定长时间。为了解决这类意外情况,RTP 包中包含了时间信息和序列号,这样接收者就可以通过它们重排数据包的时序。在这个例子中,我们就可以按顺序地播放每个 20ms 的音频数据。在会议中对每个数据源的 RTP 报文时序重排都是独立进行的。接收者也可以通过序列号来确定丢失了多少报文。

因为这个小组开会期间,会有一些人加入或退出这个网络会议,所以我们需要知道具体是谁加入了会议,以及他们有没有正常地接收到音频数据。出于这个目的,每个网络会议的客户端都会周期性的通过 RTCP 端口报告使用者的名字以及自己接收数据的情况,如果有人接收数据不正常,可能就需要对应的改变编码。而且,除了用户的名字之外,还会有一些别的信息,用来控制带宽限制。当有人从视频会议中退出时,还需要发送一个 RTCP BYE 报文。

音频和视频会议

如果这个会议既要传输音频又要传输视频的话,它们会以独立的 RTP Session 传输。也就是说,负责音频传输的部分和负责视频传输的部分会通过不同的组播地址(和端口对)分别传输各自的 RTP 报文和 RTCP 报文。在 RTP 协议这一层,音频和视频 Session 并没有被组合到一起。我们期望与会者用同一个名字来建立音频和视频 Session,这样这两个 Session 就能联系起来了。

RTP 协议之所以这样设计,一个原因是某些与会者可以选择只接收某一种类型的数据(只接收 Audio)。即便 Audio 数据和 Video 数据是独立分发的,但是我们仍然可以通过参考 RTCP 协议中时间信息来同步播放它们。

Mixers & Translators

到目前为止,我们都是假设所有的与会者想要接收同一格式的媒体数据。但是这显然不太合适,考虑一下,可能某些与会者网速相对较慢,而其他人网速却比较快。对于这种情况,我们不应该强迫所有人都用低带宽并降低音频编码的质量,而是使用 RTP 级别的中继节点(Mixer)来给周围低带宽用户分发低带宽消耗的数据。

这个 Mixer 将接收到的不同与会者的音频数据同步,并将它们耦合到一个单一流中,然后将这个流用低带宽消耗的编码方案进行压缩,最后发送给那些低带宽的与会者。Mixer 可以在 RTP 头部写一些特殊内容,来表明该 Mixer 包具体耦合了哪些与会者,这样,接收到该 Mixer 包的人就能确定当前说话的人是谁了。

此外,有些与会者可能处于应用级防火墙的后面,无法仅通过 IP 组播访问。这种情况下 Mixer 就没有什么意义了,他们需要另一类 RTP 级别的中继(Translator)。我们需要两个 Translator,安装在防火墙的两面,外面的 Translator 将收到的所有组播报文,通过一个安全连接传输给防火墙里面的 Translator。然后,防火墙里的 Translator 再将这些报文分发给内网的与会者。

层编码

多媒体应用可以根据接收者的能力或者网络拥堵的情况调整传输速率。许多实现将码率控制的责任放在了发送端。这和组播模式不太兼容,因为各个不同的数据接收者会有不同的带宽情况,这就会产生木桶效应,即带宽最差的接收者会拖垮整个会议的通讯质量。

因此,带宽自适应的工作应该放到接收者这里,发送者需要拆分出面向不同带宽与会者的媒体流(500K,2M,5M),它们分别对应了不同的组播地址,数据的接收者根据自己的带宽情况,选择加入适合的组播。

定义

  • RTP payload:RTP 包中传输的数据,比如音频采样数据或者压缩过的视频数据。
  • RTP packet:由定长 RTP 头部,数据来源者的列表,RTP payload 组成的数据包。一些下层协议可能会自己定义 RTP 的封装格式。一般来说,一个下层协议包只包含一个 RTP 包,但是也有可能多个 RTP 包被合并到一起。
  • RTCP packet:RTP 控制报文,由定长的 RTC 头部开始,之后会跟着一些结构化的元素,它们在 RTCP 发挥不同功能时,会有不同的结构。通常多个 RTCP 包会被合在一起,通过一个下层协议包一起发送。
  • Port:传输层协议中用来区分某一主机下不同应用的抽象。RTP 协议依赖更底层网络提供端口机制,继而提供多播的 RTP 和 RTCP 报文。
  • Transport address:网络地址和端口的组合,用来定位传输层的节点。
  • RTC media type:一个 RTP Session 中所用到的所有 payload 类型的合集。
  • Multimedia Session:视频会议组中同时工作的一组 RTP Session。例如,视频会议中的 Audio Session 和 Video Session。
  • RTP Session:一组参与者利用 RTP 来通讯的组合。一个参与者可以同时加入到多个 RTP Session 中。在 Multimedia Session 中,除非特意将多媒体编码进同一数据流,否则,每个数据流会通过不同的 RTP Session 传输。与会者通过 Transport address 来区分不同的 RTP Session。同一 RTP Session 的不同与会者会共享同一个 Transport address,也可能每个与会者都有自己的 Transport address。在单播的情况时,一个与会者可能用同一对端口(RTP&RTCP)来接收所有其他与会者的数据,也可能对不同的与会者采用不同的端口对(RTP&RTCP)。
  • Synchronization source (SSRC):RTP 报文流的一个 Source,由 RTP 头中定义的 32-bit 的 SSRC identifier 来标识,这样做是为了不依赖网络地址。同一个 SSRC 中发送的所有包都具有同一时序和序列号间隔,因此接收者可以通过 SSRC 将收到的数据包分组并排序。一个信号源(麦克风,摄像头,Mixer)的报文流会有由一个 SSRC 的发送器发送。一个 SSRC 可能会随着时间的变化,改变其数据格式,例如音频编码。SSRC 的身份识别码都是随机生成的,但是必须保证整个 RTP Session 中该身份识别码不会重复,这些工作是通过 RTCP 来完成的。如果一个与会者在一个 RTP Session 中发送不同的媒体数据流,那么每个流的 SSRC 必须不同。
  • Contributing source (CSRC):RTP Mixer 所混合的所有数据对应的 SSRC 的列表。Mixer 会将一个 SSRC 列表写入 RTP 头中,该列表包含了这个混合报文中包含的所有来源 SSRC。
  • End system:一个生成 RTP payload 和消费收到的 RTP payload 的应用。一个 End system 可以扮演一个或者多个 SSRC 角色,但是通常是一个。
  • Mixer:一个中介系统,它接收一个或多个 Source 的数据,随后它可能会改变这些数据的格式,并将它们合并为一个新的 RTP packet。因为,多个输入源的时序通常来说都不一致,所以 Mixer 通常会同步不同源的时间,并生成一个自己的时序来处理合并数据流。所有从 Mixer 输出的数据包都会标记上该 Mixer 的 SSRC。
  • Translator:一个中介系统,它会转发 RTP packet 但是不改变其原本的 SSRC。
  • Monitor:一个在 RTP Session 中接收 RTCP 报文的应用,它会总结数据被接收的报告,并为当前分发系统评估 QOS,诊断错误,长期统计。Monitor 可以集成进会议应用中,也可以是独立的第三方应用,只接收 RTCP 报文,但是什么都不发送。
  • Non-RTP means:为了让 RTP 提供可用服务而加入的协议或者机制。特别是在多媒体会议中,需要一种控制协议来分发组播地址和加密密钥,协调加密算法,定义 RTP payload 格式和 RTP payload 类型的动态映射。

字节序,数据对齐,时间格式

所有的整数字段都使用网络字节序(大端序),除了特别声明,数字常量由十进制表示。

所有头部数据都会根据其数据的原始长度进行对齐,比如,16-bit 的数据会对齐到偶数偏移,32-bit 的数据会对齐到可被 4 整除的偏移。此外,用 0 来作为填充字节。

Wallclock time(绝对日期和时间)是用网络时间协议(NTP)的时间格式来表示,即从 1900 年一月一日 0 点到现在的秒数。NTP 的时间戳使用了 64-bit 的无符号固定小数点的形式表示,其中头 32-bit 用来表示整数部分,后 32-bit 用来表示小数部分。RTP 的时间格式采用了 NTP 的简化版,他只用了 NTP 的 64-bit 数据的中间 32-bit,即前 16-bit 表示整数,后 16-bit 表示小数。

NTP 时间戳到 2036 年就会循环回 0,但是因为 RTP 只会使用不同 NTP 时间的差值,所以这不会有什么影响。只要一对时间戳都在同一个循环周期里,直接用模块化的架构相减或者比较就可以,NTP 的循环问题就不重要了。

RTP 数据传输协议

RTP 的定长头字段

RTP 头的格式如下:

上图中前 96-bit 的数据是每个 RTP 包都有的部分,CSRC 部分只有 Mixer 发送的报文才会有。这些字段的意义如下:

  • Version(V):2 bits,RTP 版本号,现在用的是 2。(第一个 RTP 草案用的 1)
  • Padding(P):1 bit,如果设置了该字段,报文的末尾会包含一个或多个填充字节,这些填充字节不是 payload 的内容。最后一个填充字节标识了总共需要忽略多少个填充字节(包括自己)。Padding 可能会被一些加密算法使用,因为有些加密算法需要定长的数据块。Padding 也可能被一些更下层的协议使用,用来一次发送多个 RTP 包。
  • Extension(X):1 bit,如果设置了该字段,那么头数据后跟着一个拓展数据。
  • CSRC count(CC):4 bits,CSRC 列表的长度。
  • Marker(M):1 bit,Marker 会在预设中进行定义(预设和 RTP 的关系可以参考 rfc3551,我的理解是预设是对 RTP 的补充,以达到某一类实际使用场景的需要),在报文流中用它来划分每一帧的边界。预设中可能会定义附加的 marker,或者移除 Marker 来拓展 payload type 字段的长度。
  • Payload type(PT): 7bits,该字段定义 RTP payload 的格式和他在预设中的意义。上层应用可能会定义一个(静态的类型码 <->payload 格式)映射关系。也可以用 RTP 协议外的方式来动态地定义 payload 类型。在一个 RTP Session 中 payload 类型可能会改变,但是不应该用 payload 类型来区分不同的媒体流,正如之前所说,不同的媒体流应该通过不同 Session 分别传输。
  • Sequence number:16 bits,每发送一个 RTP 包该序列号 + 1,RTP 包的接收者可以通过它来确定丢包情况并且利用它来重排包的顺序。这个字段的初始值应该是随机的,这会让 known-plaintext 更加困难。
  • Timestamp:32 bits,时间戳反映了 RTP 数据包生成第一块数据时的时刻。这个时间戳必须恒定地线性增长,因为它会被用来同步数据包和计算网络抖动,此外这个时钟解决方案必须有足够的精度,像是一个视频帧只有一个时钟嘀嗒这样是肯定不够的。如果 RTP 包是周期性的生成的话,通常会使用采样时钟而不是系统时钟,例如音频传输中每个 RTP 报文包含 20ms 的音频数据,那么相邻的下一个 RTP 报文的时间戳就是增加 20ms 而不是获取系统时间。和序列号一样时间戳的初始值也应该是随机的,而且如果多个 RTP 包是一次性生成的,那它们就会有相同的时间戳。不同媒体流的时间戳可能以不同的步幅增长,它们通常都是独立的,具有随机的偏移。这些时间戳虽然足以重建单一媒体流的时序,但是直接比较多个媒体流的时间戳是没办法进行同步的。每一时间戳都会和参考时钟(wallclock)组成时间对,而且需要同步的不同流会共用同一个参考时钟,通过对比不同流的时间对,就能计算出不同流的时间戳偏移量。这个时间对并不是和每个 RTP 包一同发送,而是通过 RTCP 协议,以一个相对较低的频率进行共享。
  • SSRC:32 bits,该字段用来确定数据的发送源。这个身份标识应该随机生成,并且要保证同一个 RTP Session 中没有重复的 SSRC。虽然 SSRC 冲突的概率很小,但是每个 RTP 客户端都应该时刻警惕,如果发现冲突就要去解决。
  • CSRC list:0 ~ 15 items, 32 bits each,CSRC list 表示对该 payload 数据做出贡献的所有 SSRC。这个字段包含的 SSRC 数量由 CC 字段定义。如果有超过 15 个 SSRC,只有 15 个可以被记录。

RTP Session 多路复用

在 RTP 中,多路复用由目标传输地址(address:port)提供,不同的 RTP Session 有不同的传输地址。

独立的音频和视频流不应该包含在同一个 RTP Session 中,也不应该通过 payload 类型和 SSRC 来区分不同的流。如果用同一个 SSRC 发送了不同的数据流,会引入如下问题:

  1. 假设 2 个音频流共享了一个 RTP Session,并且用了同一个 SSRC,如果其中一个要改变编码,这就导致了 payload 类型的改变,但是协议中没有提供方法来让接收者知道具体是哪个音频流改变了编码。
  2. 一个 SSRC 只有一个对应的时序和序列号,如果多个流有不同的时钟周期的话,就需要不同的时序。而且还不能用序列号来确认是哪个流丢包了。
  3. RTCP 发送者报告和接收者报告只描述了时序和序列号而不包含 payload 类型数据。
  4. Mixer 无法将不兼容的两个流合并。
  5. 如果一个 RTP Session 中包含了多个媒体流后就会失去如下优势:
  • 使用不同的网络路径或者分配网络资源
  • 只接收某一种媒体数据(网络较差时只接收 audio)
  • 接收方对不同的媒体类型做不同的处理

不同的流使用不同的 SSRC 但是仍然用同一个 RTP Session 发送确实可以解决前三个问题,但是仍然无法解决后两个问题。

预设可能对 RTP 头的改动

现有的这些 RTP 报文头对一般应用来说已经足够了。如果有需要,头字段可以根据预设进行一些修改,但仍要保证检测和统计功能的正常使用。

RTP 头拓展

RTP 提供了一个拓展机制,让上层应用可以将自定义的信息存储在 RTP 报文头。如果上层应用收到了无法识别的头部拓展数据,它们会忽略它。

值得一提的是,这个头部拓展是有一些限制的。如果附加信息只对某些 payload 格式才有意义,那么最好还是别把这些信息放到头部拓展中,而是放到 payload 部分。


如果 RTP Header 中的 X 位设置为 1,那么 Header 后必须跟着一个不定长度的拓展块,紧跟着 CSRC list(如果有的话)。拓展部分的头部包含一个 16-bit 的数据来描述拓展块包含多少个 32-bit 字(不包括拓展部分的头部)。因为 RTP 头部后面只能连接一个拓展块,考虑到有些应用可能会有多种类型的拓展块,所以拓展块的头 16-bit 留给开发者去自定义一些参数。

RTP 控制协议

同一个 Session 所有参与者会周期性地发送控制报文,RTP 控制协议就是通过这种方式进行的,和 RTP 数据的传播一样采用了组播的机制。下层协议必须提供数据包和控制报文的多路复用功能,例如使用独立的 UDP 端口分别传输数据和控制报文。RTCP 协议具有如下四大功能:

  1. 最主要的功能是反馈数据分发的质量。这也是 RTP 作为一个传输协议来说最关键的功能,而且它和流量控制,拥塞控制息息相关。反馈信息可能会直接影响自适应编码的控制。发送反馈报告给所有的参与者可以让它们评估遇到的数据分发问题是个人问题还是全局问题。通过 IP 组播这样的分发机制,像网络提供商这样的机构即便不加入到这个 RTP Session 中也能收到反馈信息,它们会扮演一个第三方监测者的角色去确认数据分发问题。这个反馈的功能无论是 RTCP 的发送者还是接收者都会进行报告。
  2. RTCP 还会给每个 RTP source 带一个不变的传输层身份识别符(CNAME),因为 SSRC 可能会中途改变(程序重启),所以接收者需要这个 CNAME 来持续追踪每个与会者。而且,接收者可以通过 CNAME 来将同一个与会者的所有数据流联系在一起,比如同步音频和视频。单个媒体内部的数据同步也需要 NTP 和 RTP 时间戳,这些数据都在数据发送者发送的 RTCP 报文中。
  3. 因为前两个功能需要所有的与会者都发送 RTCP 报文,所以需要适当的控制报文发送的频率以保证 RTP 协议可以在大量客户端一同加入时也能正常工作。通过每个参与者都广播控制报文的方式,每个人都能独立地计算出参与者的总数。
  4. 还有一个可有可无的功能,RTCP 可以用来共享小量的 Session 控制信息,例如辨认参与者的身份。通常来说,该功能会被那些管理比较松散的 Session 使用。RTCP 可以作为一个方便的与其他参与者沟通的通道,但是你也别期望 RTCP 可以满足一个应用的所有传输控制需求,这类需求往往是通过一个更高层的 Session 控制协议来满足。

这四个功能中,前三个应该会被所有应用场景使用(IP 组播机制下)。RTP 应用的设计者应该避免自己的应用只能工作在单播模式,RTP 应用应该设计成可拓展的,要考虑大量使用者并发时的情况。此外,RTCP 的传输应该根据发送者和接收者角色的不同而分别进行控制,例如一些单项连接,接收者的反馈信息就发不出来。

提醒:像是指定源组播路由(SSM),只有一个人可以发送数据,其他接收者不能用组播来和其他人直接通讯。对于这种情况,建议完全关闭接收者的原始 RTCP 功能,然后为这个 SSM 设定一个 RTCP 的适配器,来接收所有的反馈。

RTCP 包格式

RTCP 定义了许多包类型来传输不同的控制信息:

  • SR:发送者报告,发送者数据发送和接收的统计。
  • RR:接收者报告,只接收数据的节点的接收统计。
  • SDES:Source 描述,包括 CNAME。
  • BYE:表示退出。
  • APP:上层应用自定义。

每个 RTCP 包都有一个和 RTP 类似的固定格式的头,后面跟着长度不定的结构化数据,在不同 RTCP 类型时,这些结构化数据各不一样,但是它们必须都要 32-bit 对齐。RTCP 的头部是定长的,而且在头部有一个字段来描述这个 RTCP 数据的长度,因此 RTCP 可以被复合成一组一同发送,还不需要任何分隔符来分割出单个的 RTCP 包。下层协议可能会根据自己的情况决定将多少个 RTCP 报文复合在一起组成一个复合包。

复合包中的每个独立的 RTCP 报文都是无序的,而且可能会被随意复合。为了让协议的功能正常运作,会有如下限制:

  • 接收统计(SR|RR)的发送频率需要达到带宽的最大限制,因此每个周期发送的 RTCP 复合包都需要包含一个这类报文。
  • 一个新来的接收者需要尽可能快地得到数据源的 CNAME,因为它要用 CNAME 来确定每个数据源分别对应哪个人,并将数据源联系在一起进行同步,所以每个 RTCP 复合包必须包含 SDES CNAME(除非这个复合包被拆成两半一半加密,一般明文,这部分后面会介绍)。
  • 复合包中包类型的数量需要限制,这可以减少其他发错的包或者不相关的包被识别成 RTCP 包的可能性,还能增加第一个字中固定比特的数量。

因此,一个复合包中至少需要含有 2 种类型的 RTCP 报文,它的格式如下:

  • Encryption prefix:当且仅当这个复合包需要加密的时,那复合包在头部插入一个随机的 32-bit 数。如果加密算法需要填充数据的话,需要填充到复合包中的最后一个 RTCP 包后。
  • SS 或 RR:复合包中第一个 RTCP 包必须是一个报告报文,这可以加速报文头部数据的校验。即便没有 RTP 数据的发送和接收也要有一个报告报文,这种情况下必须发送一个空的 RR 报文,并且即便是这个复合包中的其他 RTCP 报文是 BYE 也要这么做。
  • Additional RRs:如果接收的 RTP 数据来自超过 31 个不同的源,前 31 个接收报告会写进 SR 或者 RR 报文中,多出来的接收报告应该紧跟着默认的报告报文(SR 或 RR)。
  • SDES:SDES 包必须包含 CNAME,每个复合包必须包含一个 SDES 包。如果上层应用有需要,也可以加入一些别的 SDES 报文,这视带宽限制而定。
  • BYE 或 APP:其他 RTCP 包类型(包括协议中还未定义的),可能以任意顺序跟在 SDES 后面,但是希望 BYE 包写在最后面(BYE 包需要和 SSRC/CSRC 一同发送)。

一个单独的 RTP 参与者应该在一个报告周期中只发送一个复合 RTCP 包,该周期每个参与者应该视带宽情况来估算,除非一个复合包被拆分加密。如果数据发送者的数量太多,以至于除了增加 MTU 这个方法之外,没办法将所有 RR 报文塞进一个复合包时,那么一次只会将部分 RR 数据塞进这个复合包,其他的数据就不发送了。当然,为了让所有源的接收情况都得到报告,会在多个周期内以环的形式循环共享所有源的接收情况。

为了减少数据包的开销,一般建议 Translator 和 Mixer 无论何时都能将多个源的 RTCP 报文复合成一个复合包。下图展示的就是一个 Mixer 生成的复合包的例子:

如果一个复合包的长度超过了下层网络协议的 MTU 的话,这个复合包会被拆分成多个更小的复合包分别发送。这不会对 RTCP 的带宽估计产生任何影响,因为即便 Mixer 的复合包被拆分成了多个更小的复合包,但是这个些更小的复合包也要满足 "每个复合包都要包含 SS 或 RR" 这一条件,所以每个更小的复合包至少也对应了一个参与者,这样 Mixer 生成的复合包就和它收到的 RTCP 包数量基本匹配,甚至更少。

如果某一客户端收到了它无法解析的 RTCP 类型的包,那它应该忽略这个包。附加的 RTCP 包类型会通过 IANA 进行注册。

RTCP 传输周期

RTP 的设计理念是它要能根据 Session 参与者的人数增加而进行自适应处理。例如,音频会议中同一时刻说话的一般也就那么一两个人(这就从内部限制了音频数据的传输),那么可以认为组播数据分发所用到的带宽资源和与会人数无关。控制信息的发送和音频数据的传输不同,每个人都会不停的发送 RTCP 报文,如果每个参与者的接收报告以同一个周期发送的话,RTCP 报文传输所消耗的资源会随着与会人数的增加而线性增加。因此,当与会人数增加时,RTCP 报文的发送间隔应该相应的动态地增大。

对每个 Session 来说,会有一个总的带宽限制(Session bandwidth),它会被分配给每个独立的与会者。整个网络的带宽可能会有所保留,并从网络层面强制限制 Session 的带宽。如果网络的带宽没有保留的话,也可能会有一些别的限制,不过这些都跟网络环境有关,总之最后会得出一个靠谱的 Session 最大带宽。Session 带宽可能会通过实际会消耗的网络资源进行评估,或者中途根据 Session 的剩余可用带宽来变化。

这些都和媒体数据的编码无关,但是会根据带宽的限制来选择具体使用哪种编码。通常来说,会预估 Session 中有多少参与者会同时发送数据,然后根据同时发送这类数据大概需要多少带宽这种方式来评估 Session 的带宽。在音频会议中,通常来说就是一个音频发送者所需要的带宽(一般同一时间只会有一个人说话)。对于分层编码这种情况,每一层都在一个独立的 RTP Session 中,这些 Session 都有自己独立的带宽限制。

在 RTP Session 中应该有一个管理应用来调整 Session 带宽,但是那些音频会议应用可能会基于 Session 中选用的编码格式,假设只有一个发送者发送数据,给自己设定一个默认的带宽限制。这个音频会议应用可能也会受到组播网络(或其他因素)的带宽限制。同一个 Session 的所有参与者必须使用统一的 Session 带宽限制,因为只有这样大家才是以一个相同的频率发送 RTCP 包。

Session 带宽评估过程需要考虑到下层的传输层和网络层是否有一些资源保留机制。而且上层应用也需要知道 RTP 下层使用了什么协议,但是不需要知道数据链路层及以下的协议,因为从数据链路层开始数据包的头就各不相同了。

控制报文的传输应该只使用 Session 带宽中很小的一部分,这样媒体数据的传输才会不受影响。建议 RTCP 传输使用 Session 带宽的 5%,媒体数据发送者至少要占用 1/4 的 RTCP 带宽,因为这样做的话,新加进来的人可以更快的收到媒体数据发送者的 CNAME。在某些预设中,如果发送者的数量超过 1/4 可能会完全关闭接收报告,虽然 RTP 协议标准并不推荐这样做,但是那些只有单向链路的系统或者不需要接收者反馈的系统一般是这么做的。

RTCP 报文的传输间隔一般都会稍微长一点,这样,当参与者的数量陡增时,报文的数量就不会超过带宽限制太多。当一个应用启动时,它应该等一段时间(一般是最小 RTCP 报文间隔的一半)再发送第一个 RTCP 报文,这样这可让发送间隔的计算更快的收敛。推荐 RTCP 报文发送的最小间隔是 5 秒。

RTP 的上层应用可能会使用更短的 RTCP 发送间隔,但是也会遵循如下原则:

  • 对于组播形式的 Session,只有数据发送者会使用更短的 RTCP 发送间隔。
  • 对于单播形式的 Session,无论是发送者还是接收者都有可能使用更短的 RTCP 间隔,并且它们发送初始 RTCP 前可能不会等待一段时间。
  • 所有的 Session 都应该根据最小 RTCP 发送间隔来确定参与者的超时时间。
  • 推荐的最小 RTCP 发送间隔时间使用 "360 kb/Session 带宽(kb/s)" 这种方式计算。这样当 Session 带宽大于 72kb/s 时,RTCP 发送间隔会小于 5 秒。

此外,为了让 RTCP 能在大型 Session 中正常运行,现有的算法还具有如下特点:

  • RTCP 报文发送间隔随着 Session 参与者的人数增加而线性地降低。
  • RTCP 发包间隔通常会随机缩放 0.5~1.5 倍,这样做是为了避免大量的参与者同时发送 RTCP 报文。
  • RTCP 复合包中包含的控制报文数据会根据收发包情况动态变化。
  • 因为 RTCP 报文间隔是根据已知的 Session 参与者情况计算的,所以当有新的人要加入到 Session 时,可能会错估整个 Session 的规模,而是用了较短的 RTCP 间隔,尤其是当大批量的人一齐加入 Session 时这种现象更加明显。所以,可能会有一个 "发送时机重整" 算法,它实现了一个简单的撤回机制,可以在 Session 规模持续增长时,适当的撤回一些 RTCP 报文。
  • 当有人通过发送 BYE 报文或者因为超时退出 Session 时,RTCP 的发送间隔应该缩短。
  • BYE 报文和其他 RTCP 报文相比,有一些特殊的地方。当有人想要退出,并发送 BYE 报文时,它可以在下一个发送周期到来之前就发送。当然,如果一大批人同时退出时,也会受到前面提到的 RTCP 报文撤回机制的影响。

维护 Session 成员的数量

我们已经知道了,计算 RTCP 发送间隔是需要清楚整个 Session 中成员数量的,当一个新的节点被监听到时,它就会被加入到 Session 总数中,并且大家要把它加入到一个 SSRC(CSRC)身份识别表中然后持续追踪。大家只有收到这个新节点的多个数据包,或者收到他的 SDES 包(CNAME)时才觉得这个新节点是靠谱的。当某个节点发了一个 BYE 之后,它的信息可能就会被大家删了,但是考虑到可能有丢包或者网络拥堵的情况,所以大家会先把它标记为 "收到 BYE",然后等一段时间,如果还没收到它的别的报文,这时候才会把它删了。

如果一个节点超过一个 RTCP 周期都没收到另一个节点的报文,它可能就会将其标记为不活跃,或者删了它,这就需要丢包的情况尽可能别发生。但是不丢包是不可能的,所以大家一般会将 RTCP 传输间隔乘以一个系数(大于 1 的数)作为超时时间。

对于那些参与者很超级多的 Session,可能没法去维护一个 SSRC 表来存储所有参与者的信息。通常大家都会简化这个 SSRC 表,但是需要注意的是无论怎么简化这个表都不能低估了参与者的总数,可以允许高估参与者总数。

RTCP 报文的收发规则

首先,无论是组播还是多个节点的单播都必须遵循前面提到的 RTCP 间隔。为了正常完成 RTCP 报文的收发操作,Session 中的每个参与者都会维护如下信息:

  • TP:最后 RTCP 报文的发送时间;
  • TC:当前时间;
  • TN:下一个要发送报文的时间点;
  • P-Members:计算上一个 TN 时参考的 Session 成员总数;
  • Members:当前的 Session 成员总是;
  • Senders:数据发送者总数;
  • RTCP_BW:RTCP 的目标带宽;
  • WE_Sent:从倒数第二个 RTCP 报文发送后,到现在为止,是否发送过数据;
  • AVG_RTCP_Size:平均 RTCP 复合包大小,包括传输层和网络层的头;
  • initial:是否一个 RTCP 报文都没发过。

计算 RTCP 发送间隔

为了让 RTP 协议具有可伸缩性,RTCP 的发送间隔需要随着 Session 总人数的变化而适当的缩放。结合上述的部分状态,我们按如下方式计算 RTCP 报文间隔:

  1. 如果媒体流发送者的数量小于总人数的 25% 时,这个间隔和当前节点是否是媒体流发送者有关(通过 WE_Sent 判断)。如果是媒体流发送者,计算公式为 Senders AVG_RTCP_Size / (25% RTCP_BW),如果是媒体流的接收者,计算公式为:(Members - Senders) AVG_RTCP_Size / (75% RTCP_BW)。当媒体流发送者的数量超过 25% 时,发送者和接收者会被同等对待,即它们的 RTCP 周期公式为:Members * AVG_RTCP_Size / RTCP_BW。
  2. 如果某个参与者一个 RTCP 包都还没发送,最小发送间隔间隔(Tmin)为 2.5 秒,否则为 5 秒。
  3. 决定的发送间隔(Td)会是第一步计算的值和 Tmin 中较大的那个。
  4. 发包时会在 Td 的基础上随机缩放 0.5~1.5 倍。
  5. 最终这个间隔还要除以 e-3/2=1.21828,这是为了弥补因为 "发送时机重整" 算法带来的影响(因为这个算法会导致最终 RTCP 使用的实际带宽比预计使用的带宽低)。

初始化

当一个人刚加入到 Session 中时,tp=0,tc=0,senders=0,p-members=0,members=1,we_sent=false,rtcp_bw = 5% * Session 带宽,initial=true,avg_rtcp_size 被设置为之后会发送的首个 RTCP 包的大小,然后计算发送间隔 T 时,会根据上述初始状态进行计算,并以此作为参考发送第一个包,最后将自己的 SSRC 加入到成员列表中。

接收 RTP 和 Non-BYE RTCP 包

当 RTP 或者 RTCP 包被另一个人(A)接收到了,如果对 A 来说这个包的 SSRC 他没见过,那么他就会将其加入到 SSRC 表中,并更新 Session 总人数(Members)。对每个 CSRC 也会做同样的操作。

如果收到了一个 RTP 报文,并且其对应的 SSRC 没在发送者 SSRC 表中,那他就会把它加进发送者 SSRC 表中,并更新发送者的总数(Senders)。

当每个复合 RTCP 报文被接收到时,平均 RTCP 报文大小(AVG_RTCP_Size)的状态就会更新,更新公式为:AVG_RTCP_Size = (1 / 16) last_rtcp_package_size + (15 / 16) previous_avg_rtcp_size。

接收 RTCP BYE 报文

如果接收到了 RTCP BYE 报文,会在成员列表中确认一下,如果有对应的 SSRC 项,就会把它移除并更新成员总数(Members)。同时也会在发送者 SSRC 表中做类似的操作,如果找到了就删除它并更新发送者总数(Senders)。

此外,为了让 RTCP 的传输率跟随 Session 中人数的变化而动态变化,如下算法会在收到 BYE 报文时执行:

  1. TN 按照如下公式更新:TN = TC + (Members / P-Members) * (TN - TC) 。
  2. TP 按照如下公式更新:TP = TC - (Members / P-Members) * (TC - TP) 。
  3. 下一个 RTCP 报文按照新的 TN 指示发送(比原来发的更早了)。
  4. 将 P-Members 设置成 Members 的值。

这个算法没有考虑到一个意外情况,那就是当一大波人(并不是所有人)同时退出 Session 时,会导致 RTCP 的周期降到一个非常小的值,这样可能出现错误的 Timeout 判断,最终它会导致整个 Session 的总人数降到 0。但是,这种情况一般来说很少发生,所以大家都觉得问题不是很大。

SSRC 的超时

我们需要偶尔确认一下是不是太久没收到某个与会者的报文了,一般来说每个 RTCP 周期内都必须确认。如果发现了超时,就需要将这个 SSRC 从成员列表(Members & Senders)中移除,并更新当前人数。

Member 表:一般超过 5 个发送周期(不考虑随机缩放因素)未收到某人的消息,会被确定为超时。

Sender 表:一般是 2 个发送周期。

如果某个成员被确定为超时,上一步介绍的算法就操作起来了。

发送倒计时

我们已经知道,每个 RTCP 都是周期性的发送的,当发送完一个 RTCP 报文时,就会根据 TN 新建一个倒计时,每次当倒计时归零时就会重复如下操作:

  1. 计算传输周期 T,引入随机缩放因素。
  2. 如果 TP + T <= TC,立即发送一个 RTCP 报文,并将 TP 设定为 TC,TN 设定为 TC + T,下一个倒计时会在 TN 时刻归零。如果 TP + T> TC,就不发送了 RTCP 报文,计算 TN = TC + T 后,然后重设一个定时器在 TN 归零。
  3. P-Members 设定为 Members。

如果发送了 RTCP 报文,initial 会被设定为 FALSE,AVG_RTCP_Size 会按如下方式更新:
AVG_RTCP_Size = (1 / 16) last_rtcp_package_size + (15 / 16) previous_avg_rtcp_size。

发送 BYE 报文

当某个人想要退出 Session 时,他就会发一个 BYE 报文给其他人。为了防止一大帮人同时退出 Session 时出现 BYE 报文井喷的情况,所以当 Session 人数超过 50 时,会按下述方式操作:

  1. 当一个参与者想要离开时,TP 会设置成 TC,Members 和 P-Members 会设置成 1,initial 设置成 1,we_send 设置成 false,senders 设置成 0,avg_rtcp_size 设置成复合 BYE 报文的大小。然后计算 RTCP 发送间隔 T,下个 BYE 报文会在 TN = TC + T 后发送。
  2. 每当这个要离开的人收到了别人的 BYE 报文时,Members 就会增加 1,无论这个人是否在成员列表中。Members 的数量只有收到 BYE 报文时才增加,其他报文都不管。同样,avg_rtcp_size 也只管收到的 BYE 报文的大小。Senders 数量也不变。
  3. 对了 BYE 报文来说,除了状态值的维护套路变了,发送逻辑和前面提到的都一样。通过上述方案,即可以让 BYE 报文正确地发送,还能控制整体带宽。最差的情况下,也只会导致 RTCP 报文传输占用 10% 的 Session 总带宽。

有些参与者可能不想按照上述的方式发送 BYE 报文,他们可能什么也不发就离开了。这类情况会被 Timeout 机制 hold 住。

如果一个参与者要离开时,Session 的总人数小于 50,他可能会直接发送一个 BYE 报文,也可能按照上述方案来进行。

此外还有一个无论如何都要遵循的规则,如果一个参与者一个 RTP 报文或者 RTCP 报文都没发送过的话,那他离开 Session 时绝对不能发送 BYE 报文。

更新 WE_Sent

当某个参与者最近发送过一个 RTP 后,他就会将 WE_Sent 置为 true 并将自己加入到 Senders 表中,否则如果超过两个 RTCP 发送周期的时间内都没发送过 RTP 报文,那他就会将自己从 Sender 表中移除,并将 WE_Sent 置为 false。

SDES 类报文的带宽分配

SDES 报文中除了必须要有的 CNAME 之外,还有一些别的信息,比如 NAME(个人名称),EMAIL(email 地址)等。上层应用也可以自定义的一些报文类型,但是要小心别付加了太多的自定义信息以至于拖慢了整个 RTCP 协议的运转。建议这些附加内容的带宽占用不要超过整个 RTCP 协议带宽的 20%。此外,也不要觉得每个上层应用都会包含所有的 SDES 内容。上层应用要根据实际使用的情况给这些内容分配一定的带宽,一般来说他们会通过控制发送间隔来控制这部分的带宽。

比如,一个应用的 SDES 可能只包含 CNAME,NAME,和 EMAIL,其中 NAME 可能就会比 EMAIL 分配更多的带宽。因为 NAME 会一直显示出来,而 EMAIL 可能只在点击查看的时候才显示。在每个 RTCP 发送周期里,SDES 中都会包含 CNAME。如果假设 RTCP 周期是 5 秒的话,可能每 15 秒 SDES 才会附带一个除 CNAME 以外的信息,以 2 分钟为例,其中 7 次附带的是 NAME 信息,1 次附带的是 EMAIL 信息。

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 13 收藏 5 评论 0

阿里云视频云 发布了文章 · 2月8日

如何用 4 个小时搭建一个新 “Clubhouse” ,引爆声音社交新风口

Clubhouse,基于实时音频技术的声音社交现象级火爆

最近,让硅谷两位顶级 VC 大打出手争相投资的 Clubhouse 火到了国内,甚至在社交圈里 “一码难求”,此种火爆程度的产品堪称现象级。那它究竟是有什么魅力让 2020 年 4 月诞生的它快速引爆投资圈、明星圈及互联网界呢?

Clubhouse 是一款主打即时交流的音频社交平台,其本质无非是实时音频技术,与我们所熟识的基于 RTC 技术的语音聊天室场景并无区别,所以在技术层面我们随时都可以做到,这个时代只是欠缺一个想象!

“声觉空间是有机的、不可分割的,是通过各种感官的同步互动而感觉到的空间。与此相反,理性的或图形的视觉空间是一致的、序列的、连续的,它造成一个封闭的世界,没有回音和共鸣。“ 即说明,向内观察,赋予想象,音频社交空间,俨然可以变成一个全新的社交生态。

现象级爆款的出现,引来无限的空间可能,国内的很多玩家都可以入局拓展语聊玩法,已经在赛道里的玩家可以趁势打磨产品起量。耳朵经济相比眼球经济,其实解放了双手,音频社交赛道正在繁荣。试想一下,法律普及、兴趣班、心理讨论沙龙、脱口秀、线上个唱、狼人杀 & 剧本杀等,都可以在语聊室玩出新高度 ...... 我们好像迈进了人人播客时代,同时,也在迅速催生全新形态的线上社区。

AliRTC,实现低门槛快速搭建 Clubhouse 般语聊室

语音聊天室一般由主播和在线观众组成。房间内在线观众可以听到主播的声音,在线观众也可以通过上麦功能参与语音互动。同时在语音互动过程中互动者具备丰富的功能玩法,例如播放背景音乐、播放鼓励音效、设置混响变声等音频效果。

AliRTC 语聊室 demo 展示

AliRTC 语聊室场景描述:

  • 场景:一个房间内麦上 6-12 人,麦下观众旁听房间聊天,观众一般在百到千人,部分热门房间观众数超过万人。
  • 方案:麦上用户通过 RTC 服务接入,支持背景音乐、伴奏、音效、耳返、美音,根据麦下提供二种方案:

    • 互动模式:通过 RTC 频道互动模式加入,麦上麦下均为 RTC,延时全端在 250 毫秒体验最佳,无混流方案端拉多流。
    • 旁路转推方案:麦上 RTC 加入,麦上媒体经过 RTC 混流转码后旁路转推到视频直播, 需要客户自行开发房间事件服务和集成直播流播放器。

AliRTC 语音房架构方案:


体验 AliRTC demo,每位开发者都可快速自建语聊室,人人都是语聊播主!

AliRTC 语聊室场景优势:

• 全球实时传输网络,基础设施级的节点建设和连通国内 20+ 运营商,保证通话时效性和通话质量;
• 自研弱网传输算法,音频抗丢包 70%,同等丢包环境,弱网传输效率提升 65%,弱网条件下更稳定;
• 趣味音频音效功能,提供混响 / 变声等多种预设音效,为场景增添趣味性。
AliRTC(阿里云音视频通信),让每一位开发者可以在 4 小时内快速搭建体验,且更完善达到上线要求。同时,提供源码开放、集成文档,“拿来” 即上线,助力开发者快速迭代抢占先机。

开源地址:https://help.aliyun.com/docum...

体验下载:

AliRTC 为语音聊天室提供 Android 和 iOS 的 demo通过钉钉扫码下载

更多场景模块,AliRTC 也可实现低门槛快速搭建

在此款 demo 中,除语聊室,AliRTC 还集合了众多 RTC 相关场景的基础通信服务,包含一对一语聊、音视频通话、互动大班课、超级小班课、视频互动直播。
开发者可结合自身业务需求,自由选择模块。下载源码,集成接入自身产品,实现快速搭建。

AliRTC 体验 demo 展示

想获得更多相关信息和更多有趣 demo,可入群,一起玩转实时语音!

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2月4日

阿里云联手 Intel 举办首届视频云挑战赛,40 万奖金邀你来战!


本届全球视频云创新挑战赛是由阿里云联手英特尔主办,与优酷战略技术合作,面向企业以及个人开发者的音视频领域的数据算法及创新应用类挑战。

本届大赛包括两个赛道:“算法挑战赛” 和 “创新应用挑战赛”,参赛选手可以自由报名参加任一赛道。选手可以在视频分割挑战以及视频创新应用领域中,发挥自己的创造力,探索视频云技术在互联网、零售、文娱、安防、文化、教育、金融、交通、公共安全、日常生活、公益等行业领域的应用。

赛程安排

• 报名与实名认证(即日起 —2021 年 4 月 14 日,UTC+8)
• 初赛(2021 年 2 月 1 日 - 4 月 16 日,UTC+8)
• 复赛(2021 年 4 月 21 日 - 2021 年 6 月 18 日,UTC+8)
• 总决赛(2021 年 6 月底,UTC+8)

赛道介绍

算法挑战赛道

算法挑战赛道聚焦视频人像分割领域。视频物体分割将传统图像分割问题延伸到视频领域,可服务于视频理解处理和编辑等任务,近年来成为计算机视觉领域备受关注的研究问题,该问题的目标是在视频帧中分割目标物体的区域,需精确到像素级别。作为视频中经典而重要的内容,人像分割任务将作为本竞赛的分割目标。

创新应用赛道

创新应用赛道要求应用指定的相关技术,解决视频领域和相关行业的的痛点问题,实现应用场景的创新,以技术可行性 / 前瞻性 / 落地价值作为重要评审考察点。

奖项设置


PS:现金大奖哦!

此外,复赛审核通过的排名前 12 队伍,可进入阿里云校招绿色通道哦。

参赛须知

1、报名方式:登录比赛官网,完成个人信息注册,即可报名参赛;
2、选手可以单人参赛,也可以组队参赛。组队参赛的每个团队 2-3 人,每位选手只能加入一支队伍;
3、选手需确保报名信息准确有效,组委会有权取消不符合条件队伍的参赛资格及奖励;
4、选手报名、组队变更等操作截止时间为 4 月 14 日中午 12 点;
5、各队伍(包括队长及全体队伍成员)需要在 4 月 14 日中午 12 点前完成实名认证(认证入口:天池官网 - 右上角个人中心 - 认证 - 支付宝实名认证),未完成认证的参赛团队将无法进行后续的比赛;
6、大赛官方钉钉群请扫描以下二维码加入,最新通知将会第一时间在群内同步:

进入复赛的企业参赛团队有望成为阿里云视频云合作伙伴哦!

点击链接,即可报名或查看更多赛事信息!
https://tianchi.aliyun.com/sp...

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 1月20日

阿里云 RTC QoS 屏幕共享弱网优化之若干编码器相关优化

屏幕共享是视频会议中使用频率最高的功能之一,但在实际场景中用户所处网络环境复杂,常遇到丢包或者拥塞的情况,所以如何优化弱网环境下的用户体验也成为了音视频通信中重要的一环。本文主要分享阿里云 RTC QoS 如何通过若干编码器相关优化提升弱网环境下的屏幕共享体验。

作者:长程/田伟峰
审校:泰一

内容说明:

本文介绍以下四个方面的优化:

  • Screen Content Coding Tools
  • Long Term Reference (LTR)
  • Fast QP Rate Control
  • Content Adaptive Frame Rate

对本文中效果测试的说明:

  1. 为保证视频质量,屏幕共享的 Codec 设置了最大 QP (Quantization Parameter),在本文后面的测试中,这个最大 QP 对所有配置都是一样的。
  2. 为了更好的比较,效果测试中展示了流畅性和清晰度两个维度的效果。
  3. 对于流畅性的比较,由于视频分辨率太大,所以对其画面进行了缩放,使得原始版本和改进版本可以放在同一个视野中播放,以很好的看到卡顿和延迟的改进。画面模糊是由于上述原因降分辨率所致,在清晰度的对比中可以看到原始分辨率的画面。

Screen Content Coding Tools

Codec 中增加并改进了对于屏幕内容(其内容多为如 Word、Excel、PPT 等计算机生成的图形,文字等,具有重复纹理多,色彩分布较离散,无噪声等特点)特别适合的 Coding Tools 如 Intra Block Copy (Current Picture Reference),Transform Skip 等,显著提升了屏幕内容的压缩效率,可以做到相对于原 Codec,对于屏幕内容视频相同质量下平均可以节省带宽 20%+,编码时间只增加 10%,对某些典型序列场景带宽可以节省 40%+,由于是非标准的 Coding Tools,实际使用中会根据 SDP 协商,当所有与会方都支持该特性时才会开启。

SCC Tools 效果

流畅性效果

测试 SCC Tools 在弱网下的改进效果。

test1.gif

上图中,上半部分是增加了 Screen Content Coding Tools 编码出来的码流,下半部分是原始 Codec 编出的码流,可以看出使用 Screen Content Coding Tools 之后卡顿和延迟明显降低了。

清晰度效果

上面的动图可以看到卡顿情况的改进,下图展示的是清晰度的对比,左边是原始版本,右边是增加了 SCC Tools 版本,可以看到 SCC Tools 版本和原始版本相比清晰度并没有下降。
compare1-min.png

Long Term Reference (LTR)

长期参考帧技术是一种网络模块和编码器共同配合完成的参考帧选择技术。在 RTC 场景下一般的编码参考策略是向前一帧参考(在不考虑 SVC 的情况下),因为参考的距离越近压缩效果越好,出于实时的考虑编码只有 I 帧和 P 帧,没有 B 帧。而长期参考帧是一种可跨帧的参考帧选择策略,这种策略打破了传统的向前一帧的限制,可以更加灵活地选择参考帧。

长期参考帧策略的目的是在有 P 帧丢失的场景下,接收端不需要重新请求 I 帧也能继续正确的解码和播放,其相对于 I 帧可以明显提升编码效率,节省带宽。该技术可以绕过丢失的帧,利用丢失帧之前的一个已经接收的长期参考帧作为参考进行编码 / 解码显示,从而提升弱网场景下的视频流畅性。

LTR.png

根据长期参考帧的特点和目的,实现长期参考帧技术的应用需要有接收端侧反馈信息,编码器根据接收端反馈的帧信息选择参考帧编码,在有 P 帧丢失的场景下,接收端通过请求长期参考帧恢复将很快恢复画面。由于存在接收反馈,高 RTT 时反馈延时较大将会导致参考距离变大,所以长期参考帧比较适合低 RTT 场景。在多人会议场景中,下行人数太多也会制约长期参考帧的应用。

综合来看,长期参考帧更适合 1V1 的通信场景,适合低 RTT 伴随丢包或者拥塞的弱网场景,这样的场景下长期参考帧比传统的向前一帧参考有更好的实时性和流畅性。

LTR 效果

测试 LTR 以及 SCC Tools 在屏幕共享弱网下的效果。

流畅性效果

test2.gif

上图中左上部分是没有 LTR 的效果,几乎完全卡死,左下部分是使用了 LTR 的效果,可以看到屏幕有所滚动,比左上的更流畅。同时该场景我们还进一步测试了增加 Screen Content Coding Tools,右边的是同时使用了 LTR 和 SCC 的效果,可以看到明显是三者中最流畅的。

清晰度效果

下图中左边是原始 Codec 效果,中间是增加了 LTR 的效果,右边是同时增加了 LTR 和 SCC 的效果,可以看到三者的清晰度并没有明显差别。由于该例子我们共享的是实时的滚屏 Log,所以三者的内容不是完全一样的,但是总体差别不大。

compare2-min.png

Fast QP Rate Control

屏幕共享经常会有这样的场景:演讲者的桌面静止很长时间,然后翻页。对于编码器来说,画面静止一段时间会导致 QP 逐渐降低至最小 QP,然后翻页画面突变,编码器为了控制住码率,会增加 QP。常规的编码器码率控制为了使每帧的视频质量平稳变化,会限制每帧的 QP 调整幅度,相邻两帧的 QP 变化不能太大,以得到平稳的视频观看质量体验,这样翻页时就会有一个码率的突然增加,至码率收敛到目标码率会有一个过程,在网络好的时候没有关系,可以容忍码率的波动,但是在弱网时,该码率突增就会造成卡顿,影响观看体验。

因此,我们增加了另一种编码器码率控制模式,即码率快速收敛模式 Fast QP,主要原理是其可以摆脱相邻帧 QP 不能变化太大的限制,使得实际码率可以快速收敛到目标码率,然后配合网络层的带宽估计技术,在检测到弱网的时候启用这种编码器码率控制模式,使得视频码率非常平稳,观看体验更加流畅,虽然这样可能牺牲了一些相邻帧质量变化的感受,但是此时卡顿率的体验更加明显和重要,这种平衡和取舍是值得的。

Fast QP 效果

下面展示弱网下的 Fast QP 效果。

流畅性效果

图片(示意图因平台限制图片上传大小,无法上传。可前往公众号查看原文。)

上图中一开始的那个画面(有 Twitch 单词紫色背景)是几乎静止的,只有很小范围的变化,持续了近 10 秒钟,编码器 QP 逐渐下降至很低,然后翻页到一个较复杂纹理的页面(各种分辨率卡条),此时可以看到右下的使用 Fast QP 的画面基本上可以跟得上上方本地预览画面的变化,而左下的没有开 Fast QP 的画面就卡住了,然后又翻了两页,Fast QP 版本均能跟得上变化,而没有开 Fast QP 版本直到最后一页才恢复。

清晰度效果

这里我们只展示了翻页前的清晰度,对于翻页的清晰度,由于原始版本卡住了,所以就没有展示。左边是原始的,右边是加了 Fast QP 的清晰度,由于两者都是 QP 值降到了很低,所以清晰度都很高,没有什么差异。

compare3.png

Content Adaptive Frame Rate

屏幕共享的时候如果是共享桌面文档,PPT 等内容进行演讲,一般翻页速度是相对较慢的,帧率不用很高 5-10 帧每秒即足够;而有时屏幕共享也会播放电影,动画等视频,这样要求的帧率就比较高了,最好能到 20-30 帧每秒才看起来比较舒服。如下面两图,一个是编辑 PPT 的视频,明显帧率可以比较低,另一个是广告视频,明显帧率应该高一些。

图片(示意图因平台限制图片上传大小,无法上传。可前往公众号查看原文。)

图片(示意图因平台限制图片上传大小,无法上传。可前往公众号查看原文。)

如果我们把帧率定死,如 FPS=5,则碰到播放电影的场景就显得卡顿了;而如果我们把帧率直接定成 FPS=30,那在一般共享文档,PPT 的场景又会造成码率的浪费。

视频编码器里的运动矢量 MV 信息可以很好的反应画面的运动状况,如果是共享的文档,PPT 等不怎么动的画面,则大多数 MV 都等于 0 的,而如果是播放电影,动画等运动较多的场景,则 MV 会有一定的数值,所以利用编码器的 MV 信息可以很好的判断当前共享视频的运动状况,选择合适的帧率。

由于编码器是本来就在那里的,所以不会增加额外的运动检测模块开销。本改进就是针对这种需求,根据屏幕共享的内容,利用编码器输出的 MV 信息,自适应的调整帧率。对于共享文档等不怎么动的画面,则放低帧率,对于共享电影动画等,则调高帧率,以达到既不浪费带宽,也可以有流畅的视频观看体验的目的。

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 1月18日

WebRTC 的现状和未来:专访 W3C WebRTC Chair Bernard Aboba

WebRTC 无疑推动和改变了互联网视频,而这仅仅是刚刚开始,除了大家熟悉的 WebRTC-PC、Simulcast 和 SVC,有太多的新技术和新架构出现在 WebRTC 新的标准中,比如 WebTransport、WebCodecs、AV1、E2EE、SFrame、ML 等等,这篇文章详细介绍了未来的 WebRTC-NV,不容错过。

说明:
本文为阿里云视频云翻译的技术文章
原文标题:WebRTC Today & Tomorrow: Interview with W3C WebRTC Chair Bernard Aboba
原文链接:https://webrtchacks.com/webrt...
作者:乍得・哈特(Chad Hart)
翻译:忘篱、致凡、开视、仲才、海华

Bernard 是一直聚焦在 RTC 领域的专家,W3C WebRTC 联席 Chair,WEBTRANS 和 AVTCORE 的联席 Chair,ORTC、WebRTC-SVC、WebRTC-NV 用例、WebRTC-ICE、WebTransport 和 WebRTC-QUIC 等文档的主编,微软 Teams 媒体组的首席架构师。

WebRTC 标准现状

作为 W3C WebRTC 工作组的 Chair 之一,Bernard 是 WebRTC 标准的权威人物,所以我们从工作组的章程开始聊起。

Bernard: W3C WebRTC 工作组的主要工作包括以下三个方面:

  1. 目前最重要的工作,是完成 WebRTC Peer Connection (WebRTC-PC) 标准化,以及相关的标准比如 WebRTC-Stats
  2. Capture,Streams 和 Output 相关的标准,包括 Media Capture and StreamsScreen CaptureMedia Capture from DOM ElementsMediaStream Image CaptureMediaStream RecordingAudio Output Devices,以及 Content-Hints
  3. 下一代 WebRTC,也就是 WebRTC-NV。

WebRTC-NV 是下一代 WebRTC,在当前 WebRTC 1.0 之后的标准。

Bernard: WebRTC-NV 的工作主要是四个方面。

  1. 第一类是对 WebRTC PeerConnection 的扩展。这包括 WebRTC Extensions, WebRTC-SVC, 以及 Insertable Streams。我特别强调这些工作都依赖于 “Unified Plan”,现在已经是默认的 SDP 规范了。例如,如果要使用 Insertable Streams 来支持 E2EE (End-to-End Encryption,端到端加密),那么首先要支持 “Unified Plan”。
  2. 第二类,和 WebRTC-PC 相比,还不够成熟和完善,比如 WebRTC IdentityWebRTC Priority ControlWebRTC DSCP
  3. 第三类是对 Capture 的扩展,比如 MediaStreamTrack Insertable StreamsMedia Capture and Streams Extensions,和 MediaCapture Depth Stream Extensions
  4. 第四类是独立的标准,它们没有必要依赖 RTCPeerConnection 或者 Media Capture。比如 WebRTC-ICE,目前就是独立的标准。有些标准不是 W3C WebRTC 小组定义的,比如 WebTransport,由 W3C WebTransport 小组定义;WebRTC-QUIC,由 ORTC 小组定义;WebCodecs,由 WICG 定义。

有时候 “NV” 很含糊而且挺令人迷惑的,它最初是指 ORTC,但今天它主要是指多个标准,而不是某一个单一的标准。目前 “NV” 比较含糊,有时候指的是 RTCPeerConnection 的扩展,或者 Capture APIs,或者其他的 API 比如 WebTransportWebCodecs,所以当提到 “WebRTC-NV” 时,最好能确认下到底是指什么。

WebRTC 标准的流程
WebRTC 的协议由 IETF 定义,而浏览器相关的 API 则由 W3C 定义。在标准化的过程中,是存在一些争议的。
Bernard 介绍了标准化过程的一些背景。

Chad: 能介绍下 W3C 标准制定的阶段吗?
Bernard: 第一个阶段是 CR,Candidate Recommendation。CR 意味着被广泛 Review 过了,符合标准工作组的要求,并且是可以实现的。在 CR 阶段,标准可能还未被完全实现,可能存在一些有风险的功能,或者浏览器之间的互操作性(interoperability)问题。

完整流程可以看这个链接

Chad: 您刚才提到最后一个 CR 阶段,请问这意味着在整个 W3C 的 specification 流程中会有多个 CR 阶段,还是整个 CR 阶段内还有多个子阶段?
Bernard:现在 W3C 有新的流程,适用于讨论定稿的活跃标准。不管是上述哪种情况,我们讨论的是在进入 PR 阶段前的最后一次 CR 阶段。
在 PR (Proposed Recommendation) 这个阶段时,标准中的内容都已经被实现,并且通过了互操作性测试。PR 时会收集足够需要的数据,例如 Peer Connection 涉及到了海量的数据,包括 WPT 测试结果,以及 KITE 测试结果。


WPT 是指 web-platform-tests,属于 W3C 实现的 API 的功能性验证测试,可以参考 https://wpt.fyi
KITE 是一个开源项目,属于 WebRTC 专门的互操作性测试。Dr. Alex Gouaillard 在这篇文章中有详细讨论 Breaking Point: WebRTC SFU Load Testing

Chad: WPT 是通用的功能测试,而 KITE 是 WebRTC 专门的互操作性测试。
Bernard: WebRTC 的 WPT 测试,只跑在单一的浏览器上;WebRTC 的 WPT 测试没有针对服务器的测试,而 WebTransport 是有的。因此 WebRTC WPT 测试,无法验证浏览器的互操作性,也无法验证浏览器和服务器的互操作性;而 KITE 测试是覆盖了这些方面。
Chad: 这实际上是 WebRTC 的特点,从一个浏览器发送媒体数据到另外一个浏览器。
Bernard: 为了能更方便的看到 WPT 的测试覆盖率,我们在规范中做了标记。所以除了测试结果,你还可以看到标准已经有多少被测试覆盖和验证了。

新冠对标准工作的影响
新冠对 WebRTC 产生了有趣的影响。一方面,新冠导致 WebRTC 使用量剧增,让整个社区很繁忙,也更关注扩展性和可靠性。另外,对现有的工作流程也有打断,这是否也影响到了 W3C 标准化工作?

Bernard: 我们努力向 W3C 证明,我们已经准备好进入 PR 阶段了。这是非常大的一步,但总体上还是因为新冠导致进展变慢了。虽然我们取得了很大的进展,但是新冠让大家都慢了下来。
Chad: 是因为大家忙于支持自己暴增的业务,还是没法把大家聚在一起工作?
Bernard: 新冠打乱了很多事情。例如 KITE 互操作性测试,是需要人工参与的,所以现在我们需要很费劲才能测试,因为现在大家不能在一个地方工作,特别大家还是在全球各地;想象下如果按照其他人正常上班的时间,你可能要凌晨 3 点配合测试,这是多么痛苦。
新冠不仅打乱了测试,也影响了代码实现的进度,具体可以看下面的图。目前几乎所有 PR 中的功能,都已经在至少一个浏览器中实现了,但是之前我们预期是 2020 年秋季在两个以上浏览器实现。所以目前测试和代码实现都比我们预期的慢。

来源:TPAC-2020-Meetings

WebRTC 标准有多重要?
WebRTC 目前在主流的浏览器中都已经支持,现在 WebRTC 承载了世界上主要的 VoIP 的流量,这个节点上开始下一阶段的标准化工作是否很重要?
Bernard 认为标准化不仅是写文档,更重要的是关于互操作性 (interoperability)。

Bernard: 标准主要关注测试和稳定性。WebRTC PC 最大的一个挑战就是它包含了太多的能力,只要稍微看下它相关的主要的 Bug,就可以发现对它的覆盖率远远不够;即使我们不要求完整覆盖,而只考虑 “可接受” 的覆盖率,也非常的困难;比如在复用 (Multiplexing) 方面,我们有个 Bug 影响了线上服务,但是我们却没有覆盖它。我们看到有很多 Bug 都是 WPT 覆盖不到的,这些只有 KITE 这种互操作性测试才能覆盖到,但是目前我们在 KITE 中还没有达到 100% 覆盖。
我在 RTC 领域中,碰到的最大的挑战之一,就是海量的测试用例。Chad,想象下如果让你去做测试,即使要达到 95% 的覆盖也是非常的有挑战的。当然完整的测试非常有帮助,我们当然也要考虑完整覆盖带来的巨大挑战,真的非常的难。

WebRTC 扩展

WebRTC 正在渗透越来越多的行业和场景。WebRTC 1.0 还在标准化的过程中,所以必须要想清楚如何添加新的功能。正如 Bernard 解释的,WebRTC Extensions 包含了很多没有添加到 WebRTC 1.0 的功能。

Bernard: 有很多标准都依赖 RTCPeerConnection,例如 WebRTC extensions,还有比如,RTP header extension encryption,WebRTC SVC (Scalable Video Coding)。我认为 Insertable Streams 也算 WebRTC PC 的扩展。一般情况下,都是假设使用 RTCPeerConnection 的前提下。

更安全的访问媒体设备

随着视频会议的广泛使用,出现了摄像头被错误使用,以及意外的屏幕共享等问题。另外,一般来说,在 WebRTC 服务中如何快捷访问摄像头通常是一个问题,如何平衡好隐私问题和便捷性是一个难题。此外,媒体设备可能会被恶意标识和获得个人身份信息 (fingerprinting),这对隐私保护造成很大的挑战。
getCurrentBrowsingContextMedia,是尝试解决这个难题的提案之一。

Chad: 是否能聊聊 getCurrentBrowsingContextMedia 提案?
Bernard: 这个是一个扩展标准,我认为它是 Screen Capture 的扩展。让我们先看看 Media Capture 的问题吧,主要是关于隐私和安全方面的问题。我们发现 Media Capturing Streams 对隐私很不友好;假设你把所有的媒体设备信息都给了应用,包括你没选中的设备,那么这就会造成身份信息的一个隐私问题,因为我知道了你所有的设备信息,尽管你可能不想使用的某个涉及隐私的摄像头,我也知道你有这个摄像头了。这些设备能帮助获得和标识个人身份信息 (fingerprinting),所以 Jan-Ivar 提交了一个提案,设计了和 Screen Capture 很类似的一个模型。
在 Screen Capture 共享屏幕时,应用只有获取用户选中的 Surfaces 的权限,用户没有选中的没有权限。所以并不是我能获取你打开的所有页面的权限,不然就可能看到你的一些隐私页面,比如正在购买的东西等。只有当用户选择某个页面后,应用才能获取权限并启动 Screen Capture,这就是 Jan-Ivar 提案的模型。它也会成为浏览器 Picker 的一部分,应用只能获取用户选中的页面的权限。这是个很大的变化,也引入了 Media Capture 和 Media Streams 的一些问题,比如都让用户选择指定的设备了,那么 Constrains 的意义在哪里,如果不匹配呢?
Chad: 这是否意味着,关于设备 Picker 会有更多的标准出来?
Bernard: 嗯,是的。当然我们会不断改进我们的模型,Jan-Ivar 会提交一个单独的标准,解决所有这些问题。这里麻烦的是,这是一个全新的模型,如何让大家能使用起来,可能需要花很长时间。

TPAC slide on getBrowserContextMedia proposal. 来源: TPAC-2020-Meetings

WebRTC 下一代标准

标准之争的结果之一,就是不愿给出版本号,因为大家可能会有分歧,比如应该用大版本(1.0、20),还是小版本(1.1、1.2)。曾经有段时间 ORTC 是作为 WebRTC 的候选标准。WebRTC 1.0 整合了很多我们讨论到的内容。关于后续版本,最终还是使用了一个温和和不确定的名字:“WebRTC Next Version”,或 “WebRTC-NV”。
Bernard 解释了这到底是什么意思。

Chad: 我们聊到 WebRTC 的 “Next Version”,但不是 WebRTC 2.0,是因为 WebRTC 1.0 还没完成吗?
Bernard: 我们是时候考虑不用 NV 这个词了,因为它代表两个完全不同的东西。其一,NV 是只 Peer Connection 的扩展,比如 Insertable Streams、WebRTC Extensions、WebRTC SVC 等,这些差不多就是 ORTC 定义的东西,不过已经整合到了 WebRTC-PC 中了。
其二,NV 指的是前面我提到的独立的标准,比如 WebTransport、WebCodecs、WebRTC ICE,这些完全是独立的,并不依赖 RTCPeerConnection。所以这确实是一个很大差异的版本,和之前很不一样,可以称为 “下一代” 的东西。
当然现在属于很早的阶段,WebTransport 还是 Origin Trial,WebCodecs 也是一样。以前都在 WebRTC PC 这个单体 (monolithic) 中,而现在你需要用 Web Assembly 自己实现,所以这个是非常非常不同的开发模型。
有些还没有加进去,例如 WebTransport 目前还是 client-server 模型,我们正在写一个 peer-to-peer 模型,不久就会进入到 Origin Trial 版本。所以,目前只使用 WebCodec 和 WebTransport,还无法实现 WebRTC-PC 对应的用例。
现在大家越来越关注 Machine Learning,所以需要访问 RAW Media,这是 WebRTC NV 很重要的场景,而 ORTC 没有提供这个能力。某种意义上说,WebTransport 和 WebCodecs 比 ORTC 提供了更底层的访问能力,比如 ORTC 不支持直接访问 Decoders 和 Encoders,而 WebCodecs 可以做到。我想我们已经采纳了 ORTC 的想法,并将这个想法带到了更底层的传输层。

我们将会继续讨论 ML 和 WebRTC,但先让我们继续聊聊 ORTC。
ORTC 咋样了?
Object RTC (ORTC) 是 WebRTC 的一个替代模型,它不依赖 SDP,提供更底层的组件和控制能力。Bernard 是作者之一,Microsoft 在最初的 Edge 浏览器中,支持了 ORTC,而现在却没什么声音了,那么 ORTC 发生了什么?Bernard 一个月前解释过,ORTC 很多能力,都吸收到了 WebRTC 的标准中。

Chad: 你是 ORTC 标准的作者之一,ORTC 现在是否达到了它的愿景?
Bernard: Object Model 存在于 Chromium 浏览器中,所以我们有大部分 ORTC 定义的对象,比如 Ice Transport、DTLS Transport、SCTP Transport,所有这些都在 WebRTC PC 和 Chromium 中。
ORTC 还有高级功能也被采纳了,比如 Simulcast 和 SVC,我们还实现过原始版本的 E2EE。所以目前而言,WebRTC PC 可以等价于 ORTC,加上对象模型和这些扩展。
我们也在关注一些新场景的应用,比如 IoT 的数据传输的部分,这在 WebRTC 中也有体现,比如 P2P 的数据交换。

WebTransport 新的传输

WebTransport 是由 W3C 一个专门的工作组定义的,当然和 WebRTC 有很紧密的关系。
QUIC 是一种改进的传输协议,有点像 WebTransport 可以使用的 “TCP/2”。
那么什么是 WebTransport,它是从哪里演化来的,和 WebRTC 又是什么关系?

Bernard: WebTransport 是一组 API,由 W3C WebTransport 组定义的;同时,WebTransport 也是一系列的协议,由 IETF 定义的。这些协议包括 WebTransport over QUIC,简称 QUIC Transport;也包括 WebTransport over HTTP/3,也可能 HTTP/2。因此,WebTransport 的 API 不仅仅支持 QUIC,也支持 HTTP/3,以及 HTTP/2(主要考虑 Fail-over 的回退)。
WebTransport 的 API 是一个 CS 模型的 API,构造和函数都很像 WebSocket。在 WebTransport 的构造函数中,你需要指定一个 URL。但是和 WebSocket 不同的是,WebTransport 支持可靠传输的流传输,也可以支持不可靠的数据报。

数据报,例如 UDP,应用在快速但是非可靠的传输场景中。

Bernard: 而且它是双向的,一旦客户端发起了 WebTransport,服务器也可以主动发起一路传输流。

双向的?比如像 WebSocket?

Bernard: WebScoket 只是客户端发起的,不能由服务器发起;而 WebTransport 可以由服务器发起。另外,WebTransport over QUIC 时连接是非 Pooled,而 WebTransport over HTTP/3 是 Pooled,这创造了一些新的应用场景,有些在 IETF BoFs 中由提到过。这意味着,在同一个 QUIC 连接中,你可以传输很多内容,比如 HTTP/3 请求和响应、WebTransport、Streams 和 Datagrams。
Justin Uberti 提出过一个标准 IETF RIPT BOF,就是将这些不同的东西放在了一起。在这个场景下,有来回的 RPC 请求,也包括服务器主动发起的请求。比如一个客户端,发出请求说要播放一个电影,或者进入一个游戏,或者加入视频会议,然后服务器就开始主动发起多路不同的视频流的传输了。
我认为 WebTransport 有可能会给 Web 带来革命性的变化,HTTP/3 本身就是对 Web 的一种革命。而这些关键变化,是 HTTP/3 Pooled Transport;相比之下,QUIC 就更简单了,它只是给了一个 Socket 一样的能力,你可以发送和接收数据。

WebTransport 还有多久才会上?

Bernard: 其实 WebTransport API 已经相当完善了,而且它已经完成了它的 Origin Trial 版本,在 M88 版本中。还有一些 Bug,有些部分还不能很好的工作,但是 API 基本比较稳定了;你可以写比较复杂的用例了。我们很快会提供比较完整的例子,可以让大家尝试使用。
而在服务器端,还有一些 QUIC 的互操作性问题。现在使用较多的是 Python aioquic,当然你可以用 quiche,可惜我们没有一个 Nodejs 版本。

正如 Bernard 提到的,WebTransport 是客户端服务器模型,而不是 WebRTC 这种 P2P 模型。不过,我们看到有个 P2P QUIC 的预览版了。实际上 Flippo 早在 2019 年,实现过一个 QUIC DataChannels,这个和 WebTransport 的差别是什么?

Bernard: Flippo 实现的 RTCQuicTransport,是用的 ORTC 版本,不支持 W3C Streams,用的也是 gQUIC 协议,而不是 IETF QUIC 协议。WebTransport 是基于 W3C Streams,使用的是 IETF QUIC 协议。所以,RTCQuicTransport 是过时的代码,它是老的 API 和老的协议,这部分代码已经从 Chromium 中移除了。

那么在实时场景下,我们现在如何使用 P2P WebTransport?

Bernard: 我们有个扩展标准,依然在 ORTC 工作组中。大概你可以认为是 WebTransport,不过它是用 RTCIceTransport 构造,而不是一个 URL。当然在构造函数中,需要传递一个 ICE Transport,而不是传 URL。
关于 P2P 部分,基本可以从 RTCDtlsTransport 抄过来,而且这个扩展规范本身不多,差不多 95% 的东西和 WebTransport 规范本身是一样的。
Chad: 有人这么做过吗?
Bernard: 我们目前还没有可以工作的新版本 API 和新的 QUIC 库。RTCQuicTransport 是独立的,现在 WebRTC ICE 也是独立的,不依赖 WebRTC PC;当使用 RTCIceTransport 构造 RTCQuicTransport 时,它们不会和 PC 复用。
在过去,RTCIceTransport 必须使用独立的端口,因为 gQUIC 没有复用 RTP/RTCP、STUN 和 TURN,而现在 IETF QUIC 是和这些协议复用的。

gQUIC 是 Google 的 QUIC 版本。
gQUIC 不复用端口,看起来会对端口使用,以及穿越防火墙,产生比较大的影响。

Bernard: 开发者是否想让 QUIC 和其他媒体复用同样的端口?今天在 WebRTC PC 中,Bunding 是非常非常常见的,基本上 99% 的情况下都是复用一个端口。那么 QUIC 也应该一样支持端口复用,那么我们就不应该使用之前的 API 从 URL 构造 RTCQuicTransport,而应该使用 RTCIceTransport 构造它。
这变得有点奇怪了,因为现在部分的 WebTransport 开始依赖 RTCPeerConnection。

RPC setup to send media via WebTransport. Source: IETF 107 – Justin Uberti, 107-ript-rtc-implementation-experiences

Simulcast 多播

WebTransport 看起来确实挺有潜力的。另外,几乎主流的会议服务厂家,都使用了 Simulcast,而 Simulcast 是困扰 WebRTC 的棘手问题之一,在标准和互操作性上也一直在挣扎和挤牙膏状态。

Chad: Simulcast 现在是什么情况?
Bernard: 目前已经支持了,Chromium 支持的编解码器都已经支持了 Simulcast,至少目前存在于 Chromium 中的编解码器已经支持了。所以理论上,不管是 H.264、VP8 和 VP9,都是可以用 Simulcast 的。
我们正在找 Bug,也遇到了一些比较难搞的 Bug,例如 H.264 无法正常工作。除了 KITE 全量测试之外,我们还建立了 LoopBack 测试,可以快速测试基本的能力,所以 Fippo 写了 LoopBack 测试。

如果你感兴趣,Fippo 写的关于 “Simulcast Playground” 的文章在这里

Bernard: 而这些 LoopBack 测试,没有在所有浏览器通过。原因是因为发送端是 RID,而接收端是 MID,比如发送了三条流,那么每个流会有个不同的 MID;而 Firefox 不支持 RTP 头中的 MID 扩展,所以导致了测试失败。
我们也发现,只要我们开始测试,就能发现一些不对的地方。
再举一个诡异的例子,我们开启了硬件加速,发现它不仅仅是编码速度加快,还改变了编码的字节流,这就导致互操作性测试失败了。有可能开启 Simulcast 后,你的 SFU 就扑街了。我很想和相关朋友见面聊聊,也想做更多的 Simulcast 测试,就像 Dr. Alex 做的一样,这样可以更好知道目前的状况。
如果大家都在推动和使用 Unified Plan 标准,那么我们会越来越好。

Unified Plan 是一个新的 SDP 标准,在 SDP 中定义了如何支持 Simulcast。Unified Plan 就是我们的康庄大道啊。现在情况怎样?

Bernard: 如果大家都使用 Unified Plan,那么互操作性会工作得很好;但我们离这个目标还差得挺远。目前我们还只是支持了这个功能,而且测试覆盖率在下滑,当然也不必所有浏览器都支持所有功能了才能商用,实际上很多商业应用,并不是要求支持所有的浏览器,而是支持某几个常用的浏览器。
所以关注这个问题,比较好的办法是看下测试矩阵,看主流的厂商和浏览器的运行结果,这样能知道目前是在什么状态。

当然这不是令人振奋的消息,在绝大多数浏览器上都支持当然是更好的了,不过有时候就是这样,有些功能在不同的浏览器上支持是不太一样的。

SVC 可伸缩编码

在发送方和接收方各种限制不同的场景中,SVC(Scalable Video Coding)被认为是一种比较好的方式,比如发送方发送 “多” 流,而接收方每个条件不同,有些接收高码率有些低码率。它也带来了复杂性,Sergio & Gustavo 这篇文章讨论了这个话题。
如果 Simulcast 没有准备好,我们是否能用 SVC?

Bernard: SVC 某种程度上比 Simulcast 稍微简单点,目前 Chromium 中 SVC 是实验性的功能,叫 Temporal Scalability。另外,PlanB 也支持 Temporal Scalability。所以 SVC 目前是支持的,而且也有会议服务器支持这个特性。所以对于很多会议服务商,要想达到同样的效果,SVC 实际上是更容易实现的一种方式,比支持 RID 和 MID 容易。
MID 是 SDP 中的 Media Identifier,参考 SDP Anatomy,而 RID (Restriction Identifier) 是新增的一个标准,用来表达独立的流。这部分从略,请大家自己了解相关的文档。
很多会议服务器支持 RID 和 MID,我认为 Medooze 和 Janus 都支持。而 VP8 和 VP9 是默认都支持的,解码器必须支持它,因此不用协商。当然 SFU 也可以不丢弃 SVC 的某些层,当然如果某些场景下丢弃某些层,效果肯定会比较好。

AV1 新编解码器

Chris Wendt 在很久以前写过一篇文章,关于 H.26X 和 VPX 的编解码之争,以及是否会出现一个统一的编解码器一统天下。今天,这个统一的编解码器就叫 AV1。
WebRTC 计划什么时候支持 AV1?

Bernard: 当前还没有很多设备能支持较高分辨率的 AV1 编码,因此目前 AV1 面对的挑战,是如何在这种情况下让 AV1 用起来。
Chad: 我应该向大家解释下,AV1 是下一代、开源的、免费的编解码器。
Bernard: 支持 AV1 并不会改变 WebRTC PeerConnection,反过来也是。但是 AV1 支持了很多有用的新的 Scalability 能力,如果要用到这些新能力,就是我们之前提到的 SVC 的内容了。
另外,AV1 有一个非常高效的屏幕编码(Screen Content Coding)算法,大家可能想开启它。所以我们增加了 Content Hints 的功能,这样 AV1 的屏幕编码才能用起来。
Florent Castelli 提交过一个提案,关于混合编码的 Simulcast。这个提案允许不同层(Simulcast Layer)使用不同编码;比如你可以在低码率层用 AV1 编码,分辨率 360p 或 720p,可以用软件编码,也不用硬件加速;而高分辨率层,你可以用另外的编码器,比如 VP8 或 VP9。
这个提案,允许你部分使用 AV1,而不是全用或全不用;这样就可以在 WebRTC PC 中,很快就可以用 AV1。我想大家不是很在意用的是否是 AV1,而是很在意 AV1 提供的这些新的能力,以及更小的 API 变化。我们的目标就是尽快让它用起来。
我们离 AV1 用起来也不远了,编码器和解码器库都已经准备好了,并没有特别难的问题。Dr. Alex 正在写测试用例。RTP 封装支持 AV1 也不难,这些都很简单。

那么,最难的是什么呢?

Bernard: 难点是在 RTP 扩展头的描述,一般用在 SFU 转发中,这是会议服务器中支持 AV1 最棘手的部分。另外一个难点,是 AV1 天生就支持 E2EE(端到端加密),它是基于 Insertable Streams 实现的。
AV1 作为一个编解码器,并不像它的名字那样有很大的变化。它更多是 VP8、VP9 的继续演进版本。它有 H.264 那样的 NALU 语法,所以它有点像 VP9 和 H.264 之间的过渡。
如果从会议服务器的角度看,由于天生支持 E2EE,AV1 是非常不一样的。因此,SFU 无法解析 AV1 OBU,SFU 只能依据之前的描述来做判断。本质上说,SFU 已经进入了下一个模型,在这模型中是和编解码器不相关的,SFU 独立于编解码器。

SFrame 和 E2EE

Insertable Streams 是和 E2EE(端到端加密)直接相关,和编解码器相对独立的话题。实际上我们发表过相关的文章。Emil Ivov 在 Kranky Geek 深入探讨过 e2ee。
我想和 Bernard 探讨下 Insertable Streams API。

Slide on insertable streams from TPAC. Source: TPAC-2020-Meetings
Bernard: E2EE 不只是一个简单的使用场景,Insertable Streams 实际上是提供了你访问 Frame 的能力。你可以对 Frame 做一些修改,但是你不能修改 RTP 头或扩展或类似的东西。当然你也不能大幅改变 Frame 的尺寸,比如不能添加大量的 Metadata 到 Frame。你可以修改 Frame,然后把它打包并发送。
提供 Frame 级别的操作能力的 API,主要是 WebCodecs 和 Insertable Streams。可以认为它们是对 MediaStreamTrack 的扩展,因为它们不依赖 RTCPeerConnection。在这些 API 中,你可以获取一个原始的 Frame,或者一个编码过的 Frame,然后做一些修改后,打包并发送。
目前还有一些 Bug,VP8 和 VP9 工作良好,但是 H.264 估计还不支持。
E2EE 的关键想法,是不限制开发者使用什么 Key Management。我们正在制定 E2EE 相关的标准,就是 SFrame(Secure Frame);目前还没有在 Key Management 上达成一致。事实证明,不同场景下需要不同的 Key Management。

SFrame 是一个新的标准提案,允许通过 SFU 转发 E2EE 的媒体;E2EE 的加密是在 Frame 上,而不是在 Packet 上。由于每个 Frame 可能会被分成多个 Packet,所以这样也很高效。

Source: IETF Secure Frame (SFrame) proposal
Bernard: SFrame 在 Frame 上加密,比在 Packet 上加密更灵活。比如如果要对 Frame 做签名,只需要签名一次;而对每个 Packet 签名是不可行的,比如对于关键帧,就需要签名很多个 Packet,而 SFrame 则只需要一次签名。
所以这也意味着减少了很多签名的开销,这样就可以做到 Origin Authentication,可以知道这个包是谁发出来的,而基于 Packet 的签名无法做到这点。
看起来每个人都同意,只需要一种 SFrame 的格式,但是对于 Key Management 会很麻烦。我们在 TPAC 上讨论过,是否能在浏览器中实现 SFrame,在 Key Management 上还未达成一致,这可能会导致出现多种 Key Management。

WebCodecs 新编解码能力

WebCodecs 给了开发者更底层的访问能力。

Bernard: WebCodecs 是开放给了开发者更底层的能力,这些能力已经在浏览器中了。WebCodecs 和 Insertable Streams 类似,都是 Frame 级别的操作。比如,你可以操作一个编码后的 Frame,或者你可以输入一个未编码的 Frame 然后拿到一个编码后的 Frame。
Chad: 所以,这是一个更底层的能力,包括编码器和解码器?
Bernard: 是的,解码器这部分,和 MSE 很类似。
Chad: MSE,Media Source Extensions?

Media Source Extensions,以及 Media Source API,主要是替代 Flash 的媒体能力,可以用标准 JS 代替 Flash。MSE 允许开发者输入一个封装好的媒体,比如 fMP4 切片,也支持 DRM,详细参考这里
那么 WebCodecs 和 MSE 的区别是什么呢?

Bernard: WebCodecs 解码部分和 MSE 很类似,不过输入的是编码后的 Frame,而没有外层的封装。
有朋友问,“这些东西该如何配合使用?”,我举个例子,如果你要做流媒体视频或游戏业务,你可以使用 WebTransport 接收编码好的媒体数据,然后你可以使用 MSE 或者 WebCodecs 解码和渲染。MSE 的输入是封装好的数据,而 WebCodecs 是编码好的 Frame。MSE 支持 DRM,而 WebCodecs 目前还没有。

那么在编码方面,MSE 和 WebCodecs 的差别呢?

Bernard: 这个是个有趣的话题,因为在云游戏或者电影,或者其他从云端下载的流媒体场景中,你并不需要在浏览器上编码,只需要解码。因此这些场景并不需要 WebCodecs,只有在视频上传的场景中才需要编码。如果你需要上传视频,那么你可以用 WebCodecs,然后使用 WebTransport 发送,可以用可靠流也可以用 Datagrams,如果是 Datagrams 那就要自己做 FEC 了。
如果你不是很关心延迟,那么用可靠流就很好了。只有在需要编码的场景下,WebCodecs 才具备真正的优势,不需要使用奇怪的技巧。

敢问路在何方?

发送视频是 WebRTC 很重要的能力,是否这个能力可以被 WebCodec 和 WebTransport 或者 WASM 取代呢?实际上,Zoom 就是这么做的。
Zoom 的方案是更好的方案吗?是否是未来的趋势?

Chad: 这是我们需要搞清楚的方向吗?还是这些方案都会同时存在?
Bernard: 嗯这确实是一个问题。如果端到端都是你自己的应用,那么你可以随意选择。但今天,有很多人希望使用开源的 SFU 服务器,这就必须使用标准接入了,不能随意发送任何媒体格式给 SFU。如果只是简单的视频上传的场景,可能也问题不大;不过在会议场景中,涉及的网元和终端可能会很多,保持标准接入总是一个好事情。
除了 SFU,性能也是非常关键的因素。我知道有些朋友用 WebTransport 实现会议能力,但我对这个方案表示怀疑,因为目前会议的与会者越来越多了,比如 7x7,甚至 11x11。
似乎需求永远不会被满足,比如在线课堂中,老师希望看到班上的每个人,而一个班的人数可能远不止 11 人。在这种场景下,流的数目会非常的多,而且很多都是高清流,这时候性能就真的是一个很大的问题了。WASM 或者 WebTransport 这种方式,内部有很多内存拷贝,比如在 WebTransport 中有两份拷贝,传给 WASM 时又需要拷贝一次,它们可能还不在一个线程中,这对性能优化有非常大的挑战。
Chad: 我想在这种场景下如何优化资源的使用,还是可以做很多事情的。
Bernard: 嗯没错,虽然人们总是抱怨 WebRTC 是单体应用,但是另外一方面,单体应用相对很容易做性能优化。而在 WebTransport+WebCodecs+WASM 这种模型中,很难避免大量的拷贝,也很难做深度的性能优化。

ML 机器学习

ML 是现在计算机科学界很普遍的话题,几年前甚至 Kranky Geek 2018 的主题都是 RTC AI。现在也看到 JS ML 有了很大进展,比如 Don’t Touch Your Face,以及各种 WebRTC 应用中的虚拟背景。然而 WebRTC 底层却没有太多和 ML 相关的内容,我请教了 Bernard 这个问题。

Bernard: 我们在 WebRTC-NV 的用例中,讨论大家正在尝试的热度很高的事情。我们发现,除了 E2EE(端到端加密)之外,大家最热衷做的事情就是访问 RAW 媒体,这也给 ML 打开了大门。
Chad: 我也要确认下,访问 RAW 媒体,是为了获取更低延迟吗?我做了一些尝试,发现当整个调用 Stack 很深时,很难做到低延迟。
Bernard: 很多场景都涉及到了客户端处理,比如,你获取了媒体帧,你希望先对媒体帧做一些改变,然后再发送出去。在 Snapchat 中很多特效,都是这种方式实现的,比如戴上一个虚拟的帽子或其他东西。另外一个很受欢迎的功能,就是虚拟背景,或者类似的东西。
当然,很多 ML 是在 Cloud 运行的,比如语音转换或者翻译。我不知道是否客户端也能做到这点,但目前主要是发送到 Cloud 处理。可能客户端能完成的,主要是面部识别和身体姿态识别。
长期的目标,是 Native 能实现的,都可以在 Web 实现。这不仅仅是访问 RAW 媒体,而且是要实现高效的访问。比如,在 Native 方案中,我们经常发现媒体内容只停留在 GPU,而不需要额外拷贝;目前 Web 还做不到这一点,还是存在很多拷贝。

Source: Kranky Geek Virtual 2020 – Google WebRTC project update https://youtu.be/-THOaymtjp8?t=704

在 Kranky Geek 的活动中,Google 提到了如何实现 GPU 零拷贝。

Bernard: 这是 W3C 研讨会上提出来的一个话题,出现了一个概念叫做 Web 神经网络,目前已经有很多基于 WebGL 或者 WebGPU 的 TensorFlow 的库了。如果仔细考虑,你会发现这不是高效的方式。实际上你需要的是一些基本操作,比如矩阵乘法的运算,用 WebGPU 或者 WebGL 来实现矩阵乘法这些基本操作不一定合理。所以 WebNN 从更高的层面来解决这个问题,让矩阵乘法成为一种基本运算。
这里的关键,是协调这些 API 一起工作,把数据放到正确的地方,这样才能避免拷贝。比如 WebCodecs 支持了 GPU 缓冲区,但目前这个缓冲区是只读的,如果你希望对 GPU 缓冲区的内容做 ML,这就不行了因为无法修改它,你只能用拷贝实现。

2020 年 NVIDIA 的一个产品引起了我的注意,NVIDIA 使用运行在 GPU 上的 GAN,捕获关键帧的面部信息。然后它将面部信息和关键帧结合起来,重构了整个流。这样就只需要传递面部特征信息,这可以节约很多带宽,NVIDIA 声称可以做到 H.264 码率的 1/10。这个模型还可以用在超分(辨率),面部调整,或者是模拟表情等。
这似乎是 ML 在 RTC 的革命性的应用。是否有相关的标准?

Bernard: 如果你正关注下一代编解码器的相关研究,很多都是和 ML 相关的。
新冠导致了周围发生了很多变化,包括娱乐和会议的结合。很多电视节目,包括《Saturday Night Live right》,制作过程使用了会议技术。我注意到有些剧院,已经开始使用虚拟背景。而会议本身也有很多变化,比如 Microsoft Teams 推出的 Tegother 模式,将用户从视频中抠出来放到虚拟的会议室中。在体育运动中,运动员和球都是真实的,而观众席上的观众是虚拟的或远程的。
那么实际上我们涉及到了 AR 或 VR 的范畴,重新构造了环境。我看到了娱乐和 RTC 在很多场景下的融合,这反映在了 WebTransport 或者 WebCodecs 这种工具中,它既可以用在流媒体传输中,也可以用在 RTC 中。
ML 也可以是导演,它也可以是摄影师,还可以是编辑,它可以把所有这些事情串联在一起。实际上每个方面都将可以受到 ML 的影响。
我不认为这只影响到了传统流媒体,我也不认为我们要继续使用老的 RTC 的 API。在现有 RTC 系统中,要用新的 API 重写部分服务,估计没人有动力肝这事,但是,我还是认为 WebRTC 新的 API,将开启一个流媒体和 RTC 融合的新时代,这里面有很多新东西可能我们今天都无法想象到,很多都和 AR/VR 相关。

未来已来

Chad: 最后想和大家说点啥?
Bernard: 我们聊到的很多新技术,都已经有了 Origin Trial,大家可以获取到。把它们串起来使用,非常具有启发性;当然也会发现很多不足。我并不是说它们一致性很好,实际上不是,但是能给你一个印象,那就是未来你大概能做什么。这些技术会很快面世,这会大大超过大家的预期。甚至使用这些技术的商业应用,在 2021 年就可能上线了。所以走过路过千万不要错过,这不是未来,是很快到来的现在,好好把握机会。
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 1月13日

WebRTC ICE 状态与提名处理

大家都知道奥斯卡有提名,其实在 WebRTC 的 ICE 中也有提名,有常规的提名,也有激进的提名,而且提名的候选人不一定是最优秀的候选人喔,本文就带你一探其中玄妙。文章内容主要描述 RFC 5245 中 ICE 相关的状态和 ICE 提名机制,并结合 libnice(0.14) 版本进行分析。

作者:阵图,阿里云开发工程师
审校:泰一,阿里云高级开发工程师

Scene

分析一个问题时候遇到这样的场景:服务端一个 Candidate,客户端三个不同优先级的 Candidate,但是最后居然选择了一个优先级最低的 Pair。

服务端有一个 Relay Candidate,端口 50217。

a=candidate:3 1 udp 503316991 11.135.171.187 50217 typ relay raddr 10.101.107.25 rport 40821

客户端有三个 Candidate,端口 50218(中间优先级),50219(最低优先级),50220(最高优先级)。

Candidate 1:
candidate:592388294 1 udp 47563391 11.135.171.187 50219 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag fO75 network-cost 50

Candidate 2:
candidate:592388294 1 udp 48562623 11.135.171.187 50218 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag fO75 network-cost 50

Candidate 3:
candidate:592388294 1 udp 49562879 11.135.171.187 50220 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag fO75 network-cost 50

但是最后选择的却是最低优先级的 Pair,50219。

Remote selected pair: 1:1 592388294 UDP 11.135.171.187:50219 RELAYED

Candidate's Foundation

Candidate 的 Foundation: 这里先提一下 Foundation,会涉及到 Frozen 状态。

对于一条相同的信道,可能有不同的 Candidate,比如 Relay Candidate 被发现的时候,就可以生成一个新的 Server Reflexive 类型的 Candidate,但是他们都是基于相同的本地地址(IP,端口)和协议,则可以认为这些网络是相似的,则他们就会有相同的 Foundation。其中 Foundation 在 SDP 中为第一个字段,即下面例子中的 '7'。

a=candidate:7 1 udp 503316991 11.178.68.36 51571 typ relay raddr 30.40.198.7 rport 55896

ICE States

ICE 主要有以下五种状态,其中前四种是正常的状态,第五种状态 Frozen 涉及到 ICE Frozen Algorithm

ICE 的五种状态:

  • Waiting: 当连通性检查还没有开始执行的时候(Binding Request 还没发送)。
  • In Progress: 当连通性检查发送了,但是相应检查的事务仍在执行中(Binding Request 已发送)。
  • Successed: 连通性检查执行完成且返回结果成功(Binding Request 已完成)。
  • Failed: 连通性检查执行完成且结果失败(Binding Request 已完成)。
  • Frozen: ,所有 Candidate Pair 初始化完成以后就在这个状态,对于相同的 Foundation(相似的 Candidate),会按照优先级依次选取一个 Pair,Unfreeze,并设置为 Waiting 状态,其他则保持 Frozen。直到选取的 Pair 完成,才会继续 Unfreeze 另一个 Pair。

ICE Nomination

ICE 有两种提名方式:

1.Regular Nomination

对于常规提名,主要工作流程如下:

L                        R
-                        -
STUN request ->             \  L's
<- STUN response  /  check

<- STUN request  \  R's
STUN response ->            /  check

STUN request + flag ->      \  L's
<- STUN response  /  check

Regular Nomination  

Controlling 模式下的 Agent 发起 Binding Request,并且收到对端的 Response,同时对端发起的 Connective Check 完成,Controling 一端会再次发出一个携带 USE_CANDIDATE 标志位的 Binding Request,当 Controlled 一端收到了,就接受这次提名。

2.Aggressive nomination

除了常规提名,还有一种比较激进的提名,常规提名中会新增一次握手。

L                        R
-                        -
STUN request + flag ->      \  L's

<- STUN response  /  check
<- STUN request  \  R's
STUN response ->            /  check

Figure 5: Aggressive Nomination

Controlling 模式下的 Agent 发起 Binding Request,但是在这个 Binding Request 中会直接携带 USE_CANDIDATE 的标志位,Controlled 模式下的 Agent 收到了以后就接受这次提名。在激进提名模式下,能节约一次握手过程,但是当多个 Pair 同时接受提名时,会根据这些 Pair 各自的优先级进行选择,选择出优先级最高的 Pair 作为实际的信道。

真实案例:
image

Updating States When Nomination

原文参考

当一个新的提名产生时,会对 ICE 内部状态进行对应的变化。

当一端的 Binding Request 携带了 Use Candidate 的标志位时,则会产生一次提名(Nomination)。

不管 Controlling 或者 Controlled 模式下的 Agent,处理提名的状态更新规则建议如下:

  • 如果没有提名的 Pair,则继续进行连通性检查的过程。
  • 如果至少有一个有效的提名:

    • Agent 必须删除该 Component 下的所有 Waiting 状态和 Frozen 状态的 Pair。
    • 对于 In Progress 状态下的 Pair,优先级低于当前提名 Pair 优先级的,停止重传(取消)。
  • 当某一个 Stream 的所有 Compont 都至少拥有一个提名时,且检查仍然在进行时:

    • Agent 必须将该 Stream 标记为已完成。
    • Agent 可以开始传输媒体流。
    • Agent 必须持续响应收到的消息。
    • Agent 必须重传当前仍然在 In Progress 的 Pair(优先级高于当前提名的,不然已经被删除或者取消)。
  • 当检查列表中的所有 Pair 都完成时:

    • ICE 完成。
    • Controlling Agent 根据优先级更新 Offer(貌似 WebRTC 没有这一步)。
  • 当检查列表检查有失败时:

    • 所有 Pair 都失败时,关闭 ICE。
    • 当有某个流的检查成功时,Controlling Agent 移出失败的 Pair,并更新 Offer。
    • 如果有些检查没有完成,则 ICE 继续。

Scheduling Checks

在描述提名时,还会涉及 ICE 对 Pair 的调度(当有效 Candidate 还在 In Progress 的时候但是其他 Candidate 的 Pair 已经收到 Binding Request)。

这里只讨论 Full,先不描述 Lite 模式。

ICE 的 Checks 分成两种,Ordinary Checks And Triggered Checks。

  • Ordinary Checks 是常规的 Pair 的检查,表示这些 Pair 的检查是从正常流程中切换过来的状态的检查。
  • Triggered Check 是被动触发的检查,当这些 Pair 虽然还处在不可以开始检查的状态,但是这时候收到了对端的连通性检查,这时候会对这个 Pair 进行提速,将其直接放入调度列表。

当 ICE 建立一个 Check List (每个 Stream 一个)后,会对每个 Check List 添加一个定时器,当定时器到来时,会进行如下调度:

注:这里有点不能理解,整个流程看起来是串行的,激活速度有点慢。

  • 首先调度 Triggered Check 并执行。
  • 若无,调度优先级最高的 Waiting 状态的 Pair,发送 Request,同时将状态置为 In Progress。
  • 若无,则从 Check List 中找出优先级最高的 Frozen 状态的 Pair,Unfreeze 之,并发送 Request,状态设置为 In Progress。
  • 若无,终止调度。

Case Analyzed

简单了解了 ICE 的流程后,我们回归最开始的 Case。

首先看 Add Candidate,三个 Remote Candidate 添加顺序不同,依次为 50219,50218,50220,注意,此时 50219 收到了对端的 Binding Request,激进提名,携带 USE_CANDIDATE,因此很快执行 Create Permission 并完成,这时候可以开始发送 Binding Request 了,属于 Triggered Checks 优先调度,发送 Binding Request,并进入 In Progress 状态。

注:这里除了本地 Relay 的 Pair,还有和 Turn 通信的本地 Host 类型的 Candidate。

image

接着在 50219 在其他两个 Create Permission 还没完成时候以迅雷不及掩耳之势完成了 Check,根据 rfc 8.1.2 中的描述,对于不是在 In Progress 状态的 Pair,都删除,并不参考其优先级,故最后选择了 50219 这个优先级最低的 Pair。
image

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 37 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-10-20
个人主页被 2.1k 人浏览