WebRTC-节拍器[翻译]

Paced Sending (有节奏的发送)

Paced Sending

有节奏的发送者, 通常被称为 “节拍器”, 是 WebRTC 的一部分. 在 RTP 栈中主要用于平滑的发送
数据包到网络上【为了避免网络拥塞而牺牲发送时间】.

背景

考虑一个 5Mbps 和 30fps 的视频流. 在理想情况下, 这将导致每帧数据约 21kB 大,
并打包成 18 个 RTP 包. 虽然一秒内滑动窗口的平均比特率是正确的 5Mbps,
在更短的时间尺度上, 它可以被视为每 33 毫秒爆发 167Mbps, 此外,
在突然移动(: 画面剧烈变化)的情况下, 视频编码器超出目标帧大小是很常见的, 特别是在处理屏幕共享时.
比理想尺寸大 10 倍甚至 100 倍的帧是一个非常真实的场景. 这些数据包爆发会导致几个问题,
如网络拥塞, 造成缓冲区膨胀, 甚至丢包. 大多数会议都有一种以上的媒体流, 例如视频和
音频. 如果你一口气把一帧数据放在网络上, 而这些数据包需要 100 毫秒到达另一端 -
这意味着你现在已经阻止了任何音频数据包及时到达远程端.

有节奏的发送者通过一个拥有媒体的队列缓冲区来解决这个问题, 然后使用 leaky bucket
算法将它们分配到网络上. 缓冲包含所有媒体音轨的独立 fifo 流, 这样音频就可以优先于视频
(: 通话中音频比视频优先级更高?) - 相等的 prio 流可以循环发送(:公平发送), 以避免任
何一个流阻塞其他流. 由于节拍器控制在线路上发送的比特率, 它也被用于在需要最小发送率
的情况下生成填充(: 填充冗余数据?) - 如果使用了比特率探测, 则生成分组序列.

数据包的生命周期

当使用定速发送器时, 数据包的典型路径看起来像这样:

  1. RTPSenderVideoRTPSenderAudio 将媒体打包成 RTP 包.
  2. RTP 包被发送到 RTPSender 类进行传输.
  3. 通过 RtpPacketSender 接口调用节拍器, 使数据包进入队列.
  4. 数据包被放入节拍器内的队列中, 等待适当的时刻发送它们.
  5. 在一个计算时间里, 节拍器调用 PacingController::PacketSender() 回调方法, 通常
    PacketRouter 类实现.
  6. 路由器根据数据包的 SSRC 将数据包转发到正确的 RTP 模块, RTPSenderEgress类做最后
    的时间标记, 可能会记录下来以便重新传输等.
  7. 数据包被发送到低级的 Transport 接口, 之后它就超出了作用域(: 生命周期结束).

与此异步的是, 估计的可用发送带宽是确定的 - 目标发送速率通过
void SetPacingRates(DataRate pacing_rate, DataRate padding_rate) 方法在
RtpPacketPacker 上设置.

数据包优先级

节拍器根据两个条件对数据包进行优先级排序:

  • 数据包类型, 优先级从高到低:
    1. 音频 (Audio)
    2. 重传 (Retransmissions)
    3. 视频和FEC (Video and FEC)
    4. 填充 (Padding)
  • 排队的顺序:
    排队顺序是按每个流 (SSRC) 执行的. 如果优先级相同,
    [RoundRobinPackageQueue][RoundRobinPackageQueue] 在媒体流之间交替,
    以确保没有任何流不必要地阻塞其他流.

实现

目前有两种对节拍器的实现 (尽管它们通过 PacingController 类共享大量的逻辑).

传统的 PacedSender 使用一个专用线程以 5ms 的间隔
轮询 pacing controller, 并有一个锁来保护内部状态.

较新的 TaskQueuePacedSender, 顾名思义, 使用 TaskQueue 来
保护状态和调度数据包处理.

后者是动态的, 基于实际的发送速率和约束. 避免在新的应用程序中
使用遗留的 PacedSender, 因为我们计划删除它.

数据包的路由器

一个相邻的叫做 PacketRouter 的组件被用来路由从节拍器进入正确的 RTP
模块的数据包. 它具有以下功能:

  • SendPacket 方法查找与数据包对应的 RTP 模块, 以便进一步路由到网络.
  • 如果使用发送端带宽估计, 它将填充 传输范围的序列号 扩展.
  • 生成填充. 模块支持 基于有效负载的填充的模块 具有优先级,
    最后一个发送媒体的模块始终是首选.
  • 在发送媒体后返回任何生成的 FEC.
  • 转发 REMB 和/或 TransportFeedback 消息到合适的 RTP 模块.

目前, FEC 是在每个 SSRC 的基础上产生的, 所以总是在发送媒体后从 RTP 模块返回.
希望有一天我们能用一个 FlexFEC 流覆盖多个流 - 而包路由器很可能是 FEC 生成器存在的地方.
它甚至可以作为 RTX 的替代方案用于 FEC 填充.

The API

这一节概述了与的节拍器的几个不同用例相关的类和方法.

数据包发送

对于发送数据包, 使用

RtpPacketSender::EnqueuePackets(
    std::vector<std::unique_ptr<RtpPacketToSend>> packets
)

作为构造函数的参数, 该回调在实际发送数据包时使用.

发送速率

要控制发送速率, 请使用
void SetPacingRates(DataRate pacing_rate, DataRate padding_rate)
如果包队列为空, 发送速率下降到 padding_rate 以下, 节拍器将从 PacketRouter
请求填充数据包.

为了完全暂停/恢复发送数据(例如由于网络可用), 使用 Pause()Resume() 方法.

在某些情况下, 指定的步调速率可能会被重写, 例如由于极端编码器超调.
使用 void SetQueueTimeLimit(TimeDelta limit) 来指定您希望数据包在节拍器队列中等待
的最长时间(不包括暂停). 然后, 实际发送速率可能会增加到超过 pacing_rate, 以尝试使
average 队列时间小于请求的限制. 这样做的基本原理是, 如果发送队列超过3秒, 最好冒着
数据包丢失的风险, 然后尝试使用关键帧来恢复, 而不是造成严重的延迟.

带宽估算

如果带宽估计器支持带宽探测, 它可能会请求以指定的速率发送一组探测包, 以便评估这是否会导致
网络上的延迟/丢包增加. 使用
void CreateProbeCluster(DataRate bitrate, int cluster_id) 方法 - 通过
PacketRouter 发送的数据包将在附加的 PacedPacketInfo struct 中标记相应的
cluster_id.

如果使用拥塞窗口推送, 状态可以使用 SetCongestionWindow()
UpdateOutstandingData() 更新.

还有一些方法可以控制我们的节奏:

  • SetAccountForAudioPackets() 确定音频包是否计入所消耗的带宽.
  • SetIncludeOverhead() 确定整个RTP包大小是否计入所使用的带宽(否则只计入媒体负载).
  • SetTransportOverhead() 设置每个包消耗的额外数据大小, 例如UDP/IP报头.

Stats

几种方法可以用来收集节拍器状态的统计信息:

  • OldestPacketWaitTime() 队列中最老的包被添加的时间.
  • QueueSizeData() 队列中当前的总字节数.
  • FirstSentPacketTime() 发送第一个报文的绝对时间.
  • ExpectedQueueTime() 队列中的总字节数除以发送速率.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值