阿里云视频云

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

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

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

个人动态

阿里云视频云 发布了文章 · 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

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

light-rtc: 理念与实践

在与同行交流过程中,发现很多同行对 WebRTC 改动太多,导致无法升级 WebRTC 版本。而 WebRTC 开源社区的快速迭代,让他们感到欣喜又焦虑:开源社区的迭代效果,是不是超过了他们对 WebRTC 的优化效果?我们针对特定场景优化 WebRTC 时,怎么紧跟 WebRTC 开源社区通用的优化?

作者:阿里云智能技术专家 熊金水

理念

简言之,把 WebRTC 作为 Framework 使用,而不是 Library,即:WebRTC 仓库轻量化,核心模块插件化。

详细的,WebRTC 作为 Framework 串联核心模块;核心模块既可以以插件形式使用我们的实现,也可以 Fallback 到 WebRTC 的默认实现。目的是减少 WebRTC 冲突的可能性,提高升级 WebRTC 的敏捷性。

目标:一年升级一次 WebRTC,一次花费一个人月。

架构

模块拆解

file
WebRTC 的核心模块,包括:

音频

  • ADM 采集、APM、ACM 编码;
  • NetEQ 与解码、AM、ADM 渲染;

视频

  • 采集、编码;
  • JB、解码、渲染;

通用

  • RTP 打包与解包、FEC 生成与恢复、CC 与 Pacer、ICE、SDP 信令等。

WebRTC 在长期的演进中,API 已经具备了作为 Framework 的大部分能力。红色的核心模块,已经基本可以插件化,如下面的 API:
file

仓库管理

file
light-rtc 作为 WebRTC 仓库,我们需要保留两个 Remote,一个是 Alibaba,一个是 Google。升级 WebRTC 时,我们从 Google 上 Pull 最新代码, 解决冲突,然后 Push 到 Alibaba。

对插件化的模块,我们需要放到单独的仓库 lrtc-plugin 里,这样有两个好处:

  1. 对 light-rtc 仓库改动少,减少与 Google 冲突的可能性;
  2. 更重要的,让每个开发同学,在每次改动前,更主动、更有意识的思考,放到哪个仓库更合适,否则容易惯性思维,直接改动 light-rtc。

对 lrtc-plugin 依赖的第三方库,也应该以单独的仓库存在,并保留两个 Remote,比如 Opus,这样,即使修改了 Opus 源码,仍然可以像升级 light-rtc 一样,方便的单独升级 Opus 版本。

模块

Codec

音频编解码器、视频编解码器,是我们最常优化的部分之一:

  • 新的编码工(AV1/SCC/ROI 等)优化视频质量和带宽;
  • 分辨率自适应,使不同能力(编码能力、发送带宽等)的发送端,发送不同分辨率的码流;
  • Simulcast,为不同能力(解码能力、显示能力、接收带宽等)的接收端,提供不同分辨率码流;
  • SVC,提供时域/空域分层;
  • 新的视频解码实现,规避 Mac 硬解卡死等问题;
  • 新的音频编码器,适配商用接收端;
  • ……

file

这部分插件化是相对简单的,只需要实现自己的 [Video|Audio][Encoder|Decoder]Factory 即可。以 Simulcast为例,在自己实现的 VideoEncoderFactory 里,先用 WebRTC 原始的 VideoEncoderFactory,创建多个 Encoder 对象,然后封装到一个 Simulcast Encoder 里。

ADM

很可惜,ADM(Audio Device Module)没有提供检测设备插拔的功能,需要增加 Callback 接口。

另外,虽然 WebRTC 支持样本数量的监控,但是当前只用于打印日志,如果想在此基础上做更多事情(如:发现采集样本为 0 时,重启采集),则单独做一个 AudioSampleMoniter 的类,比较有利于扩展。
file
ADM 是一个适配难点,相信是困扰 RTC 同行的共同难题。不同操作系统、不同机型,都可能有不一样的问题。例如:

  • Mac 3.5mm 耳机插拔时,偶尔崩溃;
  • Mac 获取的设备 ID 在插拔后发生变化,不能做持久化;
  • 联想 X1 电脑,多次插拔后,整个 Audio 后台服务失效;
  • 某些 Windows 机型采集不到声音;
  • 某些手机采音权限问题;
  • ……

这些修改大部分属于 Bugfix,参考“Bugfix”章节。

APM

APM(Audio Processing Module)可能是 light-rtc 相对难处理的部分。

APM 与 NetEQ 一起,可能是 WebRTC 核心模块中,开源价值最大的部分。在我对 APM 有限的认知里,对 APM 常见的优化可能有:

  • 混音后的远端信号,做滤波/均衡处理。这是业界不少音频算法的必要条件;
  • 利用 Android 手机特性,优化 AECM,尤其是 Double Talk 时的效果;
  • 啸叫检测与抑制;
  • 利用机型特性,优化 AGC,提高语音音量;
  • ……

下图是 WebRTC APM 内部模块的数据流程图:
file
从图中可以看出,APM 其实也为插件化做了准备,但是只在近端信号的尾部、远端信号的头部。从 APM 构造函数上也可以看出来:
file
滤波/均衡,可以方便的实现一个 CustomProcessing 的 render_pre_processor。

其他的优化,遵循轻量化/插件化的理念,没有现成的插件接口,我们可以创造新的插件接口,如啸叫抑制,以及 AECM 优化的部分算法。

但 APM 仍然会有很多没办法插件化的,只能修改 light-rtc 仓库,如 AECM Double Talk 优化等。

AM

AM(Audio Mixer)的插件化,可以在不修改 light-rtc 的基础上,玩出很多花样:

  • 播放本地文件;
  • 借助语音检测算法,优化语音排序,从而选出更准确的语音做混音;
  • Mono 变成 Stereo,借助 HRTF,可以在多方同时说话时提高说话人辨识度和可懂度;
  • 对 RTP 方案的回放,倍速回放时变速不变调;
  • ……

file

FEC

FEC(Forward Error Correction),常见的修改:

  • 调参,如冗余度、MaxFrames、Table 类型,包括固定参数和动态自适应调参两类,已有的插件接口 WebRTC::FecControllerFactoryInterface 即可满足;
  • RSFEC,需要创造新的插件接口;
  • Opus Inband FEC。WebRTC 动态配置的 Opus FEC 参数,不能很好的解决弱网时声音卡顿问题。这时,一个办法是把 Opus 独立成仓库,直接修改 Opus 编码器。

file

CC

CC(Congestion Control),包含两个方面,一个是 CC 算法本身,一个是 CC 关联模块。

算法本身,可以用不同的算法实现,如 WebRTC 默认的 goog_cc,也可以是 BBR,甚至是满足 WebRTC::NetworkControllerFactoryInterface 接口的外部插件。

关联模块:

  • 带宽分配:不同场景可能不一样,如视频会议里,需要“保音频、保屏幕”。可以通过 rtc::BitrateAllocationStrategy 实现插件化。

file

  • Pacer 调优:对于屏幕内容,I帧往往非常大,WebRTC 的 2.5 倍的发送带宽,会导致巨大的首帧时间。具体解法见仁见智。

……

VideoRender

Android、iOS、Mac,WebRTC 都提供了默认的实现,虽然有少量 Bug,但是基本满足需求。

Windows 平台,早期 WebRTC 提供了 D3D 的实现,最新版已经剔除,我们可以在 lrtc-plugin 仓库实现自己的 D3D,或者其他的渲染,如 QT OpenGL。

VideoProcess

WebRTC 并没有提供视频前处理(如:美颜)、后处理(如:超分辨率)的接口,但是我们完全可以像 rtc::BitrateAllocationStrategy 一样,创造 VideoProcessInterface 接口, 并在 lrtc-plugin 仓库里实现。
file
让 VideoProcessInterface 同时继承 Sink 和 Source 接口,可以方便的把多个对象串联起来。

其他 & Bugfix

其他核心模块,如 JitterBuffer、ICE 等,目前接触的主要是 Bugfix,还没有发现自己定制重写的必要。

Bugfix,往往只能修改 light-rtc 仓库。一方面,是尽量把 Bugfix 内聚成函数,减少对已有代码的修改;另一方面,尽量把 Bugfix 贡献到开源社区(Issue Tracker),既为开源社区做了贡献,也彻底避免了升级的冲突。

贡献到开源社区,往往比想象的要复杂,但也更能锻炼人。在特定场景,往往只用了 WebRTC 一部分能力,如视频 JitterBuffer,一个 Bugfix 可能只考虑到了 H264,贡献到开源社区时,则需要同时兼顾 VP8/VP9,甚至是将来的 AV1。在这个过程中,Google 工程师会在 Code Review 中与你亲密切磋,其实是非常好的锻炼机会,进一步提高对 WebRTC 的认识。

参考

WebRTC m74 源码

RSFEC:

  • WebRTC RSFEC 详解和剖析;
  • ARTP 技术探秘之:WebRTC 中支持 RS FEC。

(以上两篇文章之后将会在本号推送)

CC

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

赞 1 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-12-25

浅谈 WebRTC 的 Audio 在进入 Encoder 之前的处理流程

在 WebRTC 中,Audio 数据在被送入编码器之前,有 2 大部分需要特别关注,一是数据采集,二是 Audio Processing。
file

作者:方来,技术专家,从事 voip 应用开发。

数据采集

数据采集主要由 Audio Device 模块进行处理,而且是平台和配置相关。例如:

  1. Mac 电脑,使用的是 CoreAudio API,一般情况下使用默认内置的声卡参数 fs=48kHz,stero。
  2. Windows 电脑,WebRTC 中用的是 WASAPI。根据声卡参数不同,采样率等参数可选的比较多,例如有的电脑 builtInAEC 打开后,fs=16kHz,Mono,如果把声卡的 Audio Enhancement 关闭,则输出 fs=48kHz,stero。
  3. Android 一般使用 java 层的 AudioRecord 框架。
  4. iOS 一般使用 AudioUnit 框架。

另外,数据采集部分,还涉及到 USB 耳机,3.5mm 耳机,蓝牙耳机等外设,这些设备对音频链路上后续的 Audio Processing 也是有影响的,比如增加了 Audio 采集的delay,有 Speech Enhancement 处理的耳机会修改音频频谱,有的耳机外设使用不当可能会导致音频链路没有声音。

Audio Processing

Audio Processing 主要包括 AEC,AGC,NS 等等:

  • AEC----Acoustic Echo Cancellation,即回音消除。
  • AGC----Automatic Gain Control,即自动增益,用来调整输入信号的音量大小。
  • NS----Noise Suppression,即噪音抑制。

从 Audio Devices 输出的数据依次经过 AEC,NS,AGC 等音频处理模块。

1.AEC

AEC 算法选择

在 WebRTC 中,AEC 有 4 个可选的算法:

  1. builtInAEC,一般情况下 Windows,Android 系统,builtInAEC 默认会开启。
  2. AECM,移动端的回音消除算法,适用于 Android和 iOS。
  3. AEC 算法,适用于 Windows/Mac Desktop 的回音消除算法。当然 AEC 也可以用在移动端,某些情况下,回声泄露的性能比 AECM 好。不过最新的 WebRTC 已经把老的 AEC 的 code 移除了。
  4. AEC3 算法,Google 对老的 AEC 算法的改版,目前 AEC3 已经全面替代老的 AEC 算法。

一般情况下这 4 种 AEC 算法只能选择一种,否则会做多次 AEC,对声音的损伤也会增加。在不得已的情况下,可能会用到 2 个 AEC,例如 Windows 电脑,buildInAEC 关不掉且效果差的情况下,就必须打开 AEC3,这时是用到 2 个 AEC。

aec_dump

在一次通话中,使用 StartAecDump 开启 aec_dump 功能,aec_dump 将录制 3 个文件,一个是未进入 Audio Processing 模块的 input.wav,一个是 Speaker Render 的输出文件 reverse.wav,一个是经过 Audio Processing 处理过的 ref_out.wav。

正常情况下,input.wav - reverse.wav = ref_out.wav。

通过这 3 个文件可以分析回音消除算法是否有问题。

2.AGC

WebRTC 的 AGC 有 2 种算法:

  1. Legacy AGC
  2. AGC2

下面简单展示一下 Legacy AGC 的性能。
原始语音
原始语音

AGC 后的语音
AGC 后的语音,Legacy AGC 本身没有降噪功能,噪音和语音同时放大的。

3.NS

目前 WebRTC 的 Noise Suppression 模块,能够过滤掉比较平稳的背景噪音,例如 white nosise,空调声等。但是 NS 模块对音量非常大的背景噪音,还有 babble noise 都是失效的,这也是我们客户端在 Microphone 功放的情况下的“嘈杂不清”的因素之一(当然导致嘈杂还有其他的原因,例如 AEC 的性能等等)。

file
夹杂 white noise 的录音

file
white noise 被过滤掉

4.其他增强算法

  1. High Pass Filter,用来过滤低频噪音,比如我们可以把 100Hz 甚至 200Hz 以下的低频噪音过滤掉。
  2. Typing Detector,可以过滤掉键盘打字的声音。
  3. Residual Echo Detector,残留回音探测。

优化点

  1. 采集(当然也包括播放)容易出现没有声音问题,所以必须对采集(声卡驱动)端进行声卡适配优化。
  2. AEC 区分平台:

A. Windows 平台,一般 Windows 的声卡面板里面有一个“Audio Enhancement”,这个里面有的含有 builtInAEC,默认是打开的,通过 Windows API 关闭这个 Audio Enhancement 的几乎不可行。有的 builtInAEC 效果比较差,这时 AEC3 必须打开。通常情况下,builtInAEC 和 AEC3 同时打开,以便最大程度减少回声。
B. Mac 电脑,默认没有 buildInAEC,通常就直接使用 AEC3。

  1. 在 AEC 算法中增加“near talk”,“far talk”,“double talk”, “no talk”状态,结合这几种状态,采取不同的操作,例如在只有 far talk 的时候,不做 AGC,能够起到减少近端噪音的作用。
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
查看原文

赞 1 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-12-22

倍频程与钢琴调式的距离

单从倍频程和钢琴调式这两个名词看,距离确实有点远,一个偏科技一个偏娱乐。但是距离远不代表没有关系,下面就让我们给它们俩拉拉关系吧。

作者:良逸,阿里云技术专家,阿里云音视频通信 QoS 研发

什么是倍频程

首先什么是倍频程?从中文字义上看,就是“X 倍频率的范围”,是截至频率和起始频率之比成倍数关系的频段。与之相对应的另一个概念是等宽频程,意思是截止频率和起始频率之差相等的频段。具体定义如下:
file

为什么要用倍频程?

我们知道人耳的听觉的频率感知范围在 20Hz-20000Hz,范围非常的广,分析的时候,需要将这个频率范围划分成不同的频段(频带)来分析,怎么划分呢?于是有了上文的两种划分方式,一种是等宽频程,意味着每个划分的频带宽度是相等的;另一种是倍频程,意味着每个划分的频带宽度与上一个紧邻的频带宽度符合等比数列。

如果采用等宽频程,要表现出低频到高频的声音信息需要的数据量非常大,效率比较低,而且人耳对高低频率的敏感程度是不一样的,通常人耳对低频部分的频率变化比较敏感,而对高频部分的频率变化不太敏感,所以采用倍频程更符合人耳的听力特点,而且提高了频带数据的分析效率。

