☰
Mediasoup RTP流类型详解:SFU架构下的编解码器、RTX/FEC与Simulcast配置指南
2026/10/19 3:45:23 网站建设 项目流程

1. 先搞清楚 Mediasoup 和 RTP 流的关系

聊 Mediasoup 的 RTP 流类型之前,得先回到一个基础问题:Mediasoup 到底在 RTP 的哪一层干活?它不是 SIP 栈,不是网关,也不是媒体服务器里的“先解码再重新编码”型选手,而是一个典型的 SFU(Selective Forwarding Unit)。客户端通过 WebRTC 把加密后的 RTP 包送到 Mediasoup worker,worker 按需要做选路、转发、丢关键帧请求,但不对音频视频做混流或转码。也就是说,你在 Mediasoup 里能看到、能控制、能协商的,本质上就是“RTP 流”本身,而不是解码后的 PCM 帧或 YUV 帧。

正因为如此,RTP 流的类型就成了整个系统里最核心的“语言”。你写的每一个 Producer、每一个 Consumer,背后对应的都是一组 RTP 参数;你配置错了 payload type、搞混了 RTX 关联、漏了 FEC,表现出的问题可能比你想的还隐蔽——比如视频偶尔花屏、音频断断续续、通话 30 秒后丢包率飙升,但抓包又看不出明显报错。这篇文章我把 Mediasoup 支持的 RTP 流类型按实际工程视角拆开,你会看到编解码器选择、RTX/FEC 冗余机制、Simulcast/SVC 的结构化流、WebRTC 和 PlainTransport 两种路径的差异,以及可以直接抄走的 RtpParameters 配置样例。

适合谁看?正要接 Mediasoup 做视频会议、直播连麦、远程协作工具的开发者,尤其是第一次接触 RtpParameters 就一脸懵的人。MEDIASOUP 本身的 API 不算复杂,真正复杂的恰恰就是“RTP 流类型”这套东西。

1.1 底层三类 RTP 流:媒体流、反馈流、冗余流

如果你抓包看一段正常的 WebRTC 通话,会看到 RTP 包并不是只有一路音频、一路视频那么简单。通常你至少会看到:

  • 媒体流:真正承载 Opus 音频、VP8/VP9/H264 视频的 RTP 包。
  • 反馈流:严格来说 RTCP 不是 RTP,但它在 RTP 会话里承担“流的控制和状态同步”职能。NACK、PLI、FIR、TWCC 都在这条通道上跑。
  • 冗余流:RTX 重传包、RED 冗余音频包、ULPFEC/FlexFEC 前向纠错包。它们不是独立媒体,而是“为了保障媒体可靠传输而存在”的辅助流。

Mediasoup 对这三类流的处理方式不太一样。媒体流是它转发的核心;反馈流在 WebRTC 路径上由端点和 mediasoup 的 worker 配合生成和消费;冗余流则需要你在协商 RtpParameters 时明确告诉 Mediasoup“这条路有 RTX 关联,那个 payload type 是 RED”。如果你把这三类东西混为一谈,后面配置必然出问题。

1.2 两条接入通道带来的类型差异

还有一个经常被忽略的分类维度:RTP 流是从哪条通道进来的。Mediasoup 有两种传输方式:

  • WebRtcTransport:走 DTLS-SRTP,RTP 包加密封装,适合浏览器、App 里的 WebRTC 端。
  • PlainTransport:裸 RTP/RTCP,或者通过 SRTP 参数做 AES 加密,适合 SIP 网关、FFmpeg、GB28181 设备、自定义推流端。

这两类通道在协议栈上的差异,会直接影响你能协商的 RTP 流类型。比如 WebRTC 路径几乎必须支持 RTX、必须支持 NACK,因为浏览器端对丢包容忍度很低;而 PlainTransport 路径里,很多老设备根本不知道什么叫 RTX,你就得老老实实只协商基本媒体流。所以接下来每一节我都会尽量把“WebRTC 路径”和“PlainTransport 路径”分开说。

