WebRTC 视频轨道(Video Outbound)从采集到编码再到发送的完整流程解析

WebRTC 视频轨道(Video Outbound)从采集到编码再到发送的完整流程解析

本文基于 WebRTC M85 (branch-heads/4183) 版本进行源码分析,结合工程实践深入解读视频推流管线的各个环节。


0. 引言

在实时音视频通信领域,WebRTC 已成为事实上的行业标准。然而,许多开发者在使用 getUserMediaRTCPeerConnection 等高层 API 时,往往对视频数据从摄像头采集到网络发送的完整流程知之甚少。

本文将带你深入理解:

  • 视频帧如何从摄像头硬件进入 WebRTC 管线
  • 原始帧数据如何被编码为压缩的视频码流
  • 编码后的数据如何被封装为 RTP 包并通过网络发送
  • WebRTC 如何根据网络反馈动态调整码率
  • 源码级别的调用链分析与核心组件解读

无论你是 WebRTC 新手还是有经验的开发者,这篇文章都将帮助你建立对视频出站管线的系统性认知。


1. WebRTC 视频出站管线总体架构

WebRTC 的视频推流可以抽象为四个核心阶段,它们形成一条完整的数据管线:

复制代码
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   采集层    │ -> │   处理层    │ -> │   编码层    │ -> │   发送层    │
│  (Capture)  │    │ (Process)   │    │  (Encode)   │    │   (Send)    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
       │                  │                  │                  │
   Camera API        VideoProcessor     VideoEncoder      RTP/SRTP
   VideoFrame        滤镜/缩放/裁剪      VP8/H264/AV1      Pacing

1.1 管线的并行特性

一个关键的设计要点是:渲染和编码是并行处理的 。当摄像头采集到一帧视频数据后,该帧会被传递给 VideoBroadcaster,它采用广播模式将帧数据同时分发给多个订阅者:

  • 渲染订阅者:用于本地预览,将帧渲染到屏幕
  • 编码订阅者:用于远端传输,将帧送入编码器

这种设计确保了预览延迟最小化,同时不会阻塞编码流程。

1.2 核心组件一览

组件 职责 所在层级
VideoCapturer 封装平台相机 API,产生 VideoFrame 采集层
VideoSource 视频源管理,帧适配 采集层
VideoBroadcaster 帧数据的广播分发 分发层
VideoStreamEncoder 编码管理与帧率控制 编码层
VideoEncoder (VP8/H264/VP9/AV1) 实际的编码实现 编码层
RTPSenderVideo RTP 包封装 发送层
PacedSender 发送节奏控制 发送层
DtlsSrtpTransport SRTP 加密与传输 发送层

2. 视频采集层(Capture Layer)

2.1 getUserMedia 与平台 API

在浏览器环境中,视频采集通过 getUserMedia API 启动:

javascript 复制代码
const constraints = {
  video: {
    width: { ideal: 1280 },
    height: { ideal: 720 },
    frameRate: { ideal: 30 }
  }
};

const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoTrack = stream.getVideoTracks()[0];

getUserMedia 会请求用户授权并打开摄像头,返回一个包含 MediaStreamTrackMediaStream 对象。

在 Native 层,WebRTC 提供了 VideoCapturer 接口来封装不同平台的相机 API:

  • Android:Camera / Camera2 API
  • iOS:AVFoundation
  • Windows/macOS:DirectShow / AVFoundation

2.2 Android 采集实现详解

以 Android 为例,VideoCapturer 的初始化需要三个关键参数:

java 复制代码
public interface VideoCapturer {
    void initialize(
        SurfaceTextureHelper surfaceTextureHelper,
        Context applicationContext,
        CapturerObserver capturerObserver
    );
    // ...
}

核心组件解析:

  1. SurfaceTextureHelper :封装了 SurfaceTexture,作为相机数据的消费者。在 Android 图形系统中,SurfaceTextureBufferQueue 配合工作,接收来自相机的图像缓冲区。

  2. Surface:作为相机数据的生产者,相机硬件将捕获的图像输出到 Surface。

  3. CapturerObserver:帧回调接口,当新帧可用时被调用。