还有一个跟倍频程有关的概念是中心频率,每频程的上限与下限频率的“几何平均值”称为该频程的中心频率 f0 。特定的中心频率 f0 就代表了一个特定的频段, f0 作为基准频率,只要确定了基准频率,整个 20Hz-20000Hz 的频率范围,就可以按照倍频程的定义分段展开。通常显示倍频程,是以其带宽的中心频率线性间隔的,这叫做倍频程格式。这会导致倍频程在频谱图对应的横轴上出现相同的间隔,即使它们间隔不是均匀的。如下图所示(引用网络公开测试结果,数据来源见图片下方):
file
图片数据来源:https://community.sw.siemens....

同一个白噪声表现为窄带(蓝色)和倍频程(紫色)格式,x轴是倍频程格式。注意到,与较高频率的倍频程段相比,较低频率处的倍频带的填充数据较少。如上表格所示,这是因为较高频率的倍频带覆盖的频率范围,相比较低频率的倍频带要宽。如果将x轴线性绘制的话,效果如下图所示(引用网络公开测试结果,数据来源见图片下方):
file
图片数据来源:https://community.sw.siemens....

与倍频程格式相比,上图绘制了与之前相同的白噪声数据,但x轴是线性格式。可以看出,功率谱(蓝色)在所有频率下具有相同的密度,但每个倍频带(紫色)覆盖的频率范围越来越大。

另外,因为选取不同的基准频率,最终展开的频段格式也不一样,那如何来选取基准频率呢?标准化组织根据不同的应用场景就给出了不同的倍频程的标准定义。在声学测量中一般采用 1/3 倍频程来对频谱进行分析,国标《GB 3240-1982 声学测量中的常用频率》中对其标准频率有如下的定义:
file

八度音阶

从这里开始我们看一下倍频程跟音乐有什么关系。我们搜索“倍频程”英文翻译的时候,会发现如下结果:
file
为什么倍频程跟八度音阶都是 Octave 一个词呢?其实倍频程是借用了八度音阶的理论,因为历史上人们对音乐的认知要早于信号频谱分析的理论的掌握。根据人们的经验,音调升高一个八度,频率就增加一倍,近代频谱分析技术出现后,恰好发现这个刚好满足倍频规律。因此就按频率重新定义八度音阶。其实八度音阶在频率这个概念出现前就有了。

我们大家都知道,简谱中的八度音阶是这个样子的:
file
低音 do 跟高音 do 之间相差 8 度音,也知道女生一般比男生声音高 8 度。

这里还要说一下音高的概念,音高的英文是 pitch,而 pitch 又翻译成基音,而且 Opus 编码器里面有个功能叫 Pitch Detection,翻译成基因检测,那基音跟音高是啥关系呢?物体震动产生的声音,其中包含很多频率成分,最低的那个频率称为基频,对应产生的声音就叫基音,其他的谐波频率成分产生的声音就叫泛音。其中基音决定了人耳听到的声音的音高,而泛音形成了不同的音色,不同乐器有不同的音色,就是因为其产生的泛音成分不一样。与倍频程相关的频率划分,以及与八度音阶相关的音高,都是指的基音的频率,与泛音没有关系。

十二平均律

我们再看一下八度音阶的特点。file
我们会发现 mi 和 fa,及 si 和高音 do 之间是半音关系,其它每个音之间是全音关系。所谓半音关系意味着,半音关系的音高比全音关系的音高少一半。如果把八度音阶,按半音的频宽展开的话,将会产生 12 个半音宽的频带,这就是所谓的十二平均律,又叫十二等程律,现在的钢琴就是根据十二平均律定音的。终于跟钢琴扯上关系了。
我们又知道一个八度音阶,对应的低频和高频相差1倍,即一个倍频程,那等程分成 12 份的话,那对应的就是 1/12 倍频程。音乐领域的标准化又来了,下图就是国际标准音高与频率对照表:
file
此表规定 A4 的频率 440Hz 作为基准频率,按照 1/12 倍频程的关系将频谱展开,就得到了每个标准钢琴琴键对应的音高频率。同样钢琴上的中央 C,对应的就是 C4 的键 261.63Hz。
file

音名和唱名

我们看到八度音阶是 1234567(do re mi fa sol la si),而这里又出现了 CDEFGAB,学过音乐的人都知道,一个是唱名一个是音名。唱名是演唱用的,而音名则是与固定频率或音高挂钩的,不同的是,同一个唱名可以放在不同的音名上,可以在钢琴琴键上左右移动,但是不同的音名则是与固定频率挂钩,是不会移动的。

钢琴调式

有了全音、半音、音名、唱名,以及唱名可以在琴键上移动,就产生了调式。
首先,全音和半音的关系,决定了是大调还是小调。凡是音阶排列符合【全、全、半、全、全、全、半】结构的音阶,就是大调。一般来说,一首音乐作品的主音符是使用 1、3、5 的,而结束在 1 上的就是大调音乐。
file
凡是音阶符合【全、半、全、全、半、全、全】结构的音阶,就是小调。小调音乐一般第一个音符是从 6 或 3 开始,而结束在 6 上。
file
当然大小调还按照不同的变音规则有很多其他细分,这里就不深究了。
其次,又因为唱名可以在琴键上移动,当主音符的 do 移动到 C 上,这时的大调就称做 C 大调,同理,当主音符的 do 移动到 D 上,这时的大调就称做 D 大调,而小调,也是这么定义的。
到此,终于给这两个看似不相关的名词拉上关系了,是不是还有点意思?

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

赞 0 收藏 0 评论 0

阿里云视频云 关注了标签 · 2020-12-17

RTC

实时音视频( Real-Time Communication,简称RTC),可以实现用户的就近接入,提供网络低延迟、低丢包率的音视频通信,具有一对一、一对多、多对多的音视频通话功能。支持录制、美颜、水印、伴音、导入外部视频流、互动白板、旁路直播。满足在线教育和培训、远程医疗、视频会议、娱乐直播场景。

关注 5

阿里云视频云 发布了文章 · 2020-12-17

实战排查|为什么遮挡推流摄像头,会导致播放绿屏?

前言:做音视频的小伙伴们多少都遇到过奇怪的BUG(如:卡顿、花屏、绿屏、变声等),表象上矛盾点颇多,推理得出的结论都是:“不应该啊!”,最终你抽丝剥茧,发现真相只有一个:“事出反常必有妖”!

作者:安果,阿里云高级技术专家,从事阿里云 RTC 服务器研发

奇怪现象

背景:RTC 互动中增加对 RTMP 的支持,实现 RTC 与 RTMP 相互订阅。

遇到一个奇怪的 BUG,遮挡住 RTC 端的摄像头,有的 RTMP 播放端(iPad air 2,iPad mini 2/4)会偶发绿屏。

file

要不先发版?

初步分析问题后,我们认为这是:一个偶发的终端兼容性问题,有很大概率需要修改 RTC 端的编码来适配,耗时不好评估。

“距离发版本的时间不到 2 周,要不就先发版本吧?” 这个请求被产品无情的拒绝了(这次真的感谢你们的坚持),测试也反馈了新的情况:iPhone 6 也出现了绿屏,关闭 RTC 端的摄像头也可能绿屏,Mac 摄像头对着白色墙面也可能绿屏(测试的同学们也太能折腾了),同时确认了 RTMP 编码 RTMP 播放时相同场景不绿屏。

编码还是封装的坑?

疑难杂症先会诊,同编解码的同学一起讨论完后确认两个可能的点:

1.编码的 264 码流不兼容。
2.封装发送的 RTMP 数据不兼容。

我们制定了后续的排查方案:

1.录制 RTMP 编码和 RTC 编码的码流做对比。
2.使用 FFmpeg 发送 RTC 编码的码流确认是否绿屏。

1.码流对比

我们录制了一个完整测试过程中的码流供编解码同学分析,通过粗略的对比发现一个重要的区别 vui 中色域相关信息不一致,色域会影响 yuv->rgb 的转换。下图是不绿屏码流的 vui

file

通过这个线索,我们做了两个验证测试:

1.我们的 vui 是否会造成黑色显示异常,通过摄像头采集一个黑色手机,在图像中形成大面积黑色。验证结果:正常显示。

2.按照正常码流修改 vui。验证结果:偶发绿屏。

算法的同学接着做更深入的分析。

2.封装对比

FFmpeg 发布 RTMP 的示例:
ffmpeg -re -i green.h264 -c copy -f flv rtmp://localhost/live/livestream

测试结果:正常显示测试中绿屏的场景。

封装对比结论:封装层问题,编解码的同学可以休息了(感谢你们一起填坑)。

封装填坑记

1.怀疑一切

对于这种黑盒问题,我们只能抱着怀疑一切的态度,开始各种猜测。

Metadata 排查

背景描述:我们没有发 Metadata(不是我们懒,RTC 场景音视频不一定都存在,没有准确的 Metadata),是不是 Metadata 造成的(虽然我们有自己的答案,Metadata 就算影响也是全局的,怎么会是特定场景,还偶发)。

验证方法:先用 FFmpeg 确认下,不发 Metadata 是否正常,如果正常再加 Metadata。(为什么不先加 Metadata?这次是真懒)

ffmpeg -re -i green.h264 -c copy -flvflags no_metadata -f flv rtmp://localhost/live/livestream

验证结果:没有绿屏,一切正常,Metadata 是无辜的。

SPS、PPS 排查

背景描述:RTC 场景 SPS、PPS 是可能变化的(屏幕共享,横竖屏切换等),所以我们将 SPS、PPS 通过 Sequence Header 和 IDR 帧进行了发送,FFmpeg 在这块可能是有区别的。

验证方法:Wireshark 抓包,确认 FFmpeg 只发送了一次 Sequence Header, SPS、PPS 也随 IDR 帧发送(录制码流中 IDR 前都有 SPS、PPS)。按 FFmpeg 的修改进行测试。

验证结果:还是绿屏,不过概率有所下降。

验证外延:SPS、PPS 全用 Sequence Header 发送,不再随 IDR 帧发送。

验证结果:还是绿屏,概率比前一方案更低。

警告:SPS、PPS 全用 Sequence Header 发送,兼容性差,FFplay 有概率播放失败,vlc 无法成功播放。我们保留了 FFmpeg 相同的做法,继续排查。

2.蛛丝马迹

各种猜测验证都失败了,只好跟测试同学不断沟通,希望可以寻找到些许线索,多次沟通和锁定一个比较有价值的线索:Mac 在关闭摄像头时,RTMP 端播放绿屏概率较高。

定点排查

排查全部转移到 Mac 关闭摄像头这个场景,从埋点数据中确认:Mac 关闭摄像头后, RTMP 发送的视频数量不再增加。

用 Wireshark 抓包确认关闭摄像头时 Mac 端是否有发送视频包,意外发现不仅有视频包,还有一些 seq 不变的视频 Padding 包(有效内容为 0),突然感觉黎明就在前方了,Review 完代码确认 Padding 包影响了组帧逻辑,受 Padding 包影响大部分视频帧被丢弃了,满怀信心的修改完代码。

file

所谓希望越大,失望越大。绿屏依旧存在,而且严重了很多,多次测试下来,它成了必现问题,虽然很失望,但是从偶发问题到必现问题也是一个很大的“进步”。

3.黎明之前

最黑暗的时刻,问题终于毕现了,却没了有效的排查手段。

由于 TCP 粘包问题,Wireshark 并不能完全正确的解析出每一个 RTMP 包,没有一个高效的办法查看 RTC 转换的 RTMP 包。

在忘篱同学的帮助下,通过 SRS 的 srs_rtmp_dump, 将 RTMP 数据保存为了 FLV 文件,具体用法:

#编译
git checkout 3.0release && ./configure --osx --with-librtmp --with-research && make -j8
#保存flv
./objs/research/librtmp/srs_rtmp_dump -r rtmp://127.0.0.1:1935/live/livestream -o output.flv > t.log

通过 FFmpeg 发布 FLV 文件,绿屏问题重现了,莫名的兴奋。

ffmpeg -re -i green.flv -c copy -flvflags no_metadata -f flv rtmp://localhost/live/livestream

虽然知道了 FLV 有问题,可是接下来对于 FLV 的分析却犯难了,首先 FLV 格式分析的结果并没有任何问题,用 FFmpeg 抽取出 FLV 中的 264,再发布时也正常。这个 FLV 文件用 FFplay 播放也是有错误信息的。

file

4.完美解决

当你无计可施,看着代码发呆的时候,休息一下可能是个解决问题的好办法。上班的地铁上,终于有了头绪:mark bit、stap,组帧逻辑判断条件,导致了两帧放到了一个 FLV 的 frame 中,造成部分终端不兼容。最终测试通过,版本正常发布,感谢这个过程中每一位同学的支持与配合。

总结

  1. 即使 IDR 帧,也可以很小,小到都填不满一个 UDP 包。
  2. SPS、PPS 和很小的 IDR 可以打到一个 STAP 的 RTP 包。
  3. 纯 Padding 包一定认真对待,没有实际数据的枷锁,它们可以做一些灵活的应用。
  4. 多帧封装到一起,有兼容性问题。
  5. “事出反常必有妖”,代码没有玄学。
  6. 认真考虑数据的准确性,优先排查两端的数据。
  7. 工具可以显著的提升效率。

福利

SRS 的 srs_flv_parser 中增加了 h264 video frame 中每个 nalu 的信息打印,希望对大家以后填坑有所帮助,提前发布预览效果,看看绿屏妖的原形。

file

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

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-12-10

深入浅出 WebRTC AEC(声学回声消除)

前言:近年来,音视频会议产品提升着工作协同的效率,在线教育产品突破着传统教育形式的种种限制,娱乐互动直播产品丰富着生活社交的多样性,背后都离不开音视频通信技术的优化与创新,其中音频信息内容传递的流畅性、完整性、可懂度直接决定着用户之间的沟通质量。自 2011 年 WebRTC 开源以来,无论是其技术架构,还是其中丰富的算法模块都是值得我们细细品味,音频方面熟知的 3A 算法(AGC: Automatic gain control; ANS: Adaptive noise suppression; AEC: Acoustic echo cancellation)就是其中闪闪发光的明珠。本文章将结合实例全面解析 WebRTC AEC 的基本框架和基本原理,一起探索回声消除的基本原理,技术难点以及优化方向。

作者:珞神,阿里云高级开发工程师,负责阿里云 RTC 音频研发

回声的形成

WebRTC 架构中上下行音频信号处理流程如图 1,音频 3A 主要集中在上行的发送端对发送信号依次进行回声消除、降噪以及音量均衡(这里只讨论 AEC 的处理流程,如果是 AECM 的处理流程 ANS 会前置),AGC 会作为压限器作用在接收端对即将播放的音频信号进行限幅。

图 1 WebRTC 中音频信号上下行处理流程框图

那么回声是怎么形成的呢?

如图 2 所示,A、B 两人在通信的过程中,我们有如下定义:

  • x(n): 远端参考信号,即 A 端订阅的 B 端音频流,通常作为参考信号;
  • y(n): 回声信号,即扬声器播放信号 x(n) 后,被麦克风采集到的信号,此时经过房间混响以及麦克风采集的信号 y(n) 已经不能等同于信号 x(n) 了, 我们记线性叠加的部分为 y'(n), 非线性叠加的部分为 y''(n), y(n) = y'(n) + y''(n);
  • s(n): 麦克风采集的近端说话人的语音信号,即我们真正想提取并发送到远端的信号;
  • v(n):环境噪音,这部分信号会在 ANS 中被削弱;
  • d(n): 近端信号,即麦克风采集之后,3A 之前的原始信号,可以表示为:d(n) = s(n) + y(n) + v(n);
  • s'(n): 3A 之后的音频信号,即准备经过编码发送到对端的信号。

WebRTC 音频引擎能够拿到的已知信号只有近端信号 d(n) 和远端参考信号 x(n)。