2. 媒体流:编解码器支持清单与说明

2.1 音频 RTP 流:Opus 为主,兼顾电话事件

Mediasoup 默认能力里,音频编解码器通常包含这些:

编解码器MIME 类型时钟频率声道典型用途
Opusaudio/opus480002WebRTC 通话的主力,带宽自适应强
PCMUaudio/PCMU80001SIP/PSTN 网关对接
PCMAaudio/PCMA80001SIP/PSTN 网关对接
telephone-eventaudio/telephone-event48000 或 80001DTMF 按键音传输

我在实际项目里发现很多人只记得 Opus,把 PCMU/PCMA 当成“反正轮不到我”的配置。这其实是个误区。只要你接了 SIP 中继、语音网关,或者任何通过 Asterisk/FreeSWITCH 转进来的线路,G.711 往往就是对方唯一能给的格式。到时候你新建 Router 的时候如果没把 PCMU/PCMA 加进rtpCapabilities,终端侧拿到的能力列表里根本没有它,协商就直接失败。

telephone-event 容易被忽略,但它非常重要。浏览器里的 DTMF 按键不是通过音频流里的某种特殊音调传的,而是通过 RFC 4733 定义的 telephone-event RTP 流。Mediasoup 在 WebRTC 路径上通常会自动把它算进能力里,但如果你走 PlainTransport 对接 SIP 网关,对方发来的是 audio/telephone-event,你的RtpParameters.codecs里却没配,按键音就会完全消失。排查这个问题的时候,光看音频 RTP 是看不出来的,要抓包确认 payload type。

Opus 的话,Mediasoup 默认推荐 48000Hz、双声道。工程里通常会给 Opus 设置useinbandfec=1,再配合usedtx=0,前者保证在丢包时 Opus 内置的带内 FEC 能起作用,后者避免静音检测把关键语音段截掉。这些参数都是在 RtpParameters 的 codec parameters 里声明的,WebRTC 路径通常由浏览器自动生成,不需要你手动写;PlainTransport 路径就必须手动带上了。

2.2 视频 RTP 流:VP8、VP9、H264、AV1 的取舍

视频侧,Mediasoup 的默认能力一般覆盖 VP8、VP9、H264,AV1 则取决于你编译 worker 时有没有启用相应解码支持。

这几个编解码器在 RTP 流层面有几个明显差异:

  • VP8:RTP 封装最简单,客户端软编软解兼容性最好,WebRTC 里的兜底方案。VP8 的 RTP payload 头里有PID、Key Frame标记,丢包恢复配合 NACK 也比较成熟。做多方视频会议,VP8 仍然是最不容易出幺蛾子的选择。
  • VP9:在相同码率下画质普遍比 VP8 好,RTP 封装类似 VP8,而且支持 SVC 的可伸缩编码,可以用单路 RTP 流承载多个空间/时间层。但 VP9 软解在低端手机上压力比 VP8 大。Mediasoup 对 VP9 的 SVC 支持做得不错,不过要正确设置scalabilityMode。
  • H264:流媒体兼容性之王,尤其在 SIP、监控设备对接场景里是必选项。Mediasoup 对 H264 的处理要注意 profile-level-id 和 packetization-mode。绝大多数 WebRTC 和终端设备要求 packetization-mode=1,即非交错、单 NAL 单元模式。profile-level-id 常见的是 42e01f(Constrained Baseline),也见过 640c1f(High Profile)。如果两端 profile 不匹配,视频就是黑屏或协商失败。
  • AV1:新一代编码,压缩率比 VP9 更进一步,但浏览器和硬件解码覆盖还在爬坡期。Mediasoup 目前对 AV1 的支持依赖 worker 编译时启用 AV1 库,不少发行版镜像默认没开。你要是想试 AV1,建议先确认 worker 的编译选项,再自己往rtpCapabilities里加 AV1 的 codec,不然写进去也会在协商时被过滤掉。

