如果你一直在追这个系列,读到这一篇说明前面几篇的采集、编码、会话协商已经大致心里有数了。今天这篇我们正式进入 webrtc 核心引擎层里最贴近“网线”的部分——传输模块。这个模块负责的事可以用一句话概括:把编码器产出的 RTP 包,安全、有序、尽可能不丢地送到对端的解码器手里。它是整个 WebRTC 体系里最复杂、也最值得啃的一块,因为能不能做到低延迟、低卡顿、弱网可用,答案全藏在这一层的源码里。
我当前是基于 M114 左右分支做的走读,WebRTC 代码演进极快,部分类名和目录在版本间会有迁移,后面凡是涉及具体文件我都会同时给类名和路径,方便你在其他版本里快速找到对应实现。如果你还没下载源码,建议先通过官方 depot_tools 拉一个固定分支,不要在最新主干上边看边追,否则今天写的类明天可能就被重构了。这篇文章的主线只有一条:跟着一个 RTP 包走一遍发送和接收路径,把传输模块的家底翻个遍。
1. 传输模块在核心引擎层中的边界:从它开始,数据离开进程
很多第一次读 WebRTC 源码的人都会有个困惑:传输模块到底从哪里算起,到哪里结束?这个边界如果不先划清楚,后面翻代码会非常痛苦。我的理解是:传输模块的下游是操作系统 socket,上游是 RTP 层的数据进出接口。换句话说,凡是"把已经封装好的 RTP 包交给网络"和"从网络收到字节流后还原成 RTP 包"之间的所有逻辑,都属于传输模块的管辖范围。
1.1 传输模块的"户口本":核心类与文件分布
为了让你在源码里不迷路,我先给你一份传输模块的核心类清单,这也是我每次走读的索引:
| 类名 | 所在文件 | 核心职责 |
|---|---|---|
cricket::P2PTransportChannel |
p2p/base/p2p_transport_channel.cc |
ICE 状态机管理、连接选择、连通性检查、候选者配对 |
cricket::DtlsTransport |
p2p/base/dtls_transport.cc |
DTLS 握手、SRTP 密钥派生、加密数据收发 |
webrtc::SrtpTransport |
pc/srtp_transport.cc |
RTP/RTCP 的加解密、鉴权、序号维护 |
webrtc::RtpTransport |
pc/rtp_transport.h |
收发双向的统一入口,管理 send/receive 方向 |
RtpSenderEgress |
modules/rtp_rtcp/source/rtp_sender_egress.cc |
RTP 包组装、序号分配、发送前记录 |
TaskQueuePacedSender |
modules/pacing/task_queue_paced_sender.cc |
平滑发送,按码率预算控制发包节奏 |
RtpStreamReceiverController |
call/rtp_stream_receiver_controller.cc |
接收方向按 SSRC/mid 分发 RTP 包 |
RtpDemuxer |
call/rtp_demuxer.cc |
根据 SSRC、RSID、MID 做最终路由判定 |
这张表里的类并不是孤立存在的,而是严格分层嵌套的。你从下往上看,就是一条完整的网络栈:socket 数据先到 P2PTransportChannel,它负责从多条 ICE 连接里选一条;选完就把数据交给 DtlsTransport 解密(如果开了加密);解密后的明文 RTP 交给 SrtpTransport 做 SRTP 处理;再往上才是 RTP 层的分发和应用层回调。
1.2 为什么 WebRTC 要把传输拆成这么多层
这个分层一开始会让人觉得冗余,但你把它想成一次物流发货就通了:P2PTransportChannel 是物流公司的路线调度员,负责在地图上找一条能走的道(NAT 穿越);DtlsTransport 是保险箱厂家,负责给你提供一个只有收发双方能打开的箱子(加密信道);SrtpTransport 是打包员,负责给每个零件贴上防拆标签(SRTP 鉴权);RtpTransport 则是仓库门口的管理员,记录哪些货进来了、哪些货出去了。
每层只解决一个问题,这带来一个巨大的好处:组合灵活。比如 WebRTC 也可以跑在没有加密的信道上(本地测试时可用 cricket::BaseChannel 绕过 DTLS),或者跑在纯 P2P 没有 ICE 的中继模式下。分层清晰之后,你就能单独替换某一段实现而不影响其他层。
边界问题还要多说一句:jitter buffer(抖动缓冲)严格来说不属于传输模块,它属于 modules/video_coding,但它的输入来源和丢包反馈都跟传输模块强相关。所以第 5 章讲弱网时,我会把 jitter buffer 的等待逻辑和传输模块的重传逻辑放在一起讲,因为实际排障时这两块是分不开的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 一条 RTP 包从编码器到网卡:发送路径源码走读
很多文章介绍传输模块时喜欢按类逐个讲,我试过这种读法,效果很差,因为你不清楚这些类到底怎么协作。我建议反过来:先建立一条完整调用链,再回头看每个类里的细节。下面就是我梳理出的发送路径主线。
2.1 第一站:RtpSenderEgress 与 PacedSender 的配合
编码器出来的视频帧经过 RTP 打包器(RtpPacketizer)切成一个个 RTP 包后,并不会直接丢给网卡,而是先进入 RtpSenderEgress。这里有个很重要的设计:`R