图 2 回声信号生成模型

如果信号经过 A 端音频引擎得到 s'(n) 信号中依然残留信号 y(n),那么 B 端就能听到自己回声或残留的尾音(回声抑制不彻底留下的残留)。AEC 效果评估在实际情况中可以粗略分为如下几种情况(专业人员可根据应用场景、设备以及单双讲进一步细分):

file

回声消除的本质

在解析 WebRTC AEC 架构之前,我们需要了解回声消除的本质是什么。音视频通话过程中,声音是传达信息的主要途径,因此从复杂的录音信号中,通过信号处理的手段使得我们要传递的信息:高保真、低延时、清晰可懂是一直以来追求的目标。在我看来,回声消除,噪声抑制和声源分离同属于语音增强的范畴,如果把噪声理解为广义的噪声三者之间的关系如下图:

图 3 语音增强与回声消除的关系

噪声抑制需要准确估计出噪声信号,其中平稳噪声可以通过语音检测判别有话端与无话端的状态来动态更新噪声信号,进而参与降噪,常用的手段是基于谱减法(即在原始信号的基础上减去估计出来的噪声所占的成分)的一系列改进方法,其效果依赖于对噪声信号估计的准确性。对于非平稳噪声,目前用的较多的就是基于递归神经网络的深度学习方法,很多 Windows 设备上都内置了基于多麦克风阵列的降噪的算法。效果上,为了保证音质,噪声抑制允许噪声残留,只要比原始信号信噪比高,噪且听觉上失真无感知即可。

单声道的声源分离技术起源于传说中的鸡尾酒会效应,是指人的一种听力选择能力,在这种情况下,注意力集中在某一个人的谈话之中而忽略背景中其他的对话或噪音。该效应揭示了人类听觉系统中令人惊奇的能力,即我们可以在噪声中谈话。科学家们一直在致力于用技术手段从单声道录音中分离出各种成分,一直以来的难点,随着机器学习技术的应用,使得该技术慢慢变成了可能,但是较高的计算复杂度等原因,距离 RTC 这种低延时系统中的商用还是有一些距离。

噪声抑制与声源分离都是单源输入,只需要近端采集信号即可,傲娇的回声消除需要同时输入近端信号与远端参考信号。有同学会问已知了远端参考信号,为什么不能用噪声抑制方法处理呢,直接从频域减掉远端信号的频谱不就可以了吗?

file

上图中第一行为近端信号 s(n),已经混合了近端人声和扬声器播放出来的远端信号,黄色框中已经标出对齐之后的远端信号,其语音表达的内容一致,但是频谱和幅度(明显经过扬声器放大之后声音能量很高)均不一致,意思就是:参考的远端信号与扬声器播放出来的远端信号已经是“貌合神离”了,与降噪的方法相结合也是不错的思路,但是直接套用降噪的方法显然会造成回声残留与双讲部分严重的抑制。接下来,我们来看看 WebRTC 科学家是怎么做的吧。

信号处理流程

WebRTC AEC 算法包含了延时调整策略,线性回声估计,非线性回声抑制 3 个部分。回声消除本质上更像是音源分离,我们期望从混合的近端信号中消除不需要的远端信号,保留近端人声发送到远端,但是 WebRTC 工程师们更倾向于将两个人交流的过程理解为一问一答的交替说话,存在远近端同时连续说话的情况并不多(即保单讲轻双讲)。

因此只需要区分远近端说话区域就可以通过一些手段消除绝大多数远端回声,至于双讲恢复能力 WebRTC AEC 算法提供了 {kAecNlpConservative, kAecNlpModerate, kAecNlpAggressive} 3 个模式,由低到高依次代表不同的抑制程度,远近端信号处理流程如图 4:

图 4 WebRTC AEC 算法结构框图

NLMS 自适应算法(上图中橙色部分)的运用旨在尽可能地消除信号 d(n) 中的线性部分回声,而残留的非线性回声信号会在非线性滤波(上图中紫色部分)部分中被消除,这两个模块是 Webrtc AEC 的核心模块。模块前后依赖,现实场景中远端信号 x(n) 由扬声器播放出来在被麦克风采集的过程中,同时包含了回声 y(n) 与近端信号 x(n) 的线性叠加和非线性叠加:需要消除线性回声的目的是为了增大近端信号 X(ω) 与滤波结果 E(ω) 之间的差异,计算相干性时差异就越大(近端信号接近 1,而远端信号部分越接近 0),更容易通过门限直接区分近端帧与远端帧。非线性滤波部分中只需要根据检测的帧类型,调节抑制系数,滤波消除回声即可。下面我们结合实例分析这套架构中的线性部分与非线性分。

线性滤波

线性回声 y'(n) 可以理解为是远端参考信号 x(n) 经过房间冲击响应之后的结果,线性滤波的本质也就是在估计一组滤波器使得 y'(n) 尽可能的等于 x(n),通过统计滤波器组的最大幅值位置 index 找到与之对齐远端信号帧,该帧数据会参与相干性计算等后续模块。

需要注意的是,如果 index 在滤波器阶数两端疯狂试探,只能说明当前给到线性部分的远近端延时较小或过大,此时滤波器效果是不稳定的,需要借助固定延时调整或大延时调整使 index 处于一个比较理想的位置。线性部分算法是可以看作是一个固定步长的 NLMS 算法,具体细节大家可以结合源码走读,本节重点讲解线型滤波在整个框架中的作用。

从个人理解来看,线性部分的目的就是最大程度的消除线性回声,为远近端帧判别的时候,最大程度地保证了信号之间的相干值( 0~1 之间,值越大相干性越大)的可靠性。

我们记消除线性回声之后的信号为估计的回声信号 e(n),e(n) = s(n) + y''(n) + v(n),其中 y''(n) 为非线性回声信号,记 y'(n) 为线性回声,y(n) = y'(n) + y''(n)。相干性的计算 (Matlab代码):

% WebRtcAec_UpdateCoherenceSpectra →_→ UpdateCoherenceSpectra
Sd = Sd * ptrGCoh(1) + abs(wined_fft_near) .* abs(wined_fft_near)*ptrGCoh(2);
Se = Se * ptrGCoh(1) + abs(wined_fft_echo) .* abs(wined_fft_echo)*ptrGCoh(2);
Sx = Sx * ptrGCoh(1) + max(abs(wined_fft_far) .* abs(wined_fft_far),ones(N+1,1)*MinFarendPSD)*ptrGCoh(2);
Sde = Sde * ptrGCoh(1) + (wined_fft_near .* conj(wined_fft_echo)) *ptrGCoh(2);
Sxd = Sxd * ptrGCoh(1) + (wined_fft_near .* conj(wined_fft_far)) *ptrGCoh(2);     

% WebRtcAec_ComputeCoherence →_→ ComputeCoherence
cohde = (abs(Sde).*abs(Sde))./(Sd.*Se + 1.0e-10);
cohdx = (abs(Sxd).*abs(Sxd))./(Sx.*Sd + 1.0e-10);

两个实验

(1)计算近端信号 d(n) 与远端参考信号 x(n) 的相关性 cohdx,理论上远端回声信号的相干性应该更接近 0(为了方便后续对比,WebRTC 做了反向处理: 1 - cohdx),如图 5(a),第一行为计算近端信号 d(n),第二行为远端参考信号 x(n),第三行为二者相干性曲线: 1 - cohdx,会发现回声部分相干值有明显起伏,最大值有0.7,近端部分整体接近 1.0,但是有持续波动,如果想通过一条固定的门限去区分远近端帧,会存在不同程度的误判,反映到听感上就是回声(远端判断成近端)或丢字(近端判断为远端)。

 (a) 近端信号与远端参考信号的相干性

 (b) 近端信号与估计的回声信号的相干性

图 5 信号的相干性

(2)计算近端信号 d(n) 与估计的回声信号 e(n) 的相干性,如图 5(b),第二行为估计的回声信号 e(n),第三行为二者相干性 cohde,很明显近端的部分几乎全部逼近 1.0,WebRTC 用比较严格的门限(>=0.98)即可将区分绝大部分近端帧,且误判的概率比较小,WebRTC 工程师设置如此严格的门限想必是宁可牺牲一部分双讲效果,也不愿意接受回声残留。

从图 5 可以体会到,线性滤波之后可以进一步凸显远端参考信号 x(n) 与估计的回声信号 e(n) 的差异,从而提高远近端帧状态的判决的可靠性。

存在的问题与改进

理想情况下,远端信号从扬声器播放出来没有非线性失真,那么 e(n) = s(n) + v(n),但实际情况下 e(n)与d(n) 很像,只是远端区域有一些幅度上的变化,说明 WebRTC AEC 线性部分在这个 case 中表现不佳,如图 6(a) 从频谱看低频段明显削弱,但中高频部分几乎没变。而利用变步长的双滤波器结构的结果会非常明显,如图 6(b) 所示无论是时域波形和频谱与近端信号 x(n) 都有很大差异,目前 aec3 和 speex 中都采用这种结构,可见 WebRTC AEC 中线性部分还有很大的优化空间。

(a) WebRTC AEC 线性部分输出

 (b) 改进的线性部分输出

图 6 近端信号与估计的回声信号的对比

如何衡量改进的线性部分效果?

这里我们对比了现有的固定步长的 NLMS 和变步长的 NLMS,近端信号 d(n) 为加混响的远端参考信号 x(n) + 近端语音信号 s(n)。理论上 NLMS 在处理这种纯线性叠加的信号时,可以不用非线性部分出马,直接干掉远端回声信号。图 7(a) 第一行为近端信号 d(n),第二列为远端参考信号 x(n),线性部分输出结果,黄色框中为远端信号。WebRTC AEC 中采用固定步长的 NLMS 算法收敛较慢,有些许回声残留。但是变步长的 NLMS 收敛较快,回声抑制相对好一些,如图 7(b)。

(a)固定步长的 NLMS

(b) 变步长的 NLMS

图 7 两种 NLMS 算法的效果对比

线性滤波器参数设置

#define FRAME_LEN 80
#define PART_LEN 64
enum { kExtendedNumPartitions = 32 };
static const int kNormalNumPartitions = 12;

FRAME_LEN 为每次传给音频 3A 模块的数据的长度,默认为 80 个采样点,由于 WebRTC AEC 采用了 128 点 FFT,内部拼帧逻辑会取出 PART_LEN = 64 个样本点与前一帧剩余数据连接成128点做 FFT,剩余的 16 点遗留到下一次,因此实际每次处理 PART_LEN 个样本点(4ms 数据)。

默认滤波器阶数仅为 kNormalNumPartitions = 12 个,能够覆盖的数据范围为 kNormalNumPartitions 4ms = 48ms,如果打开扩展滤波器模式(设置 extended_filter_enabled为true),覆盖数据范围为 kNormalNumPartitions 4ms = 132ms。随着芯片处理能力的提升,默认会打开这个扩展滤波器模式,甚至扩展为更高的阶数,以此来应对市面上绝大多数的移动设备。另外,线性滤波器虽然不具备调整延时的能力,但可以通过估计的 index 衡量当前信号的延时状态,范围为 [0, kNormalNumPartitions],如果 index 处于作用域两端,说明真实延时过小或过大,会影响线性回声估计的效果,严重的会带来回声,此时需要结合固定延时与大延时检测来修正。

非线性滤波

非线性部分一共做了两件事,就是想尽千方百计干掉远端信号。

(1) 根据线性部分提供的估计的回声信号,计算信号间的相干性,判别远近端帧状态。

(2) 调整抑制系数,计算非线性滤波参数。

非线性滤波抑制系数为 hNl,大致表征着估计的回声信号 e(n) 中,期望的近端成分与残留的非线性回声信号 y''(n) 在不同频带上的能量比,hNl 是与相干值是一致的,范围是 [0,1.0],通过图 5(b) 可以看出需要消除的远端部分幅度值也普遍在 0.5 左右,如果直接使用 hNl 滤波会导致大量的回声残留。

因此 WebRTC 工程师对 hNl 做了如下尺度变换,over_drive 与 nlp_mode 相关,代表不同的抑制激进程度,drive_curve 是一条单调递增的凸曲线,范围 [1.0, 2.0]。由于中高频的尾音在听感上比较明显,所以他们设计了这样的抑制曲线来抑制高频尾音。我们记尺度变换的 α = over_drive_scaling * drive_curve,如果设置 nlp_mode = kAecNlpAggressive,α 大约会在 30 左右。

% matlab代码如下:
over_drive = min_override(nlp_mode+1);
if (over_drive < over_drive_scaling)
  over_drive_scaling = 0.99*over_drive_scaling + 0.01*over_drive;  % default 0.99 0.01
else
  over_drive_scaling = 0.9*over_drive_scaling + 0.1*over_drive; % default 0.9 0.1
end

% WebRtcAec_Overdrive →_→ Overdrive
hNl(index) = weight_curve(index).*hNlFb + (1-weight_curve(index)).* hNl(index);
hNl = hNl.^(over_drive_scaling * drive_curve);

% WebRtcAec_Suppress →_→ Suppress
wined_fft_echo = wined_fft_echo .*hNl;
wined_fft_echo = conj(wined_fft_echo);

如果当前帧为近端帧(即 echo_state = false),假设第 k 个频带 hNl(k) = 0.99994 ,hNl(k) = hNl(k)^α = 0.99994 ^ 30 = 0.9982,即使滤波后的损失听感上几乎无感知。如图 8(a),hNl 经过 α 调制之后,幅值依然很接近 1.0。

如果当前帧为远端帧(即 echo_state = true),假设第 k 个频带 hNl(k) = 0.6676 ,hNl(k) = hNl(k)^α = 0.6676 ^ 30 = 5.4386e-06,滤波后远端能量小到基本听不到了。如图 8(b),hNl 经过 α 调制之后,基本接近 0。

(a)近端帧对应的抑制系数

(b)远端帧对应的抑制系数

图 8 远近端信号抑制系数在调制前后的变化

经过如上对比,为了保证经过调制之后近端期望信号失真最小,远端回声可以被抑制到不可听,WebRTC AEC 才在远近端帧状态判断的的模块中设置了如此严格的门限。

另外,调整系数 α 过于严格的情况下会带来双讲的抑制,如图 9 第 1 行,近端说话人声音明显丢失,通过调整 α 后得以恢复,如第 2 行所示。因此如果在 WebRTC AEC 现有策略上优化 α 估计,可以缓解双讲抑制严重的问题。

图 9 双讲效果

延时调整策略

回声消除的效果与远近端数据延时强相关,调整不当会带来算法不可用的风险。在远近端数据进入线性部分之前,一定要保证延时在设计的滤波器阶数范围内,不然延时过大超出了线性滤波器估计的范围或调整过当导致远近端非因果都会造成无法收敛的回声。先科普两个问题:

(1)为什么会存在延时?

首先近端信号 d(n) 中的回声是扬声器播放远端参考 x(n),又被麦克风采集到的形成的,也就意味着在近端数据还未采集进来之前,远端数据缓冲区中已经躺着 N 帧 x(n)了,这个天然的延时可以约等于音频信号从准备渲染到被麦克风采集到的时间,不同设备这个延时是不等的。苹果设备延时较小,基本在 120ms 左右,Android 设备普遍在 200ms 左右,低端机型上会有 300ms 左右甚至以上。

(2)远近端非因果为什么会导致回声?

从(1)中可以认为,正常情况下当前帧近端信号为了找到与之对齐的远端信号,必须在远端缓冲区沿着写指针向前查找。如果此时设备采集丢数据,远端数据会迅速消耗,导致新来的近端帧在向前查找时,已经找不到与之对齐的远端参考帧了,会导致后续各模块工作异常。如图 10(a) 表示正常延时情况,(b) 表示非因果。

