媒体通道 · BaseChannel 连接协商与媒体
BaseChannel(pc/channel.cc)是 SDP 协商和媒体引擎之间那道最关键的胶水——SetLocalContent/SetRemoteContent 把协商结果落到媒体通道,它同时挂在 worker 和 network 线程上、向 RTP 解复用器注册收包。这一章读真实的 channel.h,看这道缝怎么缝起来。
媒体通道 · BaseChannel 连接协商与媒体
媒体引擎据协商建“媒体通道”,但谁在协商成功后调它、把 SDP 里谈定的编解码器 / SSRC / 方向真正落到媒体上?是 BaseChannel(pc/channel.cc)。它是控制面(SDP)和媒体面(引擎 + 管线)之间那道最关键的胶水缝。
一个 BaseChannel,不再分音视频类
读真实源码有个值得注意的点——现在的 WebRTC 没有独立的 VoiceChannel / VideoChannel 类了,统一成一个 BaseChannel,用 media_type_ 区分音视频:
// channel.h(节选,真实源码)
class BaseChannel : public ChannelInterface, ... {
BaseChannel(
TaskQueueBase* absl_nonnull worker_thread,
Thread* absl_nonnull network_thread,
...
MediaType media_type, // AUDIO 还是 VIDEO
std::unique_ptr<MediaSendChannelInterface> media_send_channel, ...);
RTCError SetLocalContent(const MediaContentDescription* content, SdpType type);
RTCError SetRemoteContent(const MediaContentDescription* content, SdpType type);
void OnRtpPacket(const RtpPacketReceived& packet) override; // 收到解复用后的包
private:
const MediaType media_type_; // 音 / 视由它区分
const std::unique_ptr<MediaSendChannelInterface> media_send_channel_;
};BaseChannel 是“一路媒体(m= line)”在实现层的化身——它持有媒体引擎建的 MediaSendChannelInterface,并通过 SetLocalContent / SetRemoteContent 把协商出的 MediaContentDescription(编解码器、SSRC、方向)落到那个媒体通道上。 每一条协商出的媒体(SDP 里一个 m= 段),对应一个 BaseChannel。它用 media_type_(AUDIO/VIDEO)区分音视频,而非过去的两个子类——这是源码近年的简化。BaseChannel 干的就是把“纸面协商”变成“媒体通道的实际配置”。
SetLocalContent / SetRemoteContent:缝合点
这道缝具体缝在哪?就在 SetLocalContent / SetRemoteContent 这两个方法——它们是SDP 协商回调到媒体层的落点:
SdpOfferAnswerHandler应用 SDP 时,对每一路媒体,调对应BaseChannel的SetLocalContent(本端 SDP)/SetRemoteContent(对端 SDP)。BaseChannel从MediaContentDescription里取出:用哪些编解码器、发送 / 接收的 SSRC(local_streams_/remote_streams_)、方向(sendrecv/sendonly/...),配置给media_send_channel_(和对应接收通道)。- 于是编解码器真正被设定、流真正开始收发。
SetLocalContent/SetRemoteContent 就是整个控制面(“协商”)和整个媒体面(“编解码收发”)的接缝——两大半系统在这里对接。这也是为什么理解 BaseChannel 是打通前 8 部分的关键:它一头连着 SDP,一头连着 Call 流。
BaseChannel 横跨两条线程
BaseChannel 一个精妙处:它同时挂在 worker 和 network 两条线程上(读源码满眼 RTC_RUN_ON(worker_thread()) 和 RTC_RUN_ON(network_thread())):
BaseChannel 的媒体配置(编解码器、方向)在 worker 线程,而包的收发 / 解复用在 network 线程——它是三线程模型在媒体通道这一层的体现。 为什么?因为 BaseChannel 干的事天然跨两个域:
- network 线程侧 —— 它向 RTP 解复用器注册(
RegisterRtpDemuxerSink,按 SSRC / payload type 声明“这些包归我”),收到包时OnRtpPacket在 network 线程被调,再转交给媒体通道。这呼应第 4 部分的解复用:BaseChannel正是解复用的注册方和接收方。 - worker 线程侧 —— 媒体配置、编解码在 worker 线程(
_w后缀的方法,如SetLocalContent_w)。
所以 BaseChannel 是network 线程(收发)和 worker 线程(处理)之间的桥——这也是它为什么持有 worker_thread_ 和 network_thread_ 两个线程句柄、方法上密布线程断言。它把“包从哪来”和“媒体怎么处理”这两件在不同线程上的事,安全地连起来。
在整个架构里的位置
至此,第 9 部分把控制面和媒体面彻底接上了。完整的一条链:
PeerConnection → 协商 → BaseChannel → 媒体引擎 → Call 流 → 管线,另一侧接 RTP 传输。BaseChannel 是这张图正中间那个把左右两半焊起来的节点。 读懂它,前 8 部分就从“一堆分立的子系统”变成“一个贯通的整体”。
小结与下一章
BaseChannel(pc/channel.cc)是控制面和媒体面之间最关键的胶水:一路协商媒体(m=)对应一个 BaseChannel(现已统一为一个类、用 media_type_ 区分音视频,不再分 VoiceChannel/VideoChannel)。它通过 SetLocalContent/SetRemoteContent 把协商出的编解码器 / SSRC / 方向落到媒体引擎的通道——这是协商到媒体的接缝。它横跨 [worker(媒体配置)和 network(收包 / 解复用注册)两线程](/manual/webrtc/threading-model),是两域之间的桥。
第 9 部分把控制面和媒体面接通了。下一章为这一部分收尾。👉 第 9 部分回顾。