WebRTC-之-JitterBuffer--让子弹飞一会儿

614 阅读4分钟

什么是抖动

音频或视频包可能到来一会儿慢,一会儿快,顺序也可能不对,这时候你不能直接播放或丢弃这些包,怎么办呢?让子弹飞一会儿。

这种时慢时快的现象叫做抖动(Jitter),是指音频或视频数据包,抵达网络远端目的地的时间变化。网络中每个数据包的转发路径不同、线路争夺(Contention), 排队或拥塞等因素都会造成抖动。

我们听到的语音,看到的视频应该是连续平滑的,而抖动会引起语音的断断续续,视频也可能会造成卡顿。

抖动的计算

到达时间的变化

抖动的消除 Jitter Buffer

把这些包放在一个叫做抖动缓冲区 Jitter Buffer 的地方缓存起来,过一段时间再一起拿出来,摆好顺序,重新组织成顺序的音频包或完整的视频帧,这个播放效果就会平滑得多。

  1. 什么时候放入

能够成功解密并校验无误的包都依次放入抖动缓冲区

  1. 什么时候取出

这个有不同的策略,简单的做法是缓冲区满了就取出来

Jitter Buffer 旨在消除解码音视频流中的抖动影响,在播放之前将每个到达的数据包缓冲一小段时间。 这样迟来和乱序的包造成的影响就会小很多

固定的Jitter Buffer 保持恒定的大小,而自适应Jitter Buffer 具有动态调整其大小的能力,它可以根据网络状状况进行调整。

例如如果由于延迟发生了 20 毫秒的阶跃变化,那么可能会因变化而导致一些短期的数据包丢弃,但Jitter Buffer 将很快重新对齐。 在许多情况下,抖动缓冲区Jitter Buffer 可以被认为是一个时间窗口,一侧(较早的一侧)与最近的最小延迟对齐,另一侧(较晚的一侧)表示丢弃数据包之前的最大允许延迟。

这些放在 Jitter buffer 中的包并未解码, JitterBuffer 常用 JitterBufferDelay 来度量,也就是音频包的长度累加起来,或者是第一个音频或视频帧收到的时间到其拿出的时间, 即退出时间减去进入时间(emit_time - ingest_time)

就音频而言,几个样本属于同一个RTP数据包,因此它们将具有相同的接收时间戳,但Jitter Buffer 发出的时间戳会不同。

在视频的情况下,该帧可能是通过几个RTP数据包接收的,因此,进入时间戳是进入Jitter Buffer 的帧的最早数据包,而退出时间戳是整个帧退出Jitter Buffer 时的时间戳。

有以下重要指标

  • jitter_buffer_depth = packet_count_in_jitter_buffer, 例如 10,指缓冲区有 10 个包
  • jitter_buffer_depth_ms = packet_count_in_jitter_buffer * packet_time, 例如 200 ms , 每个包长 20ms, 共有 10 个包
  • jitter_buffer_depth_goal = desired_packet_count_in_jitter_buffer 例如 30,指缓冲区中的目标个数是30个包

音频采样或视频帧存在于JitterBuffer中的时间,称为 JitterBufferDelay

我们一般会计算平均 JitterBufferDelay 如下

avgJitterDelay = jitterBufferDelay/jitterBufferEmittedCount

Jitter Buffer 抖动缓冲区可以分为 1)音频抖动缓冲 2)视频抖动缓冲

在电话语音或VoIP中,抖动是指预期语音包与预期到达时间之间的偏差。 例如,两个数据包可能同时到达,或者相隔很久才到达,或者由于网络拥塞而乱序。 这些问题可能会导致音频质量下降, 具体来说就是声音不清楚或断断续续。

有了抖动缓冲区这样一个中间队列,它可以根据数据包的预期定时值和序号对数据包进行排序,以最大程度地减少抖动。 使用抖动缓冲区改善通话质量有很好的效果。

按照 Jitter buffer 的长度(媒体包放在里面的时长)又可以分为两种类型:

1)FJB 固定抖动缓冲区 2)AJB 自适应抖动缓冲区。

固定缓冲区始终保持已建立的队列大小,而自适应缓冲区队列大小根据内部自适应逻辑而进行适时的增大或缩小。

class FrameList
    : public std::map<uint32_t, VCMFrameBuffer*, TimestampLessThan> {
 public:
  void InsertFrame(VCMFrameBuffer* frame);
  VCMFrameBuffer* PopFrame(uint32_t timestamp);
  VCMFrameBuffer* Front() const;
  VCMFrameBuffer* Back() const;
  int RecycleFramesUntilKeyFrame(FrameList::iterator* key_frame_it,
                                 UnorderedFrameList* free_frames);
  void CleanUpOldOrEmptyFrames(VCMDecodingState* decoding_state,
                               UnorderedFrameList* free_frames);
  void Reset(UnorderedFrameList* free_frames);
};

参考资料