25、直播推流:RTMP转WebRTC、HLS转WebRTC、低延迟直播

各位同学,今天我们来聊聊直播推流里一个绕不开的话题——协议转换。

做移动端直播,你肯定会遇到一个尴尬:服务端推过来的是 RTMP 或 HLS,但 WebRTC 只认自己的 SDP 和 ICE 那一套。怎么让它们“对话”?这就是我们今天要啃的硬骨头。

我个人习惯把这类问题分成两类:协议桥接实时转码。说白了,要么在中间搭个翻译官,要么直接把视频流“重包装”成 WebRTC 能吃的格式。

RTMP 转 WebRTC:老协议的新活法

RTMP 是 Adobe 家的老协议,延迟低(1-3秒),但依赖 Flash,移动端支持很差。WebRTC 延迟更低(200-500ms),而且原生支持浏览器和 App。

我在项目中遇到过最典型的场景:客户原有的直播系统全是 RTMP 推流,但新业务要求观众端用 WebRTC 拉流,延迟要控制在 1 秒以内。

怎么做?核心思路是:把 RTMP 流解封装,再喂给 WebRTC 的 PeerConnection

关键步骤:

  1. 用 FFmpeg 或 librtmp 接收 RTMP 流,提取出 H264 视频和 AAC 音频。
  2. 将 H264 封装成 RTP 包(WebRTC 内部走的就是 RTP)。
  3. 通过 WebRTC 的 addTrack 或自定义 MediaStream 推给 PeerConnection。

嗯,这里要注意:RTMP 的 FLV 封装和 WebRTC 的 RTP 封装,时间戳映射是个坑。RTMP 用毫秒,RTP 用 90kHz 时钟。我曾经因为忘了做这个转换,导致视频播放时音画不同步,排查了整整一下午。

// 伪代码:RTMP 到 WebRTC 的转换核心
// 假设你已经用 FFmpeg 解码出了 AVFrame
// 1. 创建 WebRTC 的 VideoTrack
const videoTrack = new MediaStreamTrack();
// 2. 将解码后的帧塞进去
videoTrack.onFrame = (frame) => {
  // 注意:这里需要做时间戳转换
  frame.timestamp = rtmpTimestamp * 90; // 毫秒转 90kHz
  peerConnection.addTrack(videoTrack);
};

我的经验:不要自己写 RTMP 解析库,直接用 FFmpeg 的 libavformat。它已经处理了 FLV 的边界情况,比如音频头缺失、视频关键帧间隔不固定等问题。我踩过这个坑,自己解析 RTMP 时遇到过一个奇葩推流端,每 10 秒才发一个关键帧,导致 WebRTC 端黑屏 10 秒。

HLS 转 WebRTC:延迟的妥协

HLS 是苹果家的协议,兼容性最好,但延迟高(10-30秒)。为什么还要转?因为很多 CDN 只支持 HLS 分发,或者你的上游源站只输出 HLS。

说白了,HLS 转 WebRTC 就是把 TS 或 fMP4 片段实时拼接成连续流

我记得有一次做教育直播,客户要求延迟低于 3 秒,但他们的源站只输出 HLS。我当时的方案是:

  1. fetchXMLHttpRequest 实时拉取最新的 .m3u8 文件。
  2. 解析出最新的 TS 片段 URL,下载并解封装。
  3. 把解出来的视频帧直接喂给 WebRTC 的 RTCRtpSender

这里有个关键点:HLS 的片段通常是 2-10 秒一个,如果你等整个片段下载完再推,延迟就上去了。我建议的做法是边下载边推——TS 文件内部是 GOP 结构,你可以解析出每个帧的起始位置,下载到一帧就推一帧。

避坑指南:我曾经因为 HLS 的片段边界处理不当,导致 WebRTC 端出现花屏。原因是 HLS 的 TS 片段可能以非关键帧开头,直接推给 WebRTC 会导致解码器状态错乱。解决方案:在片段切换时,强制 WebRTC 发送关键帧请求(PLI)。

// HLS 转 WebRTC 的片段处理逻辑
function onNewSegment(segmentUrl) {
  fetch(segmentUrl).then(response => {
    const reader = response.body.getReader();
    // 边读边解析 TS 包
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      // 解析出 NAL 单元
      const nals = parseTS(value);
      for (const nal of nals) {
        // 如果是关键帧,先发一个 PLI
        if (nal.type === 'IDR') {
          videoSender.sendRTCP({ type: 'PLI' });
        }
        videoSender.sendRTP(nal);
      }
    }
  });
}

低延迟直播:WebRTC 的终极形态

为什么我们费这么大劲转来转去?因为 WebRTC 本身就是为低延迟设计的。RTMP 能做到 1-3 秒,HLS 能做到 10-30 秒,而 WebRTC 可以做到 200-500ms

你想想看,在互动直播、在线教育、远程医疗这些场景里,500ms 和 3 秒的体验差距是巨大的。我做过一个在线钢琴教学项目,如果延迟超过 1 秒,老师和学生根本没法同步弹奏。

实现低延迟直播,除了协议转换,还有几个关键点:

  • 编码器设置:使用 H264 的 baselineconstrained-baseline profile,关闭 B 帧,减少编码延迟。
  • 网络自适应:WebRTC 内置了拥塞控制(GCC),但你需要调整 maxBitrateminBitrate,避免在弱网下过度降质。
  • 丢包重传:NACK 和 FEC 要配合使用。我习惯在丢包率低于 5% 时只用 NACK,高于 5% 时开启 FEC。

低延迟直播的核心公式:

延迟 = 编码延迟 + 网络传输延迟 + 抖动缓冲延迟 + 解码延迟

WebRTC 的优化空间主要在抖动缓冲网络传输。你可以通过调整 jitterBufferTarget 来降低缓冲延迟,但要注意不要设得太低,否则会导致频繁卡顿。

下面这张图展示了 RTMP、HLS 和 WebRTC 在直播链路中的位置,以及它们之间的转换关系:

直播推流协议转换架构 推流端(OBS/手机) RTMP 服务器 HLS 服务器 WebRTC 服务器 RTMP→WebRTC 解FLV→封装RTP HLS→WebRTC 拉TS→实时拼接 观众端 WebRTC 拉流 延迟对比 RTMP: 1-3s HLS: 10-30s WebRTC: 0.2-0.5s 注:实际延迟受网络、编码参数、服务器性能影响 核心思路:协议转换 + 实时转码 + 低延迟优化

从图中你可以看到,RTMP 和 HLS 都需要经过一个转换模块才能到达 WebRTC 观众端。这个转换模块就是我们要实现的核心。

我的建议:如果条件允许,尽量直接使用 WebRTC 推流,避免协议转换带来的额外延迟和复杂度。但现实是,很多现有系统已经跑着 RTMP 或 HLS,这时候转换就是必要的妥协。

另外,如果你用的是 iOS 端,记得在 RTCPeerConnectioniceServers 里配置 TURN 服务器。我遇到过很多次,因为 NAT 穿透失败导致 WebRTC 连接不上,最后发现是没配 TURN。

好了,关于 RTMP 转 WebRTC、HLS 转 WebRTC 以及低延迟直播的核心思路,今天就聊到这里。这些方案我都实际落地过,坑也踩了不少,希望你能少走弯路。


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