java 复制代码
// Camera2 相机打开后的回调
private class CameraStateCallback extends CameraDevice.StateCallback {
    @Override
    public void onOpened(CameraDevice camera) {
        // 设置纹理尺寸
        surfaceTextureHelper.setTextureSize(
            captureFormat.width, 
            captureFormat.height
        );
        // 创建 Surface 并关联到 SurfaceTexture
        surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
        // 创建捕获会话
        camera.createCaptureSession(
            Arrays.asList(surface),
            new CaptureSessionCallback(),
            cameraThreadHandler
        );
    }
}

2.3 从相机数据到 VideoFrame

数据流转路径如下:

复制代码
Camera Hardware 
    ↓ (输出图像)
Surface (生产者)
    ↓ (BufferQueue)
SurfaceTexture (消费者)
    ↓ (OnFrameAvailableListener)
CapturerObserver.onFrameCaptured(VideoFrame)
    ↓
NativeAndroidVideoTrackSource
    ↓
WebRTC Native Layer

SurfaceTextureBufferQueue 中有可用帧时,会触发 OnFrameAvailableListener 回调,最终调用 CapturerObserver.onFrameCaptured(frame)

java 复制代码
public class VideoSource extends MediaSource {
    private final CapturerObserver capturerObserver = new CapturerObserver() {
        @Override
        public void onFrameCaptured(VideoFrame frame) {
            // 应用帧适配参数(分辨率调整等)
            VideoFrame adaptedFrame = VideoProcessor.applyFrameAdaptationParameters(
                frame, parameters
            );
            if (adaptedFrame != null) {
                // 传递给 Native 层
                nativeAndroidVideoTrackSource.onFrameCaptured(adaptedFrame);
                adaptedFrame.release();
            }
        }
    };
}

2.4 分辨率与帧率参数

WebRTC 支持丰富的约束参数来控制采集行为:

参数 说明 典型值
width 视频宽度 640, 1280, 1920
height 视频高度 480, 720, 1080
frameRate 帧率 15, 24, 30, 60
facingMode 前/后置摄像头 user, environment
deviceId 指定设备 设备 ID 字符串

在 Native 层,这些约束会被转换为 CaptureFormat

java 复制代码
public static class CaptureFormat {
    public final int width;
    public final int height;
    public final FramerateRange framerate;
    // ...
}

3. 视频处理与编码层(Encoding Pipeline)

3.1 VideoBroadcaster:帧的广播分发

采集到的帧数据在 Native 层会经历以下调用链:

复制代码
webrtc::jni::AndroidVideoTrackSource::onFrameCaptured
    ↓
rtc::AdaptedVideoTrackSource::OnFrame
    ↓
rtc::VideoBroadcaster::OnFrame

VideoBroadcaster 是一个发布-订阅模式的实现。所有实现了 VideoSink 接口的对象都可以订阅帧数据:

cpp 复制代码
// VideoSink 接口(C++ 版本)
class VideoSinkInterface {
public:
    virtual void OnFrame(const VideoFrame& frame) = 0;
};

在 Java 层对应的接口:

java 复制代码
public interface VideoSink {
    @CalledByNative
    void onFrame(VideoFrame frame);
}

3.2 渲染与编码的并行处理

当调用 VideoTrack.addSink(sink) 添加预览画面时:

java 复制代码
videoTrack.addSink(surfaceViewRenderer);

实际上是向 VideoBroadcaster 注册了一个订阅者。这意味着:

  • SurfaceViewRenderer (预览)和 VideoStreamEncoder (编码)会同时收到帧数据
  • 预览渲染不会阻塞编码过程
  • 编码的耗时也不会影响预览延迟

3.3 编码器入口:VideoStreamEncoder

VideoStreamEncoder 是编码管线的入口,它也是 VideoSink 的实现者:

复制代码
VideoStreamEncoder::OnFrame
    ↓
VideoStreamEncoder::MaybeEncodeVideoFrame
    ↓
VideoStreamEncoder::EncodeVideoFrame
    ↓
VideoEncoder::Encode (VP8/VP9/H264/AV1)

VideoStreamEncoder 的核心职责包括:

  1. 帧率控制:根据目标帧率决定是否编码当前帧
  2. 分辨率适配:根据带宽估计动态调整编码分辨率
  3. 编码器管理:初始化和重配置底层编码器
  4. 关键帧请求:响应来自接收端的 PLI/FIR 请求

3.4 视频编码格式

WebRTC 支持多种视频编码格式,每种都有其特点:

编码格式 特点 浏览器支持
VP8 WebRTC 默认编码器,开源免费 全部
VP9 比 VP8 压缩效率高 30-40% 主流浏览器
H.264 硬件支持广泛,兼容性好 全部
AV1 最新一代编码器,压缩效率最高 Chrome/Firefox

在源码中,编码器通过工厂模式创建:

cpp 复制代码
// 编码器实现示例
class LibvpxVp8Encoder : public VideoEncoder { ... };
class VP9EncoderImpl : public VideoEncoder { ... };
class H264EncoderImpl : public VideoEncoder { ... };

从原网页的调用栈可以看到,默认使用的是 LibvpxVp8Encoder

复制代码
webrtc::LibvpxVp8Encoder::Encode
    ↓
webrtc::LibvpxVp8Encoder::GetEncodedPartitions

3.5 帧类型:I-frame 与 P-frame

视频编码中有两种主要的帧类型:

I-frame(关键帧/Intra-frame)

  • 独立解码,不依赖其他帧
  • 数据量大(通常是 P-frame 的 5-10 倍)
  • 作为解码的起点

P-frame(预测帧/Predictive-frame)

  • 基于前一帧进行差分编码

  • 数据量小,压缩效率高

  • 依赖前序帧才能解码

    I-frame → P-frame → P-frame → ... → I-frame → P-frame → ...
    ↑ ↑
    GOP 起点 GOP 起点

GOP(Group of Pictures) 定义了两个 I-frame 之间的间隔。WebRTC 默认的 GOP 通常较大(如 3000 帧),但会根据以下情况插入额外的 I-frame:

  • 接收端发送 PLI(Picture Loss Indication)
  • 接收端发送 FIR(Full Intra Request)
  • 明显的场景切换

3.6 码率控制

WebRTC 使用拥塞控制算法(如 GCC)估计可用带宽,并通过码率控制策略分配给编码器:

CBR(恒定码率)

  • 码率稳定,适合带宽受限场景
  • 画质可能波动

VBR(可变码率)

  • 根据画面复杂度调整码率
  • 画质稳定,带宽使用波动

WebRTC 通常使用 目标码率 + 最大码率 的混合策略,由 BitrateAllocator 负责分配。

3.7 编码线程模型

WebRTC 的编码采用异步线程模型:

复制代码
Capture Thread           Encoder Thread           Pacer Thread
     │                        │                        │
  OnFrame                     │                        │
     │────→ PostTask ────→ Encode                      │
     │                        │                        │
     │                   OnEncodedImage                │
     │                        │────→ EnqueuePackets ───│
     │                        │                   ProcessPackets
     │                        │                        │
  • 采集线程:接收摄像头回调
  • 编码线程:执行实际编码,CPU 密集型
  • Pacer 线程:控制发送节奏

4. RTP 打包与发送流程

4.1 编码完成到 RTP 封装

当编码器完成一帧的编码后,编码后的数据(EncodedImage)会沿着以下路径流转:

复制代码
VideoStreamEncoder::OnEncodedImage
    ↓
VideoSendStreamImpl::OnEncodedImage
    ↓
RtpVideoSender::OnEncodedImage
    ↓
RTPSenderVideo::SendEncodedImage
    ↓
RTPSenderVideo::SendVideo

4.2 RTP 包结构

一个 RTP 包的基本结构如下:

复制代码
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       Sequence Number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             SSRC                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Payload...                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

关键字段解释:

字段 大小 说明
V (Version) 2 bits RTP 版本,固定为 2
P (Padding) 1 bit 是否有填充
X (Extension) 1 bit 是否有扩展头
CC 4 bits CSRC 计数
M (Marker) 1 bit 标记帧结束
PT (Payload Type) 7 bits 负载类型(VP8=96, H264=97 等)
Sequence Number 16 bits 包序列号,用于检测丢包和排序
Timestamp 32 bits 采样时间戳(90kHz 时钟)
SSRC 32 bits 同步源标识符

4.3 视频帧切片

由于 MTU 限制(通常 1200-1400 字节),一个视频帧通常需要被分片为多个 RTP 包:

复制代码
VideoFrame (10KB)
    ↓
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│RTP Pkt 1│RTP Pkt 2│RTP Pkt 3│RTP Pkt 4│RTP Pkt 5│RTP Pkt 6│RTP Pkt 7│
│ ~1200B  │ ~1200B  │ ~1200B  │ ~1200B  │ ~1200B  │ ~1200B  │ ~1200B  │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
     │                                                           │
     │                                                           │
  M=0 (非结尾)                                                M=1 (帧结尾)

最后一个分片的 M (Marker) 位 会被设置为 1,表示帧结束。

4.4 Pacing:发送节奏控制

为了避免突发流量造成网络拥塞,WebRTC 使用 PacedSender 平滑发送 RTP 包:

复制代码
RTPSenderVideo::LogAndSendToNetwork
    ↓
RTPSender::EnqueuePackets
    ↓
PacedSender::EnqueuePackets
    ↓
PacingController::EnqueuePacket
    ↓
PacingController::EnqueuePacketInternal
    ↓ (等待 Process 调度)
PacedSender::Process  ← ProcessThreadImpl 周期调用
    ↓
PacingController::ProcessPackets
    ↓
PacedSender::SendRtpPacket

Pacing 的工作原理:

  1. RTP 包首先进入发送队列
  2. ProcessThreadImpl 周期性(约 5ms)调用 PacedSender::Process
  3. PacingController 根据目标码率计算本周期可发送的字节数
  4. 按优先级从队列中取出包并发送

这确保了即使编码器瞬间产生大量数据(如 I-frame),发送也是平滑的。

4.5 SRTP 加密

RTP 包在发送前需要经过 SRTP 加密:

复制代码
RtpSenderEgress::SendPacket
    ↓
RtpSenderEgress::SendPacketToNetwork
    ↓
cricket::WebRtcVideoChannel::SendRtp
    ↓
cricket::MediaChannel::DoSendPacket
    ↓
cricket::VideoChannel::SendPacket
    ↓
webrtc::DtlsSrtpTransport::SendRtpPacket  ← SRTP 加密在这里执行

SRTP(Secure RTP) 使用 AES-128-CM 加密负载,使用 HMAC-SHA1 进行认证。密钥通过 DTLS 握手协商。


5. 网络反馈与码率自适应机制

5.1 RTCP 反馈机制

WebRTC 使用 RTCP 协议实现双向反馈:

NACK(Negative Acknowledgement)

  • 接收端检测到丢包时发送
  • 请求发送端重传特定的 RTP 包
  • 通过序列号指定需要重传的包

PLI(Picture Loss Indication)

  • 接收端无法解码当前图像时发送
  • 请求发送端发送新的关键帧
  • 比 NACK 更激进的恢复策略

FIR(Full Intra Request)

  • 请求完整的关键帧

  • 通常用于新参与者加入或严重丢包场景

    [Sender] [Receiver]
    │ │
    │ ──────── RTP Video Packets ─────────────→ │
    │ │
    │ ←───────── RTCP NACK ─────────────────── │ (检测到丢包)
    │ │
    │ ──────── Retransmit RTP ────────────────→ │
    │ │
    │ ←───────── RTCP PLI ──────────────────── │ (解码失败)
    │ │
    │ ──────── I-frame ───────────────────────→ │
    │ │

5.2 带宽估计(BWE)

WebRTC 使用 GCC(Google Congestion Control) 算法进行带宽估计,主要基于两个信号:

1. 延迟梯度(Delay Gradient)

通过分析接收端反馈的时间戳,计算包间延迟的变化趋势:

  • 延迟增加 → 网络拥塞 → 降低码率
  • 延迟稳定/减少 → 网络良好 → 可尝试提升码率

2. 丢包率(Packet Loss Rate)

通过 RTCP RR(Receiver Report)获取丢包统计:

  • 丢包率 > 10% → 显著降低码率
  • 丢包率 2%-10% → 轻微降低码率
  • 丢包率 < 2% → 可以探测更高码率

5.3 码率自适应

带宽估计结果会反馈到编码器,形成闭环控制:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────┐  │
│  │ Encoder  │ ←─ │ Bitrate  │ ←─ │   BWE    │ ←─ │ RTCP │  │
│  │          │    │Allocator │    │(GCC/BBR) │    │  RR  │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────┘  │
│       │                                              ↑      │
│       ↓                                              │      │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────┐  │
│  │   RTP    │ ─→ │  Pacer   │ ─→ │  SRTP    │ ─→ │Network│  │
│  │ Packets  │    │          │    │          │    │      │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

自适应策略包括:

  1. 码率调整:直接改变目标编码码率
  2. 分辨率降档:在严重拥塞时降低编码分辨率
  3. 帧率调整:降低帧率以减少数据量

5.4 RTT、抖动与丢包的表现

网络问题 表现 WebRTC 应对策略
高 RTT 交互延迟大 优化缓冲区大小
抖动(Jitter) 画面卡顿 JitterBuffer 平滑
丢包 花屏、卡顿 NACK 重传 / PLI 请求 I-frame
带宽不足 画质下降 自适应降低码率/分辨率

6. 结合网页内容的流程图解析

6.1 完整调用栈分析

原网页提供了从编码到发送的完整调用栈,让我们逐层解析:

第一阶段:编码

复制代码
VideoStreamEncoder::OnFrame
    ↓
VideoStreamEncoder::MaybeEncodeVideoFrame
    ↓
VideoStreamEncoder::EncodeVideoFrame
    ↓
LibvpxVp8Encoder::Encode  ← 实际编码发生在这里
    ↓
LibvpxVp8Encoder::GetEncodedPartitions

这个阶段将原始 VideoFrame 编码为压缩的视频数据。MaybeEncodeVideoFrame 会判断是否需要编码当前帧(帧率控制)。

第二阶段:RTP 封装

复制代码
VideoStreamEncoder::OnEncodedImage
    ↓
VideoSendStreamImpl::OnEncodedImage
    ↓
RtpVideoSender::OnEncodedImage
    ↓
RTPSenderVideo::SendEncodedImage
    ↓
RTPSenderVideo::SendVideo
    ↓
RTPSenderVideo::LogAndSendToNetwork

编码后的 EncodedImage 被封装为 RTP 包。RTPSenderVideo 负责添加 RTP 头、处理分片。

第三阶段:Pacing 队列

复制代码
RTPSender::EnqueuePackets
    ↓
PacedSender::EnqueuePackets
    ↓
PacingController::EnqueuePacket
    ↓
PacingController::EnqueuePacketInternal

RTP 包进入发送队列,等待 Pacer 调度。

第四阶段:发送

复制代码
PacedSender::Process  ← 由 ProcessThreadImpl 周期调用
    ↓
PacingController::ProcessPackets
    ↓
PacedSender::SendRtpPacket
    ↓
ModuleRtpRtcpImpl2::TrySendPacket
    ↓