(a)远近端正常延时

(b)远近端非因果

图10 正常远近端延时与非因果

WebRTC AEC 中的延时调整策略关键而且复杂,涉及到固定延时调整,大延时检测,以及线性滤波器延时估计。三者的关系如下:

① 固定延时调整只会发生在开始 AEC 算法开始处理之前,而且仅调整一次。如会议盒子等固定的硬件设备延时基本是固定的,可以通过直接减去固定的延时的方法缩小延时估计范围,使之快速来到滤波器覆盖的延时范围之内。
下面结合代码来看看固定延时的调整过程:

int32_t WebRtcAec_Process(void* aecInst,
const float* const* nearend,
size_t num_bands,
float* const* out,
size_t nrOfSamples,
int16_t reported_delay_ms,
int32_t skew);

WebRtcAec_Process 接口如上,参数 reported_delay_ms 为当前设备需要调整延时的目标值。如某 Android 设备固定延时为 400ms 左右,400ms 已经超出滤波器覆盖的延时范围,至少需要调整 300ms 延时,才能满足回声消除没有回声的要求。固定延时调整在 WebRTC AEC 算法开始之初仅作用一次:

if (self->startup_phase) {
    int startup_size_ms = reported_delay_ms < kFixedDelayMs ? kFixedDelayMs : reported_delay_ms;
    int target_delay = startup_size_ms * self->rate_factor * 8;
    int overhead_elements = (WebRtcAec_system_delay_aliyun(self->aec) - target_delay) / PART_LEN;
    printf("[audio] target_delay = %d, startup_size_ms = %d, self->rate_factor = %d, sysdelay = %d, overhead_elements = %d\n", target_delay, startup_size_ms, self->rate_factor, WebRtcAec_system_delay(self->aec), overhead_elements);
    WebRtcAec_AdjustFarendBufferSizeAndSystemDelay_aliyun(self->aec,  overhead_elements);
self->startup_phase = 0;
  }

为什么 target_delay 是这么计算?

int target_delay = startup_size_ms self->rate_factor 8;
startup_size_ms 其实就是设置下去的 reported_delay_ms,这一步将计算时间毫秒转化为样本点数。16000hz 采样中,10ms 表示 160 个样本点,因此 target_delay 实际就是需要调整的目标样本点数(aecpc->rate_factor = aecpc->splitSampFreq / 8000 = 2)。

我们用 330ms 延时的数据测试:
如果设置默认延时为 240ms,overhead_elements 第一次被调整了 -60 个 block,负值表示向前查找,正好为 60 4 = 240ms,之后线性滤波器固定 index = 24,表示 24 4 = 96ms 延时,二者之和约等于 330ms。日志打印如下:

file

② 大延时检测是基于远近端数据相似性在远端大缓存中查找最相似的帧的过程,其算法原理有点类似音频指纹中特征匹配的思想。大延时调整的能力是对固定延时调整与线型滤波器能力的补充,使用它的时候需要比较慎重,需要控制调整的频率,以及控制造成非因果的风险。

WebRTC AEC 算法中开辟了可存储 250 个 block 大缓冲区,每个 block 的长度 PART_LEN = 64 个样本点,能够保存最新的 1s 的数据,这也是理论上的大延时能够估计的范围,绝对够用了。

static const size_t kBufferSizeBlocks = 250;
buffer_ = WebRtc_CreateBuffer(kBufferSizeBlocks, sizeof(float) * PART_LEN);
aec->delay_agnostic_enabled = 1;

我们用 610ms 延时的数据测试(启用大延时调整需要设置 delay_agnostic_enabled = 1):
我们还是设置默认延时为 240ms,刚开始还是调整了 -60 个 block,随后大延时调整接入之后有调整了 -88 个 block,一共调整(60 + 88) * 4 = 592ms,之后线性滤波器固定 index = 4,表示最后剩余延时剩余 16ms,符合预期。

file

file

③ 线性滤波器延时估计是固定延时调整和大延时调整之后,滤波器对当前远近端延时的最直接反馈。前两者调整不当会造成延时过小甚至非因果,或延时过大超出滤波器覆盖能力,导致无法收敛的回声。因此前两者在调整的过程中需要结合滤波器的能力,确保剩余延时在滤波器能够覆盖的范围之内,即使延时小范围抖动,线性部分也能自适应调整。

总结与优化方向

WebRTC AEC 存在的问题:

(1)线性部分收敛时间较慢,固定步长的 NLMS 算法对线性部分回声的估计欠佳;
(2)线性部分滤波器阶数默认为 32 阶,默认覆盖延时 132ms,对移动端延时较大设备支持不是很好,大延时检测部分介入较慢,且存在误调导致非因果回声的风险;
(3)基于相干性的帧状态依赖严格的固定门限,存在一定程度的误判,如果再去指导非线性部分抑制系数的调节,会带来比较严重的双讲抑制。

优化的方向:
(1)算法上可以通过学习 speex 和 AEC3 的线性部分,改善当前线性滤波效果;
(2)算法上可以优化延时调整策略,工程上可以新增参数配置下发等工程手段解决一些设备的延时问题;
(3)另外,有一些新的思路也是值得我们尝试的,如开头提到的,既然回声也可以是视为噪声,那么能否用降噪的思路做回声消除呢,答案是可以的。

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

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-12-08

揭秘 VMAF 视频质量评测标准

作者:杨洋,阿里云技术专家,从事直播相关媒体处理引擎开发

背景

图像质量的衡量是个老问题,对此人们提出过很多简单可行的解决方案。例如均方误差(Mean-squared-error,MSE)、峰值信噪比(Peak-signal-to-noise-ratio,PSNR)以及结构相似性指数(Structural Similarity Index,SSIM),这些指标最初都是被用于衡量图像质量的,随后被扩展到视频领域。
这些指标通常会用在循环用在编码内部,可用于对编码决策进行优化并估算最终编码后视频的质量。但是由于这些算法衡量标准单一,缺乏对画面前后序列的总体评估,导致计算的结果很多情况下与主观感受并不相符。

视频质量

阿里云视频云每天都会生产、处理大量来自不同应用场景、不同编码类型的视频数据。除了采用窄带高清等技术以外,我们还会借助 VMAF 标准对处理后的视频做质量评估,形成 媒体生产-->画质评估-->编码优化-->产品迭代 的技术闭环。基于这套反馈机制,对不同的场景做针对性优化,达到画质最优、成本最低的效果。

VMAF 介绍

面对不同特征的源内容、失真类型,以及扭曲程度,每个基本指标都各有优劣,缺少一个通用的、能反应主观意识的画质评估手段。VMAF (Video Multimethod Assessment Fusion) 由 Netflix 开发并开源,利用大量的主观数据作为训练集,通过机器学习的手段将不同评估维度的算法进行“融合”,得到一个能准确反映主观意志的画质评价标准。
VMAF 主要包括3种指标:visual quality fidelity(VIF)、detail loss measure(DLM)、temporal information(TI)。其中 VIF 和 DLM 是空间域的也即一帧画面之内的特征,TI 是时间域的也即多帧画面之间相关性的特征。这些特性之间融合计算总分的过程使用了训练好的 SVM 来预测。
工作流程如图:
file

VMAF 模型分析

VMAF 默认提供了 vmaf_float_v0.6.1.pkl 模型,可以分为 HDTV、Phone、4K 超分辨率三种使用模式,满足不同播放场景下的画质评测需求。

1.HDTV 模式

HDTV 是针对客厅电视的场景设计的,参照 SMPTE EG-18-1994 标准,所有训练集的主观数据 (EMOS) 是遵循这种方式收集的:将视频 scale 成 1080p,并以三倍于屏幕高度 (3H) 的观看距离或 60 像素/度的角度分辨率进行显示。
可以说,VMAF 模型试图捕捉的是 3H 外显示的 1080p 视频的感知质量。这是默认 VMAF 模型的隐含假设。因此,我们在对除 1080p 以外的其他分辨率进行 VMAF 计算时,需要将视频先 scale 成1080p,才能保证结果的准确性。

file

试想一下,如果将一个 480p 的视频用 HDTV 模式来做评测,会发生什么情况呢?这就好像 480p 视频是从 1080p 视频中剪切出来的一样。如果 480p 视频的高度为 H ',则 H = 1080 / 480 * H ',其中 H 为所显示的 1080p 视频的高度。因此,VMAF 建模的是 3H = 6.75H ' 的观看距离。
换句话说,如果你计算 480p 分辨率的视频对的 VMAF,你将预测观看距离是其高度的 6.75 倍时的感知质量。在此观看距离会隐藏大量主观画质的感知细节,从而让 VMAF 分数可能偏高。

2.Phone 模式

默认的 vmaf_float_v0.6.1.pkl 模型也提供了移动设备场景的画质评估功能,可以通过参数 “--phone-model” 开启。在此模式下,每个受试者在他/她感到舒适的距离观看视频。
在训练过的模型中,分数在 0 - 100 之间,与主观投票量表呈线性关系,粗略的 “bad” 映射为20分,“excellent” 映射为 100 分。
横向对比可以发现,如果将一部 540p 的视频分别放在标准电视、手机设备、4K 设备显示,在手机设备上的 VMAF 质量会比其他两者更快的逼近 100 分的临界值。

file

3. 4K 模式

由于 vmaf_float_v0.6.1.pkl 模型的训练集中包括了 4K 和 1080p 的视频源,当需要对比 A/B 两个 4K 视频哪一个画质更好时,也能使用此模型。
但是由于默认模型采用的是 1080p + 3H 观看距离的方式采集的 EMOS 数据,无法算出准确的 4K VMAF 分数。Netflix 后来专门提供了 vmaf_4k_v0.6.1.pkl 用于 4K 的画质评估。

VMAF 核心模块

VMAF 基于 SVM 的 nuSvr 算法,在运行的过程中,根据事先训练好的 model,赋予每种视频特征以不同的权重。对每一帧画面都生成一个评分,最终以均值算法进行归总(也可以使用其他的归总算法),算出该视频的最终评分。其中主要的几个核心模块如下:

file

VMAF 分别用 python 和 C++ 实现了两套接口,同时提供了 C 版本的 lib 库,最新版本的 ffmpeg 已经将 VMAF 作为一个filter集成进去。
下面我们分析下各个模块的作用:

Asset

一个 Asset 单元,包含了一个正在执行的任务信息。比如目标视频与原始视频的帧范围,低分辨率视频帧上采样信息等(VMAF 会在特征提取前通过上采样的方式保证两个视频分辨率相同)。

Executor

Executor 会取走并计算 Asset 链表中每一个 Asset 单元,将执行结果返回到一个 Results 链表中。Executor 类是 FeatureExtractor 与 QualityRunner 的基类。它提供了一些基函数,包括 Results 的操作函数、FIFO 管道函数、clean 函数等。

Result

Result 是以 key-value 形式,将 Executor 执行的结果存储起来。key 存储的是 “FrameNum” 或者质量分数的类型(VMAF_feature_vif_scale0_score 或 VMAF_feature_vif_scale1_score 等),value 存储的是一系列分值组成的链表。
Result 类也提供了一个汇总工具,将每个单元的质量分数汇总成一个值。默认的汇总算法是“均值算法”,但是 Result.set_score_aggregate_method() 方法允许定制其他的算法。

ResultStore

ResultStore 类提供了 Result 数据集的存储、加载的能力。

FeatureExtractor

FeatureExtractor 是 Extractor 子类,专门用于从 Asset 集合中提取特征,作为基本的特征提取类。任何具体的特征提取接口,都继承自 FeatureExtractor,例如 VmafFeatureExtractor/PsnrFeatureExtractor/SsimFeatureExtractor 等。

FeatureAssembler

FeatureAssembler 是一个聚合类,通过在构造函数传入 feature_dict 参数,指定具体的特征提取标准,将该标准提取出的特征结果聚合,输出到一个 BasicResult 对象中。FeatureAssembler 被 QualityRunner 调用,用来将提取后的特征数组传给 TrainTestModel 使用。

TrainTestModel

TrainTestModel 是任何具体的回归因子接口的基类,回归因子必须提供一个 train() 方法去训练数据集,predict() 方法去预测数据集,以及 to_file(),frome_file() 方法去保存、加载训练好的模型。
回归方程的超参数必须通过 TrainTestModel 的构造函数传入。TrainTestModel 类提供了一些基础方法,例如归一化、反归一化、评估预测性能。

CrossValidation

CrossValidation 提供了一组静态方法来促进 TrainTestModel 训练结果的验证。因此,它还提供了搜索 TrainTestModel 最优超参的方法。

QualityRunner

QualityRunner 是 Executor 子类,用来评估 Asset 任务集合的画质分数。任何用于生成最终质量评分的接口都应该继承 QualityRunner。例如跑 vmaf 标准的 VmafQualityRunner,跑 psnr 标准的 PsnrQualityRunner 都是 QualityRunner 的子类。

自定义 VMAF

最新版本的 vmaf 提供了 1080p、4k、mobilephone 三种场景下的 model 文件。Netflix 号称使用了海量的、多分辨率、多码率视频素材(高噪声视频、CG动漫、电视剧)作为数据集,得到的这三组 model。在日常使用中,这三组 model 基本满足需求了。不过,VMAF 提供了 model 训练工具,可以用于训练私有 model。

1.创建新的数据集

首先,按照 dataset 格式,定义数据集文件,比如定义一个 example_dataset.py:

dataset_name = 'example_dataset'
yuv_fmt = 'yuv420p'
width = 1920
height = 1080
ref_videos = [
{'content_id': 0,
'content_name': 'BigBuckBunny',
'path': ref_dir + '/BigBuckBunny_25fps.yuv'}
...
]
dis_videos = [{'asset_id': 0,
'content_id': 0,
'dmos': 100.0,
'path': ref_dir + '/BigBuckBunny_25fps.yuv',
}
...
]

ref_video 是比对视频集,dis_video 是训练集。每个训练集样本视频都有一个主观评分 DMOS,进行主观训练。SVM 会根据 DMOS 做有监督学习,所以 DMOS 直接关系到训练后 model 的准确性。

PS: 将所有观察者针对每个样本视频的分数汇总在一起计算出微分平均意见分数 (Differential Mean Opinion Score) 即 DMOS,并换算成 0-100 的标准分,分数越高表示主观感受越好。

2.验证数据集

./run_testing quality_type test_dataset_file [--vmaf-model optional_VMAF_model_path] [--cache-result] [--parallelize]

数据集创建后,用现有的 VMAF 或其他指标 (PSNR,SSIM) 验证数据集是否正确,验证无误后才能训练。

3.训练新的模型

验证完数据集没问题后,便可以基于数据集,训练一个新的质量评估模型。
./run_vmaf_training train_dataset_filepath feature_param_file model_param_file output_model_file [--cache-result] [--parallelize]

例如,
./run_vmaf_training example_dataset.py resource/feature_param/vmaf_feature_v2.py resource/model_param/libsvmnusvr_v2.py workspace/model/test_model.pkl --cache-result --parallelize

feature_param_file 定义了使用那些 VMAF 特征属性。例如:
feature_dict = {'VMAF_feature':'all', } 或 feature_dict = {'VMAF_feature':['vif', 'adm'], }

model_param_file 定义了回归量的类型和使用的参数集。当前版本的 VMAF 支持nuSVR 和随机森林两种机器算法,默认使用的 nuSVR。
output_model_file 是新生成的 model 文件。

4.交叉验证

vmaf 提供了 run_vmaf_cross_validation.py 工具用于对新生成的 model 文件做交叉验证。

5.自定义特征和回归因子