选编解码器不要只看“哪个画质好”,要看整个链路。有一回我做某移动端项目,VP8 在 iOS 的 Safari 上表现很好,结果换到某个定制安卓系统上,VP8 硬解支持不完整,反而 H264 稳如老狗。最后方案是服务端把 VP8、VP9、H264 都列进能力列表,让客户端按自己的偏好选择,这个思路在 Mediasoup 里非常常见。

2.3 自定义编解码器:从 PCMU 到私有 MIME

Mediasoup 的能力列表并不是写死的。router.createRtpCapabilities()返回的是基于当前 worker 支持的默认集,但你完全可以在创建 Router 之前,通过rtpCapabilities的配置把自定义 codec 加进去。例如:

const mediaCodecs = [ { kind: 'audio', mimeType: 'audio/PCMU', clockRate: 8000, channels: 1 }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '42e01f' } } ]; const router = await mediasoup.createWorker({ rtpCapabilities: { codecs: mediaCodecs } });

注意一个细节:不是你想加什么 Mediasoup 都认。worker 内部有真正的编解码器能力判断,比如你声明支持audio/G722,但 mediasoup worker 的 RTP 栈如果不认识这个 MIME,或者底层没有对应处理逻辑,它是不会放进最终能力列表的。但像 PCMU、PCMA、telephone-event 这类经典格式,通常可以直接加进去。实际上,Mediasoup 内部会根据 MIME 类型映射 payload type 和 RTP 处理方式,比如 G722 的时钟频率固定 16000。新版的 mediasoup 是支持 G722 的(如果你记得默认能力里也有?)——我的经验是:先看版本源码里的supportedRtpCapabilities,你才知道边界在哪。

私有 MIME 类型能不能加?能加,但 Mediasoup 只能做透传,它不负责编解码。你要是有自定义编码格式,比如某种低码率音频,RTP 封装和 payload type 都是自己定义的,Mediasoup 也会把它当一个普通 RTP 流转发。前提是你能保证接收端也认识这套私有封装。真实场景里,用这种情况的多半是自研硬件的音视频流,而不是通用客户端。

3. 冗余和重传:RTX、RED、ULPFEC 的工作原理与其可协商性

3.1 RTX 重传流不是普通媒体

RTX 是 RFC 4588 定义的重传流机制。简单说,媒体流丢包后,接收端通过 RTCP NACK 告诉发送端“某个序列号的包没了”,发送端用另一个 RTP 流(RTX 流)把原始包重新发一次。RTX 包的 payload 类型不同于原始媒体包,而且 RTP 头里会携带原始序列号信息。

Mediasoup 对 RTX 的支持是双向的。WebRTC 路径上,客户端通过 negotiator 把 RTX 配置协商好,Mediasoup 转发时也会保留 RTX 关联;PlainTransport 路径上,你得手动在 RtpParameters 里声明 RTX。在我的工程经验里,十次通话质量问题里至少有五次跟 RTX 配置有关,尤其是自研推流端对接 Mediasoup 时,不少开发者根本不写 RTX,丢包一上来画面就卡成 PPT。

RTX 的协商分两层:

  1. 编解码器层:需要声明一个video/rtx的 codec,并用apt参数指向原始媒体编解码器的 payload type。
  2. 编码层:每个 encoding 里可以指定rtx.ssrc或rtx.payloadType,用来明确 RTX 流的 SSRC。

WebRTC 路径上,浏览器会用编码级rtx数据来对齐;PlainTransport 路径上,你必须把主流的 SSRC 和 RTX 流的 SSRC 都写在encodings里,这样对端才能正确关联。

