# 媒体通道 · BaseChannel 连接协商与媒体

> BaseChannel（pc/channel.cc）是 SDP 协商和媒体引擎之间那道最关键的胶水——SetLocalContent/SetRemoteContent 把协商结果落到媒体通道，它同时挂在 worker 和 network 线程上、向 RTP 解复用器注册收包。这一章读真实的 channel.h，看这道缝怎么缝起来。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-08-03
- 原文：https://daiw.org/manual/webrtc/media-channels
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 媒体通道 · BaseChannel 连接协商与媒体

[媒体引擎据协商建“媒体通道”](https://daiw.org/manual/webrtc/media-engine)，但**谁在协商成功后调它、把 [SDP 里谈定的编解码器 / SSRC / 方向](https://daiw.org/manual/webrtc/sdp-and-jsep)真正落到媒体上**？是 **`BaseChannel`**（`pc/channel.cc`）。它是[控制面（SDP）](https://daiw.org/manual/webrtc/offer-answer-handler)和[媒体面（引擎 + 管线）](https://daiw.org/manual/webrtc/video-pipeline-overview)之间那道**最关键的胶水缝**。

## 一个 BaseChannel，不再分音视频类

读真实源码有个值得注意的点——现在的 WebRTC **没有独立的 `VoiceChannel` / `VideoChannel` 类了**，统一成一个 `BaseChannel`，用 `media_type_` 区分音视频：

```cpp
// 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_;
};
```

<Callout type="info">
  **`BaseChannel` 是“一路媒体（m= line）”在实现层的化身——它持有[媒体引擎建的 `MediaSendChannelInterface`](https://daiw.org/manual/webrtc/media-engine)，并通过 `SetLocalContent` / `SetRemoteContent` 把[协商出的 `MediaContentDescription`](https://daiw.org/manual/webrtc/sdp-and-jsep)（编解码器、SSRC、方向）落到那个媒体通道上。** 每一条[协商出的媒体](https://daiw.org/manual/webrtc/media-session)（[SDP 里一个 `m=` 段](https://daiw.org/manual/webrtc/sdp-and-jsep)），对应一个 `BaseChannel`。它用 `media_type_`（`AUDIO`/`VIDEO`）区分音视频，而非过去的两个子类——这是源码近年的简化。`BaseChannel` 干的就是把“[纸面协商](https://daiw.org/manual/webrtc/offer-answer-handler)”变成“[媒体通道的实际配置](https://daiw.org/manual/webrtc/media-engine)”。
</Callout>

## SetLocalContent / SetRemoteContent：缝合点

这道缝**具体缝在哪**？就在 `SetLocalContent` / `SetRemoteContent` 这两个方法——它们是[SDP 协商](https://daiw.org/manual/webrtc/offer-answer-handler)回调到媒体层的**落点**：

- [`SdpOfferAnswerHandler` 应用 SDP 时](https://daiw.org/manual/webrtc/offer-answer-handler)，对每一路媒体，调对应 `BaseChannel` 的 `SetLocalContent`（本端 SDP）/`SetRemoteContent`（对端 SDP）。
- `BaseChannel` 从 [`MediaContentDescription`](https://daiw.org/manual/webrtc/sdp-and-jsep) 里取出：用哪些[编解码器](https://daiw.org/manual/webrtc/payload-types)、[发送 / 接收的 SSRC](https://daiw.org/manual/webrtc/rtp-packet)（[`local_streams_` / `remote_streams_`](https://daiw.org/manual/webrtc/rtp-packet)）、[方向](https://daiw.org/manual/webrtc/transceivers)（sendrecv/sendonly/...），配置给 `media_send_channel_`（和对应接收通道）。
- 于是[编解码器真正被设定](https://daiw.org/manual/webrtc/media-engine)、[流真正开始收发](https://daiw.org/manual/webrtc/call-object)。

```mermaid
flowchart TD
    SDP["SdpOfferAnswerHandler 应用 SDP(第 1 部分)"] -->|"每路媒体"| SLC["BaseChannel::SetLocal/RemoteContent"]
    SLC -->|"取编解码器/SSRC/方向"| MC["配置 media_send_channel(媒体引擎的通道)"]
    MC --> STREAM["Call 的 Send/ReceiveStream 收发(第 6/7 部分)"]
    SLC -->|"注册 payload_types_"| DEMUX["RTP 解复用器(第 4 部分)"]
```

**`SetLocalContent`/`SetRemoteContent` 就是[整个控制面](https://daiw.org/manual/webrtc/peer-connection-object)（“[协商](https://daiw.org/manual/webrtc/offer-answer-handler)”）和[整个媒体面](https://daiw.org/manual/webrtc/video-pipeline-overview)（“[编解码收发](https://daiw.org/manual/webrtc/call-object)”）的接缝**——两大半系统在这里对接。这也是为什么理解 `BaseChannel` 是打通[前 8 部分](https://daiw.org/manual/webrtc/call-object)的关键：它一头连着 [SDP](https://daiw.org/manual/webrtc/sdp-and-jsep)，一头连着 [Call 流](https://daiw.org/manual/webrtc/call-object)。

## BaseChannel 横跨两条线程

`BaseChannel` 一个精妙处：它**同时挂在 [worker 和 network 两条线程](https://daiw.org/manual/webrtc/threading-model)上**（读源码满眼 `RTC_RUN_ON(worker_thread())` 和 `RTC_RUN_ON(network_thread())`）：

<Callout type="info">
  **`BaseChannel` 的媒体配置（编解码器、方向）在 [worker 线程](https://daiw.org/manual/webrtc/threading-model)，而[包的收发 / 解复用](https://daiw.org/manual/webrtc/rtp-demuxer)在 [network 线程](https://daiw.org/manual/webrtc/threading-model)——它是[三线程模型](https://daiw.org/manual/webrtc/threading-model)在媒体通道这一层的体现。** 为什么？因为 `BaseChannel` 干的事天然跨两个域：

  - **network 线程侧** —— 它向 [RTP 解复用器注册](https://daiw.org/manual/webrtc/rtp-demuxer)（`RegisterRtpDemuxerSink`，按 [SSRC / payload type](https://daiw.org/manual/webrtc/rtp-demuxer) 声明“这些包归我”），收到包时 `OnRtpPacket` 在 network 线程被调，再[转交给媒体通道](https://daiw.org/manual/webrtc/rtp-rtcp-module)。这呼应[第 4 部分的解复用](https://daiw.org/manual/webrtc/rtp-demuxer)：`BaseChannel` 正是解复用的**注册方和接收方**。
  - **worker 线程侧** —— [媒体配置、编解码](https://daiw.org/manual/webrtc/media-engine)在 worker 线程（`_w` 后缀的方法，如 `SetLocalContent_w`）。

  所以 `BaseChannel` 是[network 线程（收发）和 worker 线程（处理）之间](https://daiw.org/manual/webrtc/threading-model)的**桥**——这也是它为什么持有 `worker_thread_` 和 `network_thread_` 两个线程句柄、方法上密布线程断言。它把“[包从哪来](https://daiw.org/manual/webrtc/rtp-demuxer)”和“[媒体怎么处理](https://daiw.org/manual/webrtc/video-pipeline-overview)”这两件在不同线程上的事，安全地连起来。
</Callout>

## 在整个架构里的位置

至此，[第 9 部分](https://daiw.org/manual/webrtc/call-object)把[控制面](https://daiw.org/manual/webrtc/peer-connection-object)和[媒体面](https://daiw.org/manual/webrtc/video-pipeline-overview)彻底接上了。完整的一条链：

```mermaid
flowchart LR
    PC["PeerConnection(控制面)"] --> OA["SDP 协商"]
    OA --> BC["BaseChannel<br/>SetLocal/RemoteContent"]
    BC --> ENG["媒体引擎<br/>建媒体通道"]
    ENG --> CALL["Call 建流"]
    CALL --> PIPE["音视频管线收发"]
    BC -.->|"注册解复用/收包"| RTP["RTP 传输(第 2/4 部分)"]
```

[`PeerConnection`](https://daiw.org/manual/webrtc/peer-connection-object) → [协商](https://daiw.org/manual/webrtc/offer-answer-handler) → **`BaseChannel`** → [媒体引擎](https://daiw.org/manual/webrtc/media-engine) → [`Call` 流](https://daiw.org/manual/webrtc/call-object) → [管线](https://daiw.org/manual/webrtc/video-pipeline-overview)，另一侧接 [RTP 传输](https://daiw.org/manual/webrtc/rtp-rtcp-module)。**`BaseChannel` 是这张图正中间那个把左右两半焊起来的节点。** 读懂它，[前 8 部分](https://daiw.org/manual/webrtc)就从“一堆分立的子系统”变成“一个贯通的整体”。

## 小结与下一章

**`BaseChannel`**（`pc/channel.cc`）是[控制面](https://daiw.org/manual/webrtc/offer-answer-handler)和[媒体面](https://daiw.org/manual/webrtc/video-pipeline-overview)之间**最关键的胶水**：一路[协商媒体（`m=`）](https://daiw.org/manual/webrtc/sdp-and-jsep)对应一个 `BaseChannel`（现已统一为一个类、用 `media_type_` 区分音视频，不再分 `VoiceChannel`/`VideoChannel`）。它通过 **`SetLocalContent`/`SetRemoteContent`** 把[协商出的编解码器 / SSRC / 方向](https://daiw.org/manual/webrtc/sdp-and-jsep)落到[媒体引擎的通道](https://daiw.org/manual/webrtc/media-engine)——这是[协商](https://daiw.org/manual/webrtc/offer-answer-handler)到[媒体](https://daiw.org/manual/webrtc/call-object)的**接缝**。它**横跨 [worker（媒体配置）和 network（收包 / [解复用注册](https://daiw.org/manual/webrtc/rtp-demuxer)）两线程](https://daiw.org/manual/webrtc/threading-model)**，是两域之间的桥。

第 9 部分把[控制面和媒体面接通](https://daiw.org/manual/webrtc/call-object)了。下一章为这一部分收尾。👉 [第 9 部分回顾](https://daiw.org/manual/webrtc/call-recap)。

## 参考资料

- [1] libwebrtc 源码.`pc/channel.h` / `channel.cc`（`BaseChannel`、`SetLocalContent`/`SetRemoteContent`、`OnRtpPacket`、`media_type_`）。
- [2] 关联[SDP 协商](https://daiw.org/manual/webrtc/offer-answer-handler)、[媒体引擎](https://daiw.org/manual/webrtc/media-engine)、[RTP 解复用](https://daiw.org/manual/webrtc/rtp-demuxer)、[线程模型](https://daiw.org/manual/webrtc/threading-model)。
- [3] libwebrtc 源码.`pc/channel_interface.h`（`ChannelInterface`）。