vmaf 具有很好的可扩展性,不仅可以训练私有的 model,也可以定制化或插入第三方的特征属性、SVM 回归因子。
通过 feature_param_file 类型文件,支持自定义或插入第三方特征,需要注意的是所有的新特征必须要是 FeatureExtractor 子类。类似的,也可以通过 param_model_file 类型文件,自定义或插入一个第三方的回归因子。同样需要注意的是,所有创建的新因子,必须是 TrainTestModel 子类。

总结

一套普适的主观质量评价标准,不仅要有时域、空域的多维度评价标准,符合主观意识的融合算法,还要有大量有效的、符合标准规范的 EMOS 数据集。是一个需要长期投入,不断迭代的优化过程。

阿里云视频云技术公众号分享视频云行业和技术趋势,打造“新内容”、“新交互”。
查看原文

赞 0 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-11-24

WebRTC SDP 详解和剖析

WebRTC 是 Web Real-Time Communication,即网页实时通信的缩写,是 RTC 协议的一种 Web 实现,项目由 Google 开源,并和 IETF 和 W3C 制定了行业标准。在国内 WebRTC 已经获得了越来越多厂商的支持,应用前景变得更加广阔,所以我们也开设专栏,分享阿里云内部的 WebRTC 研究工作。

本篇是阿里云视频云 WebRTC 技术专栏系列文章的第一篇,作者将从 WebRTC SDP 例子和关键属性的角度为大家深度剖析解读,其中也分享了阿里云技术专家的一些实践经验,希望能对大家有所帮助或者启发。后续 WebRTC 技术专栏系列将继续推出 WebRTC ICE/DTLS/SRTP/RTCP/TURN 的详解与剖析,欢迎关注我们的公众号。

作者:
忘篱,阿里云高级技术专家,负责阿里云 RTC 服务器研发;
泰一,阿里云高级开发工程师,从事阿里云 RTC 服务器研发

SDP关键属性

Overview

狭义的说 WebRTC 是指浏览器端,浏览器端如何直接交换数据呢?肯定是没法完全独立完成的,必须得依靠服务器。一般依赖几种服务器:

  1. Signaling 信令服务器,也就是交换房间和会议的媒体信息,以及会议期间的消息,媒体描述使用的是 SDP 协议,也就是本文剖析的重点。
  2. ICE 服务器,可以分为帮助两个客户端打洞建立 P2P 连接的 STUN 服务器,还有如果连不通就直接转发的 TURN 服务器。ICE 的信息叫 Candidate,可以通过 SDP 交换,或者通过 Trickle。
  3. SFU 或 MCU 服务器,如果多个人开会,每个端都向其他参会的端直接发送数据叫 MESH,但是 MESH 明显有局限性,SFU 就是转发可以让客户端只上行一路流转给其他客户端,而 MCU 更强大,可以上下行都只有一路流。
Note: WebRTC 除了传输,还有一个重要特性就是安全性,也就是 DTLS,而 DTLS 有些信息就是通过 SDP 传递的,后面会有相关的技术文章来介绍 DTLS。

下面,我们正式介绍 SDP 协议。

What's SDP

本文开篇的 SDP 关键属性图,已经帮助我们以全局的视角一窥 SDP 的模样。SDP 描述了媒体会话,网络信息、安全特性、传输策略等,图中的每一个 SDP 属性都在不同的应用场景下发挥着不同的作用,不可小觑。

接下来,我们进一步给出 SDP 的官方定义:SDP(Session Description Protocol) 是一种会话描述协议,基于文本,其本身并不属于传输协议,需要依赖其它的传输协议(比如 SIP 和 HTTP)来交换必要的媒体信息,用于两个会话实体之间的媒体协商。

WebRTC 的 Offer 和 Answer 包含了 SDP。相关的 RFC 包括:

  1. 1998, RFC2327
  2. 2006, RFC4566

一个不错的 WebRTC 的 SDP 例子分析

Offer and Answer

WebRTC 使用 Offer-Answer 模型交换 SDP,Offer 中有 SDP,Answer 中也有。例如 Alice 和 Bob 通过 WebRTC 通信:

// Alice Offer
v=0
o=- 2397106153131073818 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE video
a=msid-semantic: WMS gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS
m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:l5KU
a=ice-pwd:+Sxmm3PoJUERpeHYL0HW4/T9
a=ice-options:trickle
a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE:DA:64:A0:37:7E:61:CB:9D:91:9B:44:F6:C9:AC:3B:37:1C:00:15:4C:5A:B5:67:74
a=setup:actpass
a=mid:video
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=ssrc-group:FID 2527104241
a=ssrc:2527104241 cname:JPmKBgFHH5YVFyaJ
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS c7072509-df47-4828-ad03-7d0274585a56
a=ssrc:2527104241 mslabel:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS
a=ssrc:2527104241 label:c7072509-df47-4828-ad03-7d0274585a56

// Bob Answer
v=0
o=- 5443219974135798586 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE video
a=msid-semantic: WMS uiZ7cB0hsFDRGgTIMNp6TajUK9dOoHi43HVs
m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MUZf
a=ice-pwd:4QhikLcmGXnCfAzHDB++ZjM5
a=ice-options:trickle
a=fingerprint:sha-256 2A:5A:B8:43:66:05:B3:6A:E9:46:36:DF:DF:20:11:6A:F6:11:EA:D9:4E:26:E3:CE:5A:3A:C6:8D:03:49:7B:DE
a=setup:active
a=mid:video
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=ssrc-group:FID 3587783331
a=ssrc:3587783331 cname:INxZnBV2Sty1zlmN
a=ssrc:3587783331 msid:uiZ7cB0hsFDRGgTIMNp6TajUK9dOoHi43HVs a3b297e7-cdbe-464e-a32c-347465ace055
a=ssrc:3587783331 mslabel:uiZ7cB0hsFDRGgTIMNp6TajUK9dOoHi43HVs
a=ssrc:3587783331 label:a3b297e7-cdbe-464e-a32c-347465ace055
Remark: 用 Chrome 浏览器,先打开 webrtc-internals,然后打开 Alice 页面点 Share 按钮,接着打开 Bob 页面点 Share,看到上面的 Offer 和 Answer。

交换完 SDP 后,会交换 Candidate:

// Alice Candidate
candidate: candidate:1912876010 1 udp 2122260223 30.2.220.94 52832 typ host generation 0 ufrag l5KU network-id 1 network-cost 10
candidate: candidate:1015535386 1 tcp 1518280447 30.2.220.94 9 typ host tcptype active generation 0 ufrag l5KU network-id 1 network-cost 10

// Bob Candidate
candidate:1912876010 1 udp 2122260223 30.2.220.94 51551 typ host generation 0 ufrag MUZf network-id 1 network-cost 10

最后 Alice 和 Bob 通信的 Candidate pair,选择的是 UDP 通道:

file

Alice 发送的 Video 的信息:

file

Alice 收到的 (Bob 的) Video信息:

file

一般来说,推流方先发起 Offer,接收方给 Answer。比如客户端推流到 SFU,客户端发起 Offer 推流,SFU 给客户端 Answer,客户端将流推到 SFU,SFU 再转发给其他客户端。Licode 和 Janus 都是这种做法,这种方式下,如果客户端需要拉取其他的客户端的流,一般需要使用另外的 PeerConnection,接收 SFU 的 Offer,生成 Answer 后回应给 SFU。

不过,推流方发起 Offer 不是必须的,接收方也可以给 Offer,推流方给 Answer。比如 MediaSoup 这种 SFU,客户端先给一个 Offer 给 SFU,SFU 只是检查这个 Offer 中的媒体特性,然后 SFU 会生成 Offer(包含会议中的其他客户端的流,如果没有人则没有 SSRC)给客户端,客户端发送 Answer 给 SFU。这种方式的好处是其他客户端加入,以及流的变更(比如关闭视频打开视频时),都可以使用 Reoffer,也就是统一由 SFU 发起新的 Offer,客户端响应,SFU 和客户端的交互模式只有一种。

SDP Structure

SDP 描述分为两部分,分别是会话级别的描述(session level)和媒体级别的描述(media level),其具体的组成可参考 RFC4566,带星号 (*) 的是可选的。常见的内容如下:

Session description(会话级别描述)
         v=  (protocol version)
         o=  (originator and session identifier)
         s=  (session name)
         c=* (connection information -- not required if included in all media)
         One or more Time descriptions ("t=" and "r=" lines; see below)
         a=* (zero or more session attribute lines)
         Zero or more Media descriptions

Time description
         t=  (time the session is active)

Media description(媒体级别描述), if present
         m=  (media name and transport address)
         c=* (connection information -- optional if included at session level)
         a=* (zero or more media attribute lines)

对照 Alice 的 Offer(只包含了视频没有开启音频):

// Session description
v=0
o=- 2397106153131073818 2 IN IP4 127.0.0.1
s=-
c=IN IP4 0.0.0.0

// Time description
t=0 0

// Session Attributes
a=group:BUNDLE video
a=msid-semantic: WMS gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS

// Media description
m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:l5KU
a=ice-pwd:+Sxmm3PoJUERpeHYL0HW4/T9
a=ice-options:trickle
a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE:DA:64:A0:37:7E:61:CB:9D:91:9B:44:F6:C9:AC:3B:37:1C:00:15:4C:5A:B5:67:74
a=setup:actpass
a=mid:video
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=ssrc-group:FID 2527104241
a=ssrc:2527104241 cname:JPmKBgFHH5YVFyaJ
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS c7072509-df47-4828-ad03-7d0274585a56
a=ssrc:2527104241 mslabel:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS
a=ssrc:2527104241 label:c7072509-df47-4828-ad03-7d0274585a56

SDP Line 是顺序相关的,比如 a=rtpmap:96 后面的都是它相关的设置,直到下一行是a=rtpmap或者其他属性。

SDP Line 没有统一的 Schema 描述,也就是没有一个固定的规则能解析所有 Line,SDP Grammer 只是描述了 SDP 相关的属性,具体每个属性的表达需要根据属性定义,定义在 RFC 4566,例如:

a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]

SDP 解析时,每个 SDP Line 都是以 key=... 形式,解析出 key 是 a 后,可能有两种方式,可参考 RFC4566

a=<attribute>
a=<attribute>:<value>

比如 c=IN IP4 0.0.0.0,key 为 c。
比如 a=rtcp-mux,key 为 a,attribute 为 rtcp-mux,没有 value。
比如 a=rtpmap:96 VP8/90000,key 为 a,attribute 为 rtpmap,value=96 VP8/90000。

有时候并非冒号 (:) 就一定是 <attribute>:<value>,实际上 value 里面也会有冒号,比如:

a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS

Session Level Field

会话级别的 SDP 描述字段包括:v、o、s、c、b、t。

  • v(version)

SDP 协议版本,值固定为 0。

  • o(origin)

代表会话的发起者。

  • s(session name)

会话的名称,每个 SDP 中有且仅能有一个 s 描述,其值不能为空。

  • c(connection data)

携带了会话的连接信息,其实就是 IP 地址。
SDP 的会话级别描述可以包含该字段,每一个媒体级别的描述也可以包含该字段,如果会话级别和媒体级别都有 c line,那么以媒体级别的 c line 为准。
因为 WebRTC 使用 ICE candidate 交换地址信息,所以不会用到 c line,不过这并不代表 c line 没有用,在 SIP 视频会议场景中,c line 就必不可少了,文末会再次介绍该字段。

  • b (bandwidth)

表示会话或媒体使用的建议带宽。

  • t(timing)

指定了会话的开始和结束时间,如果开始和结束时间都为 0,那么意味着这次会话是永久的。

关于会话级别字段的更详细的描述,请参考 RFC 4566

Media Codecs

会话级别描述完成后,后面就是零到多个媒体级别描述,比如:

// Session Description
v=0
......

// Audio Media Description
m=audio 9 UDP/TLS/RTP/SAVPF 111
......

// Video Media Description
m=video 9 UDP/TLS/RTP/SAVPF 96 97
......

这个 SDP 描述了一个音频和一个视频,它的格式参考 RFC4566:

m=<media> <port> <proto> <fmt> ...

其中,后面的一串数字 11196 97 就是 fmt,分别代表音频和视频的 Media Codec,后面会跟着 rtpmap、rtcp-fb、fmtp 这些属性来做进一步的详细的描述。

m=audio 9 UDP/TLS/RTP/SAVPF 111
a=mid:audio
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1

m=video 9 UDP/TLS/RTP/SAVPF 96 97
a=mid:video
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
Remark: 当然,M line 的类型不是只有 audio 和 video,还有 application(bfcp)、text 等媒体类型。

Remark: a=mid 属性可以认为是每个 M 描述的唯一 ID。比如 a=mid:audio,那么 audio 这个字符串就是这个 M 描述的 ID。有的时候 mid 属性值也可以用数字表示,比如 a=mid:0,那么 0 也是这个 M 描述的 ID。mid 值一般和 grouping 传输属性的 BUNDLE 策略结合来用,比如 a=group:BUNDLE audio video,代表本次会话将对 mid 为 audiovideo 的 M 描述进行复用传输。

Remark: M line 的数字 9 代表该媒体类型的传输端口,在 RTC 场景中都是使用 ICE candidate 的地址信息进行数据传输,所以 M line 的 port 并没有用到。不过,在 SIP 的场景下,M line 的 port 就十分重要了,此时,port 代表 RTP 端口,而且必须是偶数。结合 SDP 会话级别描述中的 C line 中的 IP 地址,我们就可以知道 SIP 的这路媒体流的传输地址。

Remark: RTX 表示是重传,比如 video 的 97,就是 apt=96 的重传。也就是说如果用的是 97 这个编码格式,它是在 96(VP8) 基础上加了重传功能。

而一共有多少媒体流,则是通过 SSRC 指定的:

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=ssrc:2582129002 cname:8Y1pmIKBijmWeALu
a=ssrc:2582129002 msid:34fD1qguf2v79436S1khLkth8Nb6LbedcF9H bab38910-40cd-4581-9a20-e3f558abb397
a=ssrc:2582129002 mslabel:34fD1qguf2v79436S1khLkth8Nb6LbedcF9H
a=ssrc:2582129002 label:bab38910-40cd-4581-9a20-e3f558abb397

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=ssrc:565530905 cname:8Y1pmIKBijmWeALu
a=ssrc:565530905 msid:34fD1qguf2v79436S1khLkth8Nb6LbedcF9H 2c533cfe-b6bf-41a8-93f0-1ca031436702
a=ssrc:565530905 mslabel:34fD1qguf2v79436S1khLkth8Nb6LbedcF9H
a=ssrc:565530905 label:2c533cfe-b6bf-41a8-93f0-1ca031436702
Remark: SSRC 就包含了需要发送的媒体流,另外 Offer 和 Answer 中都可以包含 SSRC。比如客户端和 MediaSoup 通信时,MediaSoup 总是给客户端发 Offer,MediaSoup 的 Offer 包含了 MediaSoup 要发送(转发其他客户端的流给客户端)的媒体流 SSRC,同时客户端的 Answer 中也包含了自己要推送的 SSRC 流,他们的类型都是 sendrecv。

Remark: msid 对应了NetStream.id,也就是代表了不同的媒体源,这些 SSRC 可以是不同的媒体源。

如何确定最后的编码?对方会在 Answer 中给出,比如上面 Offer 给出了多个编码,在 Answer 中会选择一个:

m=audio 9 UDP/TLS/RTP/SAVPF 111
m=video 9 UDP/TLS/RTP/SAVPF 100 102 127 125 108 124
a=rtpmap:100 H264/90000
a=rtpmap:102 H264/90000
a=rtpmap:127 H264/90000
a=rtpmap:125 H264/90000
a=rtpmap:108 red/90000
a=rtpmap:124 ulpfec/90000