很多新手会把 RTX 当成“可选项”,这是大忌。对 WebRTC 来说,RTX 在过去几年基本是必选,Chrome 在正常网络协商里都会带 RTX 能力。如果 mediasoup 这边的rtpCapabilities里没有 RTX,浏览器虽然未必直接拒绝,但它会降低抗丢包能力;对 PlainTransport 的 SIP 终端来说,RTX 可能不被支持,你就不用强配,但必须接受弱网下的丢包后果。

3.2 RED/ULPFEC 与 FEC 配置

FEC 类的 RTP 流类型在 Mediasoup 生态里相对少见,但存在。最常见的组合是:

  • RED(RFC 2198):冗余音频编码,把前一个音频包和当前包打包在一起,典型配置是audio/redplusapt指向 Opus。Chrome 在某些场景会启用 RED,比如通话质量评估不佳时。如果你没在rtpCapabilities里声明audio/red,浏览器通常也不会主动使用它,所以影响不大。
  • ULPFEC(RFC 5109):不平衡前向纠错,用于视频。ULPFEC 可以恢复部分丢包,但会增加带宽。Mediasoup 对它能透传,但不会主动生成 FEC 包。
  • FlexFEC(RFC 8627):更灵活的 FEC 方案,在部分浏览器和 WebRTC 框架里有支持。

Mediasoup 对 FEC 流的处理原则是“可以协商、可以转发,但不会自动创建”。也就是说,如果你通过自定义rtpCapabilities把video/ulpfec加进去,且客户端协商成功,那么客户端发的 FEC 包会作为一个独立的 RTP 流被 Mediasoup 转发给其他 Consumer。可要是转发后接收端不认这个 FEC codec,它就只会把这一路当成垃圾流量处理。所以在实际项目里,FEC 配置前要想清楚:终端是不是真的支持、带宽够不够、丢包率是不是值得开 FEC。多数做视频会议的项目是优先 NACK+RTX,FEC 只作为音频 RED 的补充。

3.3 RTCP NACK/PLI 和扩展头的配合

RTX 和 FEC 都要靠 RTCP 反馈来触发。Mediasoup 对 NACK 的处理不是简单透传——当 Consumer 侧发现丢包时,mediasoup 的 worker 会自己生成 NACK 或 PLI 请求,并根据配置决定是否重传。这也是 SFU 和纯转发的区别:纯转发不会管你丢不丢包,SFU 得维护每个 Consumer 的重传状态。

RTP 扩展头也是“流类型”不可分割的一部分。Mediasoup 支持协商常见的 RTP header extension,包括:

  • abs-send-time:用于带宽估计。
  • transport-wide-cc:传宽带反馈,是 WebRTC 新版拥塞控制的基础。
  • sdes:rtp-stream-id和sdes:repair-stream-id:Simulcast 里非常关键,用于标识 RID。
  • urn:ietf:params:rtp-hdrext:sdes:mid:MID 扩展,区分多路媒体。

在 WebRTC 路径上,这些扩展头由端点协商并自动配合。PlainTransport 路径上,如果对端发过来的 RTP 带扩展头但你没声明,可能只是没被利用,一般不会直接报错。但如果你要支持 Simulcast 流,那sdes:rtp-stream-id必须放到rtpCapabilities.headerExtensions里,否则终端无法把不同 RID 对应到不同层。

4. 结构化的流类型:单流、Simulcast、SVC

4.1 编码维度的流类型

很多人以为一个视频 Producer 就是一路 RTP 流。其实不然。在 Mediasoup 里,一个 Producer 可以携带一个或多个encodings,每个 encoding 都是一条独立的 RTP 流。这就是 Simulcast 的基础。

Simulcast 的意思是,发送端同时编码出多路不同分辨率的视频流,比如 1080p、720p、360p。在 Mediasoup 里对应:

encodings: [ { rid: 'f', maxBitrate: 3000000 }, { rid: 'h', maxBitrate: 1500000 }, { rid: 'q', maxBitrate: 500000 } ]

