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。
关键步骤:
- 用 FFmpeg 或 librtmp 接收 RTMP 流,提取出 H264 视频和 AAC 音频。
- 将 H264 封装成 RTP 包(WebRTC 内部走的就是 RTP)。
- 通过 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。我当时的方案是:
- 用
fetch或XMLHttpRequest实时拉取最新的 .m3u8 文件。 - 解析出最新的 TS 片段 URL,下载并解封装。
- 把解出来的视频帧直接喂给 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 的
baseline或constrained-baselineprofile,关闭 B 帧,减少编码延迟。 - 网络自适应:WebRTC 内置了拥塞控制(GCC),但你需要调整
maxBitrate和minBitrate,避免在弱网下过度降质。 - 丢包重传:NACK 和 FEC 要配合使用。我习惯在丢包率低于 5% 时只用 NACK,高于 5% 时开启 FEC。
低延迟直播的核心公式:
延迟 = 编码延迟 + 网络传输延迟 + 抖动缓冲延迟 + 解码延迟
WebRTC 的优化空间主要在抖动缓冲和网络传输。你可以通过调整 jitterBufferTarget 来降低缓冲延迟,但要注意不要设得太低,否则会导致频繁卡顿。
下面这张图展示了 RTMP、HLS 和 WebRTC 在直播链路中的位置,以及它们之间的转换关系:
从图中你可以看到,RTMP 和 HLS 都需要经过一个转换模块才能到达 WebRTC 观众端。这个转换模块就是我们要实现的核心。
我的建议:如果条件允许,尽量直接使用 WebRTC 推流,避免协议转换带来的额外延迟和复杂度。但现实是,很多现有系统已经跑着 RTMP 或 HLS,这时候转换就是必要的妥协。
另外,如果你用的是 iOS 端,记得在 RTCPeerConnection 的 iceServers 里配置 TURN 服务器。我遇到过很多次,因为 NAT 穿透失败导致 WebRTC 连接不上,最后发现是没配 TURN。
好了,关于 RTMP 转 WebRTC、HLS 转 WebRTC 以及低延迟直播的核心思路,今天就聊到这里。这些方案我都实际落地过,坑也踩了不少,希望你能少走弯路。