虽然 Video 编码有 100 到 125,但是他们都是 H.264,而 108 和 124 则是 FEC,基于 H.264。

PlanB and UnifiedPlan

上面的 MediaCodecs 中,没有规定如何指定多条流。实际上 Audio 和 Video 都有多个 SSRC,每个 SSRC 的编码可能相同但也可能不同。比如互联网视频会议,用移动端接入时,编码可能都是 H.264,但是和其他终端接入时可能会有其他编码。

如果 SSRC 的编码不相同,那么将这些 SSRC 放在同一个 M 描述就会有问题,这就是 PlanB 和 UnifiedPlan 的关键所在。对于 PlanB 只有一个 M(audio) 和 M(video),他们的编码要相同,当有多路媒体流时,则根据 SSRC 去区分。UnifiedPlan 则可以有多个 M(audio) 和 M(video),每路流都有自己的 M 描述,这样就可以支持不同的编码。

PlanB 和 UnifiedPlan 其实就是 WebRTC 在多路媒体源(multi media source)场景下的两种不同的 SDP 协商方式。如果引入 Stream 和 Track 的概念,那么一个 Stream 可能包含 AudioTrack 和 VideoTrack,当有多路 Stream 时,就会有更多的 Track,如果每一个 Track 唯一对应一个自己的 M 描述,那么这就是 UnifiedPlan,如果每一个 M line 描述了多个 Track(track id),那么这就是 Plan B。

Note: 当只有一路音频流和一路视频流时,Plan B 和 UnifiedPlan 的格式是相互兼容的。

Remark: Chrome 早期支持的是 PlanB,目前最新版本也支持了 UnifiedPlan,参考 Need to implement WebRTC "Unified Plan" for multistream

PlanB 参考下图:

file

UnifiedPlan 参考下图:

file

Candidate

Candidate 就是传输的候选人,客户端会生成多个 Candidate,比如有 host 类型的、有 relay 类型的、有 UDP 和 TCP 的,如下图所示:

sdpMid: audio, sdpMLineIndex: 0, candidate:2213672593 1 udp 2122260223 30.2.228.19 51068 typ host
sdpMid: video, sdpMLineIndex: 1, candidate:2213672593 1 udp 2122260223 30.2.228.19 55061 typ host

sdpMid: audio, sdpMLineIndex: 0, candidate:3446803041 1 tcp 1518280447 30.2.228.19 9 typ host
sdpMid: video, sdpMLineIndex: 1, candidate:3446803041 1 tcp 1518280447 30.2.228.19 9 typ host

sdpMid: video, sdpMLineIndex: 1, candidate:150963819 1 udp 41885439 182.92.80.26 54400 typ relay raddr 42.120.74.91 rport 37714
sdpMid: audio, sdpMLineIndex: 0, candidate:150963819 1 udp 41885439 182.92.80.26 59241 typ relay raddr 42.120.74.91 rport 49618
Remark: 我们去掉了后面的属性,比如 generation 0 ufrag kce9 network-id 1 network-cost 10,这些属于 Candidate 的描述,和连通性检查等相关。

客户端自己生成了 6 个 Candidates,3 个 Audio 和 3 个 Video,2 个 TCP 和 4 个 UDP,4 个 host 和 2 个 relay。当然对方也会有很多 Candidate,接下来就是自己的 Candidates 和对方的 Candidates 匹配连通(ICE Connectivity Checks),形成 CandidatePair 也就是传输通道。Candidate 还附带了网络属性,比如network-cost 会在 ICE Connectivity Checks 时用到。

Remark: 关于 Candidate 的类型,还有 srflx 以及 prflx,关于这两种 Candidate 类型的定义以及区分,后面会在 ICE 相关的技术文章中介绍。

Remark: 关于 ICE Connectivity Checks 我们会在后面给出详细的分析,涉及到了STUN协议。下面会总结出 ICE 相关的 SDP 信息。

SDP 和 Candidate 都是通过信令交换的。如果对方只给了 relay 的 Candidate,例如:

sdpMid: audio, sdpMLineIndex: 0, candidate:150963819 1 udp 41885439 182.92.80.26 51542 typ relay raddr 42.120.74.91 rport 56380

这种情况下,肯定最后连通的 CandidatePair 是 Relay 对 Relay,如下图所示:

file

file

从这个图中能看出来这个传输通道的发送和接收码率、包的个数、RTT 和丢包率等信息。

实际上,由于我们这个客户端还有 host 类型的 Candidate,所以它会尝试直接用 host 的这个 Candidate 和对方的 relay 直接连接:

sdpMid: audio, sdpMLineIndex: 0, candidate:2213672593 1 udp 2122260223 30.2.228.19 51068 typ host

Statistics Conn-audio-1-1
googActiveConnection    false

file

当然,由于没有连通所以这个 CandidatePair 就不可用。

Remark: WebRTC 是具备在多个 Candidate 之间切换的能力的,具体在 ICE Connectivity Checks 中我们再分析。

上面的 Candidates 自己生成了 2 个 Relay 的 Candidates,一个是 audio 的一个是 video 的,为何只用到了audio 的呢?这就是下面的 BUNDLE 涉及的了。

Bundle and RTCP-MUX

传输时,可以复用媒体通道,一种是音频和视频的复用,一种是 RTCP 和 RTP 的复用。

RTCP 和 RTP 复用,表示 Sender 使用一个传输通道(单一端口)发送 RTP 和 RTCP:

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=rtcp-mux

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=rtcp-mux

此时,Receiver 必须准备好在 RTP 端口上接收 RTCP 数据,并需要预留一些资源,比如 RTCP 带宽。

音频和视频复用时,最后只会用一个 Candidate 传输,比如客户端自己的 SDP Offer,和两个 relay 的Candidates:

a=group:BUNDLE audio video

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=mid:audio

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=mid:video
sdpMid: video, sdpMLineIndex: 1, candidate:150963819 1 udp 41885439 182.92.80.26 54400 typ relay raddr 42.120.74.91 rport 37714
sdpMid: audio, sdpMLineIndex: 0, candidate:150963819 1 udp 41885439 182.92.80.26 59241 typ relay raddr 42.120.74.91 rport 49618

这表示最终 audio 和 video 尽管可能有独立的 Candidate,但是如果对方也是 BUNDLE,那么最终只会用一个 Candidate。例如,如果对方的 Answer 是:

a=group:BUNDLE audio video

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=mid:audio

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=mid:video
sdpMid: audio, sdpMLineIndex: 0, candidate:150963819 1 udp 41885439 182.92.80.26 51542 typ relay raddr 42.120.74.91 rport 56380

最后它们只会用一个 Candidate 传输。如下图所示:

file

rtcp-mux 将 RTP 和 RTCP 复用到单一的端口进行传输,这简化了 NAT traversal,而 BUNDLE 又将多路媒体流复用到同一端口进行传输,这不仅使 candidate harvesting 等 ICE 相关的 SDP 属性变得简单,而且又进一步简化了 NAT traversal。

rtcp-mux 是与 RTC 传输相关的重要的 SDP 属性,关于它的 SDP 协商的原则如下:

  1. 如果 Offer 携带 rtcp-mux 属性,并且 Answer 方希望复用 RTP 和 RTCP 到单一端口,那么 Answer 必须也要携带该属性。
  2. 如果 Offer 没有携带 rtcp-mux 属性,那么 Answer 也一定不能携带 rtcp-mux 属性,而且 Answer 方禁止 RTP 和 RTCP 复用单一端口。
  3. rtcp-mux 的协商和使用必须是双向的。

举个例子。客户端去订阅服务器的流,客户端的 Offer 没有携带 rtcp-mux 属性,那么服务器会认为客户端不支持 rtcp-mux,也不会走 rtcp 复用的流程。相反,服务器会分别创建 RTP 和 RTCP 两个传输通道,只有当两个通道的 ICE 和 DTLS 都成功,才会认为本次订阅的传输通道建立成功,继而向客户端发流。

试想,如果因为你的疏忽导致 Offer 漏掉了 rtcp-mux 属性,那么你将永远等不到服务器 Ready 的那一天。所以,SDP 看似只是一些文本,很简单,但是只有在项目的实战中,多遇到几个坑,才能更深切的体会到 SDP 属性的含义以及这些属性是如何在 RTC 场景中去发挥作用的。

Remark: 关于 rtcp-mux 更详细的协商细节请参考 RFC 8035

Remark: 关于 rtcp-mux 场景下如何通过头部字段区分 rtp 和 rtcp,请参考 RFC 5761

ICE Connectivity

这里我们只说明 SDP 中和 ICE Connectivity Checks 相关的信息,具体的过程我们会在其他文章中单独分析。

SDP 中和 ICE 相关的信息包括:

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=ice-ufrag:kce9
a=ice-pwd:M31WxfrwmrFvPws4+tPdbsCE
a=ice-options:trickle

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=ice-ufrag:kce9
a=ice-pwd:M31WxfrwmrFvPws4+tPdbsCE
a=ice-options:trickle

ufrag 和 pwd 就是 ICE short-term 认证算法用到的用户名和密码。而 trickle 说明 SDP 中没有包含 candidate 信息,Candidate 是通过信令单独交换的,这样可以做到 Connectivity checks 和 Candidate harvesting 并行处理,提高会话建立的速度。

DTLS

这里我们只说明 SDP 中关于 DTLS 的信息,具体的 DTLS 握手过程会在 DTLS 相关的技术文章中单独分析。

SDP 中和 DTLS 相关的信息包括:

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126B0:A2:B3:AB:0B:A3:44:22:B1:C8:69:52:ED:04:E8:5A:A4:C3:7A:A6:55:F3:BA:76:62:26:4B:F7:9F:DD:F1:BD
a=setup:actpass

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
a=fingerprint:sha-256 B0:A2:B3:AB:0B:A3:44:22:B1:C8:69:52:ED:04:E8:5A:A4:C3:7A:A6:55:F3:BA:76:62:26:4B:F7:9F:DD:F1:BD
a=setup:actpass

其中 fingerprint 是 DTLS 过程中的 Certificate 证书的签名,防止客户端和服务器的证书被篡改。

另外,setup 指的是 DTLS 的角色,也就是谁是 DTLS Client(active),谁是 DTLS Server(passive),如果自己两个都可以那就是 actpass。这里我们是 actpass,那么就要由对方在 Answer 中确定最终的 DTLS 角色:

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=fingerprint:sha-256 B1:FD:D6:2D:94:4E:33:A1:8C:9D:EF:ED:EB:AC:CC:2D:E2:37:15:9B:24:8C:BF:F2:7D:6A:B3:81:23:AA:13:54
a=setup:active

对方是 active,也就是 DTLS Client,那么自己就只能是 DTLS Server,会由对方发起 DTLS ClientHello 开始DTLS 过程。

Stream Direction

媒体流的方向有四种,分别是 sendonly、recvonly、sendrecv、inactive,它们既可以出现在会话级别描述中也可以出现在媒体级别的描述中。

  • sendonly 表示只发送数据,比如客户端推流到 SFU,那么会在自己的 Offer(or Answer) 中携带 senonly 属性
  • revonly 表示只接收数据,比如客户端向 SFU 订阅流,那么会在自己的 Offer(or Answer) 中携带 recvonly 属性
  • sendrecv 表示可以双向传输,比如客户端加入到视频会议中,既要发布自己的流又要订阅别人的流,那么就需要在自己的 Offer(or Answer) 中携带 sendrecv 属性
  • inactive 表示禁止发送数据,比如在基于 RTP 的视频会议中,主持人暂时禁掉用户 A 的语音,那么用户 A 的关于音频的媒体级别描述应该携带 inactive 属性,表示不能再发送音频数据。
NOTE: RFC 4566: senonly 和 recvonly 属性仅应用于媒体,不用于媒体控制相关的协议。比如在基于 RTP 的媒体会话中,即使是 recvonly 模式,也仍然要发送 RTCP 包,即使是 senonly 模式,也依然会接收并正常处理 RTCP 包。

媒体流方向的四个属性很重要,在组装 SDP 时要仔细校验,保证流方向的正确性。

举个例子,客户端去订阅服务器的流。如果此时客户端的 Offer 携带的属性并不是 recvonly 而是 sendonly,那么即使在信令层面的确是订阅的语义,但是由于某些服务器对 SDP 各属性的校验是十分全面和严格的(本该如此),这种场景下,服务器将不会发送媒体流到客户端,而且服务器回复的 Answer 可能根本不会携带 SSRC。

RTCP Feedback

下面,我们聊一下 rtcp-fb 这个媒体级别的 SDP 属性,它能告诉我们媒体会话能够对哪些 RTCP 消息进行反馈,是一个和 QoS 相关的重要的 SDP 属性。

m=video 9 UDP/TLS/RTP/SAVPF 96
a=mid:video
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack pli

如上 SDP 信息,这是一个视频的 M 描述,VP8 编码,payload type 是 96。最后的 3 个 rtcp-fb 属性则说明了对于 96 这个 media codec 来讲,在网络拥塞控制方面支持 twcc;在 ARQ 方面支持 nack 处理,能够重传丢失的 RTP 包;在关键帧方面支持 fir 和 pli 处理,有能力进行关键帧的发送。

在做 SIP 的时候,遇到过一个坑:向某台型号的 SIP 设备发送 PLI 请求后,并没有收到关键帧,经过一番折腾,最后发现,这台设备的 rtcp-fb 描述如下:

m=video 16402 RTP/AVP  34
a=rtpmap:34 H263/90000
a=fmtp:34 CIF4=1;CIF=1;QCIF=1;SQCIF=1
a=sendrecv
a=rtcp-fb:* ccm tmmbr
a=rtcp-fb:* ccm fir

也就是说这台设备只支持 FIR 请求,没有处理 PLI 请求的能力(PS: 为什么没能早一些检查 SDP 的 rtcp 反馈能力,泪目)。在此也想着重强调一下:对于一些很专业严谨的系统或者设备而言,SDP 完全体现了它们所拥有的能力,也可以让我们发现其不具备的能力。SDP 的每一个属性都是有其存在意义的,万万不可忽略。

Note: rtcp-fb 不能用于会话级别的描述中,只能用于媒体级别的描述,而且其 M 描述的 proto 字段一定要指定 AVPF。

Note: 存在这种格式,a=rtcp-fb:* ccm fir,星号是一个通配符,表示该 M 描述下的所有类型的 media codec 都支持 fir 的处理和关键帧的反馈。

Compare with SIP SDP

RTC 场景与 SIP 场景下的 SDP 描述的不同表现在传输、媒体、信令三个层面。

Transmission Level

  1. 建连流程。RTC 场景下的音视频媒体流建连流程一般是 ICE + DTLS,而 SIP 场景下没有这套建连流程,所以也没有 ICE/DTLS 相关的 SDP 属性,比如 ufrag、pwd、setup、fingerprint 等。
  2. 端口复用。RTC 场景下一般都是音视频流以及 RTP/RTCP 复用单一端口,通过 SSRC 区分每一路流,通过数据包的头部字段值来区分 RTP/RTCP,而 SIP 场景下不会复用端口,因此没有 rtcp-mux 属性,也没有 grouping 相关的属性,比如 BUNDLE,且音视频的 RTP 和 RTCP 都是独立端口进行传输,共有四个,所以天然可以使用端口来区分流以及 RTP/RTCP,因此也没有 SSRC 属性。
  3. 链路探测。RTC 场景下一般通过 ICE 的 STUN 探测环节来发现对端经过 NAT 映射之后的出口地址,称为 srflx,而 SIP 场景下需要自己实现对端地址发现的功能,以获取到 SIP 设备经过 NAT 映射之后的出口地址。
  4. 地址信息。RTC 场景下通过 SDP 的 candidate 交换对端地址信息,SIP 场景下通过 C line 的 ip 以及 M line 的端口来交换对端地址信息。