RID 是每路子流的标识。Mediasoup 的 worker 会维护这些子流,并根据各 Consumer 的带宽、订阅需求选择转发哪一层。对 Consumer 来说,它可以选择只看q层,也可以看h层,还可以动态切换。如果你转发的是 WebRTC 客户端的 Simulcast,别忘了扩展头里要有sdes:rtp-stream-id。

SVC 则是另一种“单流多层”的方式。VP9 和 AV1 的 SVC 模式,或者 H264 的某些扩展,可以在一条 RTP 流里分层编码,时间层和空间层嵌套在同一个 bitstream 里。Mediasoup 对 VP9 SVC 有专门支持,会在 RtpParameters 里用scalabilityMode声明,比如L3T3、L2T1。WebRTC 端点协商成功后,Consumer 可以只订阅其中部分层,实现码率自适。

两者判断标准很简单:Simulcast 是“多条编码流”,SVC 是“一条编码流但内部可分”。Mediasoup 对 Simulcast 的支持更成熟,因为 WebRTC 客户端普遍支持;SVC 在 VP9 上表现不错,但 VP8 没有标准的 SVC。实际项目里如果接收端是浏览器,我建议优先 Simulcast;如果是自研解码器,并且带宽受限,SVC 往往更省带宽。

4.2 Consumer 如何将多种流映射到用户

RTP 流的类型不只取决于上游怎么发,还取决于下游怎么收。Mediasoup 的 Consumer 在创建时,会传入rtpCapabilities,它代表接收端的能力。Mediasoup 会根据 Consumer 的rtpCapabilities和 Producer 的rtpParameters,自动做一次“能力交集”计算,挑选出可以被 Consumer 支持的编解码器、RTX 和 Simulcast 层。

这里隐藏了一个关键逻辑:如果 Consumer 不支持某个编解码器,Mediasoup 不会转码,它只会把支持的编解码器选出来。比如 Producer 是 H264,Consumer 只支持 VP8,那么协商结果就是空,你会得到一个没有 codec 的 Consumer,视频无法显示。不少人在客户端排查问题时查了半天,才发现是 Consumer 端 rtpCapabilities 没包含 H264。

所以,当你听到“Mediasoup 支持的 RTP 流类型”时,要理解两个层面:

  • Router 层:Mediasoup worker 能接受和处理哪些流(编码、RTX、FEC、Simulcast 等)。
  • Consumer 层:具体某个接收端能消费哪些流(双方能力交集)。

5. WebRTC 与 PlainTransport 的流类型差异

5.1 DTLS-SRTP 包裹的 RTP 流

WebRtcTransport 是 Mediasoup 最常用的传输类型。端点和 mediasoup 之间先做 DTLS 握手,协商出 SRTP 密钥,之后所有 RTP/RTCP 包都走 SRTP 加密。对 RTP 流类型的影响是:你看到的 payload type、SSRC、扩展头都是加密后的外部形态,但 SRTP 不影响流本身的结构。真正影响流类型协商的是 SDP。

Mediasoup 本身不是 SDP 栈,它接收的是经过mediasoup-client或sdp-transform解析后的RtpParameters。所以你在写 WebRTC 客户端时,经常看到这样的流程:

const rtpCapabilities = await router.rtpCapabilities; const transport = createWebRtcTransport(); const { rtpParameters } = await transport.connect(); const producer = await transport.produce({ rtpParameters });

这里的rtpParameters就是 WebRTC 端点上真正的 RTP 流描述。浏览器会根据本地编解码器能力、RTX、FEC、Simulcast 等自动生成,然后逐项告诉 Mediasoup。这条路径上,RTP 流类型是最全的:Opus、VP8、VP9、H264、telephone-event、RTX、RED、ULPFEC、Simulcast 都可能出现。

