
简介webrtc-streamer 是面向 WebRTC 实时音视频开发者的开源流媒体服务器预编译包适用于在线会议、远程教育、协同办公等需要浏览器端低延迟音视频传输的场景帮助开发者快速搭建可用的流媒体服务环境。压缩包共 94 个文件约 7.29MB以 29 个 js 脚本、23 个 html 页面、5 个 css 样式及 woff、svg、eot 等前端资源为主另含 wasm、tfjs、posenet、coco-ssd、blazeface、deeplab、body-pix 等模型文件以及 exe 可执行程序、config.json 配置、Procfile 与说明文档覆盖服务端运行与浏览器端演示的完整结构。已有 856 人学习下载。通过该包可了解 WebRTC 对等连接、信令交换、ICE/STUN/TURN 穿透与数据通道等核心机制并借助预置示例页面与模型资源快速验证视频流拉取、AI 视觉推理与前端交互效果为调试和优化实时通信性能提供可复用的参考。1. 浏览器里跑起 RTSPwebrtc-streamer 到底解决了谁的痛手头有一批网络摄像机RTSP 地址在 VLC 里播得好好的可一旦要嵌到浏览器页面里就卡住了——Chrome 不支持 RTSPSafari 也不支持Flash 早就退役H5 的 video 标签只认 HTTP 渐进下载和 HLS。这个场景下webrtc-streamer 就是那个把 RTSP 转成 WebRTC 流、让浏览器原生播放的中间件。它用 C 写成内部依赖 libwebrtc 做媒体协商对外暴露一个轻量 HTTP 服务前端引一个 JS 文件就能把 RTSP 源拉进 video 标签。适合谁做安防监控面板、工业视觉看板、远程设备预览的开发者尤其是那些不想在服务端做转码、又希望延迟压到几百毫秒以内的场景。它不解决录像存储也不做 AI 分析只干一件事把 RTSP 流搬进浏览器搬得稳、搬得快。2. 拆开 webrtc-streamer信令、转发与 ICE 的三段式结构2.1 从 RTSP 到 WebRTC 的数据链路webrtc-streamer 的核心逻辑分三段。第一段是 RTSP 拉流进程启动后按需连接摄像机用 FFmpeg 的 libavformat 解析 RTP 包拿到 H.264 或 H.265 裸流。第二段是媒体转发把裸流喂给 libwebrtc 的 VideoTrack由它做 RTP 打包、时间戳对齐和 NACK/PLI 重传。第三段是信令交换浏览器通过 WebSocket 发 offerwebrtc-streamer 回 answer双方走标准 ICE 流程建立 PeerConnection。整个过程没有转码H.264 进来还是 H.264 出去CPU 占用主要花在 RTP 封包和 ICE 候选收集上。为什么选 WebRTC 而不是 HLSHLS 延迟通常在 5 到 15 秒切片越小延迟越低但请求数暴涨WebRTC 走 UDP配合 jitter buffer 能把延迟压到 200 到 500 毫秒。代价是 WebRTC 对网络抖动更敏感丢包超过 10% 画面就会花。常见做法是在内网或专线环境用 WebRTC跨公网时配合 TURN 中继但 TURN 会引入额外跳数延迟反而可能超过低延迟 HLS。所以选型前先问自己延迟要求到底是多少如果 3 秒能接受HLS 更省心如果必须 1 秒以内WebRTC 是唯一选择。2.2 编译与启动参数怎么设才不翻车webrtc-streamer 官方提供预编译二进制但生产环境建议自己编因为默认编译可能没开 H.265 或没带特定硬件加速。编译前先确认系统有 CMake 3.10、GCC 7 和 libssl-dev。下面是我在 Ubuntu 20.04 上跑通的流程# 安装依赖libssl 用于 WebSocket 的 wss 支持 sudo apt update sudo apt install -y cmake g libssl-dev libavcodec-dev libavformat-dev libavutil-dev # 拉源码注意用 --recursive 拉子模块libwebrtc 在子模块里 git clone --recursive https://github.com/xxx/webrtc-streamer.git cd webrtc-streamer # 创建构建目录Release 模式开 -O2Debug 模式体积大且慢 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DENABLE_H265ON make -j$(nproc)编译完成后生成webrtc-streamer可执行文件。启动时最关键的参数是-H指定 HTTP 监听端口-S指定 STUN 服务器地址。如果摄像机在同一个局域网STUN 可以填内网地址跨网段则填公网 STUN但注意不要用需要认证的 STUN 服务webrtc-streamer 不支持带凭据的 STUN。# 监听 8000 端口STUN 指向内网一台可达的服务器 ./webrtc-streamer -H 0.0.0.0:8000 -S stun:192.168.1.100:3478 # 如果摄像机 RTSP 地址带用户名密码在 URL 里直接写 # rtsp://admin:pass123192.168.1.64:554/Streaming/Channels/101启动后访问http://localhost:8000/webrtcstreamer.html?videortsp://...就能看到画面。注意 URL 里的 RTSP 地址必须做 URL 编码否则?和会被 HTTP 服务截断。我一般用 Python 的urllib.parse.quote先处理一遍from urllib.parse import quote rtsp rtsp://admin:pass123192.168.1.64:554/Streaming/Channels/101 encoded quote(rtsp, safe) print(fhttp://localhost:8000/webrtcstreamer.html?video{encoded})参数说明-H的0.0.0.0表示监听所有网卡生产环境建议绑具体 IP-S可以多次指定webrtc-streamer 会按顺序尝试-T指定 TURN 服务器格式是turn:user:passhost:port但 TURN 只在 ICE 直连失败时启用不要一上来就配 TURN否则所有流量都走中继延迟反而高。2.3 前端集成video 标签与 JS API 的配合前端集成有两种方式。最简单的是用官方提供的webrtcstreamer.js它封装了 WebSocket 信令和 video 标签的绑定!DOCTYPE html html head script srchttp://localhost:8000/webrtcstreamer.js/script /head body video idvideo autoplay muted playsinline/video script // 实例化时传入 webrtc-streamer 的服务地址 const webRtcServer new WebRtcStreamer(video, http://localhost:8000); // connect 方法触发信令参数是 RTSP 地址和音频开关 webRtcServer.connect(rtsp://admin:pass123192.168.1.64:554/Streaming/Channels/101, null, stun:192.168.1.100:3478); /script /body /htmlconnect的第三个参数是 STUN 地址如果服务端启动时已经指定了-S这里可以传null。muted属性必须加否则 Chrome 的自动播放策略会拦截。playsinline在 iOS Safari 上必须加不然视频会全屏播放。另一种方式是直接调 WebSocket API适合需要自定义信令逻辑的场景。webrtc-streamer 的 WebSocket 端点默认在ws://localhost:8000/ws消息格式是 JSON包含type、sdp、id等字段。自己实现的话需要处理 offer/answer 的 SDP 交换和 ICE 候选的 trickle。我一般不建议自己写除非要集成到已有的信令框架里因为 libwebrtc 的 SDP 格式在不同版本间有差异webrtc-streamer 的 JS 库已经做了兼容。3. 避坑指南RTSP 地址编码、H.265 兼容与 ICE 失败排查3.1 坑一RTSP 地址里的特殊字符导致 404现象浏览器控制台报Failed to load resource: the server responded with a status of 404但 RTSP 地址在 VLC 里能播。原因RTSP URL 里的?、、在 HTTP 查询字符串里有特殊含义webrtc-streamer 的 HTTP 服务按?video后面的内容解析如果 RTSP 地址本身带?解析就会截断。解决对 RTSP 地址做完整 URL 编码quote(rtsp, safe)里的safe表示连/和:都编码。编码后的地址在服务端会自动解码不影响 RTSP 连接。3.2 坑二H.265 流在 Chrome 里黑屏现象webrtc-streamer 日志显示流已连接但 Chrome 的 video 标签一直黑屏Safari 却正常。原因Chrome 对 H.265 的 WebRTC 支持不完整libwebrtc 默认编译没开 H.265 的 RTP 打包。Safari 因为系统级支持 H.265 硬解所以能播。解决编译时加-DENABLE_H265ON并且确认摄像机输出的是 H.265 的 baseline profile。如果摄像机只支持 main profileChrome 仍然可能解不了。最稳的办法是让摄像机同时输出 H.264 和 H.265 两路流WebRTC 用 H.264录像用 H.265。3.3 坑三ICE 候选收集失败导致连接超时现象前端connect后一直停在connecting状态webrtc-streamer 日志里没有ICE connection state: connected。原因STUN 服务器不可达或者服务端和客户端不在同一个 NAT 后host 候选无法直连。解决先确认-S指定的 STUN 地址能 ping 通。如果在内网STUN 可以填服务端自己的 IPwebrtc-streamer 会返回 host 候选。跨公网时如果双方都在对称 NAT 后必须配 TURN。TURN 服务器可以用 coturn 自建配置里开lt-cred-mech和realmwebrtc-streamer 启动时用-T turn:user:passhost:port指定。3.4 坑四多路流并发时 CPU 飙升现象单路流 CPU 占用 5%开到 10 路时 CPU 跑到 80%画面开始卡顿。原因webrtc-streamer 每路流独立跑一个 libwebrtc 的 PeerConnectionRTP 封包和 ICE 保活都是单线程。10 路流意味着 10 个线程在抢 CPU。解决一是开硬件加速编译时加-DENABLE_VAAPION或-DENABLE_NVENCON让 GPU 做 H.264 解码和缩放二是限制单进程的流数量超过 8 路就起多个 webrtc-streamer 实例用 Nginx 做反向代理按路径分流。我一般按每 6 路一个实例来部署留点余量给突发流量。3.5 坑五WebSocket 断连后 video 标签不自动重连现象网络抖动后 WebSocket 断开video 标签停在最后一帧不会自动恢复。原因webrtc-streamer 的 JS 库没有内置重连逻辑connect只调一次。解决在oniceconnectionstatechange里监听disconnected和failed状态手动调webRtcServer.disconnect()再connect()。注意重连前要等 2 到 3 秒避免频繁重连把服务端打挂。下面是我常用的重连封装let retryCount 0; const maxRetry 5; function startStream() { const server new WebRtcStreamer(video, http://localhost:8000); server.connect(rtspUrl, null, stun:192.168.1.100:3478); // 监听 ICE 状态失败时触发重连 const pc server.getPeerConnection(); pc.oniceconnectionstatechange () { if (pc.iceConnectionState failed || pc.iceConnectionState disconnected) { if (retryCount maxRetry) { retryCount; setTimeout(() { server.disconnect(); startStream(); }, 3000); } } }; }参数说明maxRetry设 5 次超过后提示用户手动刷新setTimeout的 3000 毫秒是经验值太短会导致服务端还没释放旧连接就建新连接太长用户等不及。4. 进阶用 API 动态拉流与延迟实测4.1 通过 HTTP API 动态添加和删除流webrtc-streamer 除了 WebSocket 信令还暴露了一组 HTTP API可以在不重启进程的情况下动态拉流。/api/add添加流/api/remove删除流/api/list列出当前所有流。这个在按需预览的场景里很有用——用户点哪个摄像机就拉哪路不点不拉省 CPU。# 添加一路流返回流的 id curl http://localhost:8000/api/add?urlrtsp://admin:pass123192.168.1.64:554/Streaming/Channels/101 # 列出所有流返回 JSON 数组 curl http://localhost:8000/api/list # 删除指定 id 的流 curl http://localhost:8000/api/remove?id0注意/api/add的url参数同样需要 URL 编码。/api/list返回的 id 是自增整数删除后不会复用。如果前端要维护流列表建议在添加时把返回的 id 存下来删除时按 id 删不要按 URL 删因为同一个 URL 可能被添加多次。4.2 延迟实测从摄像机到浏览器的端到端耗时延迟是 WebRTC 方案的核心指标但很多人只测了“感觉”没有量化。我一般用秒表法摄像机对着一个毫秒计时器浏览器截屏对比两个时间差。更精确的做法是用getStats()拿 RTP 时间戳和本地渲染时间的差值。// 在 video 标签播放后调用获取当前流的统计信息 const pc webRtcServer.getPeerConnection(); pc.getStats().then(stats { stats.forEach(report { if (report.type inbound-rtp report.kind video) { // jitterBufferDelay 是抖动缓冲引入的延迟单位秒 console.log(jitterBufferDelay:, report.jitterBufferDelay); // framesDecoded 和 framesReceived 的差值反映丢帧 console.log(framesDecoded:, report.framesDecoded); console.log(framesReceived:, report.framesReceived); } }); });实测数据参考内网千兆环境H.264 1080p25fpsjitterBufferDelay 在 0.05 到 0.15 秒之间端到端延迟 200 到 350 毫秒。跨公网 4G 环境jitterBufferDelay 涨到 0.3 到 0.8 秒端到端延迟 600 毫秒到 1.5 秒。如果 jitterBufferDelay 持续超过 1 秒说明网络抖动严重要么加 TURN 中继要么降码率。4.3 一个容易被忽略的参数ICE 候选类型过滤webrtc-streamer 默认会收集所有类型的 ICE 候选包括 host、srflx、relay。在纯内网环境host 候选就够了srflx 和 relay 反而增加协商时间。可以在启动时加-i参数指定只收集 host 候选# 只收集 host 候选适合摄像机和服务端在同一局域网 ./webrtc-streamer -H 0.0.0.0:8000 -i host如果服务端和客户端跨网段但能直连用-i host,srflx。只有在必须走 TURN 时才加relay。这个参数能省 1 到 2 秒的 ICE 协商时间在按需拉流场景里体感很明显。从那以后我每次部署 webrtc-streamer都强制走一遍“编码 RTSP 地址 → 确认 ICE 候选类型 → 压测并发路数”这三步少一步都可能在上线后翻车。希望帮到你。本文还有配套的精品资源点击获取