【音视频】 WebRTC-jitterbuffer延时分析

参考文章:
https://mp.weixin.qq.com/s?__biz=MzkzMDI4ODk4OQ==&mid=2247483922&idx=1&sn=8af99d518440099fa02cf5fbd4946d43&chksm=c27dc620f50a4f36df58086aad84b1eabadc92fdc5ef1c8b97383ed1510cd43ef9348fc24fe4&cur_album_id=2101036772167090177&scene=190#rd

一、前言

jitterbuffer 也叫抖动缓冲区,分为jitter和buffer两部分即延时和缓冲区管理。工作在接收端,通常在播放器,主要目的是保证平滑播放,常见的抖动缓冲区分为静态抖动缓冲区和自适应抖动缓冲区

  • 静态抖动缓冲区缓冲区时长固定
  • 自适应抖动缓冲区可以自适应网络抖动、解码延时的变化。WebRTC采用自适应抖动缓冲区,本文重点分析WebRTC中对延时的计算。

二、jitterbuffer基本思想

要保证平滑播放,对于接收到的数据帧尽量按照原始数据帧的采集间隔进行播放。但由于网络延时、解码延时、渲染延时的存在,如果收集到一帧数据立即播放就会造成快放或卡顿。WebRTC收集到完整一帧数据后不是立即发送到解码器而是添加一个延时后才开始解码,避免有些帧到的太慢从而造成卡顿,原则上只要数据帧在该延迟时间内到达就不会引起卡顿,WebRTC中的延迟是动态变化的。

  • 假设数据包A、B、C、D以30ms间隔发送数据,网络时延分别为(10ms、30ms、10ms、10ms), 此时到达时间为(40ms、90ms、100ms、130ms), 产生的时间间隔分别为(50ms、10ms、30ms)。

  • 为了平滑播放先在缓冲区中延时20ms再开始播放,此时播放时间分别为(60ms、90ms、120ms、150ms), 播放间隔与发送间隔同为30ms。

  • 假设缓冲区大小为10ms, 此时播放时间分别为(50ms、80ms、110ms、140ms), 为了产生30ms的播放间隔B数据包需要丢弃。

可见抖动缓冲区的大小至关重要,小了会产生丢包,大了会增加延时。抖动缓冲区的理想状态是数据包在网络中的延迟与其在缓冲区中的延迟之和相等,一般的抖动缓冲区的消除思想是将抖动缓冲区的大小设置成目前测量的最大网络延迟。

三、jitterbuffer 基本流程

jitterbuffer分为buffer和jitter:

  • buffer主要包括PacketBuffer、RtpFrameReferenceFinder、FrameBuffer。PacketBuffer保证数据帧的完整性,RtpFrameReferenceFinder给每个帧设置参考帧,FrameBuffer保证帧的连续性和可解码性。

  • jitter部分主要涉及到延时的计算,FrameBuffer在InsertFrame时会设置帧的期望接收时间,FindNextFrame中会设置帧的渲染时间,GetNextFrame中会更新网络延时jitter。

3.1 常用类介绍

视频jitterbuffer 的jitter计算主要涉及到以下几个类:

  1. RtpVideoStreamReceiver:rtp数据接收;
  2. VideoReceiveStream: 负责数据驱动,接收到完整的数据帧,插入到frame_buffer并在适当时机执行解码操作;
  3. FrameBuffer:负责帧的连续性和可解码性,连续性是指某帧的所有参考帧都已经收到,帧的可解码性是指帧的所有参考帧都已经被解码。保存未解码的帧,已解码的历史帧;
  4. VCMJitterEstimator:计算抖动jitter值;
  5. VCMTiming:计算当前延迟currentDelayMs,用于计算渲染时间;
  6. VCMCodecTimer: 统计解码延时。

3.2 基本流程分析

jitterbuffer的执行流程如下图所示:

在这里插入图片描述

  1. RtpVideoStreamReceiver 收到rtp数据包后解包后封装成VCMPacket,并插入到PacketBuffer中;

  2. 在PacketBuffer中组帧,若发现完整的数据帧则回调RtpVideoStreamReceiver的OnAssembledFrame函数;

  3. 在RtpVideoStreamReceiver中调用成员RtpFrameReferenceFinder进行参考帧设定,参考帧设置完成后回调RtpVideoStreamReceiver的OnCompleteFrame接口,接着回调VideoReceiveStream的OnCompleteFrame接口;

  4. 在VideoReceiveStream中将一帧数据插入到FrameBuffer中,更新帧的引用完整性和解码完整性;

  5. 解码时刻到来从FrameBuffer中取出一帧解码,并在渲染延迟后开始渲染。