// RTC 场景
a=candidate:1 1 udp 2013266431 30.27.136.138 14306 typ host

// SIP 场景
c=IN IP4 30.41.5.131
m=audio 2352 RTP/AVP 107 108 114 104 105 9 18 8 0 101 123
m=video 2374 RTP/AVP 97 126 96 34 123

Media Level

  1. 屏幕共享。SIP 场景下通过 BFCP 协议来进行屏幕共享的协商,通过 a=content 属性来区分主流(main)和共享流 (slides),而 RTC 场景下通过外部/业务信令来进行屏幕共享的协商,主流和共享流的 SDP 描述一致,不会区分。
  2. Media Codec。目前,RTC 场景下的音视频编码普遍是 Opus + H.264/VP8,SIP 场景下,对于音频编码,有很多 SIP 设备并不支持 Opus,而采用比较古老的音频编码,比如 G722、PCMA、PCMU,对于视频编码,普遍支持 H.264,一般不支持 VP8。

Signaling Level

  1. SDP 交换。都是 Offer/Answer 模型,RTC 场景下主要通过 HTTP/TCP 协议交换 SDP,一般是在 HTTP body 中携带 SDP 信息。SIP 场景下可以通过 UDP/TCP/TLS 协议交换 SDP,在 INVITE 和 200 OK 中携带 SDP 信息。

Summary

其实,SDP 文本化的协议格式本身很简单,其难点在于不同的应用场景(比如传统 SIP 视频会议或者 RTC 场景)下扩展出的纷繁复杂的属性及其含义,这些 SDP 属性散落在众多的 RFC 以及草案之中,不下一定的功夫是很难做到全面理解与掌握的(PS:每当说到此处,心里总是一万个马奔腾,WebRTC 的 RFC 太多了而且互相关联互相引用,看完这些 RFC 要准备好视力下降 0.2 度)。

下一篇,我们会重点讲一下 WebRTC ICE,包括连通性检测、状态切换、trickle 以及 nomination。感谢阅读。

阿里云视频云技术公众号分享视频云行业和技术趋势,打造“新内容”、“新交互”。
查看原文

赞 1 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-11-18

2020双十一,阿里云GRTN拉开直播和RTC技术下半场的序幕

直播,已经成为了“剁手党”们最喜闻乐见的一种购物形式。对直播体验的极致追求,也是淘宝技术人们长期的努力方向。为了提升用户购物体验,让直播更加丝滑,让剁手更快一些,在2020双十一期间,淘宝首次启用了阿里云CDN的GRTN全球实时传输网络。数据显示,和传统的HTTPFLV/RTMP方式相比,在启用了GRTN后,直播端到端的延时降低了83%。那么,GRTN到底是什么?其背后究竟隐藏了哪些核心技术?

这篇文章会通过回顾互联网直播技术的发展历程,深度剖析直播延时的技术挑战,并解读阿里云全球实时传输网络GRTN的设计思路、技术原理、特质与应用实践,以及GRTN在摆脱传统直播技术所面临的内卷化(Involution)窘境所作出的尝试。GRTN不单单是为互联网直播而设计,诸如实时音视频RTC等流媒体技术的使用者,比如云会议、云游戏、云桌面等,在将业务迁移至GRTN后可以有什么新玩法和创新机遇?本文将为您解答。

作者:子融,阿里云高级技术专家,负责阿里云视频直播产品和流媒体实时加速平台研发

互联网直播技术的进化趋势

互联网直播技术的发展大致可以被分为了4个阶段:分别是创新期、演进期、量产期和瓶颈期。

file

互联网上的第1场比较有名的直播还要追溯到20多年前,那是20世纪的最后一年,维多利亚秘密(Victoria Secret)在线上直播了她们的时尚走秀,也就是大家今天比较熟知的维密秀,尽管画面及其不清晰,但也吸引了数以百万级的观众,充分展现了直播这个新物种巨大的吸引力。

要知道今天全球著名的流媒体公司Netflix奈非当时还是在靠DVD租赁来维持生计。

这段时期我们称之为直播技术的创新期,它革命性的将观众的观影体验从离线文件下载和DVD租赁升级到了线上,但这个时期的直播体验还是比较差的,体现在时延和卡顿上就是分钟级的延时并且经常卡顿。

接下来,伴随着互联网的基础设施的演进,流媒体技术也得到了长足的发展,这其中典型的代表是流媒体技术演进出了一种对CDN非常友好的模式,即媒体流切片模式,媒体流被分割成2-10s不等的切片文件,并通过CDN来进行分发。

这种特性很好地适应了互联网延时抖动,从而提供了一种相对流畅的观影体验,并且将时延从数分钟压缩到了数十秒。这一时期我们称之为互联网直播的演讲期,这一时期的直播应用主要以电视台体育赛事为主。

时间来到2016年,随着移动互联网迎来4G时代,美女主播、游戏主播等应用的兴起,互动直播开始爆发,各种直播App如雨后春笋般涌现,这一时期,网红们已经可以通过自己的手机随时随地的开播,此时国内主流的协议有耳熟能详的RTMP、HTTPFLV、HLS等,由于底层的传输仍然采用TCP,延时普遍在5-10s之间,但画面已经比较清晰和流畅了。

时至今日,互联网直播经历了4年的高速期发展,用户对体验的要求越来越高,传统的5-10s延时很难进行实时互动,比如时下很火的直播带货和在线教育业务,主播和观众、老师和学生的实时互动体验还是有很大的改进空间的,另外随着5G时代的到来,新的场景,比如AR/VR沉浸式直播、4K全息投影远程直播都要求更高带宽和更低延时。

但直播技术近几年却未能有本质性的突破,各家直播CDN厂商都投入了大量的精力在对现有基于TCP的RTMP/FLV直播体系的质量优化上,主要优化手段有精细化的调度、精准的覆盖、优质的资源、优化缓存命中率、TCP协议栈优化、直播业务行为分析等,质量优化系统做得越来越精致。

但在时延的提升上也就是在几百ms左右,甚至就是在扣那几十ms,卡顿的降低也都是在几个百分点左右,对实际用户体验的提升已经是非常有限了,互联网直播技术开始遇到了瓶颈,这种内卷化的发展其实是一定程度上制约了业务的发展。

互联网直播延时分布和技术挑战

那么如何才能在延时上有所突破呢?要解决这个问题,首先需要剖析一下直播延时的整体分布,互联网直播全链路可以分为7个步骤:分别是采集、编码、发送、分发、接收、解码和渲染。

file

其中采集+编码,解码+渲染总体延时比较固定,共100ms左右,变动比较大的部分是分发和接收,从数十毫秒到数秒不等,主要取决链路时延抖动、协议栈的优化情况,以及CDN资源的覆盖情况。

在传统的架构里,这个7个环节相互独立,互不相干,这样做的好处是团队分工比较明确,但问题就是优化手段很难做到跨界融合,导致无法做到系统级优化。

比如,编码器如果可以考虑发送时的拥塞情况来实时调整码率就可以一定程度上缓解拥塞,从而降低延时;再比如传统的流媒体传输中媒体数据发送和底层的传输是相互独立的,底层TCP传输的拥塞控制算法是个通用算法,不会考虑媒体的特性。

这样的一个分层结构是很难形成即时反馈系统的,那么为了保障流畅度,缓存区的大小设计会相对保守,从而牺牲了端到端的时延,如果传输层和应用层是一体化的,QoS控制针对媒体特性来专门设计,同时配合编码侧的码率控制,就能通过组合拳的方式,大大地降低延时。

所以上述各个环节应该是环环相扣,做到全链路相互感知才能将延时压缩到极致。

业界主流低延时直播方案对比

业界主流的5种流媒体协议和技术,其中包括WebRTC、QUIC、SRT、CMAF、LLHLS。这里的对比从下述8个维度来展开:

file

提出时间:WebRTC是最早被提出的,QUIC紧随其后,最晚的是去年Apple新发布的LLHLS。

完备度:这里的完备度主要关注这项技术是否涉及我们前面提到的直播全链路中的各个环节,比如WebRTC我们认为是全覆盖的,它涉及了从采集、编解码、传输和渲染的全部环节,所以严格来讲WebRTC并不是一个协议,而是一个开放的实时流媒体通信框架;那我们再来看QUIC,它是一个正在被IETF标准化的新一代传输协议;SRT在2017年刚开源的时候的只是一个视频传输协议,但随着很多编码器厂商的支持,也开始可以影响编码侧的码率,从而保持相对稳定的时延。

底层传输协议和类型:WebRTC、QUIC、SRT都是基于UDP的而且都是流式的传输,而CMAF和LLHLS都是切片方式的,底层基于HTTP。

标准和终端支持:WebRTC已经是W3C标准,并且使用了大量的IETF RFC规范,目前几乎所有的浏览器以及手机操作系统都支持WebRTC;QUIC预计在今年年底会正式成为下一代HTTP标准即HTTP/3,目前Chrome已经支持。

场景和延时:WebRTC是为实时音视频通信场景设计,端到端延时是在400ms以内,250ms左右;而其它几个协议要做到2s以内,都还需要很多的额外技术投入。

综合各方面因素,阿里云的新一代传输网络选择了WebRTC技术,不仅延时低,而且支持单通道全双工,可以做到真正意义上的低延时+互动。

GRTN的定位

为了能够降低直播的端到端延时,阿里云CDN与视频云联合手淘技术、达摩院XG实验室在先后从直播、短延时直播拓展到RTC领域,并在 QoS和AAA方面发力,最终成功构建了GRTN(Global Realtime Transport Network)全球实时传输网。

GRTN的定位是基于中心云和边缘云的异构节点,构建超低延时、全分布式下沉的通信级流媒体传输网络。

GRTN目前融合了互联网直播和RTC等多种业务场景的音视频流传输和交换。基于GRTN的短延时直播RTS可以支持标准H5 WebRTC推播,在千万级并发情况下延时可以控制在1s以内;RTC端到端延时可以控制在250ms左右。

GRTN架构

下图是一个传统互动直播系统的典型架构,这个架构的特点是:

  • 树状层级结构
  • 上行推流主流协议:RTMP/WebRTC
  • 下行播放主流协议:HTTPFLV/RTMP/HLS
  • 直播分发和RTC推流系统分离
  • 端到端延时~6s

file

传统架构的主要缺点是:

  • 成本高,主要是媒体数据经过的链路长、直播分发和RTC推流系统孤立
  • 延时大,因为采用基于TCP的RTMP/HTTP-FLV协议,而且媒体数据经过的链路长
  • 扩展难,由于RTMP/HTTP-FLV协议在传输上不是全双工的,所以业务形态是只能支持单向直播,视频互动需要借助旁路的连麦系统。

相比于传统的直播架构,GRTN架构的技术特点是:

  • 混合组网:树状层级结构+对等图形网
  • 能力下沉:协议边缘卸载+内部传输协议归一化
  • 控制和数据分离:动态路径规划+全分布式SFU

file

架构升级所带来的核心价值是:

  • 降成本,GRTN是一个多业务融合的网络,可以支持直播、RTC和视频上云等多种场景,业务复用率高,另外 GRTN内部链路更短,节点内的成本也更低。
  • 提质量,GRTN内部组网支持采用动态选路的方式来构建的网状结构,内部链路延时可以做到20ms左右,并且内部链路采用了私有协议来进行高效传输。另外客户端的推流和分发都是基于WebRTC来构建的,QoS拥塞控制是专门针对流媒体特性来进行设计的,并且还在基于线上数据建设进行持续迭代和打磨。
  • 易扩展,GRTN支持了WebRTC协议,可以在单个连接通道上进行全双工的通信,从而可以很自由的进行发布和订阅媒体流,在业务的扩展性上带来了更大的想象空间。

GRTN核心技术 – 对等组网和路径动态规划

传统的直播架构是一种层级的树状结构,由于媒体流的链路相对比较固定,这种结构的在产品初期可以把研发资源更多的投入在媒体协议的处理上,对于快速构建产品能力是相对风险可控的。

但随着业务的发展,这种架构的缺陷也会越发明显,比如延时高、成本高,而且扩展性也比较差等,在一定程度上是阻碍业务发展的,比如延时很难突破到6s以下,视频的互动只能借助旁路连麦系统等。

为了根本上解决这一系列问题,并结合层级结构有助于系统运维和容量评估,而网状结构有益于构建高质量和低成本的网络的特性,GRTN采用了混合组网方式,即层级结构和对等图形方式相结合的组网的方式。

选路中心会周期性收集内部链路探测的结果,为了配合动态组网,流媒体大脑模块需要对流信息进行管理,同时还需要支持路径切换、容量规划以及在成本和质量之间做综合的调度。

GRTN核心技术 – 多路径传输

为了能够提高GRTN内部链路传输的可靠性,以及考虑在成本和质量间的均衡,GRTN支持如下3种内部链路多路径传输模式:竞速模式、备选模式和智能模式,可以在高可靠,质量,成本等诸多因素控制下进行适配和自适应的切换。

GRTN核心技术 – 能力下沉

流媒体技术向来以协议多著称,主要是因为业务的多样性导致,下面是流媒体行业的技术进化趋势对比表:

file

上表中只整理了相对比较通用的协议,可以看到流媒体协议纷繁复杂,在传统的架构里这些协议的处理在中心完成,边缘主要做透传分发。

这样的问题就是协议处理的链路太长,不仅成本高而且延时大,那么很自然的一个想法就是将协议和媒体处理能力下沉到CDN的边缘,中心只是做管控,从而做到类似Service Mesh的设计思想,将控制与数据分离。

因为这些协议的本质都是在传输音视频的基本流ES(Elementary Stream,比如常见的H.264/H.265/AAC/OPUS/VP8/VP9/AV1等),不同的协议解决的是不同的封装格式的传输问题。

比如有TS(Transport Stream)、PS(Program Stream)、MP4、fMP4(fragment MP4)、FLV等,而不同的封装格式本质上就是针对不同场景下如何封装ES流的问题。

因此在边缘设计一种通用的针对不同ES流的传输协议和缓存系统是完全可行的。GRTN将协议处理能力下沉到了边缘节点,目前可以支持RTMP、HTTP-FLV、WebRTC、GB28181等流式协议。

GRTN核心技术 – 双向实时信令网

前面提到GRTN核心价值之一是高质量,高质量除了延时低以外,还需要考虑快速容灾切换能力,以及提升首屏秒开率等核心指标。

在RTC场景下有一个比较常用的功能是客户端网络的Mobility,比如用户在开会的过程中回家或是离开家的时候手机网络需要在4G和wifi之间切换,另外考虑客户端接入的CDN节点出现异常的时候。

这两种情况都会造成客户端在和GRTN通信过程中切换接入节点,GRTN构建的双向的实时信令网能够做到切网消息的毫秒级传递,当有一个发布端的媒体流发生网络切换后,订阅的客户端对GRTN内部发生的切换行为是完全无感知的。

GRTN核心技术 – 持续迭代的QoS

GRTN之所以能够做到在直播延时由6s降低到1s以内,RTC通信延时做到250ms左右,除了图形网的结构的改造以及协议下沉等技术外,最核心的还是有采用了有媒体特性感知的QoS,这和TCP或QUIC这类通用QoS策略在本质上是不一样。

