媒体协商 · MediaSessionDescriptionFactory 怎么取编解码器交集
SDP 协商里最实质的一步,是「双方支持的编解码器取交集」。这活儿由 pc/media_session.cc 的 MediaSessionDescriptionFactory 干:CreateOfferOrError 列出我方能力,CreateAnswerOrError 挑出双方都支持的。这一章读它怎么把「能力清单」协商成「一致约定」。
媒体协商 · MediaSessionDescriptionFactory 怎么取编解码器交集
上一章的 SdpOfferAnswerHandler 是协商的驾驶舱,但它把「具体怎么生成一段媒体描述、怎么和对端的能力取交集」这件脏活,委托给了另一个组件——pc/media_session.cc 里的 MediaSessionDescriptionFactory。这一章看协商最实质的一步:编解码器怎么谈拢。
分工:驾驶舱调度,工厂造描述
先厘清上一章和这一章的关系:
SdpOfferAnswerHandler(pc/sdp_offer_answer.cc)—— 管流程与状态机:什么时候该生成 offer、应用描述后推进到哪个状态、下推给谁。MediaSessionDescriptionFactory(pc/media_session.cc)—— 管内容:把「我方有哪些轨道、支持哪些编解码器」这份能力,构造成一个结构化的SessionDescription(那个内部的、拆开信封的版本)。
它的两个核心方法,和 JSEP 的提议 / 应答严丝合缝(pc/media_session.h):
// 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,里面列上我方支持的所有编解码器——比如音频列 Opus、G.711;视频列 VP8、VP9、AV1、H264。每个编解码器分配一个 payload type(SDP 里 a=rtpmap 那个数字)。offer 是「能力的完整清单」:我全都能,你挑吧。
Answer:取交集,这是协商的实质
生成 answer 才是协商真正发生的地方。工厂拿着对端的 offer,对每条 m-line 做一件事——求交集:
协商编解码器 = 求两方能力的交集。 生成 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 部分)、RTCP 反馈类型(支不支持 NACK、Transport-CC……)等一系列细节。每一样都是「取双方都支持的」。
不止编解码器:一条 m-line 要谈的东西
一条媒体行协商的,远不止编解码器。工厂在构造每条 media content 时,还要定下(第 2、3 部分会展开):
| 要素 | 谈什么 | 归哪部分 |
|---|---|---|
| 编解码器 | 取交集 + 对齐 payload type | 本章 |
| 方向 | sendrecv / sendonly / recvonly / inactive | 收发器 |
| RTP 头扩展 | 双方都支持的扩展及其 ID | 第 4 部分 |
| RTCP 反馈 | NACK / PLI / Transport-CC 支不支持 | 第 4/5 部分 |
| ICE 凭据 | ufrag / pwd | 第 2 部分 |
| DTLS 指纹与角色 | fingerprint / setup | 第 3 部分 |
| BUNDLE | 这些 m-line 能否共用一条传输 | 传输控制器 |
MediaSessionDescriptionFactory 就是把这一大堆,逐条 m-line 地谈拢、写进 SessionDescription。难怪 SdpOfferAnswerHandler 要把这活儿单独委托出去。
一张流程图
小结与下一章
媒体协商的实质,是求两方能力的交集,由 pc/media_session.cc 的 MediaSessionDescriptionFactory 完成:CreateOfferOrError 摊开我方全部能力,CreateAnswerOrError 吃对端 offer、逐条 m-line 取交集(编解码器用 GetNegotiatedCodecsForAnswer,还要对齐 payload type、谈头扩展 / RTCP 反馈 / ICE / DTLS / BUNDLE)。交集非空,这路媒体就能通。SdpOfferAnswerHandler 调度流程,它负责造内容。
协商结果里的「方向」(sendrecv 等)和「这一路媒体归谁收发」,落在一个核心抽象上——收发器 RtpTransceiver。它是 Unified Plan 的基石。下一章读它。👉 收发器:RtpTransceiver 与 Unified Plan。
参考资料
- [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」。