3.3 插入到FrameBuffer后处理流程

对FrameBuffer的主要操作分为读取和写入,以下流程图是帧插入到FrameBuffer后详细处理流程。

在这里插入图片描述

读取

  1. VideoReceiveStream start中会启动解码线程,解码线程最终会调用frame_buffer的FindNextFrame查找可解码的数据帧并返回一个等待时间;
  2. 延迟该等待时间后调用GetNextFrame取出可解码的数据帧执行解码操作;
  3. 在GetNextFrame中会根据实际的解码时间更新延迟信息。

写入

填充好参考帧的完整帧后会回调 VideoReceiveStream 的OnCompleteFrame接口,该函数调用frame_buffer的InsertFrame把一帧数据写入JitterBuffer里之后执行以下操作:

  1. 检查视频帧是否有效。缓冲区是否满等;
  2. 更新帧信息,主要是设置帧的还未连续的参考帧数量,并建立被参考帧与参考他的帧之间的参考关系;
  3. 如果不是重传帧,更新帧的渲染时间;
  4. 如果当前帧连续则传播帧的连续性;
  5. 触发解码任务,寻找待解码的帧,并发送到解码任务队列。

四、jitterbuffer 延时分类

主要有三种,网络抖动延时、解码延时、固定渲染延时。网络抖动延时是由网络抖动造成的,解码延时是解码器解码产生的是个统计值,固定渲染延时一般是10ms。

4.1 接收到一帧rtp数据时间线

在这里插入图片描述

如上图所示,对应的名称与含义如下:

名称含义类型
now_ms当前时间,单位ms时间点
expect_decode_time期望开始解码时间时间点
actual_decode_time实际解码时间时间点
decode_finish_time解码结束时间时间点
render_time渲染时间时间点
wait_time到期望开始解码的等待时间时间段
current_delay当前延时时间段
target_delay目标延时,最优延时时间段
frame_delay数据帧产生的延迟时间段
render_delay固定10ms延迟时间段

其中

  • expect_decode_time = render_time_ms - decode_delay_ms - render_delay_ms_
  • frame_delay = actual_decode_time - expect_decode_time
  • current_delay = max(current_delay + frame_delay, target_delay)
  • wait_time = render_time - now_ms - decode_delay - render_delay
  • decode_delay = decode_finish_time - expect_decode_time

4.2 wait_times计算

在上述时间线图中用wait time 表示,指接收一帧数据到解码最小的等待时间。

int64_t VCMTiming::MaxWaitingTime(int64_t render_time_ms,
                                  int64_t now_ms) const {
  rtc::CritScope cs(&crit_sect_);

  const int64_t max_wait_time_ms =
      render_time_ms - now_ms - RequiredDecodeTimeMs() - render_delay_ms_;

  return max_wait_time_ms;
}

4.3 目标渲染时间 RenderTime 计算

期望的开始渲染时间,对应上述t3,整个计算过程如下图所示。其中会涉及到抖动jitter、期望接收时间、真实延时、当前延时、解码延时等的计算。

在这里插入图片描述

RenderTime = estimated_complete_time_ms(期望接收时间) + actual_delay(当前延时);

4.4 抖动jitter计算

4.4.1 jitter定义

定义:指由于各种延时的变化导致网络中的数据分组到达速度的变化。可将抖动定义为数据流在发送端发送间隔与接收端接收间隔之差:Ji = Si - Ri i= 1, 2, ...,n 其中,

  1. si为发送第i和第i+1个数据包的发送间隔;
  2. Ri为接收第i和第i+1个数据包的到达间隔;
  3. Ji为数据包i的抖动延迟。

更直观的可以用下图描述, 其中jitter delay 就是产生的网络延时,会经卡尔曼滤波进行处理,得到最优值

在这里插入图片描述

计算步骤分为以下两步

  1. 计算帧间延迟 = 两帧的接收时间差 - 两帧的发送时间差, 用VCMInterFrameDelay表示;
  2. 根据VCMInterFrameDelay计算的帧间延迟计算出最优抖动值,用VCMJitterEstimator表示,是一个卡尔曼滤波的过程。

