WebRTC(九):JitterBuffer

JitterBuffer

Jitter

Jitter”指的是连续到达的媒体包之间时间间隔的变化。在网络传输中,由于:

  • 网络拥塞
  • 路由路径变化
  • 队列排队
  • 不同链路带宽差异

导致包之间的接收时间不一致,这就是网络“抖动”。

作用

**JitterBuffer(抖动缓冲区)**的作用是:

  • 缓冲网络传输过来的数据包
  • 重新排序乱序的包
  • 缓冲一定时间再输出
  • 实现稳定的音视频帧输出,避免播放中出现 卡顿、跳帧、音频破音

工作流程图

网络接收 ← UDP/RTP 包 ← jitterbuffer ← 解码器 ← 播放器/渲染器↑          ↑排序 + 重组 + 时间控制  + 丢包填补(PLC)

流程详解

1. 接收数据包

  • 每个 RTP 包有 sequence numbertimestamp
  • 收到包后,判断是否乱序、丢包。

2. 缓存和排序

  • 将包插入 buffer 中合适位置(基于 sequence number 排序)。

3. 播放控制

  • 到达播放时间时,提取对应时间戳的包进行解码。
  • 若包未到达(丢包或延迟):
    • 等待一段时间(等待时间配置或自适应);
    • 或直接丢帧;
    • 或填补(音频使用 PLC,视频可能重复前帧或跳过)。

4. 自适应控制

  • 根据网络条件(RTCP 报告、丢包率、延迟)动态调整缓冲大小(WebRTC 的核心机制之一)。

常用参数

参数说明
初始缓冲时长(如 50ms)启动播放前预缓存的时长
最大缓冲时长(如 200ms)抖动缓冲的最大范围
播放时钟控制何时从 buffer 中读包
最大乱序范围防止恶意/错误乱序拖垮 buffer

WebRTC中JitterBuffer

WebRTC 是目前最复杂、最智能的抖动缓冲实现之一,支持:

  • 音频 JitterBuffer
  • 视频 JitterBuffer
  • 网络自适应算法
  • FEC(前向纠错)/NACK(重传)
  • Audio/Video 同步

音频 JitterBuffer

模块路径:webrtc/modules/audio_coding/neteq/

功能

  • 乱序处理;
  • 丢包补偿(使用 PLC、CNG、FEC);
  • 动态调节;
  • 语音平滑(低码率时很关键);

原理

             RTP Packet↓NetEq::InsertPacket↓[DecoderBuffer + PacketBuffer]↓NetEq::GetAudio (解码并补偿)↓音频帧 → 播放器

PLC、CNG、FEC

概念
缩写全称作用
PLCPacket Loss Concealment在音频丢包时生成“伪造音频”以避免突兀中断
CNGComfort Noise Generation在静音时生成背景噪声,防止“死寂”现象
FECForward Error Correction通过多发送冗余信息,在接收端恢复丢失的数据包
PLC(Packet Loss Concealment)

目标:帧丢失时,合成一个与上一帧相似的语音片段,避免“卡顿”或“哑音”。

常用方法

  • 波形复制:简单地复制上一帧波形;
  • 线性预测(LPC):建模语音信号特性,预测缺失内容;
  • 谱域合成:复制频谱形状,适用于宽带语音(如 Opus);

WebRTC 实现

  • 位于 NetEq 模块中的 Expand 类;
  • 插入虚拟音频帧(通常是 10ms);
  • 结合时间戳推进逻辑,自动衔接解码帧。

NetEq::GetAudio() 会判断是否缺帧,如缺则调用 Expand::Process() 生成伪音频。

CNG(Comfort Noise Generation)

目标:通话静音时生成背景噪声,增强自然感、避免“真空”感。

常用方法

  • 在“活动语音”段估计背景噪声特征;
  • 静音时合成类似背景噪声(白噪声加滤波);
  • 由编码器定期发送 SID(Silence Insertion Descriptor)帧;

