源码地图 · 5000 个文件怎么组织

面对 libwebrtc 5000+ 个文件,先要一张地图。这一章讲清顶层目录的分工与分层依赖:api/ 是唯一的公共入口,pc/ 实现 PeerConnection,p2p/ 管连接,modules/ 装下所有重活,rtc_base/ 是地基——以及它们谁依赖谁。

作者 David更新于 第 2 篇(共 112 篇)

源码地图 · 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/ —— 官方源码仓库。

本页目录