4.4.2 jitter 计算模型

设T(i) 为发送时间,t(i) 为接收时间,d(i)为第i帧的接收时间之差减去发送时间之差。d(i)就是相邻两帧产生的延时,W(i)是误差。
d(i) = t(i) - t(i-1) - (T(i) - T(i-1)) +W(i)
     = (t(i) - T(i)) - (t(i-1) - T(i-1)) + W(i)
     = L(i)/C(i) - L(i-1)/C(i-1) + w(i)  (此处假设C(i) = C(i-1) 即网路传输速率不变)
     = dL(i) / C(i) + w(i)
d(i) 就是最终延迟
L(i) 为每帧的数据量大小
dL(i) 为两帧数据量差值

上述jitter = d(i) = dL(i) / C(i) + w(i)

由于观测过程会存在误差,导致帧间抖动以及网络传输速率不准,引入了卡尔曼滤波去修正。jitter的主要目的是根据视频帧的延时时间计算出目标渲染时间。

4.5 期望接收时间 estimated_complete_time_ms计算

期望接收时间由TimestampExtrapolator计算。在代码中用estimated_complete_time_ms表示。TimestampExtrapolator是一个卡尔曼滤波器,其输入为帧的rtp时间和接收时间,根据输入帧的时间戳的间隔计算期望接收时间,得到一个最优的帧接收时间。

4.5.1 主要原理

在这里插入图片描述

TimestampExtrapolator 负责期望接收时间计算。FrameBuffer对于收到的每帧数据都会包含rtp时间和实际的接收时间。

  1. 第一帧接收时间定义为startMs;
  2. 第一帧rtp时间定义为firstTimestamp;
  3. 当前帧的rtp时间为R(k);
  4. 当前帧的期望接收时间为T(k);
  5. 当前抖动延时为jitterTimestamp, 在代码中是W[1]
  6. 千分之一采样率samplerate/1000, 在代码中是W[0]

4.5.2 基本模型

不同帧的采样率和jitter是线性关系,误差U服从高斯分布。

在这里插入图片描述

4.5.3 获取当前帧的期望接收时间

计算rtp时间差:timestampDiff = R(k) - firstTimestamp

期望接收时间:T(k) = startMs + (timestampDiff - w[1]) / w[0] + 0.5

这里的关键是计算jitterTimestamp,基本原理是根据每一帧的实际接收时间和rtp时间根据卡尔曼滤波器计算抖动值。具体的计算过程可以参见附录部分。

4.6 真实延时actual_delay计算

计算Render_time时关键在于计算实际延时。actual_delay主要由current_delay_ms_的值决定,该值会控制在[min_playout_delay_ms_, max_playout_delay_ms_] 范围内。

4.7 当前延时current_delay_ms_计算

current_delay_ms_ 主要在UpdateCurrentDelay中计算, 下面是主要的代码。