WebRTC 实现

  • 使用 RFC 3389 标准 CNG;
  • 位于 NetEq 的 ComfortNoise 模块;
  • 接收 SID RTP 包并生成伪噪声;
  • 在编码器中设置:audio_coding_module->EnableCN(true);
FEC(Forward Error Correction)

目标:通过发送冗余信息,让接收端自行恢复丢失的帧。

常用方法

  • Opus 内建 FEC:发送低码率副本;
  • Redundant Encoding (RED):同一个 RTP 包携带多个编码帧;
  • ULPFEC(RFC 5109):按 RTP 层进行异或编码恢复丢包;

WebRTC 实现

  • 支持 Opus FEC(内建);

  • 支持 RED + ULPFEC 组合(多用于视频,但音频也适用);

  • 启用方式:

    config.audio.send_codec_spec.codec_inst.pltype = 111; // opus
    config.audio.send_codec_spec.enable_fec = true;
    

Opus 中 FEC 和 DTX 可协同工作(低带宽时启用 DTX 静音,失真时启用 FEC)

对比
技术工作阶段需编码器支持占带宽延迟对音质的作用
PLC接收端平滑丢包间断
CNG编码 + 解码极低模拟背景环境
FEC编码 + 解码主动对抗丢包,避免掉帧
WebRTC 中启用方式
启用 PLC(默认开启)

无需显式设置,NetEq 自动启用:

NetEq::GetAudio() 自动判断是否丢包 → Expand::Process()
启用 CNG
AudioSendStream::Config config;
config.send_codec_spec.codec_inst.pltype = 9; // G.729 CN
config.send_codec_spec.enable_dtx = true;     // 打开 DTX

对于 Opus,也可以开启 DTX(自动静音 + CNG):

config.send_codec_spec.enable_dtx = true;
启用 FEC(以 Opus 为例)
config.send_codec_spec.enable_fec = true;

也可通过 SDP 启用 RED + ULPFEC:

a=rtpmap:111 opus/48000/2
a=fmtp:111 useinbandfec=1; usedtx=1

NetEq

功能

功能说明
抖动缓冲缓解网络抖动带来的乱序、延迟不稳定
解码插件式音频解码器支持
丢包补偿(PLC)使用语音扩展、静音插入等技术“补”上丢帧
噪声生成(CNG)模拟背景噪声防止静音突兀
拓展播放/速率控制实现播放速度调节(例如加速恢复)
DTMF 支持电话拨号音的内联处理
关键类:NetEqImpl

核心类是:

class NetEqImpl : public NetEq {public:int InsertPacket(const RTPHeader& header, rtc::ArrayView<const uint8_t> payload) override;int GetAudio(AudioFrame* audio_frame) override;...
};
InsertPacket()
int NetEqImpl::InsertPacket(const RTPHeader& header,rtc::ArrayView<const uint8_t> payload)

处理 RTP 包输入:

  • 插入 packet_buffer_
  • 检查有效性、乱序
  • 更新时间戳信息
GetAudio()
int NetEqImpl::GetAudio(AudioFrame* audio_frame)

执行一次音频播放输出:

  • 调用 decision_logic_->GetDecision() 选择行为
  • 行为包括:
    • kNormal:正常解码
    • kExpand:PLC 补偿
    • kAccelerate:播放加速
    • kCng:背景噪声
  • 执行相应模块生成音频帧返回
运行机制:时间推进和缓冲策略

NetEq 使用内部“播放时钟”推进播放,假设 10ms 一帧,每次 GetAudio() 会:

  1. 计算目标 timestamp
  2. 判断当前 packet buffer 是否含有该 timestamp 的帧
  3. 没有 → 触发补偿
  4. 有 → 解码返回
源码解析
PacketBuffer

存储 RTP 包,支持按 timestamp 排序 + 乱序插入:

class PacketBuffer {bool InsertPacket(Packet&& packet);absl::optional<Packet> GetNextPacket(uint32_t timestamp);
};
DecoderDatabase

注册各种 RTP payload type 到解码器:

class DecoderDatabase {bool RegisterPayload(uint8_t payload_type, AudioDecoder* decoder);AudioDecoder* GetDecoder(uint8_t payload_type);
};

可扩展添加自定义解码器。

Expand(PLC)

用于在丢包时合成连续音频:

class Expand {void Process(AudioFrame* frame);
};

算法核心:基于最近解码帧的频率模式生成伪数据。

视频 JitterBuffer

模块路径:webrtc/modules/video_coding/

功能

  • 基于帧(Frame)级缓存;
  • 管理多个 RTP 包拼装一个视频帧;
  • 处理 I/P/B 帧依赖关系;
  • 异步解码与播放,配合 AVSync。

核心类:

  • VCMJitterBuffer:包级缓存;
  • FrameBuffer:帧组装器;
  • FrameBufferController:根据解码状态/网络反馈动态调节 buffer;

原理

1. DeliverRtp(RTP packet)↓
2. Insert into FrameBuffer (reorders and assembles)↓
3. Mark frame as complete↓
4. Notify decoder thread (via AsyncInvoker)↓
5. Decoder calls NextFrame()↓
6. FrameBuffer returns suitable frame based on timing

源码解析

FrameBuffer 接口类
class FrameBuffer {public:void InsertFrame(std::unique_ptr<EncodedFrame> frame);std::unique_ptr<EncodedFrame> NextFrame();
};

特点:

  • 接收完整帧(非 RTP 包级);
  • 和 NACK 控制、帧到达策略分离;
  • 提供解码时间控制(配合 Timing 类);
RtpVideoStreamReceiver

接收 RTP 包并重组帧,组装完成后推入 FrameBuffer

bool RtpVideoStreamReceiver::OnRtpPacket(const RtpPacketReceived& packet)
  • 组装 VCMPacket(含 marker bit, seq, timestamp);
  • 查找是否构成完整帧(依赖 FrameBuffer::InsertFrame());
  • 完整帧则通知解码线程处理。
VideoReceiveStream::StartDecodeLoop()

负责调用解码逻辑:

std::unique_ptr<EncodedFrame> frame = frame_buffer_->NextFrame();
decoder_->Decode(frame);

解码线程会持续等待并从 FrameBuffer 中提取适合解码的帧。

时间同步逻辑(配合 Timing 类)

视频帧不是立刻解码,而是要等待“最佳播放时间”:

Timing::RenderTimeMs(uint32_t frame_timestamp, int64_t now_ms)

内部通过系统时间、RTP timestamp 差计算出:

  • 当前帧是否提前(buffering)
  • 当前帧是否延迟(丢帧)
  • 帧间 jitter 均值估计(变更播放时钟)
丢包处理(NACK / Frame Missing)
  1. FrameBuffer::InsertFrame() 内部跟踪丢帧(依据 sequence number);
  2. 控制模块向上层触发 NACK;
  3. 使用 rtp_rtcp::RTCPeerFeedback 上报丢帧;
  4. 等待 retransmit 后再组帧。

动态自适应机制

WebRTC 会根据网络反馈(RTCP)动态调整 jitterbuffer:

网络状态Buffer 调整策略
抖动变大增大 buffer 延迟,提升稳定性
网络稳定减小 buffer,降低延迟
丢包严重增加 buffer + 请求重传(NACK)
无法重传使用 FEC 或插入静音/伪帧

与 AV 同步的协作

WebRTC 中,音频是时钟主导(anchor),视频 jitterbuffer 会与音频同步,控制渲染时间戳,使音画同步。

总结

WebRTC 的 JitterBuffer 构建了高度模块化、可插拔、跨平台的实时缓冲机制,实现了在复杂网络环境下高质量的音视频通信体验。

特性音频(NetEq)视频(FrameBuffer)
缓冲粒度RTP 包(10ms)视频帧
解码策略严格 10ms 推进根据时间和帧依赖
丢包处理PLC / CNGNACK / 丢弃
时间同步插值输出 / 静音填充Timing::RenderTimeMs 控制
解码控制内部自动控制外部线程主动拉帧解码
延迟适配加速 / 减速控制解码时机或丢帧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86703.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【推荐100个unity插件】在 Unity 中绘制 3D 常春藤,模拟生长——hedera插件的使用