正因为自动生成,你也要警惕浏览器“自作主张”。Chrome 在启用 FEC、RED 时,生成的 RtpParameters 会非常复杂,codecs数组里可能多达七八项,里面各种rtx、apt关联。Mediasoup 会把这些能力都接住,但你排查问题时要能看懂每个 codec 的来源。

5.2 PlainTransport 的裸 RTP 流、多播与 comedia

PlainTransport 就完全是另一套玩法了。它不跑 DTLS,直接收裸 RTP。这种模式常见于:

  • FFmpeg 推流到 Mediasoup。
  • SIP 网关把 RTP 转到 Mediasoup。
  • 自研嵌入式设备用 RTP 协议对接。
  • 从 CDN 拉流转推。

裸 RTP 模式下,所有 RTP 流类型都要手动配置。没有 SDP 协商过程,没有 DTLS 自动密钥交换,甚至连 SSRC 是谁你都得自己定义。典型的connect()调用:

const plainTransport = await router.createPlainTransport({ listenIp: { ip: '0.0.0.0', announcedIp: '203.0.113.10' }, rtcpMux: true, comedia: false }); await plainTransport.connect({ ip: '192.168.1.50', port: 5004, rtcpPort: 5005 });

comedia是一个很实用的选项。它允许对端不预先告知 IP 和端口,Mediasoup 从收到的第一个 RTP 包里自动学习对端地址。做 FFmpeg 推流时,comedia: true能省去先获取对端地址再 connect 的麻烦。缺点是它要求对端持续发包,否则超时后连接会失效。

PlainTransport 还支持多播。如果listenIp配的是组播地址,connect()时指定的对端 IP 也可以是组播组。这样你就能把一路 RTP 流发给多个接收者。应用场景比如 IP 摄像头的组播流接入,或者向一组设备广播音频。不过组播模式下没有 ACK/NACK,丢包恢复能力为零,应用时要评估网络质量。

裸 RTP 流通常也不带 WebRTC 那一堆反馈机制。NACK、PLI 这类 RTCP 反馈在 PlainTransport 里能不能用,取决于两端怎么实现。Mediasoup 的 PlainTransport 能收发 RTCP,但如果你不配置rtcpMux: false,还得单独处理 RTCP 端口。工程上我建议 SIP 对接场景打开rtcpMux: true,省一个端口也省一类 NAT 问题。

5.3 SRTP 参数与 RTCP-mux 的可选性

PlainTransport 不只是支持裸 RTP,也支持 SRTP。通过srtpParameters指定加密算法和密钥:

await plainTransport.connect({ ip: '203.0.113.20', port: 5004, srtpParameters: { cryptoSuite: 'AES_CM_128_HMAC_SHA1_80', keyBase64: '...' } });

这是一种 AES 加密的 RTP 流,区别于 WebRTC 的 DTLS-SRTP。适用于那些不支持 DTLS 但支持 SRTP 的硬件设备、SIP 网关。流类型层面,SRTP 没有改变媒体编码、RTX、FEC 的协商方式,只是让 RTP 包在网络上不可见。所以你在抓包排查时,如果发现 payload 全是乱码,别以为流类型错了,先确认是不是 SRTP 加密。

RTCP-mux 在 PlainTransport 里也可以关闭。关闭后,RTP 走一个端口,RTCP 走另一个端口,这对老式 SIP 设备很常见。打开时,RTP 和 RTCP 复用同一个端口,浏览器和现代 WebRTC 设备都这么玩。从实践角度看,rtcpMux 开启能减少 NAT 穿透的复杂度,但也要求对端支持 RTP/RTCP 混跑。如果遇到对端只发裸 RTP、完全不发 RTCP,你就得检查 mediasoup 对端口的映射是否切到rtcpPort了。

6. RtpParameters 实操配置示例

6.1 一个完整 RtpParameters 实例

下面是一份常见的、经我验证可用的 RtpParameters,适用于 PlainTransport 接收 Opus 音频加 H264 视频:

