用Docker部署webrtc-streamer,将RTSP监控视频无插件低延迟接入浏览器
做安防项目的人大概都被同一件事折磨过监控摄像头出来的RTSP流不能在浏览器里直接打开VLC能播、手机App能播唯独网页这个最容易分发的终端成了老大难。之前试过把视频推成HLS延迟高得离谱声音和画面能差上好几秒后来换WebRTC又要自己搭信令服务器维护成本蹭蹭涨。直到用了mpromonet/webrtc-streamer这个开源项目配合Docker部署一条命令就能把RTSP流变成浏览器直接播放的WebRTC直播流实测延迟压到500毫秒内。这篇教程把我从零开始部署到解决各种坑的经验完整记录下来不管你是Windows、macOS还是Linux只要跟着一步步走基本都能跑起来如果你已经跑起来但播放不流畅也建议直接跳到第6章看排查清单。1. 为什么我最后选了mpromonet/webrtc-streamer1.1 它解决的是浏览器播放RTSP的老大难监控摄像头最通用的输出协议是RTSP但浏览器对这个协议完全没有原生支持。你没法在HTML5的video标签里直接填一个rtsp://地址Chrome、Edge、Firefox都没戏。传统的折中方案是拉取RTSP后转成HLS或者RTMP再播放HLS虽然兼容性好但切片缓冲把延迟推到了5到10秒对监控和对讲场景基本不可用。RTMP则需要Flash早就被浏览器淘汰了现代项目里不会再碰。webrtc-streamer的思路是服务器端替你接住RTSP流然后通过WebRTC协议投递给浏览器。浏览器端不需要任何插件只要是一个支持WebRTC的现代浏览器就能以低于1秒的延迟看到画面。它本质上是一个C写的轻量流媒体代理官方仓库mpromonet/webrtc-streamer在GitHub上已经维护了多年很多安防、无人值守、远程巡视项目都在用稳定性经过了不少真实环境的验证。1.2 它和动辄全家桶的WebRTC网关有什么不同如果你搜过WebRTC流媒体服务大概率会看到Janus、mediasoup这些重量级项目。它们确实很强大能处理复杂的SFU场景、万人会议、多房间管理但部署复杂度也上了一个量级依赖数据库、需要单独的信令服务、客户端SDK要花时间学习。对于把一路RTSP变成网页可看这种单点需求它们属于杀鸡用牛刀。webrtc-streamer定位很明确就是轻、快、直接。它把信令服务、媒体代理、HTTP接口打包在一个容器里启动后既提供WebRTC信令也负责媒体流的拉取和转发。浏览器端可以直接用官方内置的HTML页面也可以通过HTTP API和JavaScript库嵌入到自己的项目里。下面这张表格是我当时做选型时的对比方案部署复杂度WebRTC能力适合场景上手时间webrtc-streamer低单镜像即可点对点投递适合少量并发监控视频上网页、远程巡视半天Janus高需编译配置数据库完整SFU支持大规模会议视频会议、互动直播一周以上mediasoup高Node.js开发需要自己写逻辑纯SFU能力灵活但全要自己搭需要深度定制的复杂场景两周以上传统HLS转流低nginxffmpeg即可无WebRTC延迟高对延迟不敏感的直播一天所以如果你的核心诉求是快速把摄像头画面搬到浏览器里路径越短越好webrtc-streamer几乎是最省事的选择。1.3 为什么必须用Docker而不是源码编译这个项目是C写的源码编译依赖一大堆库FFmpeg、libnice、libsrtp、jsoncpp等等。我在Ubuntu上手动编译过一次光拉依赖装环境就折腾了大半天中途还遇到某个库版本不对导致编译失败最后换成Docker镜像十分钟就把服务跑起来了。Docker镜像里已经把所有的编译产物和运行环境封装好了你不需要关心源码怎么编、缺什么依赖只要拉镜像、跑容器、映射端口就能启动。而且Docker可以固定在某个镜像Tag升级或回滚都很干净不会在宿主机上留下一堆散落的库文件。2. 部署前的环境准备先把这三个坑排掉2.1 Docker环境的检查与虚拟化报错无论用Docker Desktop还是Linux原生的Docker Engine部署这本项目前都得先确认Docker本身能正常工作。在终端执行docker version docker info如果docker version能输出客户端和服务端版本说明Docker基本可用。我看到很多新手在Windows上装完Docker Desktop打开就报“virtualization support not detected”或者“Virtualization support wasnt detected”这一般不是Docker的问题而是宿主机虚拟化环境没有开。后面第6章我会详细讲排查链路这里先提醒你Windows下务必确认BIOS/UEFI里的VT-x或AMD-V开启Hyper-V、虚拟机平台、WSL2这几个Windows功能建议全部勾上。装完以后重启系统再用docker version确认。Linux上如果用的是老内核某些模块没加载也会出问题最常见的是overlay文件系统、netfilter相关模块可以提前执行sudo modprobe overlay sudo modprobe br_netfilter跑一遍顺手加上docker compose插件。macOS相对省心Docker Desktop一般装上就能用但要注意如果电脑是M1/M2这类Apple Silicon芯片镜像能不能跑取决于Docker Desktop的Rosetta兼容层后面部署时最好指定--platform linux/amd64以防万一。2.2 端口、防火墙和网络拓扑webrtc-streamer默认用8000端口提供HTTP访问8443提供HTTPS访问。Docker启动时如果只映射了8000:8000那就只开了一个HTTP入口。这里有个容易忽略的细节WebRTC的真正媒体传输走的是UDP虽然webrtc-streamer的HTTP端口同时承担了一部分WebRTC信令和媒体协商的职责但如果你的浏览器和服务器之间UDP被防火墙挡了播放就会出现卡顿甚至完全黑屏。所以部署前最好确认三条规则宿主机防火墙要放行TCP 8000/8443端口。如果WebRTC媒体协商后走的是UDP传输宿主机上要能转发UDP包。容器和摄像头要能网络互通尤其是摄像头在另一个网段或容器里访问不到时需要检查路由和安全组。最简单的方式是先在本地网络内测试不开任何云安全组等全部跑通再逐层收紧规则。2.3 准备视频源真摄像头和模拟流部署完总得有一路源来测试。真摄像头通常给一个RTSP地址例如rtsp://admin:password192.168.1.100:554/Streaming/Channels/101如果手里没有摄像头可以用FFmpeg搭一个本地模拟RTSP流。需要先装一个RTSP服务器我用的是开源的rtsp-simple-server现在叫mediamtx然后执行ffmpeg -re -i test.mp4 -c copy -f rtsp rtsp://localhost:8554/test这样本地就有了一路rtsp://localhost:8554/test的流。需要注意的是webrtc-streamer容器是独立网络命名空间如果模拟流跑在宿主机上容器里不能直接用localhost要改成宿主机在局域网里的IP或者使用Docker的host网络模式。最稳妥的办法是用docker run --network host这样容器和宿主机共享网络localhost就能直接访问模拟流。3. Docker部署从拉镜像到看到画面3.1 最小启动命令直接把官方镜像拉下来跑docker pull mpromonet/webrtc-streamer docker run -d --name webrtc-streamer -p 8000:8000 mpromonet/webrtc-streamer第一行拉取镜像第二行以后台模式启动一个名为webrtc-streamer的容器并把宿主机的8000端口映射到容器的8000端口。启动后访问http://localhost:8000你会看到一个很简朴的网页页面里通常包含一个流列表和输入框把RTSP地址填进去点连接浏览器就会请求摄像头画面。如果一切顺利页面很快会显示实时视频。这个最小命令基本够用但有个问题容器停掉就自动删除了吗其实不是-d后台运行时容器还会保留。如果你不想让容器堆积可以在第一次测试时临时加上--rm参数容器一停就自动清理。我倒是建议在正式使用前都给容器起一个稳定名字方便后面看日志和改参数。3.2 常用参数和目录挂载这个镜像默认入口就是webrtc-streamer可执行文件你可以在docker run时直接追加参数。比如要改HTTP端口docker run -d --name webrtc-streamer -p 8080:8080 mpromonet/webrtc-streamer --port8080我一般会加上--restart unless-stopped这样宿主机重启后容器能自动拉起来适合部署在摄像头机房里长期跑。如果希望容器内生成的日志持久化或者需要挂载配置文件可以在启动时加上-v参数。项目本身支持通过-v挂载配置文件但不同版本参数略有差异最稳妥的办法是先不带配置文件跑起来再用docker exec -it webrtc-streamer webrtc-streamer --help看一眼当前镜像支持的参数列表。所谓“保姆级”不是把命令背下来而是知道怎么在出问题时自己找到答案。3.3 启动验证与日志观察容器启动后用三个手段验证是否正常docker ps # 看容器是否有“Up”状态 docker logs webrtc-streamer # 看启动日志有没有报错 curl http://localhost:8000/api/getCameraList # 请求一个HTTP接口看是否有JSON返回/api/getCameraList是项目的一个简易接口用来确认HTTP服务是否响应。如果curl有返回说明HTTP层通了。日志里如果出现类似Receive timeout、Connection refused之类的内容通常是后端拉流问题而不是服务本身挂了。我第一次部署时踩了个小坑docker run后马上执行curl结果连接拒绝吓得以为没起来。后来发现是容器内部还在初始化FFmpeg库加载需要几秒钟。所以验证时最好先docker logs看一眼确认启动完成后再测。4. 这一条直播链路到底是怎么打通的4.1 浏览器不认RTSPwebrtc-streamer做了什么我们平时说WebRTC直播其实包含两条链路第一服务器拉取摄像头或视频源的RTSP流第二浏览器和服务器之间建立WebRTC连接服务器把媒体数据推给浏览器。webrtc-streamer做的事情就是在这两条链路的中间做一个协议转换。它内部通过FFmpeg库识别并解码/转封装RTSP流然后按照WebRTC的要求进行编码和打包最终从服务器发出RTP/UDP包。浏览器端不需要安装任何插件因为WebRTC本身就是浏览器内置的能力。你在网页里调用了RTCPeerConnection浏览器会自动完成本地的音视频采集、编码协商、网络打洞等工作。webrtc-streamer相当于是把摄像头那边的RTSP“吃”进肚子里再用浏览器听得懂的WebRTC语言吐出来。4.2 信令、SDP和ICE三个不可绕开的概念WebRTC最让人头疼的是信令Signaling过程。浏览器和服务器要建立连接得先交换双方的音视频能力、网络地址、加密指纹等信息这些信息放在SDPSession Description Protocol里面。webrtc-streamer用HTTP/WebSocket接口实现了这套信令浏览器端加载一个JavaScript库后会自动和服务器交换SDP。交换完SDP后双方开始进行网络探测这一步叫ICE。ICE的作用是找出所有可用的网络路径直连、中继、NAT穿透等等。如果浏览器和服务器在同一个局域网内ICE会走一条很短的主机路径延迟最低如果跨越公网就需要STUN/TURN服务器辅助。webrtc-streamer内置了基本的ICE处理流程但对公网环境下的TURN支持比较有限所以如果跨网络延迟很高更推荐用Nginx或者其他网关先打通网络再接入。4.3 H.264直通 vs 转码的取舍一条摄像头RTSP流能不能被webrtc-streamer高效转发很大程度取决于视频编码格式。H.264是浏览器和WebRTC都支持的主流格式如果源流是H.264webrtc-streamer可以尽量做“直通”转发也就是不重新编码直接把已经压缩好的H.264数据封装进WebRTC的RTP包。这种方式CPU开销很小延迟也最低。但如果摄像头输出的是H.265HEVC很多浏览器默认不支持WebRTC对H.265的兼容性也差webrtc-streamer可能就需要转码。转码意味着FFmpeg要解码再重新编码CPU占用成倍增长延迟也会增加。我实测过一台普通4核服务器直通H.264可以同时转发多路1080P但转码一路4K就快被吃满了。所以如果方案还没锁定摄像头型号优先选带H.264编码输出的型号会省很多心。5. 浏览器端接入先快速体验再写页面5.1 打开内置页面填入RTSP地址镜像内置了一个HTML页面路径一般是/stream.html。比如你是本机部署直接访问http://localhost:8000/stream.html?urlrtsp%3A%2F%2Fadmin%3Apassword%40192.168.1.100%3A554%2FStreaming%2FChannels%2F101注意URL需要编码密码里的特殊字符如果不编码服务器解析会出错。如果你不想手动编码直接访问http://localhost:8000/stream.html页面上提供一个输入框把原始的RTSP地址粘贴进去也行。这个内置页面虽然简陋但非常适合调试。输入地址后观察视频能不能出如果这里都出不来等会儿写自己的页面大概率也出不来。5.2 用iframe嵌入自己的后台系统很多人的需求是把自己内部的监控后台里嵌入实时画面。最快的办法不是引入JavaScript SDK而是用iframe嵌套内置页面iframe srchttp://your-server:8000/stream.html?url... width800 height450 scrollingno frameborder0/iframe这种方式简单粗暴但有明显的缺点地址栏直接暴露了RTSP URL而且内置页面的样式和你自己的后台往往不搭。所以正式集成时我建议使用官方JavaScript库webrtcstreamer.js。它在镜像的/webrtcstreamer.js路径下可以加载还需要一个HTML5video标签。大致代码结构如下script srchttp://your-server:8000/webrtc-streamer.js/script script var webRtcStreamer new WebRtcStreamer(video, http://your-server:8000); webRtcStreamer.connect(rtsp://admin:password192.168.1.100:554/Streaming/Channels/101); /script官方API在不同版本之间有小改动使用前最好在浏览器开发者工具的Console里打印一下WebRtcStreamer对象确认当前版本支持的构造函数方式。对于大多数人来说把这些代码抄下来再配合Angular或者Vue的生命周期函数去调用connect和disconnect就能嵌进自己的项目。5.3 接入HTTPS的坑如果你是在http://localhost下调试一切正常。但一旦部署到服务器通过IP访问或者域名是httpChrome在获取麦克风/摄像头权限时就会比较严苛。WebRTC采集本地音视频时需要getUserMedia权限浏览器通常要求安全上下文HTTPS或localhost才能调用。如果你只需要看摄像头画面不采集本地麦克风问题还不大一旦需要双向语音就要保证页面在HTTPS环境下运行。生产环境建议用Nginx反代一层把对外端口设为443并配置证书然后反向代理到容器内部的8000端口。Nginx配置大致是server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里的location /会代理所有HTTP和WebSocket请求webrtc-streamer内置页面和信令请求都能通。跑起来后浏览器访问https://your-domain.com/stream.html就可以了,不会再有权限弹窗的麻烦。6. 常见问题排查都是实测踩过的6.1 Docker Desktop虚拟化检测不到这个问题在Windows上出现频率极高我帮两个同事处理过。现象是Docker Desktop图标启动了但几秒钟后弹窗“Docker Desktop failed to start because virtualisation support wasnt detected.” 排查链路从硬件到软件层层往下确认CPU虚拟化开关。重启电脑进BIOS/UEFI找到“Intel Virtualization Technology”或者“SVM Mode”设为Enabled。打开Windows功能。进入“控制面板 - 程序 - 启用或关闭Windows功能”勾选“Hyper-V”、“虚拟机平台”、“适用于Linux的Windows子系统”。以管理员身份打开PowerShell执行bcdedit /set hypervisorlaunchtype auto然后重启。如果电脑本身就是一台云服务器或虚拟机嵌套虚拟化往往不支持Docker Desktop在本地虚拟机里会大概率启动失败。这种情况考虑换物理机或者直接在云服务器上的Linux里装原生的Docker Engine。装完以后重新运行Docker Desktop等托盘图标变绿再执行docker version看服务端信息。6.2 容器起来了但8000端口打不开docker ps明明显示容器是Up状态但浏览器访问http://localhost:8000一直超时。排查时先按顺序检查docker logs webrtc-streamer docker port webrtc-streamer curl http://127.0.0.1:8000/api/getCameraList如果docker port显示映射结果为0.0.0.0:8000-8000/tcp说明端口映射没问题。这时候先看是不是容器内部服务没起来日志里有没有启动异常。如果日志干干净净但依然不通考虑防火墙。Windows下打开“高级安全Windows Defender防火墙”新建入站规则放行TCP 8000端口Linux下执行sudo ufw allow 8000。macOS一般在系统设置隐私与安全里检查。还有一种少见但很坑的情况宿主机上恰好有别的服务也占用了8000端口。Docker启动时不报错但映射失败或冲突这时候先netstat -anp | grep 8000确认端口归属。6.3 RTSP流在VLC正常webrtc-streamer却拉不动这是最常见的“能播但拉不动”问题。VLC能播放只证明网络里有一路RTSP流但webrtc-streamer容器去访问时走的是另一条路径。可能原因有三个第一RTSP URL里的特殊字符没编码。比如密码是abc123会把URL解析到主机名位置越传越乱。要对用户名密码做百分号编码比如把转成%40。第二网络隔离。容器和摄像头不在同一网段或者主机防火墙限制了来源IP。最简单的验证方法是在容器里执行docker exec -it webrtc-streamer bash apt update apt install -y vlc但容器里往往没有调试工具也可以改用ffmpeg测试ffmpeg -i rtsp://... -t 5 out.mp4如果能出来文件说明容器到摄像头的网络是通的。第三RTSP传输协议问题。很多摄像头默认用UDP传输RTSP但防火墙对UDP不友好容器内尝试TCP传输反而更稳定。webrtc-streamer在启动参数或配置里可以指定RTSP传输方式但不同版本写法不同建议先看日志里的FFmpeg报错。日志如果出现Connection timed out大概率就是UDP被防火墙丢了。6.4 页面一直黑屏或播放几秒后卡住黑屏和卡顿要区别对待。黑屏说明播放器组件没拿到正确的视频流先确认两点浏览器控制台有没有报错服务端日志有没有反馈媒体状态。正常情况下webrtc-streamer页面打开后会有一段时间建立WebRTC连接视网络和CPU情况一般1到3秒就出画面。超过5秒没画面多半是媒体协商卡住了。播放几秒后卡住常见原因有两种摄像头码率太高服务器转发不过来CPU被占满或者是网络不稳定UDP丢包严重。WebRTC本身有拥塞控制会根据丢包动态降低码率但如果网络抖动太激烈画面就会花屏或卡死。这种情况的临时手段是降低摄像头编码码率、降低分辨率或者把摄像头改成H.264 baseline profile。长期方案是确保浏览器和服务器之间的UDP路径畅通尽量别让TURN中继参与能直连就直连。6.5 自签证书和混合内容导致的播放失败如果你用Nginx反代后只配了HTTPS但页面里加载的webrtc-streamer.js地址仍然是http://浏览器会拦截“混合内容”JavaScript无法执行。排查方法是打开浏览器开发者工具切到Console页看到类似Mixed Content: The page at https://... was loaded over HTTPS, but requested an insecure resource http://...就是这个问题。把脚本地址改成HTTPS或者直接用相对路径/webrtc-streamer.js即可。如果用了自签证书浏览器也会警告甚至阻止WebRTC连接。本地调试可以在浏览器里点“高级”继续访问但生产建议用免费的Lets Encrypt证书而不是自签名。7. 生产环境里我再补几个操作7.1 加个restart策略让容器自己活下来开发调试时无所谓但部署到现场后如果设备断电重启容器拉不起来就很麻烦。所以正式运行的容器一定要加重启策略docker run -d --name webrtc-streamer \ --restart unless-stopped \ -p 8000:8000 \ mpromonet/webrtc-streamerunless-stopped意味着即使Docker守护进程重启只要不是手动docker stop容器就会自动拉起来。这对现场无人值守的场景特别实用。7.2 资源限制与监控WebRTC转发是CPU密集型任务如果同一台机器上还跑着别的东西最好给容器限制资源。Docker提供了--cpus和--memory参数docker run -d --name webrtc-streamer \ --cpus2 --memory1g \ -p 8000:8000 \ mpromonet/webrtc-streamer这样容器最多用2个CPU核和1G内存避免某个流量高峰把宿主机拖垮。监控方面可以用docker stats webrtc-streamer实时查看CPU和内存占用。我在一个中型项目里用这种方式限制到1核单路720P H.264直播时CPU占用在30%左右表现很稳定。7.3 反向代理和真正的HTTPS前面已经写了用Nginx反代的配置这里再强调两个细节。第一webrtc-streamer会用到WebSocket信令Nginx代理时需要设置Upgrade头否则页面会一直停留在“连接中”状态。第二如果容器暴露了多个端口8000和8443建议统一由Nginx代理443端口对外避免直接暴露8000端口引来扫描和攻击。安全方面生产环境一定要给RTSP地址加访问控制不要让未认证的前端页面直接把摄像头地址传给用户。可以在自己的后端做一个转发逻辑前端请求自己的接口后端再向webrtc-streamer发起拉流RTSP地址始终保存在服务端。这样即使页面被爬虫拿到也不会泄露摄像头内网地址。按这个路径把容器跑起来后我实际测试下来从填入RTSP地址到出画面基本在2秒内本地局域网延迟只有两三百毫秒。如果你部署中遇到比上面更诡异的现象我的建议是先把问题分解成容器层、网络层、WebRTC协商层三层逐层用日志和curl排除。这个项目本身不复杂大部分坑都出在环境上而不是代码里。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →