< 返回版块

shiqifeng2000 发表于 2026-07-14 09:35

Tags:webcodec, http, rust, tokio, quic

Banner

背景:我需要构建一个支持屏幕共享的 Web 端视频会议系统。


为什么又要造一个轮子?

这篇文章里,我想聊聊一个基于 HTTPWebCodec 的实时会议架构。这里说的"Web",指的是完全跑在浏览器里的技术——不需要插件,不需要原生依赖。

一个实时会议系统,本质上就是音视频的实时传输。背后涉及的核心问题无非三个:

  • 网络 —— 怎么把数据从一端送到另一端
  • 编解码 —— 怎么把原始媒体数据压缩成可传输的格式
  • 质量控制 —— 怎么在网络波动时保证体验

一个典型的多人会议,拓扑可能是这样的:

          Alice
         /     \
        /       \
       /         \
     Bob ------- Carol

在 WebRTC 的世界里,这叫 P2P(点对点) 模式。理论很美好,但现实很骨感:

  • 每个 Peer 需要通过 STUN 服务器发现自己的公网地址
  • NAT 穿透失败时,还得靠 TURN 服务器中转流量
  • ICE 信令还需要额外服务器交换 SDP 信息
  • 大部分 WebRTC 实现不重用 UDP 端口,资源消耗很大

WebRTC vs SFU vs RTSP/RTMP

一个更可扩展的方案是 SFU(选择性转发单元) 模式:每个客户端把流推到中央服务器,服务器再复制转发给其他人:

Banner

这概念上和 RTSP/RTMP 有点像,但问题是——浏览器不支持 RTSP/RTMP,因为安全策略限制。

那怎么在 Web 上看 RTSP/RTMP 流呢?常见的做法是 转封装 成 HLS 或 MPEG-DASH,浏览器就能用 <video> 或 MSE 播放了。但这个思路代价很大:

  1. 多次内存拷贝 —— 从 RTP 解包成裸 H.264,再打包成 fMP4,每一层都增加 CPU 负担和延迟
  2. GOP 级缓冲 —— HLS/DASH 至少要攒够一个 GOP 才能开始播放,这就 1~2 秒延迟,对于无人机、远程驾驶这类场景是致命的
  3. 丢包敏感 —— 乱序或丢包会搞乱转封装过程,需要额外的完整性保障,这又是一个大坑

那现在 Web 端到底能干啥?

  • WebRTC:功能完整但太重(多服务器、高端口占用、内部逻辑黑盒)
  • HLS/DASH:稳定但延迟高,只适合"看"不适合"聊"
  • WebCodecs(下面要说的):新出的浏览器 API,把编解码能力暴露给开发者,给了我们精细控制的可能性

看起来现在并没有一个"完美"的 Web 会议协议——要么继承重量级框架,要么在延迟和控制力之间做取舍。


所以 WebCodecs 来了:开发者友好的编解码 API

WebCodecs 的出现改变了很多事情。它直接把底层的视频/音频编码器、解码器接口暴露给 JavaScript,不再像 WebRTC 那样把所有东西包在黑盒里。

官方示例可以看这里,支持的编解码器列表在这里

这意味着开发者可以:

  • 从摄像头或屏幕采集原始帧,自行编码
  • 逐帧解码接收到的流
  • 自定义传输逻辑 —— 重传、优先级调度、自适应码率等

再配合 WritableStream 这类 Web 原生流式 API,我们基本上有了搭建自定义媒体管线的所有积木。


还缺什么?—— 传输协议

现在编解码能力有了,但还缺一个为 WebCodecs 量身定制的传输协议:

  • 要处理流完整性、丢包恢复、乱序重组
  • 要对 HTTP 友好(过防火墙、负载均衡、代理)
  • 当 HTTP/3(QUIC)不可用时,要有 TCP 降级方案

HTTP/3 + QUIC 是理想选择 —— 多路复用、低延迟、原生流抽象。但目前不是所有基础设施都支持,所以得有降级方案。

我们的设计:HTTP + WebCodecs + 智能降级

我们的协议同时支持 HTTP/3(QUIC)HTTP/2/1.1 with keep-alive 作为降级。遇到 TCP 的队头阻塞问题时,可以:

  • 切换到低画质流
  • 或者为不同媒体流开启多条 TCP 连接

整体架构大致如下:

                             SERVER
══════════════════════════════════════════════════════════════════════

                 +--------------------------------+
                 |     Media Scheduler (MTL)      |
                 |--------------------------------|
                 | Frame Window                   |
                 | Frame Database                 |
                 | GOP Dependency Graph           |
                 | Path Scheduler                 |
                 | Quality Controller             |
                 +--------------------------------+
                   │        │        │        │
         HTTP #1   │ HTTP #2│ HTTP #3│ RTCP-like
───────────────────┼────────┼────────┼──────────────
                   │        │        │
                   ▼        ▼        ▼
          TCP1          TCP2          TCP3          TCP4
══════════════════════════════════════════════════════════════════════
                              NETWORK
══════════════════════════════════════════════════════════════════════

客户端同样维护多条 TCP 流,各自承载不同的 Feed。另外有一个控制通道(类似 RTCP)负责反馈和重传请求:

══════════════════════════════════════════════════════════════════════
                              NETWORK
══════════════════════════════════════════════════════════════════════
          TCP1          TCP2          TCP3          TCP4

HTTP Conn1      HTTP Conn2      HTTP Conn3      Control Conn
     │               │               │               ▲
     └──────┬────────┴───────────────┘               │
            ▼                                        │
     +----------------------------------------------+
     |          Frame Reassembler                    |
     +----------------------------------------------+
                     │
                     ▼
          +-------------------------+
          | Receiver Window         |
          |-------------------------|
          |100 ✓                    |
          |101 ✓                    |
          |102 ✗                    |
          |103 ✓                    |
          |104 ✓                    |
          +-------------------------+
                     │
           Missing Frame Detector
                     │
                     ▼
             RTCP-like Feedback
                     │
                     ▲
       Request Frame102 via other TCPs
                     │
                     ▲
     Drop blocking TCP, starting new ones

这套方案和 QUIC + WebRTC 有点像,但更轻量,且完全走 HTTP。在中等网络条件下表现不错,弱网环境可能会吃力——按需选用。


自定义帧格式:为 WebCodecs 量身打造

传统的 RTP 包是围绕 UDP MTU(~1500 字节)设计的,H.264 的分片打包方案也很复杂(参见 RFC 6184)。

对于 WebCodecs 管线,我们需要更简洁、更贴合浏览器 API 的格式。最关键的是 RFC 6381 定义的编解码器配置字符串,例如:

const encoderConfig = {
  codec: "avc1.4d4028",
  width: 800,
  height: 600,
  framerate: 30,
  avc: { format: "annexb" }
};

这个字符串告诉浏览器用哪个编解码器、哪个 profile 和 level。我们的自定义协议定义了四种帧类型:

类型 名称 说明
vcnf 视频配置 编解码器初始化数据(SPS/PPS 等)
acnf 音频配置 音频编解码器初始化数据
vfrm 视频帧 编码后的 H.264/H.265/VP9 数据
afrm 音频帧 编码后的 AAC/Opus 数据

每帧前面加一个简单的二进制头,多条 TCP 连接承载不同的 Feed。视频帧(vfrm)支持分片,大 IDR 帧可以拆成多个包发送。

详细的包结构如下:

vcnf — 视频配置

+------------------------------------------------+
| SEP(帧起始标记)                                |
+------------------------------------------------+
| BoxName = "vcnf"                                |
+------------------------------------------------+
| PayloadLen                                      |
+------------------------------------------------+
| Width(uint16)                                 |
+------------------------------------------------+
| Height(uint16)                                |
+------------------------------------------------+
| CodecLen(uint16)                              |
+------------------------------------------------+
| RFC6381 编解码器字符串                           |
+------------------------------------------------+
| ExtraDataLen(uint32)                          |
+------------------------------------------------+
| AVC/HEVC 额外数据(SPS/PPS 等)                 |
+------------------------------------------------+

acnf — 音频配置

+------------------------------------------------+
| SEP                                             |
+------------------------------------------------+
| BoxName = "acnf"                                |
+------------------------------------------------+
| PayloadLen                                      |
+------------------------------------------------+
| RFC6381 编解码器字符串                           |
+------------------------------------------------+
| 编解码器额外数据(如有)                         |
+------------------------------------------------+

afrm — 音频帧

+------------------------------------------------+
| SEP                                             |
+------------------------------------------------+
| BoxName = "afrm"                                |
+------------------------------------------------+
| PayloadLen                                      |
+------------------------------------------------+
| FEED(uint32)— Feed 标识                       |
+------------------------------------------------+
| Sequence(uint32)— 序列号                      |
+------------------------------------------------+
| Timestamp(uint64)— 媒体时间戳                 |
+------------------------------------------------+
| FrameID(uint64)— 帧唯一 ID                    |
+------------------------------------------------+
| Duration(uint32)— 采样时长                    |
+------------------------------------------------+
| Payload(编码后的音频数据)                      |
+------------------------------------------------+

vfrm — 视频帧(支持分片)

+------------------------------------------------+
| SEP                                             |
+------------------------------------------------+
| BoxName = "vfrm"                                |
+------------------------------------------------+
| PayloadLen                                      |
+------------------------------------------------+
| FEED(uint32)                                  |
+------------------------------------------------+
| Sequence(uint32)                              |
+------------------------------------------------+
| Timestamp(uint64)                             |
+------------------------------------------------+
| FrameID(uint64)                               |
+------------------------------------------------+
| FragmentID(uint16)— 分片 ID                   |
+------------------------------------------------+
| FragmentCount(uint16)— 总分片数               |
+------------------------------------------------+
| FrameType(uint8)                              |
|   0 = IDR, 1 = P, 2 = B                        |
+------------------------------------------------+
| DependencyID(uint64)— 参考帧 ID               |
+------------------------------------------------+
| Duration(uint32)                              |
+------------------------------------------------+
| PayloadLen(uint32)                            |
+------------------------------------------------+
| NAL 负载(编码后的视频数据)                     |
+------------------------------------------------+

这个格式轻量、易解析,和 WebCodecs 的 EncodedVideoChunk / EncodedAudioChunk API 配合得很好。


工程实现的一些想法

服务端打算用 Rust 配合 Tokio 来实现。Tokio 支持 SO_REUSEPORT,配合 epoll 能高效处理大量并发连接,比 WebRTC 那种每个 Peer 一个 UDP 端口的模式要节省资源。

客户端直接用浏览器原生的 VideoEncoderVideoDecoderAudioEncoderAudioDecoder,喂数据走我们自定义的协议包格式就行。


总结

我们设计了一套实时会议系统,核心思路是:

  • WebCodecs:在浏览器端实现灵活、低延迟的编解码
  • HTTP/3(QUIC)+ TCP 降级:兼顾新协议的优势和老基础设施的兼容性
  • 自定义帧格式:为浏览器 API 量身定制,不背 RTP 的历史包袱
  • 多 TCP 连接:缓解队头阻塞,提升并行度

这套方案在弱网环境下不是最优解,但在 HTTP/3 或可靠 TCP 可用的场景下表现良好。关键是它给了开发者对媒体管线的完全控制权,又没有 WebRTC 那样的复杂度和黑盒限制。

如果你对这个方向感兴趣,欢迎来 Webtalk 仓库提 issue、PR,或者单纯聊聊你的想法。一起把 Web 原生实时通信这件事做得更好。


写代码不易,实时尤难 —— 共勉。


Ext Link: https://shiqifeng2000.github.io/2026-07-10-webtalk/

评论区

写评论

还没有评论

1 共 0 条评论, 1 页