const audioRtpParameters = { mid: 'audio', codecs: [ { mimeType: 'audio/opus', clockRate: 48000, channels: 2, payloadType: 100, parameters: { useinbandfec: 1, usedtx: 0 } } ], headerExtensions: [], encodings: [ { ssrc: 11111111 } ], rtcp: { cname: 'audio-cname', reducedSize: true } }; const videoRtpParameters = { mid: 'video', codecs: [ { mimeType: 'video/VP8', clockRate: 90000, payloadType: 96, rtx: { payloadType: 97 } }, { mimeType: 'video/rtx', clockRate: 90000, payloadType: 97, parameters: { apt: 96 } } ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 1 }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 2 } ], encodings: [ { ssrc: 22222222, rtx: { ssrc: 22222223 } } ], rtcp: { cname: 'video-cname', reducedSize: true } };

注意几个坑:

  1. payload type 选择不能跟 RTP 标准保留类型冲突,通常 96 以上比较安全。
  2. video/rtx必须用parameters.apt指明它服务的是哪个媒体 codec。否则这路重传流就是个“孤魂野鬼”,接收端根本不知道它该重传谁的包。
  3. encodings里的rtx.ssrc必须和编解码器层的rtx.payloadType配合使用。两者缺一不可,只填一个都会导致重传链路断裂。

6.2 添加 RTX/FEC 的写法

当你想让一条视频流支持 RTX 时,尽量把编解码器、payload type、SSRC 的关联一次性定清楚。参考上面代码里的写法。如果你的场景要用 RED 冗余音频,可以这样加:

codecs: [ { mimeType: 'audio/opus', clockRate: 48000, channels: 2, payloadType: 100, parameters: { useinbandfec: 1, usedtx: 0 } }, { mimeType: 'audio/red', clockRate: 48000, channels: 2, payloadType: 101, parameters: { '100': 100 } }, { mimeType: 'audio/rtx', clockRate: 48000, payloadType: 102, parameters: { apt: 100 } } ]

这里audio/red的parameters['100'] = 100表示 RED 冗余的“原始 payload type 映射”,告诉接收端 RED 的负载里可以携带哪种原始编码。这个写法和 RFC 2198 的封装一致。然后你还需要单独为 RED 流配一个 SSRC。如果对端不协商 RED,这个 codec 就会被忽略,不影响主媒体流。

FEC 的 ULPFEC 写法类似:

{ mimeType: 'video/ulpfec', clockRate: 90000, payloadType: 103, parameters: { apt: 96 } }

但请注意,ULPFEC 是否真能被对端解析,完全取决于解码器实现。我的建议是:在服务端能力列表里保留 ULPFEC 选项,但不要强制要求所有终端都支持。这属于“有更好,没有也接受”。非法强制会导致部分终端协商失败。

6.3 常见配置错误和检查方法

配置 RtpParameters 最常见的错误,我列一个速查表:

错误现象可能原因检查方法
Producer 一直不 readyencodings里没给 SSRC,或 SSRC 与 codec 不匹配看 mediasoup 日志里的 RtpParameters 回显
对端能听到声音但没画面视频 payload type 和 SDP 不一致抓包对比 SDP 里的 PT 与 RTP 头里的 PT
NACK 重传不生效video/rtx的apt指定错误打印 rtpParameters 检查 rtx 块
Simulcast 只有一层扩展头没带sdes:rtp-stream-id抓包看 RTP 扩展头里有没有 RID
DTMF 没声音telephone-event codec 没配置查看 RtpParameters.codecs 里是否有 telephone-event

检查配置时,最直接的办法不是反复重启服务,而是打印每个 Producer 创建后实际的rtpParameters,再跟对端 SDP 或抓包结果对照。Mediasoup 的日志信息非常详细,创建失败时会明确告诉你“invalid RTP parameters”之类的提示,顺着它指出的字段找问题,效率最高。