WebRTC的QoS是一个针对流媒体特性的多维决策体系,涉及到的算法和策略参数非常多,为了方便业务层对底层QoS算法和参数的择优,GRTN设计了一套可插拔的的QoS集成框架,结合GRTN数据化的质量评估体系,可以做到一次集成持续迭代,不同的算法和参数都可以利用GRTN的A/B质量评估体系进行线上评估,形成赛马机制。

同时QoS和文章前面提到的动态路径规划也是有很多结合点的,QoS研究中的一个很重要课题就是需要区分出网络的抖动和拥塞,如果是拥塞那就需要反馈给上游进行信源带宽调配(比如降码率,流切换等)。

但如果只是短暂的抖动,就可以启用相对激进的抗丢包策略,动态路径规划也面临类似的问题,如果是只是短暂的拥塞,可以保持当前链路并借助QoS的抗丢包策略来扛,但如果是链路拥塞了,则需要尽快切换链路。

GRTN核心技术 – 流媒体孪生

GRTN升级到网状结构后也会面临一些新的挑战。众所周知,在618和双11等大促活动期间确保CDN资源的充足供应是至关重要的,在传统的层级结构下可以通过业务命中率来分别对L1/L2/中心分别进行评估,而在网状结构下内部链路是动态规划出来的,也就意味着流量的分布也是动态的。

这对于如何评估 CDN的整体容量提出了新的挑战;再比如动态选路算法如何在质量和成本之间找到均衡点,以确保GRTN系统的低成本高质量?

为了解决此类问题,GRTN借鉴数字孪生 (Digital Twin https://en.wikipedia.org/wiki... 的思想设计了一个流媒体孪生(Streamimg Media Digital Twin)系统,用于容量评估、算法训练、事件复盘和模拟压测等。

GRTN核心技术 – 可编程

流媒体技术的上层业务场景非常丰富,比如电商直播、视频会议、在线教育、企业直播、新零售等,因此有很多定制化开发的需求。

可编程化改造是GRTN在提升系统稳定性上的一次尝试,目前GRTN的中心流媒体大脑,节点侧的业务模块,媒体数据发送模块、媒体信令处理模块等都已经进行了可编程化改造,大部分情况下都可以避免二进制的发布。

阿里云超低延时直播产品RTS

为了更加方便客户和行业拥抱GRTN,阿里云基于GRTN打造了超低延时直播服务RTS,其有四个技术特性:

一、秒级延时和卓越的抗弱网能力,在相同卡顿率下延时可以降低80%,相比于传统的RTMP和FLV的5-10s延时,RTS的延时可以达到1s以内,并且还在基于线上的大数据,在自我学习和持续迭代中。

二、成熟稳定,RTS历经2年多时间的潜心研发,并经历了淘宝直播618大促的线上考验,目前已经在淘宝直播上线。

三、开放标准,为了能够方便自研播放器的客户使用我们的RTS服务,阿里云的WebRTC接入的信令协议的完全开放的、透明的。

四、广覆盖和高并发,RTS服务是构建在阿里云2800+边缘节点之上,可以支持千万级并发播放。

客户接入RTS的两种解决方案

一、对于使用云厂商播放SDK的客户,升级播放SDK后可根据业务灵活选择网络传输协议,打造更高性价比组合。

1.在现有的直播业务新增一个RTS播流域名,一个推流两种方式拉流。

2.主播端无需改造,仅升级播放SDK,播放端自动识别不同URL参数。

file

二、对于自研播放器的客户,阿里云开放与标准WebRTC协议对接代码示范,客户自行升级网络模块,底层网络对接更透明。
1.在现有的直播业务新增一个RTS播流域名,一个推流两种方式拉流。

2.主播端不用改造,仅升级播放器网络模块,拉取超低延时流播放。

file

关于低延时+互动体验升级路径的未来展望

在流媒体业务的用户体验升级上,GRTN今天所带来的不仅仅是延时的降低,另外一个很重要的能力就是GRTN所提供的灵活的互动能力,GRTN让直播和RTC的边界模糊,让连麦和会议的界限模糊。在GRTN的世界里只有媒体流的发布和订阅关系。

  • 直播:1人发布多人订阅
  • 1v1客户端连麦+直播:3人发布多人订阅,这里的第3个发布是来自主播侧的合屏流。
  • 1v1服务侧连麦+直播:3人发布多人订阅,这里的第3个发布是来自于服务侧的合流发布,当合流发布上来的时候,可以利用GRTN的切流能力做到在连麦切换的时候观众无感。
  • 视频会议:多人发布多人订阅,GRTN的切流能力可以用于会议中的视频大小流(高清晰度和低清晰度)切换。

再配合GRTN基于成本和稳定性所提供的分级的时延能力50ms/250ms/800ms/6s/…,就可以勾兑出不同的场景和产品化能力。

在线教育体验升级

在过去,在线教育比较典型的架构是旁路直播模式,即云端有个基于WebRTC的RTC媒体中心,老师以及需要实时互动的学员会接入到WebRTC频道中,然后由RTC媒体中心,转推一路RTMP流到直播CDN,进行旁路直播。

由于直播时延大且协议的不一致性,在这种情况下观看直播的学员如果要切入到RTC频道中进行互动,体验是比较差的,有时还会有黑屏中断。

file

如果希望降低延时并完全解决黑屏问题,那就需要将云端的WebRTC双向通信频道的能力也下沉到GRTN上,由GRTN来承载,将互动和分发进行融合,从而形成一体化的超低延时互动大频道,通过业务层来控制WebRTC流的订阅关系,直播和互动频道间不再有明确的界限,可以灵活的根据业务情况按需在2种模式间进行切换,而且这种切换对学员和老师都是完全无感的。

电商直播体验的升级

当下的直播带货整体架构和上面讲的在线教育没有本质区别,在架构升级路径上也是可以采用上述同样的思路。这里值得一提的是在使用了GRTN后,业务方的技术团队可以将精力更多的集中在做很多创新的事情上。

比如在小主播的带货房间里,以前的互动只有文字或是1v1的连麦,接入到GRTN后,很容易就可以将这个单向带货介绍的房间改造成一个类似视频会议的多人互动讨论房间,用户互动体验提升后是否也能像时延降低一样带来GMV的转换呢?

file

本篇文章重点介绍的是GRTN的服务侧核心能力,希望能给做流媒体技术的同学带来些许启发。
感谢大家的阅读。

阿里云视频云技术公众号分享视频云行业和技术趋势,打造“新内容”、“新交互”。
查看原文

赞 1 收藏 0 评论 0

阿里云视频云 发布了文章 · 2020-11-13

解密猫晚直播技术:如何保障全球200多个国家和地区同时在线狂欢?

2020年11月10日的”猫晚“,又是一场视觉盛宴,满满的双11仪式感。

自2015年以来,每年的猫晚都是重金打造、众星云集,并通过广电卫视和互联网多渠道同时进行全球多屏分发。

想要把这场极具艺术价值的舞台风景,通过直播技术演幻到移动端,还原真实,甚至超越真实的音效与视觉,并非易事,但却是阿里云视频云的执着之事。

众所不知,阿里云视频云团队在猫晚这样的重量级晚会直播上,一直承担着重要的技术角色,今年如是,每年如是。

2020 天猫晚会

具体说来,猫晚有两大特点:第一个是广电级别的匠心制作。精心的编排,豪华的阵容,酷炫的舞台效果,堪称顶级现场制作。第二个是极致专业,专业的演员和表演,同时现场有专业的各种设备,诸如灯光、摄影、音响和调音台等等设备以及专业的现场导播制作团队。

要把这场实时晚会盛宴进行多屏分发到全球各地,确保屏幕前的观众一起参与狂欢,感受晚会氛围,就需要背后的技术指标至少要满足三点:高可靠、高画质、高音质。

首先,高可靠。确保晚会表演的每一帧画面、每一个声音都能让屏幕前的观众看得到、听得到、感受得到,那么就要求除了各个云端服务必要的高可靠性以外,还需要端到端的全链路来保障每个链路环节的高可靠。

再者,高画质与高音质。如此匠心之作的晚会,要求区别于一般的泛娱乐主播秀,不仅要注重屏幕前观众的视觉享受,同时也必须要保证他们听觉上的享受,要让屏幕前的观众有如临现场的观看体验。

怎样达到这些技术指标?来看看阿里云视频云的技术解决方案。

高可靠保障:端到端全链路主备双重机制

针对高可靠,阿里云视频云是采用端到端的全链路主备双重机制来实现,同时值得强调的是,阿里云不是采用两条主备全链路,而是针对全链路的每个环节,进行主备双重机制重保来实现的。主备双重机制可以保证主设备或者主链路环节出了任何抖动、卡顿或者故障等问题,其备用环节会立即补上,让播放端无感知,最大可能保证观看流畅。

阿里云视频云把全链路拆分为三大环节: 活动现场、阿里云端、用户侧多屏播放端。(下图所示)

高可靠 - 全链路重保方案

在活动现场环节,主要是信号采集、处理、编码、上传几个模块。其中信号的采集和处理是通过现场的导播制作团队来保证高可靠的,比如:可以通过添加多个专业设备和专业人士以及多条线路。
而编码和上传就是信号IP化,这需要进行主备重保来保证该环节的高可靠。现场制作而来的信号需要同时传输给主备两个编码器,这两个编码器使用同一组信号源,同一组编码参数进行编码,然后经过不同运营商的多条专线网络上传到阿里云直播服务中心。

在阿里云端环节中,我们采用双中心多机房、主备合流、无缝切换的技术加上资源隔离以及机器独享、冗余的方式来确保云端各个环节的高可靠性。其中双中心多机房是为了容灾,而主备合流是为了保证主备链路环节的流,能够动态选取最优帧率的链路,无缝合并成一路输出。

同时在播放侧,阿里云视频云除了通过全球2800多个CDN节点进行多屏分发外,在播放技术上,采用双回源、多码率组合的播放方式,集成了阿里云播放SDK 的播放端,可以根据分发的网络情况进行网络自适应来切换不同的转码档次,确保在网络不是很好的情况下也能保障非常顺畅的观看体验。

高音质保障:SRT接入+全链路杜比

在高音质方面,阿里云视频云采用了SRT接入和全链路杜比音频技术,来让屏幕前的观众听到比现场还真实的全景声音。

在直播链路中支持SRT协议接入,确保广电行业的视频流可以进行低延迟、高可靠的接入到互联网进行分发。SRT是基于UDP的低延时、高可靠传输协议,如果要接入一路流的话,需要打开端口,但是多路流的区分以前只能通过端口来区分,而现在,阿里云支持可以通过streamID的方式区分不同流来接入,从而进行端口复用。

用户可以通过任何一款的支持SRT的推流端设备或者软件直接推流到阿里云边缘节点即可使用,比如业内通用的OBS桌面软件推流端或者其它硬件SRT推流设备。同时阿里云视频云与Haivision 厂商密切合作,也支持Haivision的Media Gateway 和硬件编码器等各种硬件直接接入。

阿里云视频云为真实还原晚会现场声音,采用了杜比全景声技术,支持端到端全链路杜比音频传输,确保各个转码档次的音频也是杜比音质,让网络环境不好的屏幕前的观众也能享受到高音质的全景声,感受甚至比现场还真实。

高画质保障:窄带高清2.0+FPGA265编码

在高画质方面,采用了独有的窄带高清2.0技术和FPGA265编码。

阿里云视频云窄带高清2.0技术,是从人眼视觉模型出发,将视频的优化目标从经典的“保真度最高”调整为“主观体验最好”。凭借独有算法,突破当代视频编码器的能力上限,在节省码率的同时,也能提供更加清晰的观看体验。

而使用FPGA265 编码器来保证码率更多的节省,同时FPGA编码器也能够提供更好的实时压缩性能,确保高画质视频码率更低,画质更真。

我们知道 H265 具有很好的压缩比,但是在直播过程中如果使用CPU转码,很难达到实时,因为265的计算复杂度比较高,针对高分辨率、高帧率、高码率的视频源,使用CPU去做高画质的压缩计算来实现265转码,实时性是几乎不可能有保证的,而且CPU机器的成本也是比较高的。

所以,业内通用的做法是倾向于使用 GPU 或者 FPGA 等借助硬件来实现H265的实时编码。

达摩院XG实验室精心打造了一款实时的高性能的H265 FPGA硬件编码器“XGH265”, 不管是从压缩效果还是性能等各个维度来看,都已经达到了业内顶尖水平,同时它还支持可配置的视频前处理的能力,在画质更清晰的观看体验下,压缩比和压缩性能最好。

在压缩性能上, XGH265 的压缩性能和X265 的slowpreset 档次相当。我们要知道,在直播过程中针对1080p的源流能够达到 X265 slow 档次的实时压缩性能是非常困难的,在业内的各种GPU\FPGA等硬件编码器几乎是不可能实现的,在实时和画质之间无法同时平衡。而XGH265是目前市场上罕见的一款能够支持1080p 60fps实时,且压缩性能相当X265的slow 档次FPGA卡。目前阿里云达摩院XG实验室正在针对4K甚至更高的分辨率提出更好的方案,后续也会推向市场。

从高可靠、高音质、高画质维度,阿里云视频云提供了端到端的全链路重保解决方案,来保证猫晚这类重大活动的直播,稳如泰山、丝般顺滑、身临其境。

阿里云视频云:回顾猫晚直播重保六年

从猫晚一开始存在的时候,即2015年,阿里云视频云团队便开始猫晚护航,期间,团队经历也过了很多重大活动直播的护航。

项目的技术负责人蔡鼎说到,“团队从刚刚开始的紧张,到如今的有条不紊、平淡不惊,表明了视频云对自身云端服务的稳定性的信心,同时,团队针对端到端的全链路各种指标的监控告警、降级措施、应急预案演习等已做得越来越完善”。

针对护航,阿里云视频云团队也有相应严苛的护航手册和条例,所有这些的改进都是通过一次次护航经验的复盘、迭代打磨出来的。

蔡鼎分享到,“以前我们的护航是边看日志边看监控,而目前我们是边看直播边看监控告警,未来,我们甚至期望可以做到边看直播边购物,和其它购物者一样,我们要让护航全自动化,碰到问题能够自动决策各种应急预案和措施,同时,业务方自身也能够使用工具来自我保障,最大限度实现全链条的自动智能安全保障。总言之,我们希望每次重大活动直播都是无惊无险、平平淡淡、稳如泰山。“

重大盛典/赛事直播的视频云技术能力

利用端到端的全链路重保解决方案,阿里云视频云可将技术复用在更多业务场景上,如曾支持的春晚直播、元旦跨年直播、国庆阅兵直播、世界杯直播等等,其中,2018年的世界杯直播项目尤为深刻经典。

在支持大型晚会、体育赛事、竞技赛事的直播能力上,阿里云视频云的突出优势在于:端到端的全链路主备解决方案、各种指标的监控、水位告警、大盘监控,以及可视化画面合屏盯屏、主备合流、帧率码率监控等一整套经过多次大型直播活动打磨过的护航解决方案。

未来,该技术解决方案还有更多的升级空间,技术负责人介绍到,“目前,重保全链路方案,在主备链路合流上还只能做到3-5秒的抖动,我们期望后续真正做到主备链路帧级无缝合流的能力,让播放端体验完全无感知,从而以端到端的高可靠全链路方案,达到广电级别的重保效果。“

阿里云视频云引领直播行业的前瞻性技术,不断探索与升级,期待更多视觉盛宴以身临其境的直播体感而来。

阿里云视频云技术公众号分享视频云行业和技术趋势,打造“新内容”、“新交互”。
查看原文

赞 0 收藏 0 评论 0