void VCMTiming::UpdateCurrentDelay(int64_t render_time_ms, //期望渲染时间, 时间点
                                   int64_t actual_decode_time_ms) {//当前解码时, 时间点
  rtc::CritScope cs(&crit_sect_);
  uint32_t target_delay_ms = TargetDelayInternal(); // 目标延迟 时间段
  int64_t delayed_ms =
      actual_decode_time_ms -
      (render_time_ms - RequiredDecodeTimeMs() - render_delay_ms_);
  if (delayed_ms < 0) {
    return;
  }
  if (current_delay_ms_ + delayed_ms <= target_delay_ms) {
    current_delay_ms_ += delayed_ms; 
  } else {
    current_delay_ms_ = target_delay_ms;
  }

current_delay_ms 先计算实际的解码时间与期望解码时间之间的延时,该延时加上上次计算的的当前延时,逼近于目标延时。

4.8 目标延时target_delay计算

target_delay 是当前的目标延时,是由网络抖动、解码延时、渲染延时叠加产生。

target_delay = max(min_playout_delay_ms_,
                  jitter_delay_ms_ + RequiredDecodeTimeMs() + render_delay_ms_)

jitter_delay_ms_ 是网络抖动延时,通过CalculateDelay计算,RequiredDecodeTimeMs 是解码延时,render_delay_ms_ 是固定渲染延时。

4.9 解码延时 decode_delay计算

解码延时在代码中通过RequiredDecodeTimeMs计算,统计最近最多10000次时间窗口内的解码时间消耗,计算95百分位数作为解码时间。

int VCMTiming::RequiredDecodeTimeMs() const {
  const int decode_time_ms = codec_timer_->RequiredDecodeTimeMs();
  assert(decode_time_ms >= 0);
  return decode_time_ms;
}

具体的计算在VCMCodecTimer类中进行。

五、查看各指标变化

以上各延时的值可以在chrome://webrtc-internals页面中看到:

名字含义
googDecodeMs最近一次解码耗时.
googMaxDecodeMs最大解码耗时,95分位数,大于采样集合95%的解码延时
googRenderDelayMs渲染延时,10ms
googJitterBufferMs网络抖动延时
googMinPlayoutDelayMs最小播放时延,音视频同步器输出的视频帧播放应该延迟的时长
googTargetDelayMs目标时延
googCurrentDelayMs当前时延,用于计算RenderTime

下图是实际观测到的各值变化情况:

在这里插入图片描述

<think>好的,我现在需要回答如何在Web端通过代码配置或修改WebRTC的jitter buffer大小。首先,我需要回忆一下WebRTC的基本架构和相关API。WebRTC的jitter buffer主要用于处理网络抖动,确保音视频流的顺畅播放。通常,这部分配置可能涉及浏览器提供的API或者WebRTC的内部设置。 根据用户提供的引用内容,引用[1]提到了WebRTC与直播相关的原理,尤其是信令传输的机制,这可能涉及到数据传输的稳定性,而jitter buffer正是处理这种不稳定性的组件。引用[2]提到了切换不同源的RingBuffer,这可能与媒体流的缓冲管理有关,但具体到jitter buffer的调整,可能需要更深入的理解。 接下来,我需要确定WebRTC在浏览器端是否允许开发者直接调整jitter buffer的大小。查阅WebRTC的文档,发现jitter buffer的配置通常是通过RTCRtpReceiver的参数来调整的。例如,在创建PeerConnection时,可以设置一些参数来控制jitter buffer的行为。不过,浏览器的实现可能存在差异,不同版本也可能有不同的支持情况。 另外,WebRTC的jitter buffer分为音频和视频两种,音频的jitter buffer可能通过AudioContext的相关设置间接影响,而视频的jitter buffer可能需要通过SDP(Session Description Protocol)的参数进行调整。例如,在SDP中设置a=rtcp-fb:...来调整缓冲区大小。 不过,直接通过JavaScript代码修改jitter buffer的大小可能并不直接支持,因为浏览器通常不暴露这些底层参数。可能需要通过实验性的API或浏览器特定的设置来实现。例如,Chrome浏览器可能允许通过chrome://flags中的设置进行调整,但这属于用户端配置,开发者无法通过代码控制。 总结可能的步骤:首先检查RTCRtpReceiver的设置,是否有相关参数;其次,查看SDP协商过程中是否可以添加自定义参数;最后,考虑浏览器是否提供实验性API。此外,可能需要依赖浏览器的内部实现,而无法直接调整,这时可以通过调整播放延迟或使用其他缓冲策略来间接影响jitter buffer的行为。</think>在WebRTC中,jitter buffer的大小通常由浏览器自动管理且未直接暴露给开发者,但可通过以下方法间接调整: 1. **通过SDP参数调整** 在建立连接时,可在SDP应答中尝试添加缓冲控制参数: ```javascript pc.createOffer().then(offer => { offer.sdp = offer.sdp.replace(/(a=mid:video\r\n)/g, "$1a=x-google-max-playout-delay=2000\r\n"); return pc.setLocalDescription(offer); }); ``` `a=x-google-max-playout-delay`参数可影响最大延迟(单位:ms),间接调整缓冲时间[^1]。 2. **使用实验性参数** 在创建PeerConnection时尝试配置: ```javascript const pc = new RTCPeerConnection({ encodedInsertableStreams: true, // Chrome实验性参数(可能随版本失效) optional: [{ googCpuOveruseDetection: false, googSuspendBelowMinBitrate: true }] }); ``` 3. **调整播放延迟策略** 通过`<video>`标签属性控制: ```html <video id="remoteVideo" autoplay playsinline onloadedmetadata="this.playbackRate = 1.2"> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值