RtpSenderEgress::SendPacket
    ↓
RtpSenderEgress::SendPacketToNetwork

Pacer 按照节奏从队列取出包并发送。

第五阶段:传输层

复制代码
cricket::WebRtcVideoChannel::SendRtp
    ↓
cricket::MediaChannel::DoSendPacket
    ↓
cricket::VideoChannel::SendPacket
    ↓
webrtc::DtlsSrtpTransport::SendRtpPacket  ← SRTP 加密

最终通过 DTLS-SRTP 安全传输发送到网络。

6.2 关键设计要点

  1. 编码器可插拔LibvpxVp8Encoder 可替换为任何 VideoEncoder 子类(VP9、H264、AV1)

  2. 异步处理ProcessThreadImpl::Process 表明 RTP 包的发送是异步的,与编码解耦

  3. 分层清晰

    • webrtc:: 命名空间:WebRTC 核心
    • cricket:: 命名空间:PeerConnection 层
    • 层间通过接口通信,便于扩展

7. 相关示例代码

7.1 基础:获取摄像头并建立连接

javascript 复制代码
// 1. 获取本地视频流
async function startCapture() {
  const constraints = {
    video: {
      width: { ideal: 1280 },
      height: { ideal: 720 },
      frameRate: { ideal: 30 }
    },
    audio: true
  };
  
  const localStream = await navigator.mediaDevices.getUserMedia(constraints);
  
  // 显示本地预览
  document.getElementById('localVideo').srcObject = localStream;
  
  return localStream;
}

// 2. 创建 PeerConnection 并添加轨道
async function createConnection(localStream) {
  const configuration = {
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' }
    ]
  };
  
  const peerConnection = new RTCPeerConnection(configuration);
  
  // 添加本地轨道
  localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream);
  });
  
  return peerConnection;
}

7.2 配置编码参数:setParameters

javascript 复制代码
// 获取视频发送器并配置编码参数
async function configureVideoEncoding(peerConnection) {
  const videoSender = peerConnection.getSenders()
    .find(sender => sender.track?.kind === 'video');
  
  if (!videoSender) return;
  
  const parameters = videoSender.getParameters();
  
  // 配置编码层
  if (parameters.encodings && parameters.encodings.length > 0) {
    parameters.encodings[0] = {
      ...parameters.encodings[0],
      maxBitrate: 2500000,        // 最大码率 2.5 Mbps
      maxFramerate: 30,           // 最大帧率 30fps
      scaleResolutionDownBy: 1.0  // 不缩放分辨率
    };
  }
  
  await videoSender.setParameters(parameters);
}

7.3 监控发送统计

javascript 复制代码
// 获取视频发送统计信息
async function getVideoOutboundStats(peerConnection) {
  const stats = await peerConnection.getStats();
  
  stats.forEach(report => {
    if (report.type === 'outbound-rtp' && report.kind === 'video') {
      console.log('=== Video Outbound Stats ===');
      console.log(`Codec: ${report.mimeType}`);
      console.log(`Bitrate: ${report.bytesSent} bytes sent`);
      console.log(`Frames Encoded: ${report.framesEncoded}`);
      console.log(`Frames Sent: ${report.framesSent}`);
      console.log(`Packets Sent: ${report.packetsSent}`);
      console.log(`Quality Limitation: ${report.qualityLimitationReason}`);
      
      // 计算实时码率
      // (需要保存上次统计进行差值计算)
    }
  });
}

// 定期监控
setInterval(() => getVideoOutboundStats(peerConnection), 1000);

7.4 请求关键帧

javascript 复制代码
// 当需要强制刷新时,请求编码器生成关键帧
async function requestKeyFrame(peerConnection) {
  const videoSender = peerConnection.getSenders()
    .find(sender => sender.track?.kind === 'video');
  
  if (videoSender) {
    const parameters = videoSender.getParameters();
    // 设置这个标志会请求下一帧为关键帧
    parameters.encodings[0].keyFrameInterval = 1;
    await videoSender.setParameters(parameters);
    
    // 恢复正常间隔
    setTimeout(async () => {
      parameters.encodings[0].keyFrameInterval = undefined;
      await videoSender.setParameters(parameters);
    }, 100);
  }
}