7. 排障速查表与经验总结

7.1 网络抓包经验:从 pcap 倒推流类型

遇到 RTP 流问题,我建议先别猜,直接抓包看 RTP。用 Tcpdump 或 Wireshark 抓对应端口的数据包,然后观察:

  1. 有没有 RTCP 包?如果有,说明对端至少在做基本控制和反馈。
  2. RTP 包的 payload type 是多少?和代码里配置的 PT 是否一致。
  3. RTP 头里有没有扩展?看扩展头 ID 能不能匹配你声明的 headerExtension 配置。
  4. 有没有多个 SSRC?如果有,可能是在跑 Simulcast 或 RTX。

比如你配置的是 VP8,但抓包看到 PT 96 的负载头第一个字节不是 VP8 描述符的期望值,那说明对端实际发送的编码不是 VP8,只是 PT 配成了 96。Wireshark 自带 RTP 解析器,能根据 PT 自动解析出编码类型。遇到“能通但内容花屏”的情况,先看这里,十有八九是编解码器不一致。

7.2 两边不匹配的排障

WebRTC 路径中,一个常见的坑是:Router 的能力列表和客户端本地的能力列表没有一个交集。排查顺序应该是这样的:

  1. 打印 Router 的rtpCapabilities,确认服务端支持哪些 codec。
  2. 打印客户端本地的rtpCapabilities,看它支持哪些 codec。
  3. 找交集。如果交集为空,问题就在能力列表配置上。
  4. 如果交集不为空但协商不出 Producer,再检查encodings和headerExtensions是否匹配。

我特别提醒一点:rtpCapabilities里的headerExtensions常常被人忽视。有些终端在协商时对 MID 扩展、RID 扩展要求严格,缺一个扩展就会导致 Producer 创建失败。尤其是 iOS Safari 的某些版本,对 MID 扩展的依赖很强,你在服务端能力里漏了sdes:mid,它干脆就不给你出流。

7.3 编码支持编译开关

Mediasoup 的 worker 在不同发行版本里,编译选项可能有差异。有的预编译包里只带 VP8、VP9、H264 和 Opus,不会自动支持 AV1。你需要确认两件事:

  • 你的 worker 版本对应的源码里,有没有开启av1相关开关。
  • 你的rtpCapabilities里是否包含了 AV1 codec。

如果你想测试 AV1,建议在当前环境先编译一个有完整 AV1 解码支持的 worker,再用 Chrome 或支持 AV1 的客户端去协商。如果协商出来的媒体流路径上,RTP 的 payload 内容和码流特征符合 AV1 封装,说明整个链路就是通的。否则就退回到 VP9,省心很多。

8. 一点实操心得

做 Mediasoup 项目这些年,我最大的体会是:RTP 流类型不是“配置项”,而是“你提供给整个媒体链路的地图”。地图画得越细,系统越稳;地图画错了,后面所有优化都是空中楼阁。所以无论你是刚开始搭建视频会议,还是在维护一个已经上线的直播系统,先花一天时间把自己所有 Producer、Consumer 的 RtpParameters 完整梳理一遍,比盲目调带宽和服务器参数有用得多。

另外,日志和抓包工具一定要提前备好。Mediasoup 自带日志质量很高,但很多人习惯只看应用层错误,不关注 worker 日志里关于 RTP 参数校验的警告。建议生产环境里把 worker 日志级别调到 debug,至少在排查问题时开起来。抓包工具优先选 Wireshark,它对 RTP 流的解析能力比大多数商业工具还直观。

最后分享一个小技巧:我在写对接模块时,都会先写一个“RtpParameters 自检函数”,把 codecs、encodings、rtcp、headerExtensions 的必备字段校验一遍,再把输出的 rtpParameters 和抓包结果对比。这样可以让很多人容易踩的坑在代码审查阶段就暴露出来,而不是等到线上音视频质量出问题了再满世界找原因。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询