收发器(Transceiver)机制:RTCRtpTransceiver 概念、addTrack 与 addTransceiver 的区别、方向属性

聊到 WebRTC 的媒体协商,有个概念你迟早得正面硬刚——收发器(Transceiver)。说实话,我早年刚接触 WebRTC 时,总觉得这玩意儿有点抽象。后来在做一个多人视频会议项目时,被一个诡异的“音频没声音、视频黑屏”的 bug 折磨了两天,最后发现是收发器方向属性配错了。嗯,从那以后,我再也不敢小看它了。

今天我们就来把 RTCRtpTransceiver 彻底讲透。包括它是什么、怎么用、以及那几个让人容易混淆的方向属性。

1. 什么是 RTCRtpTransceiver?

简单说,一个收发器就是一条“双向车道”。它同时管理一个发送器(RTCRtpSender)和一个接收器(RTCRtpReceiver)。

  • 发送器:负责把本地音视频数据编码后发出去。
  • 接收器:负责接收远端的音视频数据并解码。

收发器把这两个东西绑定在一起,统一管理。你想想看,每次媒体协商时,SDP 里每一行 m= 描述,背后其实就对应着一个收发器。

核心要点:一个 RTCRtpTransceiver 对应 SDP 中的一个媒体行(m-line)。它决定了这条媒体流是发、是收、还是双向。

我个人习惯把收发器理解成“一个媒体通道的控制器”。你不需要分别操作 sender 和 receiver,直接操作 transceiver 就够了。

2. addTrack 与 addTransceiver 的区别

这是面试常考题,也是实际开发中容易踩坑的地方。我们直接看对比。

特性 addTrack() addTransceiver()
返回值 RTCRtpSender RTCRtpTransceiver
是否自动创建接收器 是(隐式创建) 是(显式创建)
能否指定方向 不能(默认 sendrecv 能(可指定方向)
能否设置编码参数 不能 能(如码率、分辨率等)
适用场景 简单的一对一通话 复杂的多人会议、SFU 场景

addTrack 的用法

const sender = pc.addTrack(track, stream);
// 返回的是 sender,但内部自动创建了一个 transceiver
// 你无法直接控制这个 transceiver 的方向

addTransceiver 的用法

const transceiver = pc.addTransceiver('video', {
  direction: 'sendonly',
  streams: [stream],
  sendEncodings: [
    { maxBitrate: 500000 },
    { maxBitrate: 200000, scaleResolutionDownBy: 2 }
  ]
});
// 返回的是 transceiver,你可以直接控制方向、编码参数等

我在项目中遇到过一个问题:用 addTrack 添加了音频轨,结果对方一直收不到。排查了半天,发现是因为 addTrack 默认创建的是 sendrecv 方向,但我的场景只需要单向发送。换成 addTransceiver 并指定 sendonly 就解决了。

我的建议:除非你写 demo,否则优先用 addTransceiver。它给你更多控制权,后期调试也方便。

3. 方向属性:sendrecv / sendonly / recvonly / inactive

这四个方向属性,说白了就是告诉对方:“我这边的收发器,打算怎么用这条媒体通道。”

  • sendrecv:双向。我既能发也能收。这是默认值。
  • sendonly:只发不收。比如你只推流,不看对方的画面。
  • recvonly:只收不发。比如你只看别人的画面,自己不推流。
  • inactive:既不收也不发。相当于暂时禁用这条通道。

你可能会问:“方向属性到底在哪起作用?”

答案是:在 SDP 协商时。每个 m= 行里都有一个 a=sendrecv 或类似的属性行。双方交换 SDP 后,会根据各自的方向属性做“与运算”,得出最终的有效方向。

方向协商规则

  • 本地 sendonly + 远端 recvonly = 单向发送成功
  • 本地 sendrecv + 远端 sendrecv = 双向通信
  • 本地 sendonly + 远端 sendonly = 两边都想发,没人收,凉凉

我曾经在调试一个 SFU 转发服务时,发现某路视频流始终无法显示。查了 SDP 才发现,SFU 那边设置的是 recvonly,但客户端这边也是 recvonly——两边都只想收,没人发,当然没画面。改了一端为 sendonly 后,问题解决。

避坑指南

  • 不要滥用 inactive。我曾经用它来“暂停”一条流,结果重新激活时忘了改方向,导致后续协商一直失败。
  • 多人场景下,建议用 addTransceiver 并显式指定方向,避免默认 sendrecv 带来的带宽浪费。

4. 知识体系图:收发器核心逻辑

下面这张图帮你理清收发器、发送器、接收器以及方向属性的关系。

RTCRtpTransceiver 核心结构 RTCRtpTransceiver 方向: sendrecv | sendonly | recvonly | inactive RTCRtpSender RTCRtpReceiver 编码并发送本地音视频 接收并解码远端音视频 一个 Transceiver 管理一对 Sender + Receiver,对应 SDP 中的一个 m-line

从图中你可以看到:收发器是顶层控制器,它握着方向属性的开关,同时指挥着发送器和接收器干活。方向属性决定了这两个“小弟”谁上场、谁休息。

5. 实际开发中的选择建议

说了这么多,到底什么时候用 addTrack,什么时候用 addTransceiver

  • 简单的一对一通话:用 addTrack 就够了,代码简洁。
  • 多人会议、SFU 场景:必须用 addTransceiver。你需要精确控制每条流的方向和编码参数。
  • 需要动态切换方向:比如从“只看画面”切换到“我也要发言”,用 transceiver.direction 属性直接改,比重新 addTrack 方便得多。

小技巧:调试时,打印 pc.getTransceivers() 看看所有收发器的状态。我经常用这招排查“为什么对方收不到我的流”之类的问题。

好了,关于收发器机制,今天就聊到这。记住一句话:收发器是 WebRTC 媒体协商的基石。搞懂它,后面学 SDP 协商、Simulcast、SVC 都会轻松很多。


公众号:蓝海资料掘金营,微信deep3321