7.5 Simulcast 配置

javascript 复制代码
// 配置多层编码(Simulcast)
async function setupSimulcast(peerConnection, localStream) {
  const videoTrack = localStream.getVideoTracks()[0];
  
  const transceiver = peerConnection.addTransceiver(videoTrack, {
    direction: 'sendonly',
    sendEncodings: [
      { rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 4 },
      { rid: 'mid', maxBitrate: 500000, scaleResolutionDownBy: 2 },
      { rid: 'high', maxBitrate: 1500000, scaleResolutionDownBy: 1 }
    ]
  });
  
  return transceiver;
}

8. 总结

关键要点回顾

  1. 管线架构 :WebRTC 视频出站管线分为采集、处理、编码、发送四个阶段,渲染和编码并行处理。

  2. VideoBroadcaster:核心的帧分发组件,采用发布-订阅模式,将帧数据广播给所有 VideoSink 订阅者。

  3. 编码器抽象 :通过 VideoEncoder 接口实现编码器的可插拔设计,支持 VP8、VP9、H264、AV1 等多种格式。

  4. RTP 封装:视频帧被切片为多个 RTP 包,每个包包含序列号、时间戳等关键元数据,最后一个分片设置 Marker 位。

  5. Pacing 机制 :通过 PacedSender 平滑发送流量,避免突发数据造成网络拥塞。

  6. 安全传输:所有 RTP 数据通过 DTLS-SRTP 加密后发送。

  7. 自适应码率:基于 RTCP 反馈(NACK、PLI、RR)和带宽估计,动态调整编码码率和分辨率。

设计思路

WebRTC 的视频出站管线体现了以下设计原则:

  • 关注点分离:采集、编码、传输各层职责明确
  • 可扩展性:通过接口和工厂模式支持多种编码器
  • 实时性优先:并行处理、异步发送、Pacing 平滑
  • 网络适应性:完整的反馈机制和自适应策略

理解这些底层机制,将帮助你更好地调试 WebRTC 应用、优化视频质量,以及在需要时进行源码级别的定制开发。


参考资料:

  • WebRTC 学习指南 - 视频推流过程
  • WebRTC M85 源码 (branch-heads/4183)
  • RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
  • RFC 4585 - Extended RTP Profile for RTCP-Based Feedback (RTP/AVPF)
相关推荐
summerkissyou198721 小时前
Audio-音频-播放的方式
android·音视频
吃好喝好玩好睡好21 小时前
OpenHarmony 设备中 Electron 桌面 + Flutter 移动端音视频流互通实战
flutter·electron·音视频
飞Link1 天前
【网络与 AI 工程的交叉】多模态模型的数据传输特点:视频、音频、文本混合通道
网络·人工智能·音视频
音视频牛哥1 天前
从“能播”到“能控”:深入解读 SmartMediakit 与 OTT 播放器的架构裂变
音视频·ott·低延迟rtsp播放器·smartmediakit·低延迟rtmp播放器·低延迟音视频技术方案·具身智能低延迟rtsp方案
Likeadust2 天前
视频直播点播平台EasyDSS如何重塑媒体行业的内容分发与交互体验
音视频·媒体
赫尔·普莱蒂科萨·帕塔2 天前
【翻译】从生成的人体视频到物理可行的机器人轨迹
机器人·音视频
EasyCVR2 天前
智慧油田视频融合平台EasyCVR重塑油田油井智能监管新体系
音视频
小尧嵌入式2 天前
音视频入门基础知识
开发语言·c++·qt·算法·音视频
别动哪条鱼2 天前
FFmpeg AVFormatContext 分配函数详解
数据结构·ffmpeg·音视频