海康威视多路播放简洁方案:RTSP转HLS实现浏览器无插件监控

海康威视多路播放简洁方案:RTSP转HLS实现浏览器无插件监控 简介这是一份面向海康威视摄像头多路监控场景的简洁版播放器Demo基于Visual Studio 2013和官方SDK编写适合安防集成人员、C开发者或具备基础网络视频知识的学习者参考。压缩包约123.6MB共130个文件其中动态库和静态库接近80个另有头文件、C源码、VS工程文件以及编译产生的中间文件并包含可直接运行的exe程序借助这些内容可以很快还原出可编译、可调试的完整工程。目前已有1631人学习。示例重点演示了设备登录、视频流获取、解码显示和多路独立线程调度等内容同时涉及硬件加速与界面多画面刷新策略能帮助读者理清海康SDK的调用链和关键参数含义。通过对照源码还可以学习如何初始化播放库、注册实时流回调、按窗口句柄渲染视频帧以及如何在多条视频流之间合理分配线程资源这些内容对安防项目、课程设计或毕业设计都有直接复用价值。资源包内附带完整运行库文件打开工程后可快速编译体验省去单独查找SDK依赖的步骤。 做监控项目的人应该都有过这种经历客户开口就是“我要在大屏上同时看16路、32路监控”可你打开官方客户端一看界面臃肿、操作绕、还得装一堆插件。更麻烦的是要集成到自己的系统里SDK文档翻半天ActiveX插件早被Chrome封得死死的最后十几路画面死活出不来。我这次做的就是一套“海康威视多路播放简洁版”目标很明确——不碰重型平台、不依赖已废弃的浏览器插件用最精简的链路把多路实时画面稳定播出来方便任何做二次集成的人直接抄作业。这套方案的核心思路是后端用海康设备的ISAPI/RTSP接口取流交给FFmpeg转成HLS切片前端用hls.js在网页里拼一个多路网格。全程不需要官方SDK不需要浏览器插件只要能访问到设备的RTSP端口就能在任意现代浏览器里播放。适合安防集成商做项目Demo、门店多店巡检、临时活动保障这些场景。1. 项目定位与方案选型1.1 什么场景需要“多路播放简洁版”先说清楚这个项目适合谁。如果你是在做海康威视的二次开发想把监控画面嵌入自己的Web系统或者临时搭一个多路预览页面用于项目演示那这套东西就是为你准备的。它不追求平台级的用户管理、录像回放、报警联动只解决一个核心痛点多路实时画面如何在浏览器里同时、稳定地播放。我遇到的实际场景是给一个连锁门店做远程巡检——总部要在大屏上看20多家店的前端画面。客户的诉求就三条网页打开就能看不用装插件画面要稳定不能播几分钟就黑屏每人权限范围内能分屏切换。官方综合管理平台功能确实全但部署一套服务器、配完一堆模块对这个小项目来说太重了。这时候“简洁版”的价值就体现出来了轻量、可裁剪、代码在自己手里改起来快。还有一类场景是活动保障。比如展会现场要看各入口的实时人流设备是临时装的第二天就要撤。这时候你不会去搭什么正式平台一台笔记本跑个Python服务加几个FFmpeg进程就能把所有画面聚合到一个页面上交给现场人员盯着。方案的核心竞争力不在功能多而在“快”和“省”。1.2 为什么放弃SDK和浏览器插件方案最早接触海康播放的人多半经历过两个世代的方案。第一个世代是官方SDK ActiveX/NPAPI插件在IE浏览器里嵌控件通过SDK的播放库拉流渲染。这个方案画面延迟低、功能全但问题也是致命的Chrome和Edge都先后封禁了NPAPI和ActiveX现在只有老旧的IE内核或者特定的Chromium版本还能跑。你总不能要求客户为了看监控专门装一个旧版浏览器吧。第二个世代是用浏览器厂商自己的插件比如海康出的Chrome插件。这个方案解决了一部分兼容问题但部署仍然麻烦——每台客户端机器都要手动装插件、加白名单、信任证书。而且插件方案通常只对应特定型号或固件版本设备一多、型号一杂兼容性就容易翻车。对于要集成到自己系统里的开发者来说依赖一个插件的黑盒排查问题很痛苦。放弃SDK直接播放还有一个更实际的原因RTSP流没法被浏览器原生解析而SDK的播放器内核在非IE浏览器里根本没有可用的容器。与其去死磕SDK和插件的兼容性不如在后端把RTSP流统一转成浏览器原生支持的HTTP协议HLS前端就是一个标准的HTML5 Video标签。这样链路虽然多了一道转换但每一环都是标准协议出问题也容易排查。实测下来从发起播放到画面出来大概2到4秒对于监控场景完全能接受。2. 整体设计与关键原理2.1 播放链路RTSP → HLS → 浏览器这个方案的完整链路是这样的前端页面发起请求到后端接口后端拿到设备ID和通道号拼接出RTSP地址拉起FFmpeg进程把视频流转成HLS分片.m3u8 .ts文件放到本地HTTP目录里前端再用hls.js加载播放。为什么中间要加一个FFmpeg转码层因为浏览器不认RTSP协议这是最大的原因。RTSP是实时流传输协议需要维护一个长连接会话浏览器压根没有原生实现。而HLS是基于HTTP的天然适合浏览器播放还能利用HTTP缓存做多路并发。另外转码层还可以顺便做码流适配比如把海康主码流的H.265转成H.264避免浏览器解码不了。有人会问“为什么不用WebRTC延迟不是更低吗”WebRTC确实延迟能压到500毫秒以内但代价是实现复杂度高得多需要自己搭信令服务器还要处理STUN/TURN穿透。HLS虽然延迟稍高通常在3到8秒但对于巡检、大屏展示这种场景没人会揪着这3秒说话。项目的定位是“简洁”所以选型上优先考虑链路简单、各环节可控的方案而不是某个指标最优的方案。要注意的是HLS的延迟主要来自分片。我实际测试中把分片时长设成2秒列表只保留3个分片播放器追到最新分片之后端到端延迟能控制在3到5秒。如果你要更低延迟可以了解LL-HLS低延迟HLS标准但浏览器兼容性还不均衡现阶段不建议作为主方案。2.2 设备取流的核心细节海康设备的RTSP地址格式非常固定熟练了闭着眼都能拼rtsp://用户名:密码设备IP:554/Streaming/Channels/101最后的101就是通道编号规则第一位是通道号后两位是码流类型。101表示通道1的主码流102表示通道1的子码流通道2就是201和202以此类推。做多路播放的时候特别建议优先用子码流。子码流分辨率低、码率小比如主码流是1080P/4Mbps子码流通常是CIF或720P/512Kbps。20路同时预览主码流的带宽压力是很大的子码流不仅省带宽FFmpeg转码的CPU占用也低很多。只有在单画面全屏查看细节时才切主码流。设备侧的配置主要注意两点一是RTSP服务必须开启新设备默认是开的但有些项目出货时被人为关掉过需要登录设备Web管理页面确认二是用户名密码要有访问摄像头的权限建议用一个专门的“预览账号”只给实时预览权限不要用管理员账号跑在公网上安全性太差。如果要动态获取设备列表可以调用ISAPI接口GET /ISAPI/System/Video/inputs/channels这个接口返回XML里面包含了每个通道的编号、名称、分辨率、编码格式等信息。我后面的实操代码里就是用这个接口来生成设备清单的。设备少的话直接写死在配置文件里更省事。2.3 转流层设计要点FFmpeg是这个项目最核心的依赖它的稳定性直接决定多路画面能不能持久跑。我在转流层做了几个设计决策这里单独讲一下。第一个决策是强制用TCP传输RTSP加参数-rtsp_transport tcp。默认的UDP模式在弱网环境容易丢包画面会出现花屏、马赛克。TCP虽然握手过程略慢但一旦建立连接传输稳定性好很多。在局域网内测试TCP模式的画面基本是干净的。第二个决策是在转流时直接拷贝视频流而不重新编码加参数-c:v copy。海康摄像头输出的H.264/H.265码流是标准的FFmpeg可以直接把视频流从RTSP容器搬运到HLS容器里不做任何转码。这个过程CPU占用极低一台普通i5工控机带20路子码流毫无压力。实测下一路720P子码流转HLSCPU占用只有2%-5%。第三个决策是加断线自动重启机制。FFmpeg进程一旦因为网络波动退出如果没有守护进程这一路画面就永久黑了。我写了一个简单的进程监控循环检测到FFmpeg退出就等3秒重启并且设置了一个最大重启次数防止“死循环暴力重启”把机器拖垮。生产环境的话可以考虑把每个通道放进systemd服务管理或者用supervisor但我的简洁版直接用Python脚本搞定。3. 动手实操10分钟搭起多路播放页3.1 环境准备与目录结构实操之前先把环境准备好。你需要一台能访问到海康设备的Linux服务器Windows也行但命令稍微改改装好Python 3、FFmpeg以及对应Python的Flask库。前端不需要构建工具纯静态HTMLJS就行这对很多人来说是个加分项。我习惯的目录结构是这样hikvision-grid/ ├── app.py # Flask后端提供设备列表接口 ├── ffmpeg_manager.py # FFmpeg进程管理模块 ├── config.py # 设备配置写IP、账号、通道 ├── static/ │ ├── index.html # 多路播放页面 │ ├── hls.min.js # hls.js拷贝到本地 │ └── style.css # 页面样式 └── stream/ # HLS切片输出目录按通道建子目录 └── ch_1/ ├── index.m3u8 └── segment_0001.tsFFmpeg要确认版本2.8以上就能满足基本需求新版更好。安装命令在不同系统上不一样Ubuntu下是sudo apt install ffmpegCentOS下可能需要先装EPEL源。装完跑一下ffmpeg -version确认H.264解码器h264和HLS封装器hls都在后面才能干活。3.2 核心代码设备发现与FFmpeg进程管理核心代码分两块第一块是读取设备配置并启动FFmpeg进程。我这里做了一个FFmpegManager类负责维护所有通道的转流进程。import subprocess import time import logging logging.basicConfig(levellogging.INFO) class FFmpegManager: def __init__(self): self.processes {} def start_channel(self, channel_id, rtsp_url, output_dir): cmd [ ffmpeg, -rtsp_transport, tcp, -i, rtsp_url, -c:v, copy, -an, -f, hls, -hls_time, 2, -hls_list_size, 3, -hls_flags, delete_segments, -progress, pipe:2, f{output_dir}/index.m3u8 ] proc subprocess.Popen( cmd, stdoutsubprocess.DEVNULL, stderrsubprocess.PIPE ) self.processes[channel_id] proc logging.info(f通道 {channel_id} 转流已启动) def check_and_restart(self): for channel_id, proc in self.processes.items(): if proc.poll() is not None: logging.warning(f通道 {channel_id} 异常退出3秒后重启) time.sleep(3) # 重新拼接rtsp并拉流 self.start_channel(channel_id, ...)代码里加-an是因为监控画面原本就没有音频或者我们不需要音频省掉音频轨能减少封装开销。-hls_flags delete_segments这个参数很重要它让FFmpeg自动删除过期分片不然磁盘会被ts文件塞满。第二块是Flask接口提供设备列表和当前播放状态。我用ISAPI接口去拉设备通道信息把通道号、名称、码流地址整理成JSON返回给前端。from flask import Flask, jsonify, request import requests app Flask(__name__) DEVICES [ {id: 1, name: 门店A-入口, ip: 192.168.1.64, username: admin, password: password, channel: 1, stream_type: 2}, ] def build_rtsp(device): return (frtsp://{device[username]}:{device[password]} f{device[ip]}:554/Streaming/Channels/ f{device[channel]}0{device[stream_type]}) app.route(/api/devices) def device_list(): result [] for dev in DEVICES: result.append({ id: dev[id], name: dev[name], hls_url: f/stream/ch_{dev[id]}/index.m3u8 }) return jsonify(result) if __name__ __main__: app.run(host0.0.0.0, port5000, threadedTrue)这里有个小坑要提醒Flask默认只监听127.0.0.1如果你要让局域网内其他机器访问这个页面必须指定host0.0.0.0。我在项目里吃过这个亏自己在服务器上测试正常换台电脑访问就打不开页面排查半天才发现是监听地址的问题。3.3 前端多路播放页面前端页面我用了一个最朴素的方式CSS Grid做网格布局通过循环JS生成视频标签然后交给hls.js去播放。没有引入Vue、React这些框架因为对多路监控这种固定网格的场景来说原生JS已经足够简洁还省去了构建步骤。!DOCTYPE html html langzh head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title多路监控·简洁版/title style .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; background: #000; } .cell { aspect-ratio: 16/9; background: #111; position: relative; } video { width: 100%; height: 100%; object-fit: contain; } /style /head body div classgrid idgrid/div script srchls.min.js/script script async function init() { const res await fetch(/api/devices); const devices await res.json(); const grid document.getElementById(grid); grid.innerHTML devices.map(d div classcell video style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />