源码地图 · 5000 个文件怎么组织
面对 libwebrtc 5000+ 个文件,先要一张地图。这一章讲清顶层目录的分工与分层依赖:api/ 是唯一的公共入口,pc/ 实现 PeerConnection,p2p/ 管连接,modules/ 装下所有重活,rtc_base/ 是地基——以及它们谁依赖谁。
源码地图 · 5000 个文件怎么组织
打开 libwebrtc,第一感觉往往是「无从下手」。但这套代码其实分层极其清晰——理解顶层几个目录的分工与依赖方向,你就有了一张能一直用下去的地图。这一章就画这张图。
唯一的入口:api/
从依赖关系切入最清楚。项目自带的架构文档 g3doc/index.md 开宗明义:
The public API of the WebRTC library consists of the
api/directory and its subdirectories. No other files should be depended on by webrtc users.
翻译过来:api/ 是使用者唯一该依赖的目录,里面全是接口(纯虚类,名字多以 Interface 结尾)和数据结构——PeerConnectionInterface、RtpSenderInterface、VideoEncoderFactory……它们定义「能做什么」,却不含「怎么做」。api/ 之外的一切,都是实现细节,随时可能变,用户不该直接依赖。
这个「接口与实现分离」的纪律,是理解整个代码库的钥匙:api/ 声明契约,其余目录履行契约。
分层依赖
其余目录,大致按这样的层次依赖(上层用下层,下层不知上层):
越往下越通用、越稳定;越往上越贴近「一次通话」的业务。下面自上而下逐层认识。
会话层:pc/
pc/ 实现 PeerConnection——那个「上帝对象」。它把一次通话的控制面都装在这里:
pc/peer_connection.cc——PeerConnection本体。pc/sdp_offer_answer.cc——SdpOfferAnswerHandler,SDP 提议 / 应答协商的状态机。pc/jsep_transport_controller.cc—— 把协商结果落到底层传输(ICE/DTLS)。pc/rtp_transceiver.cc/pc/rtp_sender.cc/pc/rtp_receiver.cc—— 收发的收发器。pc/channel.cc—— 媒体通道,连接 SDP 与媒体引擎。pc/data_channel_controller.cc—— 数据通道管理。
这是第 1 部分的主战场。
连接、中枢、媒体引擎:p2p/ call/ media/
中间这层是「怎么把媒体真正收发起来」:
p2p/—— ICE 连接建立。核心p2p/base/p2p_transport_channel.cc(候选配对与连通性检查)、p2p/base/port_allocator.cc(候选采集)、STUN / TURN 的实现。第 2 部分主角。call/——Call中枢对象(call/call.cc)。它管理一次通话里所有的发送流 / 接收流,并持有拥塞控制的总控RtpTransportControllerSend。第 9 部分主角。media/—— 媒体引擎的胶水层。media/engine/webrtc_voice_engine.cc、media/engine/webrtc_video_engine.cc把上层的轨道 / 编解码器接到下层的 RTP 模块。
而发送 / 接收管线本身,放在顶层的 video/ 与 audio/:video/video_stream_encoder.cc(编码管线)、video/video_receive_stream2.cc(接收管线)、audio/audio_send_stream.cc 等。
重活都在 modules/
modules/ 是功能模块的大仓库,WebRTC 绝大部分「硬核算法」都在这:
| 目录 | 内容 | 本教程 |
|---|---|---|
modules/rtp_rtcp/ | RTP / RTCP 打包、发送、反馈 | 第 4 部分 |
modules/congestion_controller/ | 拥塞控制 GCC、带宽估计 | 第 5 部分 |
modules/pacing/ | 平滑发包 PacedSender | 第 5 部分 |
modules/video_coding/ | 抖动缓冲、帧组装、FEC、打包 | 第 6 部分 |
modules/audio_coding/ | 音频编解码、NetEq 抖动缓冲 | 第 7 部分 |
modules/audio_processing/ | APM:回声消除 AEC3、降噪、增益 | 第 7 部分 |
modules/desktop_capture/ | 屏幕 / 窗口采集 | 第 12 部分 |
数据通道的 SCTP 实现单独放在 net/dcsctp/(第 8 部分)。
地基:rtc_base/
最底层的 rtc_base/,是与 WebRTC 业务无关的通用基础设施——线程与任务队列(rtc_base/thread.cc、TaskQueue)、同步原语、时间与时钟、缓冲区(CopyOnWriteBuffer)、引用计数、速率统计…… 几乎每个上层模块都用到它。第 10 部分专讲。
其余还有:logging/ 与 rtc_event_log/(观测,第 11 部分)、stats/(统计报告)、sdk/(Android / iOS 平台封装,第 12 部分)。
命名约定:一个类,三个名字
读 pc/ 及以下的代码时,你会反复撞见同一个东西的三种命名。记住这个约定能省很多困惑:
同一个组件常有三个名字:XxxInterface、XxxProxy、Xxx。 以 g3doc/threading_design.md 的说法:
XxxInterface(在api/)—— 公共接口,用户依赖这个。XxxProxy(不在api/)—— 代理,负责把调用转发到正确的线程上执行。Xxx(不在api/)—— 真正干活的实现,只能在它的「归属线程」上被创建、调用、销毁。
比如 PeerConnectionInterface / PeerConnectionProxy / PeerConnection。为什么要多一层 Proxy?因为 WebRTC 是多线程的,代理负责把你从任意线程发起的调用,安全地「跳」到组件的归属线程——这是它保证线程安全的核心手法,我们专门用两章讲。
一个找路技巧:header guard 就是路径
本教程统一用上游 canonical 路径(如 modules/rtp_rtcp/source/rtp_sender.h)。当你手上的源码路径和这里对不上时(比如某些工具会重排目录),有个万无一失的办法:看头文件的 include guard——它直接编码了 canonical 路径。例如 rtp_sender.h 顶部写着:
#ifndef MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_
#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_把下划线换成斜杠、去掉结尾 _H_,就是 modules/rtp_rtcp/source/rtp_sender.h。整个代码库严格遵守这个约定,所以任何文件的规范路径,读它自己的 guard 就知道。
小结与下一章
libwebrtc 分层清晰:api/(唯一公共入口,全是接口)→ pc/(会话层,PeerConnection)→ p2p/ call/ media/(连接、中枢、媒体引擎)→ modules/(所有重活:RTP、拥塞控制、音视频编解码、APM)→ rtc_base/(通用地基)。命名上一个组件常有 Interface / Proxy / Impl 三副面孔;header guard 则直接编码 canonical 路径。
有了地图,该看动态了:一次通话从无到有,这些目录里的对象按什么顺序、怎么协作把连接搭起来?👉 一次通话的一生。
参考资料
- [1] libwebrtc 源码.
g3doc/index.md(api/作为唯一公共依赖)、g3doc/threading_design.md(Interface/Proxy/Impl 命名)。 - [2] libwebrtc 源码.顶层目录
api/pc/p2p/call/media/modules/rtc_base/。 - [3] WebRTC 项目.「Development」https://webrtc.googlesource.com/src/ —— 官方源码仓库。