注意&#xff1a;考虑到后续接触的插件会越来越多&#xff0c;我将插件相关的内容单独分开&#xff0c;并全部整合放在【推荐100个unity插件】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 效果演示 文章目录 效果演示前言一、常春藤生成器工具下载二、工具使用1、…

【三维重建】【3DGS系列】【深度学习】3DGS的理论基础知识之高斯椭球的几何变换

【三维重建】【3DGS系列】【深度学习】3DGS的理论基础知识之高斯椭球的几何变换 文章目录 【三维重建】【3DGS系列】【深度学习】3DGS的理论基础知识之高斯椭球的几何变换前言模型变换(Model Transformation)观测变换(Viewing Transformation)视图变换(View Transformation)投影…

EXISTS 和 NOT EXISTS 、IN (和 NOT IN)

在 SQL 中&#xff0c;EXISTS、NOT EXISTS 和 IN 都是用于子查询的条件运算符&#xff0c;用于根据子查询的结果过滤主查询的行。它们之间的区别主要体现在工作方式、效率、对 NULL 值的处理以及适用场景上。 1. EXISTS 和 NOT EXISTS 作用&#xff1a; EXISTS: 检查子查询是…

GitHub 趋势日报 (2025年06月25日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 880 awesome 788 build-your-own-x 691 free-for-dev 427 best-of-ml-python 404 …

互联网大厂Java求职面试:Java虚拟线程实战

互联网大厂Java求职面试&#xff1a;Java虚拟线程实战 文章内容 开篇&#xff1a;技术总监与程序员郑薪苦的三轮对话 在一场紧张而严肃的Java工程师面试中&#xff0c;技术总监张工正对候选人郑薪苦进行深入提问。郑薪苦虽然性格幽默&#xff0c;但对技术有着扎实的理解。今天…

网络安全的两大威胁:XSS与CSRF攻击实例解析

在网络攻击中,XSS跨站脚本攻击(Cross Site Scripting)与CSRF跨站请求伪造攻击(Cross-Site Request Forgery)是两种常见的攻击方式,它们之间存在显著的区别。以下是对这两种攻击方式的详细比较: 一、攻击原理 XSS跨站脚本攻击 攻击者通过在Web页面中注入恶意脚本来实现攻…

如何一次性将 iPhone 中的联系人转移到 PC

许多重要的联系人都存储在您的 iPhone 上。为了保护关键信息&#xff0c;您可能需要将联系人从 iPhone 转移到 PC&#xff0c;这是一种有效的联系人备份方法。如果您在将 iPhone 联系人转移到电脑上遇到困难&#xff0c;现在可以从本文中学习 5 个有效的解决方案&#xff0c;然…

Spring Boot开启定时任务的三种方式 【@EnableScheduling注解,SchedulingConfigurer接口,Quartz 框架】

Spring Boot 开启定时任务的三种方式​ ​ ​ 在 Spring Boot 应用开发过程中&#xff0c;定时任务是十分常见的需求&#xff0c;比如定时清理日志文件、定期备份数据库数据、定时发送邮件提醒等。Spring Boot 提供了多种开启定时任务的方式&#xff0c;本文将详细介绍三种常见…

LLM 编码器 怎么实现语义相关的 Token 向量更贴近? mask训练:上下文存在 ;; 自回归训练:只有上文,生成模型

LLM 编码器 怎么实现语义相关的 Token 向量更贴近? 目录 LLM 编码器 怎么实现语义相关的 Token 向量更贴近?mask训练:上下文存在自回归训练:只有上文,生成模型一、核心机制:损失函数与反向传播的“语义校准”1. 损失函数的“语义约束”2. 嵌入层参数的“动态调整”二、关…

从OCR瓶颈到结构化理解来有效提升RAG的效果

当人们探讨如何让人工智能系统更好地从文档中查找和使用信息时&#xff0c;通常关注的是令人瞩目的算法和前沿的大型语言模型。但问题是&#xff1a;如果文本提取的质量很差&#xff0c;那么后续的努力都将付诸东流。本文探讨OCR质量如何影响检索增强生成&#xff08;RAG&#…

SpringBoot -- 整合Junit

11.SpringBoot 整合 Junit 11.1 为什么需要单元测试 由于在SpringBoot开发过程中&#xff0c;每开发一个模块&#xff0c;有时需要从 controller、service、mapper 到甚至 xml 文件的编写全部开发完毕才能进行测试&#xff0c;这是十分浪费时间的&#xff0c;比如开发人员想测…

虚拟机远程连接编译部署QT程序

概要 逻辑 我们需要凑齐 QT库、交叉编译工具、sysroot这三大件。 交叉编译的程序是部署到板卡环境运行,需要构建和板卡一样的库环境。 sysroot是我们在虚拟机上自己命名的一个文件夹,包含开发板的运行系统所需的所有文件。 虚拟机是x64版本,开发板是arm64版本。 如果开发板…

基于SpringBoot的智慧旅游系统

以智慧旅游系统的设计与实现为研究对象&#xff0c;旨在通过科技手段提升旅游业的管理效能和游客体验。在系统设计方面&#xff0c;深入分析了地理特征、丰富的文化底蕴以及多样的自然景观。结合这些独特之处&#xff0c;构建了一个多层次的旅游管理系统&#xff0c;包括景点信…

下载最新版本的OpenOCD

Download OpenOCD for Windowsd&#xff1a; https://gnutoolchains.com/arm-eabi/openocd/

Geollama 辅助笔记:raw_to_prompt_strings_geo.py

1 GeoLifePreprocessingDF 1.1 创造函数 1.2 读取原始数据 读取这个DataFrame 1.3 处理原始DataFrame 1.4 生成对应prompt 1.5 打乱轨迹 1.6 打乱轨迹里面的事件

TDengine 如何打破工业实时数据库势力边界?

打破工业实时数据库势力边界&#xff0c;TDengine 时序数据库与工业 SCADA 深度融合 随着 时序数据库&#xff08;Time Series Database&#xff09;的日益普及&#xff0c;越来越多的工业自动化控制&#xff08;工控&#xff09;人员开始认识到其强大能力。然而&#xff0c;时…

渗透靶场:事件和属性被阻止的反射xss

本关很多标签被拦截了&#xff0c;需要使用 burp 模糊测试哪个标签可以用 <a>和<animate>可以使用&#xff0c;<animate>是<svg>标签中用来给动画设定属性的&#xff0c;看看<svg>可不可用 利用<svg>、<animate>、<a>来构造 这…

STM32中Usart的使用

目录 一、USART简介 1.电平标准 2.通信接口 3.硬件电路 4.串口参数以及时序 5.串口时序 二、USART结构介绍 1.USART功能框图 ​编辑 1.1 功能引脚 1.2 数据寄存器 1.3 控制器 1.4 波特率发生器 1.5简化结构图 2.数据帧 一、USART简介 USART&#xff08;Universa…

鸿蒙HarmonyOS 5小游戏实践:数字记忆挑战(附:源代码)

数字记忆挑战游戏&#xff1a;打造提升大脑记忆力的鸿蒙应用 在当今数字时代&#xff0c;人们的记忆力面临着前所未有的挑战。从日常的待办事项到复杂的工作任务&#xff0c;强大的记忆力都是提高效率和表现的关键。本文将介绍一款基于鸿蒙系统开发的数字记忆挑战游戏&#xf…

记录一个C#/.NET的HTTP工具类

记录一个C#/.NET的HTTP工具类 using Serilog; using System.Net; using System.Text; using System.Text.Json;namespace UProbe.Common.Comm.Http {public class HttpClientHelper{/// <summary>/// 发送HttpGet请求/// </summary>/// <typeparam name"T…