RTSP转WebRTC实战:海康摄像头流Web端播放全攻略

📅 发布时间:2026/9/8 8:42:08
RTSP转WebRTC实战:海康摄像头流Web端播放全攻略 简介浏览器原生不支持直接播放RTSP流而WebRTC又缺少统一的媒体接入方式二者在编码格式、信令流程和传输通道上均有差异。这份压缩包面向需要将RTSP摄像头流接入Web前端播放的开发者提供了一套基于webrtc-streamer的完整转换方案内含Windows AMD64可直接运行的webrtc-streamer程序以及配套的前端JavaScript库、HTML示例页面、样式与模型文件覆盖拉流、转码、SDP协商、播放器集成全链路音频侧也会完成Opus/G.711格式转换适合直接用于视频监控或低延迟直播原型验证。包体共94个文件以js和html为主辅以css、wasm、json、字体图标等资源整体仅8.77MB同时包含README与配置样例便于快速启动服务并调整参数。当前已有521人学习和下载适合正在研究WebRTC网关接入、网页实时视频播放或摄像头低延迟调用的开发者参考实践。 前阵子接了一个项目需求把海康摄像头的RTSP视频流实时展示到Web管理后台。刚拿到需求的时候我下意识觉得这应该是前端拉个流地址塞进video标签就行结果查了一圈资料才发现浏览器根本不认RTSP协议。中间踩了一堆坑从转流服务选型、信令交换到移动端自动播放最后总算平稳上线。这篇文章就完整记录一下我是怎么把RTSP转成WebRTC并在前端页面播放的包括方案对比、完整实操、排错经验给同样要做类似需求的人一个可以直接参考的路径。1. 浏览器为什么播不了RTSP问题出在协议本身先说清楚一个最容易被忽略的概念RTSP不是单纯的数据流协议它更像是一个“遥控器”。RTSPReal Time Streaming Protocol本身负责的是会话控制比如告诉服务器“我要播放”、“我要暂停”、“我要快进”而真正的媒体数据视频帧、音频帧走的是RTPReal-time Transport Protocol。所以一个完整的RTSP会话实际上是RTSP控制信道加上RTP媒体信道协同工作的结果。浏览器端的问题就在这里。HTML5的video标签只支持HTTP(S)协议族下能直接解析的媒体格式比如HLS的m3u8ts切片、DASH的mpd或者直接放一个mp4文件。浏览器没有内置RTSP/RTP协议栈也没有办法自己去跟RTSP服务器完成“控制媒体”的双通道会话。早期PC端靠的是ActiveX插件、VLC插件或者各家厂商的Web播放控件这本质上是在浏览器里塞了一个独立的播放器程序和安全沙箱的理念完全相反所以在移动端和现代浏览器里根本走不通。那为什么最终选了WebRTC而不是HLS或者HTTP-FLV延迟是最核心的因素。下面这张对比表是我实际测试多路流之后整理出来的典型延迟范围播放方案协议基础典型延迟浏览器兼容性HLSHTTP TS切片5~15秒原生支持HTTP-FLVHTTP FLV封装1~3秒需flv.jsWebSocket FLVWebSocket FLV封装1~3秒需flv.jsWebRTCUDP SRTP200~500毫秒原生支持除部分老版本监控场景最核心的诉求是“实时”。如果走HLS十几秒的延迟在安防场景下是完全不可接受的——哪有人在监控画面里接受10秒钟之前的影像WebRTC走的是UDP传输配合SRTP加密、拥塞控制和自适应码率能把端到端延迟压到几百毫秒这个区间。整条链路的架构也很清晰摄像头/RTSP源 - 转流服务拉RTSP转WebRTC - 信令协商 - 浏览器前端播放转流服务是这个链路的核心。它需要做三件事一是作为RTSP客户端主动去拉取摄像头的RTSP流二是把RTP媒体数据重新封装成WebRTC能够协商和传输的格式三是跟浏览器完成SDPSession Description Protocol和ICEInteractive Connectivity Establishment的信令交换。只要这个服务可靠前端就能用原生的RTCPeerConnection播放无需任何插件和第三方库。2. 转流服务怎么选四类方案的取舍确定要转WebRTC之后选型是最费时间的一步。市面上能干的方案不少但定位差异很大我花了几天时间对比测试最后才确定方案。方案实现语言部署复杂度性能与并发适合场景MediaMTXGo单二进制即可运行单机中等并发轻量少量摄像头接入、快速上线、嵌入式设备ZLMediaKitC中等依赖较多高并发功能全面支持GB28181、录像回放安防项目、大规模设备接入、需要录像的平台SRSGo/C较高高并发生态好支持RTMP/HLS/WebRTC直播场景、大规模分发、推拉流混合JanusC较高依赖较多网关型架构适合SFU二次开发需要深度定制WebRTC回话、多人通信场景最终我选了MediaMTX。理由很直接这个项目要接入的摄像头数量并不多大概十几路的规模但需要快速验证整个链路。MediaMTX是一个Go写的单二进制服务下载下来一条命令就能启动配置也是简单的YAML格式对“把一路RTSP流转出去”这个需求来说几乎是零学习成本。它的定位就是轻量的媒体流网关不做重业务但该有的WebRTC信令接口、标准协议支持WHEP等都有。ZLMediaKit我也仔细评估过。它确实更强大支持GB28181国标接入、HLS、HTTP-FLV、WebRTC还带录像和回放API是很多安防平台的标准选择。但部署和配置复杂得多需要自己编译或维护Docker镜像依赖的库也比较多。如果项目启动时就是几十上百路设备接入或者明确要做录像回放我建议直接上ZLMediaKit省得后面迁移。但只是想把摄像头画面搬到网页上先跑通MediaMTX是最短路径。这里还要多说一句安全方面的问题。RTSP地址往往带设备账号密码比如海康的取流地址格式是rtsp://用户名:密码IP:554/Streaming/Channels/101。如果让浏览器直接去拉RTSP等于把设备凭证暴露给了每个访问页面的人这在安全上是不可接受的。用转流服务统一拉流之后前端拿到的只是转换后的WebRTC流摄像头本身对浏览器完全不可见账号密码只存在于服务端配置里从源头解决了凭证泄露问题。3. 实操用MediaMTX把RTSP转成WebRTC前端播放这一部分是最核心的实操环节我会把从服务端配置到前端播放的完整链路拆开来讲。3.1 起一个MediaMTX实例并配置RTSP源MediaMTX的安装非常省事GitHub Releases里下载对应平台的压缩包解压后有mediamtx可执行文件和mediamtx.yml配置文件。部署在服务器上我一般用Docker因为可以避免宿主机环境差异docker run --rm -d \ --name mediamtx \ -p 8554:8554 \ -p 1935:1935 \ -p 8888:8888 \ -p 8889:8889/udp \ -v /opt/mediamtx/mediamtx.yml:/mediamtx.yml \ bluenviron/mediamtx:latest端口说明8554是RTSP服务端口1935是RTMP端口8888是HTTP API和WebRTC信令端口8889/UDP是WebRTC媒体传输端口。如果不需要RTMP接入1935可以不开。配置文件里最核心的是paths部分它定义了“流从哪儿来、往哪儿去”。把摄像头RTSP源配置进去的方式如下paths: camera1: source: rtsp://admin:password192.168.1.64:554/Streaming/Channels/101 camera2: source: rtsp://admin:password192.168.1.65:554/Streaming/Channels/101修改配置后重启服务MediaMTX会自动去拉取这个RTSP地址并在内部准备好转发成各种协议WebRTC、HLS、RTSP等的能力。3.2 先验证源再验证转流很多人在这一步直接打开前端看画面结果一片黑根本分不清是源的问题还是转换的问题。我的习惯是先排查源头再排查转流服务。先用ffprobe确认RTSP源本身能正常拉流ffprobe -rtsp_transport tcp -i rtsp://admin:password192.168.1.64:554/Streaming/Channels/101 -show_streams -format json能正常输出视频流信息编码格式、分辨率、帧率等就说明源没有问题。顺便说一句海康威视的RTSP地址有固定规律主码流通常是/Streaming/Channels/101子码流是102调试的时候优先用子码流码率低、传输压力小先把链路跑通再换主码流。接下来验证MediaMTX的API是否正常工作。MediaMTX的WebRTC信令接口支持标准的WHEPWebRTC-HTTP Egress Protocol也保留了一套简单直接的HTTP接口。我先用curl看当前服务有哪些流curl http://localhost:8888/api/v3/paths/list返回的JSON里如果能看到camera1这个path说明RTSP源已经被MediaMTX正常接管了。3.3 前端原生JS播放WebRTC流这里我直接用原生JavaScript写了一个简版播放器目的是让你看清楚WebRTC播放的底层逻辑。核心流程分三步请求SDP offer、交换ICE候选、建立连接后把媒体流挂到video标签上。const pc new RTCPeerConnection({ iceServers: [ { urls: stun:stun.l.google.com:19302 } ] }); pc.addTransceiver(video, { direction: recvonly }); pc.addTransceiver(audio, { direction: recvonly }); pc.ontrack (event) { const videoEl document.getElementById(player); videoEl.srcObject event.streams[0]; videoEl.play().catch(() {}); }; // 等待收集一些ICE候选后发送offer到MediaMTX pc.onicecandidate (event) { if (event.candidate) { sendIceCandidate(event.candidate); } }; async function startPlay() { const offer await pc.createOffer(); await pc.setLocalDescription(offer); const response await fetch(http://localhost:8888/stream/webrtc, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ type: request, streamId: camera1 }) }); const data await response.json(); await pc.setRemoteDescription(new RTCSessionDescription({ type: answer, sdp: data.sdp })); } async function sendIceCandidate(candidate) { await fetch(http://localhost:8888/stream/webrtc/icecandidate, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ type: iceCandidate, streamId: camera1, candidate: candidate }) }); } startPlay();这段代码把WebRTC播放的原理展示得比较清楚浏览器先创建RTCPeerConnection主动生成一个SDP offer描述自己“想收到什么格式的媒体”然后把这个offer通过HTTP POST发给MediaMTXMediaMTX作为WebRTC的answer端返回它自己的SDP描述浏览器setRemoteDescription之后双方就能协商出一套互相兼容的编码参数。之后两边通过ICE候选交换网络路径信息穿透NAT建立UDP通道媒体数据就开始流动了。3.4 更省事的方案直接用WHEP和官方播放器自己写信令逻辑虽然能深入理解原理但如果想快速上线MediaMTX已经内置了WebRTC播放页面。启动服务后直接访问http://localhost:8888/选择对应的流ID即可播放官方前端已经封装好了所有信令交换逻辑。如果要在自己的前端项目里集成建议直接走WHEP标准协议。WHEP全称是WebRTC-HTTP Egress Protocol规范约定了一套“POST一个offer SDP返回answer SDP再POST ice候选”的HTTP接口格式未来切换支持WHEP的服务端时前端代码不用重写。MediaMTX新版对WHEP的支持已经比较完善接口路径通常是/whep。4. 上线后踩过的坑端口、弱网和移动端链路跑通只是第一步真正让我印象深刻的是项目上线前的联调阶段。以下几个问题每一个都花了我不少时间去排查。4.1 UDP端口与NAT第一次测试的时候我在本地Windows环境跑MediaMTX浏览器也是本机的一切正常。但把服务部署到云服务器之后前端画面就一直卡在“连接中”。用Wireshark抓包发现RTCPeerConnection发出的ICE candidate里全是内网IP和腾讯云的内网地址。原因很简单云服务器有多个网卡内网IP 公网IPMediaMTX默认收集到的是内网IP作为WebRTC的媒体地址浏览器根本无法通过这个内网IP访问到服务器。解决办法是在配置里手动指定公网地址。配置项是webrtcAdditionalHostswebrtcAdditionalHosts: - 203.0.113.10同时要注意云安全组和Docker端口映射。WebRTC的媒体走UDPMediaMTX的默认UDP端口是8889安全组必须放行UDP入方向Docker也记得用-p 8889:8889/udp把UDP端口映射出来。这是最容易踩的坑HTTP能通不代表UDP能通而WebRTC恰恰是重度使用UDP的。4.2 弱网环境下的卡顿优化热搜词里有人问“webrtc弱网卡顿怎么优化”这确实是真实场景里绕不开的问题。WebRTC自身有拥塞控制机制比如GCC算法会根据网络状况动态调整码率但这不代表你什么都不用管。我在实际测试中发现几个配置对弱网表现影响很大。一是RTSP源的编码参数。如果摄像头端GOP关键帧间隔设置得太大新观众加入时可能要等很久才能等到关键帧表现就是黑屏几秒钟。建议把摄像头的I帧间隔调小比如1~2秒一个关键帧。二是在MediaMTX接收RTSP时传输方式建议用TCP避免UDP传输丢包导致源端花屏再污染到WebRTC端。三是在前端创建RTCPeerConnection时codec优先级里可以让H.264排在VP8前面因为多数摄像头和转流服务的硬件编码都是H.264可以省掉转码开销弱网时表现更好。4.3 移动端H5自动播放与无声问题移动端H5播放视频有两个老生常谈但避不开的坑。第一个是自动播放限制。iOS Safari和微信内置浏览器要求视频不能带声音自动播放。我踩过的是WebRTC的媒体流虽然没有设置autoplay属性但调用video.play()时如果包含音频且没有用户手势会返回一个rejected Promise表现为“画面出来了没声音”。处理方式有两个方向一是页面初始化时让用户点击一次“进入监控”按钮在点击回调里创建RTCPeerConnection并调用play()这个用户手势足以解锁音频播放二是先静音播放等用户主动点击时再打开声音。第二个是安卓WebView的video标签“脱离文档流”问题。部分定制ROM的浏览器尤其是安卓系统自带浏览器遇到video标签会强制全屏播放甚至悬浮在页面上方。解决方案是给video标签加上这些属性让播放器保持内联video idplayer playsinline webkit-playsinline x5-playsinline x5-video-player-typeh5 x5-video-player-fullscreenfalse muted autoplay /video4.4 并发带宽估算别等服务器被打挂了再算WebRTC模式是“每个观众一条独立媒体流”。这和HLS/RTMP那种“服务器只推一路CDN节点分发给很多人”的模式完全不同。也就是说如果有50个用户同时在看同一个摄像头的画面服务器就要对外发送50路一模一样的视频流出口带宽就是“单路码率 × 观看人数”。我简单算过一笔账一路1080P摄像头主码流大约4Mbps50个观众就是200Mbps的出口带宽按云厂商的带宽计费这是一笔不小的开销。所以在设计阶段就要考虑清楚如果是几十人以内的小范围监控后台MediaMTX单机直接扛没问题如果要做几百上千人观看的直播型应用必须引入SFU选择性转发单元架构或CDN边缘分发或者退一步用HLS/HLS低延迟做大规模分发只在关键对讲场景用WebRTC。没有一种方案是万能的选型永远取决于并发规模。5. 从“能播”到“可靠”鉴权、监控与后续演进项目上线跑了两周稳定之后我开始思考一个更重要的问题这个方案怎么长期稳定地跑下去而不是靠人肉盯守。第一件事是给MediaMTX的接口加上访问控制。MediaMTX的HTTP API和WebRTC信令接口默认是裸奔的任何知道服务器IP和端口的人都可以请求拉流甚至查看服务状态。虽然转流方案已经保护了摄像头的RTSP凭证但服务本身也需要防护。我直接用Nginx做了反向代理用proxy_pass转发到MediaMTX的8888端口同时加了Token校验每次前端请求信令接口都要带一个短期有效的Token由后端统一签发和验证。这样就把“获取流地址”和“播放”两个动作分离了只有通过业务系统校验的用户才能拿到Token并播放。第二件事是监控RTSP源头是否掉线。摄像头是7×24小时运行的设备难免出现断网、断电、异常重启的情况。MediaMTX本身有API可以查询流状态但我更推荐写一个定时巡检脚本每隔一分钟用ffprobe去拉一下RTSP地址探测失败就告警到企业微信或者短信。不要小看这一步摄像头的稳定性往往比服务器差没有监控的话某一路画面黑了可能几天都没人发现。第三件事是考虑架构演进。项目后期如果设备数量从十几路涨到上百路我建议迁移到ZLMediaKit。它支持GB28181国标接入、设备目录、录像回放、按需拉流更贴合安防业务模型。而在纯WebRTC分发场景之外还可以结合边缘计算节点就近转发降低中心服务器的带宽压力。值得放心的是因为前端已经按WHEP标准协议实现了播放逻辑换服务端时只需要改接口地址前端播放器几乎不用动前期的标准化投入在这里就体现出了价值。最后再说一个调试小技巧排查RTSP相关问题时别光看服务端日志用Wireshark抓包往往更快定位问题。如果抓包时看不到RTSP协议可以在分组列表里右键任意RTSP相关报文选择Decode As手工指定为RTSP协议。这个不起眼的小操作帮我解决过好几次“源明明有流但拉不下来”的疑难杂症。根据我这次项目的经验做视频流迁移“先验证源、再转流、最后调前端”这个排查顺序能省下大量时间希望这篇记录能让你少走些弯路。本文还有配套的精品资源点击获取