# 媒体协商 · MediaSessionDescriptionFactory 怎么取编解码器交集

> SDP 协商里最实质的一步，是「双方支持的编解码器取交集」。这活儿由 pc/media_session.cc 的 MediaSessionDescriptionFactory 干：CreateOfferOrError 列出我方能力，CreateAnswerOrError 挑出双方都支持的。这一章读它怎么把「能力清单」协商成「一致约定」。

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

# 媒体协商 · MediaSessionDescriptionFactory 怎么取编解码器交集

[上一章](https://daiw.org/manual/webrtc/offer-answer-handler)的 `SdpOfferAnswerHandler` 是协商的**驾驶舱**，但它把「具体怎么生成一段媒体描述、怎么和对端的能力取交集」这件脏活，委托给了另一个组件——`pc/media_session.cc` 里的 **`MediaSessionDescriptionFactory`**。这一章看协商最实质的一步：**编解码器怎么谈拢**。

## 分工：驾驶舱调度，工厂造描述

先厘清[上一章](https://daiw.org/manual/webrtc/offer-answer-handler)和这一章的关系：

- `SdpOfferAnswerHandler`(`pc/sdp_offer_answer.cc`)—— 管**流程与状态机**：什么时候该生成 offer、应用描述后推进到哪个状态、下推给谁。
- `MediaSessionDescriptionFactory`(`pc/media_session.cc`)—— 管**内容**：把「我方有哪些轨道、支持哪些编解码器」这份能力，构造成一个结构化的 [`SessionDescription`](https://daiw.org/manual/webrtc/sdp-and-jsep)（那个内部的、拆开信封的版本）。

它的两个核心方法，和 JSEP 的提议 / 应答严丝合缝（`pc/media_session.h`）：

```cpp
// pc/media_session.h:MediaSessionDescriptionFactory(节选)
RTCErrorOr<std::unique_ptr<SessionDescription>> CreateOfferOrError(
    const MediaSessionOptions& options, const SessionDescription* current_desc);
RTCErrorOr<std::unique_ptr<SessionDescription>> CreateAnswerOrError(
    const SessionDescription* offer, const MediaSessionOptions& options,
    const SessionDescription* current_desc);
```

`CreateOfferOrError` 生成 offer（「我能做这些」），`CreateAnswerOrError` **吃着对端的 offer** 生成 answer（「这些里我也支持的，是这些」）。二者都吃一个 `MediaSessionOptions`（每条 m-line 想怎么配），吐一个 `SessionDescription`。

## Offer：摊开我方全部能力

生成 offer 时，逻辑是「**把我方支持的都列出来**」。对每条要协商的媒体（一路音频、一路视频……），工厂生成一条 [m-line](https://daiw.org/manual/webrtc/sdp-and-jsep)，里面列上**我方支持的所有编解码器**——比如音频列 Opus、G.711；视频列 VP8、VP9、AV1、H264。每个编解码器分配一个 **payload type**（SDP 里 `a=rtpmap` 那个数字）。offer 是「能力的完整清单」：我全都能，你挑吧。

## Answer：取交集，这是协商的实质

生成 answer 才是**协商真正发生**的地方。工厂拿着对端的 offer，对每条 m-line 做一件事——**求交集**：

<Callout type="info">
  **协商编解码器 = 求两方能力的交集。** 生成 answer 时，`MediaSessionDescriptionFactory` 对每条媒体，把「**对端 offer 里列的编解码器**」和「**我方支持的编解码器**」取交集，只保留双方都有的。源码里这一步由 `GetNegotiatedCodecsForAnswer`（`pc/media_session.cc` 通过一个 codec vendor 辅助对象）完成。

  举例：对端 offer 视频列了 `[VP8, VP9, H264]`，我方支持 `[VP8, AV1]`，交集就是 `[VP8]`——这通电话的视频**最终用 VP8**。如果交集为空（一个共同编解码器都没有），这条媒体就没法协商，会被拒绝（端口设为 0）。**这就是为什么两个 WebRTC 端点总能通：只要有一个共同编解码器，交集非空，协商就成。**

  交集之外，还要对齐**payload type**（双方对同一编解码器可能用了不同的数字，answer 要采纳 offer 的编号）、协商 **RTP 头扩展**（[第 4 部分](https://daiw.org/manual/webrtc/source-map)）、**RTCP 反馈**类型（支不支持 NACK、Transport-CC……）等一系列细节。每一样都是「取双方都支持的」。
</Callout>

## 不止编解码器：一条 m-line 要谈的东西

一条媒体行协商的，远不止编解码器。工厂在构造每条 media content 时，还要定下（[第 2、3 部分](https://daiw.org/manual/webrtc/source-map)会展开）：

| 要素 | 谈什么 | 归哪部分 |
| --- | --- | --- |
| 编解码器 | 取交集 + 对齐 payload type | 本章 |
| 方向 | sendrecv / sendonly / recvonly / inactive | [收发器](https://daiw.org/manual/webrtc/transceivers) |
| RTP 头扩展 | 双方都支持的扩展及其 ID | 第 4 部分 |
| RTCP 反馈 | NACK / PLI / Transport-CC 支不支持 | 第 4/5 部分 |
| ICE 凭据 | ufrag / pwd | [第 2 部分](https://daiw.org/manual/webrtc/source-map) |
| DTLS 指纹与角色 | fingerprint / setup | [第 3 部分](https://daiw.org/manual/webrtc/source-map) |
| BUNDLE | 这些 m-line 能否共用一条传输 | [传输控制器](https://daiw.org/manual/webrtc/offer-answer-handler) |

`MediaSessionDescriptionFactory` 就是把这一大堆，**逐条 m-line 地谈拢、写进 `SessionDescription`**。难怪 [`SdpOfferAnswerHandler`](https://daiw.org/manual/webrtc/offer-answer-handler) 要把这活儿单独委托出去。

## 一张流程图

```mermaid
flowchart TD
    OFFER["对端 offer(SessionDescription)"]
    MINE["我方能力(MediaSessionOptions:轨道 + 支持的编解码器)"]
    OFFER --> F["MediaSessionDescriptionFactory<br/>CreateAnswerOrError"]
    MINE --> F
    F --> INT["逐条 m-line 求交集:<br/>编解码器 ∩ · 头扩展 ∩ · RTCP 反馈 ∩"]
    INT --> ANS["answer(SessionDescription)<br/>= 双方一致的约定"]
```

## 小结与下一章

**媒体协商的实质，是求两方能力的交集**，由 `pc/media_session.cc` 的 `MediaSessionDescriptionFactory` 完成：`CreateOfferOrError` 摊开我方全部能力，`CreateAnswerOrError` **吃对端 offer、逐条 m-line 取交集**（编解码器用 `GetNegotiatedCodecsForAnswer`，还要对齐 payload type、谈头扩展 / RTCP 反馈 / ICE / DTLS / BUNDLE）。交集非空，这路媒体就能通。[`SdpOfferAnswerHandler`](https://daiw.org/manual/webrtc/offer-answer-handler) 调度流程，它负责造内容。

协商结果里的「方向」（sendrecv 等）和「这一路媒体归谁收发」，落在一个核心抽象上——**收发器 `RtpTransceiver`**。它是 Unified Plan 的基石。下一章读它。👉 [收发器：RtpTransceiver 与 Unified Plan](https://daiw.org/manual/webrtc/transceivers)。

## 参考资料

- [1] libwebrtc 源码.`pc/media_session.h` / `.cc`（`MediaSessionDescriptionFactory`、`CreateOfferOrError` / `CreateAnswerOrError`、`GetNegotiatedCodecsForAnswer`）。
- [2] libwebrtc 源码.`pc/session_description.h`（`SessionDescription` 与 `MediaContentDescription`）。
- [3] IETF RFC 8829「JSEP」§ 编解码器协商；RFC 3264「An Offer/Answer Model with SDP」。
