拓冰建站拓冰建站
首页 / 资讯中心 / 正文

RTMP转WebRTC低延迟直播实战:基于SRS的Docker部署与播放测试

之前做直播项目时业务方经常提一个需求“画面要低延迟最好控制在 1 秒以内但我们的摄像头和编码器只支持 RTMP 输出”。浏览器原生又不能直接播放 RTMPHTTP-FLV 延迟又不理想。后来在测试环境里用 SRS 把 RTMP 转成 WebRTC才把问题彻底打通。本文就用一套最小可复现的 rtmp2webrtc 测试环境带你从环境搭建、协议转换、推流验证到 WebRTC 播放完整跑一遍。文章会包含 Docker 部署、配置文件、ffmpeg 推流命令、网页播放器代码和常见排错思路适合刚接触流媒体转换、或者已经在做直播/监控项目想降低播放延迟的开发者。1. 背景与核心概念1.1 为什么需要 RTMP 转 WebRTC在真实项目中RTMP 和 WebRTC 经常是两个独立的技术栈但业务上却要把它们串起来。RTMP 目前依然是很多摄像头、编码器、推流软件的标准输出协议。传统 OBS、硬件编码器、监控摄像头几乎都支持 RTMP 推流。但浏览器端对 RTMP 的支持已经基本消失。以前用 Flash 播放 RTMP 已经过时现代浏览器不可能直接解析 RTMP 流。如果要实现“摄像头 RTMP 推流网页端低延迟观看”最直接的办法是在服务器上搭建一个中间层把 RTMP 接收下来再转换成浏览器能消费的流。这个中间层可以是 HTTP-FLV、HLS 或 WebRTC。HTTP-FLV 延迟虽然不错但相比 WebRTC 还是不够低HLS 延迟通常在 3 到 10 秒只适合点播或对延迟不敏感的场景。WebRTC 的优势在于基于 UDP 传输具备实时通信能力播放延迟通常可以控制在 1 秒以内。所以“RTMP 推流 WebRTC 播放”成了低延迟直播测试和落地的主流组合。1.2 RTMP 与 WebRTC 的核心差异搞清楚这两个协议的差异对理解转换原理很有帮助。RTMP 是基于 TCP 的实时消息传输协议。它把视频、音频、metadata 封装成 FLV 标签通过长连接推送到服务器。TCP 保证了可靠性但会有一定的缓冲和排队延迟。早期 RTMP 用于 Flash 播放器到现在依然是行业里最常见的推流协议。WebRTC 是一套浏览器实时通信标准主要使用 UDP 传输 RTP/RTCP 媒体数据。它通过 ICE 做连接协商用 SDP 交换媒体能力基于 SRTP 加密传输。浏览器原生支持 WebRTC所以不需要安装插件或 Flash。下面用表格对比会更直观对比项RTMPWebRTC传输层TCPUDP RTP/RTCP浏览器支持不支持原生支持典型角色推流端为主播放端/双向通话延迟范围2 到 5 秒0.2 到 1 秒加密可配默认 SRTP端口固定 1935动态 RTP 端口/UDP简单理解RTMP 在推流阶段有优势WebRTC 在播放和双向通信阶段有优势。服务器需要做的就是接收 RTMP 流解出 FLV 封装中的视频帧和音频帧再按 WebRTC/SDP 协商出来的能力重新封装成 RTP 包发送给浏览器。1.3 一套完整的 rtmp2webrtc 测试环境包含什么一套可复用的 rtmp2webrtc 测试环境通常由以下几个部分组成流媒体服务器承担 RTMP 接收和 WebRTC 转换任务例如 SRS。推流端使用 ffmpeg 拉取本地视频文件或者直接生成测试画面把 RTMP 流推到服务器。播放端一个普通的 Chrome/Edge 浏览器页面使用 WebRTC API 从服务器拉流。网络环境服务器需要开放 RTMP 的 TCP 端口、HTTP API 端口以及 WebRTC 媒体传输的 UDP 端口。本文选择 SRS 作为测试服务器因为它开源、部署简单并且原生支持 RTMP 与 WebRTC 的双向转换。你可能还听说过 Janus、mediacodec、GStreamer 等方案但 SRS 在中文社区资料比较丰富单机测试也最省事。2. 环境准备与版本说明2.1 操作系统与硬件要求测试环境不要求高性能服务器。本文的示例在 Linux 系统上演示如果你用的是云主机、虚拟机或本地 Linux 开发机都可以。常见环境如下操作系统Ubuntu 20.04 / 22.04CentOS 7 及以上或者兼容性较好的 Linux 发行版。CPU/内存2 核 4G 起步单路流媒体转换测试完全够用。浏览器Chrome 或 Edge 最新版本。如果你的测试机是国产化平台例如麒麟操作系统搭配飞腾/鲲鹏或兆芯等架构部署思路也类似。优先选择支持对应 CPU 架构的 Docker 镜像如果没有现成镜像可以改用源码编译方式。SRS 对 ARM64 的编译还算友好但具体还要按实际系统环境调整这里重点演示配置思路而不是特定发行版的命令。2.2 软件工具清单为了跑通测试环境我们需要的工具如下工具用途说明Docker / Docker Compose启动 SRS 服务快速、环境隔离ffmpeg模拟 RTMP 推流可使用虚拟视频源SRS 5流媒体服务器接收 RTMP 并转 WebRTCChrome 浏览器播放 WebRTC 流支持 WebRTC API版本说明SRS 5 开始完整支持 WebRTC 播放所以本文以 SRS 5 为例。SRS 6 也保留了类似配置细节上你可以参考官方文档做微调。不要盲目用 latest 镜像固定具体大版本更利于复现环境。如果你不希望安装 Docker也可以直接从源码编译 SRS但需要额外安装 Go、gcc、cmake 等依赖。本文优先使用 Docker 方案因为它可以把注意力集中在流媒体本身而不是编译环境上。2.3 测试目录规划建议在测试机上单独建立一个目录方便清理和备份。/data/rtmp2webrtc/ ├── conf/ │ └── srs.conf ├── pages/ │ └── player.html └── logs/conf 目录放 SRS 自定义配置文件。pages 目录放 WebRTC 播放页面。logs 目录留作日志备份。实际使用时按你的习惯调整目录位置。下面所有命令都默认在 /data/rtmp2webrtc 下执行。3. 核心原理拆解3.1 SRS 在测试环境中的双重角色SRSSimple Realtime Server在本文测试环境中承担两个角色RTMP 流媒体服务器监听 1935 端口接收 ffmpeg 或其他推流端发来的 RTMP 流。WebRTC 网关通过 HTTP 信令接口与播放端交换 SDP通过 UDP 端口传输 RTP 媒体数据。当 ffmpeg 推流到 SRS 后SRS 内部会生成一条流。这条流先以 RTMP/FLV 的形式存在服务器上。当播放端发起 WebRTC 请求时SRS 读取这条流中的 H.264 视频帧把它们封装到 RTP 包中通过协商好的 UDP 路径发送给浏览器。这里有一个关键点RTMP 到 WebRTC 的转换并不等于转码。默认情况下SRS 不改变视频编码格式而是做“转封装/转协议”。如果推流端编码是 H.264WebRTC 播放端同样收到的也是 H.264。转码会消耗大量 CPU所以通常只在编码格式不兼容时才考虑。3.2 WHIP 与 WHEP 信令流程WebRTC 连接并不是“服务器直接往浏览器扔数据”它需要先完成信令协商。两端要先交换 SDPSession Description Protocol告诉对方自己支持什么编码、用什么 IP 和端口通信。SRS 5 支持两类标准信令接口WHIPWebRTC-HTTP Ingest Protocol用于 WebRTC 推流。WHEPWebRTC-HTTP Egress Protocol用于 WebRTC 拉流播放。本文场景是浏览器拉流所以走 WHEP 流程。大致步骤如下浏览器创建一个 RTCPeerConnection 对象。调用 createOffer 生成自己的 SDP offer。把 SDP offer 通过 HTTP POST 发送到 SRS 的 WHEP 地址。SRS 返回 SDP answer。浏览器把 answer 写入 remoteDescription。双方通过 ICE 完成 UDP 连接。浏览器收到 RTP 媒体数据并渲染到 video 标签。整个流程看似复杂但 SRS 简化了大部分服务端逻辑浏览器侧使用原生 WebRTC API 就能完成。3.3 媒体格式兼容性注意事项WebRTC 对音频编码有比较强的偏好。浏览器普遍支持 Opus而 RTMP 推流端很多默认使用 AAC。SRS 在某些版本中支持 AAC 到 Opus 的转换但不同版本和配置下行为可能不一样。为了减少测试环节的不确定性我们可以在 ffmpeg 推流时直接输出 H.264 视频和 AAC 音频如果播放端没有声音再尝试推 Opus 音频。视频方面H.264 是最稳妥的 WebRTC 播放格式几乎所有浏览器都能硬解。如果你推流端输出的是 H.265浏览器 WebRTC 默认不支持通常需要先转成 H.264。这一点在视频监控项目中尤其常见。4. 完整实战搭建 rtmp2webrtc 测试环境4.1 编写 SRS 配置文件首先创建配置文件 /data/rtmp2webrtc/conf/srs.conf。# 文件路径/data/rtmp2webrtc/conf/srs.conf listen 1935; max_connections 1000; daemon off; srs_log_tank console; http_api { enabled on; listen 1985; } http_server { enabled on; listen 8080; dir ./objs/nginx/html; } rtc_server { enabled on; listen 8000; candidate $CANDIDATE; } vhost __defaultVhost__ { rtc { enabled on; } }配置说明listen 1935RTMP 接收端口。http_api.listen 1985HTTP API 端口用于查看流信息、调用管理接口。http_server.listen 8080HTTPS 静态文件服务端口方便直接访问播放页面。rtc_server.listen 8000WebRTC RTP/RTCP 媒体传输的 UDP 监听端口。rtc_server.candidateSRS 向播放端通告的 IP 地址。这里写成 $CANDIDATE 是为了通过环境变量动态注入你也可以在测试环境里直接写成服务器实际 IP。candidate 配置是 rtmp2webrtc 测试环境中最容易踩坑的地方。如果服务器是公网机器或云主机candidate 必须配置成客户端能访问到的 IP。如果配置成了 127.0.0.1 或内网 IP浏览器虽然完成了信令协商但媒体包可能永远到不了播放端。4.2 Docker 启动 SRS进入测试目录执行以下命令启动 SRS 容器。假设你的服务器 IP 是 192.168.1.100请替换成实际环境。cd /data/rtmp2webrtc docker run -d --name srs \ -p 1935:1935 \ -p 1985:1985 \ -p 8080:8080 \ -p 8000:8000/udp \ -v /data/rtmp2webrtc/conf/srs.conf:/usr/local/srs/conf/srs.conf \ -e CANDIDATE192.168.1.100 \ ossrs/srs:5执行后查看容器日志docker logs -f srs如果启动正常日志中会显示配置加载成功并出现 RTMP、HTTP API、RTC Server 的监听信息。看到类似下面的内容时说明服务已经起来了rtmp: listen tcp://0.0.0.0:1935 http api: listen tcp://0.0.0.0:1985 rtc_server: listen udp://0.0.0.0:8000注意容器的 8000 端口映射方式与普通 TCP 端口不同需要显式声明为 udp。漏掉 /udp 声明会导致浏览器可以完成信令协商但媒体数据无法传输播放画面会一直黑屏。4.3 验证 SRS 服务是否正常在浏览器访问下面的地址如果能打开 SRS 的默认 HTTP 页面说明静态服务器正常http://192.168.1.100:8080/访问 HTTP API 查看流列表curl http://192.168.1.100:1985/api/v1/streams此时还没有推流返回结果中 streams 数组应该为空。这个接口在后面可以用来确认流是否成功推上来。4.4 使用 ffmpeg 模拟 RTMP 推流为了方便测试我使用 ffmpeg 的 lavfi 虚拟设备生成画面和声音不需要准备真实视频文件。先测试视频和音频均为 H.264 AAC 的推流命令ffmpeg -re -f lavfi -i testsrcsize1280x720:rate25 \ -f lavfi -i sinefrequency1000:sample_rate44100 \ -c:v libx264 -preset veryfast -b:v 1500k \ -c:a aac -b:a 128k \ -f flv rtmp://192.168.1.100:1935/live/livestream命令参数解释-re以实时速率读取输入模拟真实推流。testsrcffmpeg 内置测试画面画面带有彩色条和时间信息。sine1kHz 正弦波模拟音频。libx264H.264 编码。aacAAC 音频编码。rtmp://192.168.1.100:1935/live/livestream推流地址其中 live 是应用名livestream 是流名。如果你希望验证 WebRTC 音频兼容性可以把音频编码改成 Opusffmpeg -re -f lavfi -i testsrcsize1280x720:rate25 \ -f lavfi -i sinefrequency1000:sample_rate44100 \ -c:v libx264 -preset veryfast -b:v 1500k \ -c:a libopus -b:a 64k \ -f flv rtmp://192.168.1.100:1935/live/livestream当 ffmpeg 成功推流后再次查看流列表curl http://192.168.1.100:1985/api/v1/streams如果返回结果表明存在 live/livestream 这条流说明 RTMP 推流已经成功。4.5 编写 WebRTC 播放页面下面创建一个最简单的 WebRTC 播放页面。这个页面不依赖任何第三方库使用浏览器原生 API 和 SRS 的 WHEP 接口完成拉流。文件路径/data/rtmp2webrtc/pages/player.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleSRS WebRTC 播放测试/title style body { font-family: Arial, sans-serif; padding: 20px; } video { width: 640px; background: #000; } /style /head body h2RTMP 转 WebRTC 播放测试/h2 video idplayer autoplay controls muted/video p idstatus正在连接.../p script const video document.getElementById(player); const status document.getElementById(status); // 改成你的服务器实际地址 const whepUrl http://192.168.1.100:1985/rtc/v1/whep/?applivestreamlivestream; async function startPlayback() { // 1. 创建 RTCPeerConnection const pc new RTCPeerConnection({ iceServers: [{ urls: stun:stun.l.google.com:19302 }] }); // 2. 声明接收音视频 pc.addTransceiver(video, { direction: recvonly }); pc.addTransceiver(audio, { direction: recvonly }); // 3. 收到媒体流后渲染到 video pc.ontrack (event) { if (event.streams event.streams[0]) { video.srcObject event.streams[0]; status.textContent WebRTC 播放中; } }; // 4. 创建并设置本地 SDP const offer await pc.createOffer(); await pc.setLocalDescription(offer); // 5. 通过 WHEP 接口发送给 SRS const resp await fetch(whepUrl, { method: POST, headers: { Content-Type: application/sdp }, body: offer.sdp }); if (!resp.ok) { throw new Error(WHEP 请求失败: resp.status await resp.text()); } // 6. 设置远端 SDP const answerSdp await resp.text(); await pc.setRemoteDescription({ type: answer, sdp: answerSdp }); } startPlayback().catch((err) { status.textContent 播放失败: err.message; console.error(err); }); /script /body /html把这个页面放到 SRS 的 HTTP 服务目录下或者单独用 Nginx 托管。最简单的方式是直接使用 Docker 命令拷贝到容器内。但更推荐在宿主机上通过 Nginx 或 Python HTTP 服务访问避免修改容器内部文件造成环境不可恢复。如果使用 SRS 的 8080 静态服务可以直接把页面放到容器映射目录中。不过上面的配置里 http_server 的 dir 指向的是容器内的 ./objs/nginx/html因此更方便的办法是把 player.html 放到 SRS 的 html 目录然后在浏览器访问http://192.168.1.100:8080/player.html拷贝方式docker cp /data/rtmp2webrtc/pages/player.html srs:/usr/local/srs/objs/nginx/html/player.html4.6 运行与验证在浏览器打开 player.html如果一切正常你会看到测试画面同时页面提示“WebRTC 播放中”。验证要点画面是否流畅延迟是否符合预期。声音是否正常。如果你用 AAC 推流没有声音可以换成 Opus 推流命令再试。浏览器控制台没有 WebRTC 连接失败的错误。为了更直观地观察延迟ffmpeg 生成的 testsrc 画面上带有时间信息。你用手机秒表或另一台显示当前时间的设备对比一下就能粗略判断延迟高低。正常情况下延迟通常在 1 秒以内。5. 常见问题与排查思路5.1 问题速查表问题现象常见原因解决思路页面一直转圈无法播放candidate 配置错误将 srs.conf 中 candidate 改成客户端可达 IP信令正常但画面黑屏UDP 8000 端口不通检查云安全组和服务器防火墙放通 UDP 8000浏览器报 CORS 错误播放页面与 API 不同源用 Nginx 将页面和 /rtc/v1/ 接口代理到同一域名ffmpeg 推流失败RTMP 端口未放通检查 TCP 1935 端口有画面没有声音音频编码不兼容试试推 Opus 音频或检查 SRS 是否启用转码延迟很高是否真正建立了 WebRTC 连接检查页面是不是走了 HTTP-FLV/HLS 回流5.2 典型问题信令通了但画面一直黑屏这是 rtmp2webrtc 测试环境中最常见的问题。现象是浏览器控制台没有报错WHEP 请求成功返回RTCPeerConnection 状态也正常但 video 标签一直黑屏。根本原因通常是 UDP 媒体端口不通。WebRTC 的信令走 HTTP只要 TCP 1985 端口能访问信令流程就会成功。但真正的视频数据是走 UDP 8000 端口发送的。如果服务器防火墙或云安全组没有放通 UDP 8000媒体包就会被丢弃。排查顺序查看 SRS 容器是否监听 UDP 8000docker exec -it srs ss -lunp | grep 8000在客户端测试 UDP 端口是否可达。UDP 没有 telnet 那样的三次握手但可以用 nc 做简单验证nc -vuz 192.168.1.100 8000这个命令能证明 UDP 包是否有去有回不过不同系统下结果不一定绝对准确。更可靠的判断方式是在 SRS 容器里开启详细日志观察是否有 RTP 包进来。检查云厂商安全组。云主机通常有两个防火墙层级操作系统内部防火墙firewalld/iptables和安全组。两个地方都要放通 UDP 8000。解决方案在防火墙中放通 UDP 8000然后重启 SRS 容器并重新播放验证。5.3 典型问题CORS 跨域错误当你用浏览器直接访问另一个端口的 WHEP 接口时如果 SRS 没有返回正确的 Access-Control-Allow-Origin 响应头控制台会报跨域错误播放自然失败。规避方法有两种。第一种把播放页面放到 SRS 的 8080 端口静态目录中但 API 是 1985 端口这仍然是跨域。所以更推荐第二种。第二种用 Nginx 做反向代理。把页面和 API 统一暴露在同一个域名和端口下。Nginx 配置示例server { listen 80; server_name 192.168.1.100; location /rtc/ { proxy_pass http://127.0.0.1:1985; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } }配置完成并重启 Nginx 后播放页面要改成访问 Nginx 的地址const whepUrl http://192.168.1.100/rtc/v1/whep/?applivestreamlivestream;页面本身通过http://192.168.1.100/player.html访问。这样页面和接口就在同一个源下不会有跨域问题。5.4 典型问题Candidate 地址不对SRS 的 rtc_server.candidate 决定了 SRS 在 SDP 中通告给浏览器的媒体连接地址。如果这个地址是内网 IP 或者 127.0.0.1浏览器会尝试往这个地址发媒体数据自然连不上。在 Docker 部署方式下还需要注意容器网络模式。如果你把 candidate 设成了容器内部 IP浏览器访问不到。正确做法是设置成宿主机可访问的 IP 或域名。如果部署在云主机上candidate 可以填云主机私网 IP也可以填公网 IP。这里没有绝对标准原则只有一个客户端能够访问到该地址。对于公网播放场景candidate 必须配置成公网 IP。修改配置文件后一定要重启容器docker restart srs然后重新打开播放页面测试。6. 最佳实践与工程建议6.1 测试环境与生产环境的差异测试环境跑通后不代表生产环境可以直接按同样方式部署。下面几点在工程上尤其值得注意。生产环境建议使用固定版本镜像而不是 latest。Docker 镜像的 latest 标签会随时间变化容易导致测试和生产版本不一致。推荐在 Docker Compose 文件里写死镜像 tag。生产环境还要考虑域名和 HTTPS。浏览器对 WebRTC 的限制非常严格如果页面通过 HTTPS 访问媒体流的信令和传输会有更统一的兼容性。虽然这个纯拉流示例不一定强制要求 HTTPS但在真实业务中页面和 API 都建议统一走 HTTPS/WSS。6.2 配置管理SRS 配置文件应该纳入版本管理。不同环境使用不同配置文件例如srs.conf公共配置。srs.conf.test测试环境配置。srs.conf.prod生产环境配置。candidate 这类和环境强相关的配置不要写入公共配置应该通过环境变量或部署管道注入。这样同一份配置可以在不同环境之间切换不容易出现“本地能跑线上黑屏”的问题。6.3 安全与访问控制SRS 默认的 HTTP API 端口如果直接暴露到公网任何人都有可能调用接口查看流信息或执行管理操作。测试环境问题不大但生产环境至少做好两步限制 API 端口访问来源例如只允许管理网段访问 1985。对播放和推流做鉴权SRS 支持回调鉴权可以在推流或播放时通过 HTTP 回调到业务服务器校验身份。涉及服务器防火墙和云安全组配置时遵循最小权限原则。只放通必要的端口不要顺手放行整个网段更不要在生产环境关闭防火墙做测试。6.4 性能与稳定性媒体服务器对 UDP 端口和网络质量比较敏感。大规模并发播放时UDP 8000 端口会成为瓶颈单节点能力有限。建议在接入生产流量前做压测观察 CPU、内存和带宽占用。时间同步也是一个容易被忽视的点。WebRTC 对时间戳和 RTCP 反馈有要求服务器时间漂移会影响播放稳定性。部署服务后建议开启 NTP 时间同步。如果测试环境是国产化平台例如麒麟系统 ARM 架构部署前先确认镜像是否支持对应 CPU 架构。不支持的架构就用源码编译不要盲目用docker pull避免启动时出现 exec format error。7. 总结与下一步这篇文章从零搭建了一套 rtmp2webrtc 测试环境核心内容包括RTMP 与 WebRTC 的协议差异。SRS 如何接收 RTMP 流并转换给浏览器。Docker 启动 SRS 的具体步骤。ffmpeg 推流命令和 WebRTC 网页播放器。黑屏、CORS、candidate 配置错误的排查方法。把这套环境跑通之后你可以继续往更多方向深入例如测试 SRT 协议接入、WebRTC 推流、多路流转发、集群部署、鉴权回调等。RTMP 转 WebRTC 只是流媒体接入层的一个环节掌握它以后再去理解低延迟直播的全链路架构就不会觉得有障碍了。如果你在搭建过程中遇到其他问题建议先把容器日志打开再按“信令是否通过、媒体端口是否放通、candidate 是否正确、编码格式是否兼容”的顺序逐步排查。这套思路能覆盖大部分测试环境中的故障场景。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门