简介:面向需要快速部署GB28181视频监控平台的技术人员,这套wvp-GB28181-pro与ZLMediaKit集成安装脚本包,将直装版程序与依赖环境打包在一起,解决从零编译、环境配置繁琐的问题。压缩包共包含六个文件,其中四个为脚本、一个为组件压缩包、一个为说明文档,整体大小约为七百六十五兆字节。脚本分别负责数据库、缓存、运行环境及主程序的一键安装,组件包内为媒体服务相关依赖,文档则提供清晰的操作指引。正常使用场景下,只需在平台配置文件中修改对接地址,即可接入国标摄像头并启用全部功能,极大降低部署门槛。目前已有两千五百五十三人学习下载,适合安防监控集成商、运维工程师及视频平台二次开发人员作为开箱即用的搭建参考。
1. wvp-GB28181-pro和ZLMediaKit的打包安装脚本:把一套国标视频接入环境从一夜压缩到一刻钟
接到一个园区视频接入需求,现场几十台支持GB28181协议的摄像头,要统一接入一个平台做实时预览和录像回放。新上的服务器啥都没有,按老办法装:先装JDK、MySQL、Redis,再编译ZLMediaKit,最后把wvp-GB28181-pro的jar包和SQL跑起来,中间还得把SIP端口、媒体端口段、hook回调一条条对上。顺利的话一个晚上,不顺利的话第二天继续。打包好的安装脚本解决的问题,就是把wvp-GB28181-pro(国标信令平台)和ZLMediaKit(流媒体服务)连同它们各自的依赖一次部署到位。有一说一,脚本解决的是“装好”,不是“调好”,设备侧的SIP参数和平台侧的ID、密码、媒体端口最终还是要你对齐。这篇文章适合做安防集成、视频上云、国标设备接入的交付和运维同学,它能让你从手工搭环境里腾出手,把精力放到更费设备的对接和排障上。
2. 先分清信令和媒体:wvp-GB28181-pro和ZLMediaKit在链路里各管哪一段
2.1 GB28181的三件事决定了wvp的定位
GB28181是一个基于SIP的国标协议。摄像头、NVR、平台在协议里都是SIP UA。wvp-GB28181-pro在Java里实现了一个SIP服务器,并在此基础上做了三件核心事:注册、目录、点播。
注册是第一步。设备端配置一个SIP服务器地址、端口和ID,启动后向平台发REGISTER,平台回200 OK,设备就“在线”了。目录是第二步,平台下发查询目录的指令,设备把它下挂的通道列表回传,平台入库。点播是第三步,用户在页面点一个通道,wvp通过SIP INVITE通知设备“把流推到某某地址的某某端口”,这个地址和端口不是wvp自己的,而是ZLMediaKit提前占好的收流端口。
理解这一点很重要。很多人部署完发现设备在线但不出流,上来就查ZLMediaKit,其实方向错了,先查SIP INVITE有没有被回200,再查设备有没有真的往目标地址推流。wvp管的是“说”,ZLMediaKit管的是“收”和“转”,链条必须先对。
2.2 ZLMediaKit把“流到了之后”的所有事接管下来
摄像头按INVITE里的地址推流过来后,ZLMediaKit开始干活。它做的事情分几层:先把RTP/RTSP流接住,然后按需转封装成RTMP、HLS、HTTP-FLV、WebRTC等给前端播放,同时可以按计划做录像,也可以做多路分发,一路进来几十路出去。
ZLMediaKit经常被单独拿来当“拉流转推”的工具用,但在GB28181这条链路里,它的角色是“被动收流+主动分发”。被动收流指的是设备主动往它的某个端口推;主动分发指的是它把流用各种协议吐给播放端。你排查时如果发现播放端拉不到流,先分清是哪一段断了:设备有没有推上来,ZLMediaKit有没有接住,分发端口有没有监听。这三个问题用的命令完全不同。
这个“接住”的动作在ZLMediaKit里叫RTP代理,它会为每一路点播动态分配一个本地端口来接收媒体流,端口从一个预设范围内取。这也是为什么部署脚本里不是开一个端口,而是开一整段端口。
2.3 三个接头:SIP端口、媒体端口段、hook回调
wvp和ZLMediaKit之间有三个“接头”,接线没对齐,平台就是半残的。
| 接头 | 所在服务 | 常见默认 | 配错后的表现 |
|---|---|---|---|
| SIP注册端口 | wvp | UDP 5060 | 设备注册不上,页面一直离线 |
| 媒体收流端口段 | ZLMediaKit | 10000-20000 | 设备推流被丢,播放黑屏 |
| hook回调 | ZLMediaKit 到 wvp | HTTP接口,需配secret | 流状态不更新,录像上报丢失 |
先说SIP端口,它走UDP,而且必须和设备端的配置一字不差。设备端填的SIP服务器ID、密码、端口,和wvp里配置的要一致,否则注册包到了也回不了200。
第二个接头是媒体端口段。ZLMediaKit的RTP代理每接一路流就占一个端口,所以脚本里要预留一个连续的UDP端口段。如果只放行一个端口,第一路能看,第二路必黑。
第三个接头是hook。ZLMediaKit收到流、流断开、录像文件生成完之后,会向wvp的一个HTTP接口发POST事件。这里有两样东西要对:一是回调地址,二是secret。地址错,wvp收不到事件;secret不一致,wvp直接回401拒绝。
提示:wvp里配置的媒体服务地址,填的是ZLMediaKit对外的地址,千万别填127.0.0.1,除非两个服务确实在同一台机器的同一个网络命名空间里。
3. 安装脚本做了什么:从检查环境到拉起服务,一次手工部署被拆成几步
3.1 第一步:探测发行版和基础依赖
常见安装脚本不会上来就装,先做环境判断,把系统版本、架构、可用内存打出来,没通过就直接退出,避免装到一半发现缺编译器。ZLMediaKit是C++写的,即使下release包,也可能需要在本地编一部分动态库;wvp是Java包,要跑在JDK17这类环境上。依赖这一层,脚本把gcc、g++、cmake、openssl-devel、MySQL、Redis、Nginx一次性装齐。
#!/bin/bash # 简单识别发行版并安装基础依赖 source /etc/os-release if [[ "$ID" == "centos" || "$ID" == "rhel" ]]; then yum install -y gcc gcc-c++ make cmake openssl-devel libstdc++-static yum install -y mysql-server redis nginx elif [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then apt-get update apt-get install -y build-essential cmake libssl-dev apt-get install -y mysql-server redis-server nginx else echo "不支持的系统: $ID" exit 1 fi逻辑说明:先读 /etc/os-release 拿发行版ID,再走对应包管理器。CentOS系装的是 libstdc++-static,Ubuntu系装的是 libssl-dev,两者名字不一样,这是ZLMediaKit编译时最常见的两个依赖差异。这套脚本适合在全新机器上跑;如果内网不能访问外网源,就要给脚本加一个“离线模式”,把rpm/deb包提前下好放进本地目录,安装时直接指向本地源,很多现场服务器其实是不通外网的,这一步不是可选项。
3.2 第二步:端口规划和防火墙放行
装完依赖不等于能跑,端口才是第一个翻车点。部署脚本里会把端口定义成变量,集中在脚本头部,便于一次改完,也便于交付时让客户一眼看到整个平台要开放哪些端口。
# 端口规划统一放头部,改一处生效 SIP_PORT=5060 # GB28181 SIP 注册端口,UDP WEB_PORT=8080 # wvp 管理页面/API/WebSocket MEDIA_START=10000 # ZLMediaKit RTP收流起始端口 MEDIA_END=20000 # ZLMediaKit RTP收流结束端口 ZLM_API_PORT=80 # ZLMediaKit RESTful API 端口 ZLM_RTSP_PORT=554 # RTSP 分发端口放行策略建议写成函数,避免在脚本里散落一地的firewall命令:
open_ports() { if command -v firewall-cmd >/dev/null 2>&1; then firewall-cmd --permanent --add-port=${SIP_PORT}/udp firewall-cmd --permanent --add-port=${ZLM_API_PORT}/tcp firewall-cmd --permanent --add-port=${ZLM_RTSP_PORT}/tcp # 媒体端口段统一放行,否则并发点播必炸 firewall-cmd --permanent --add-port=${MEDIA_START}-${MEDIA_END}/udp firewall-cmd --reload fi }为什么媒体端口要一段而不是几个?因为ZLMediaKit为每路流动态分配端口,不是固定用某一个。把10000-20000整段放行,看起来开销大,实际上这些端口不是常开的监听端口,是收到RTP包才占用,不必担心安全面扩大太多。生产环境如果隔离要求高,可以在防火墙里加源IP白名单,只允许摄像头网段的IP访问媒体端口段。
3.3 第三步:初始化数据库和配置文件占位
wvp的jar包里不直接带配置,部署时把 application.yml 和 SQL脚本放到指定目录,启动时加载。脚本常用做法是把配置里的IP、密码写成占位符,再用sed替换成实际值,这样模板文件和脚本逻辑可以分开维护。
# 从模板生成真实配置 sed -e "s|{{DB_HOST}}|127.0.0.1|g" \ -e "s|{{DB_PASSWORD}}|${DB_PASSWORD}|g" \ -e "s|{{SIP_ID}}|${SIP_ID}|g" \ -e "s|{{SIP_PASSWORD}}|${SIP_PASSWORD}|g" \ -e "s|{{ZLM_ADDR}}|${ZLM_ADDR}|g" \ -e "s|{{ZLM_SECRET}}|${ZLM_SECRET}|g" \ application.tpl.yml > application.yml # 灌初始化SQL mysql -uroot -p"${DB_PASSWORD}" < init.sql这一步的几个参数要解释清楚:DB_PASSWORD是初始化时给MySQL root设的密码,脚本里应该先生成随机值再显示给用户;SIP_ID是SIP域标识,和设备端填的SIP服务器ID完全一致才能注册;ZLM_SECRET是wvp和ZLMediaKit共享的密钥,两边必须一样。模板文件的好处是版本升级时,只改模板,不用改脚本逻辑。
SQL初始化注意:wvp的初始化SQL是按MySQL 5.7/8.0的常见语法写的。如果你的MySQL是8.0以上,默认认证插件是caching_sha2_password,老版本依赖会有兼容问题。脚本里通常补一句ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '${DB_PASSWORD}';把认证方式兜底成原生密码,避免Java连接池报认证错误。
3.4 第四步:用systemd托管两个服务,启动顺序有讲究
手工部署的另一个问题是没有守护,进程一断平台就没了。脚本到这一步会为两个服务写systemd unit,对运维来说后面开机自启、看日志都方便。一个典型的wvp服务文件长这样:
cat > /etc/systemd/system/wvp.service <<'EOF' [Unit] Description=wvp-GB28181-pro After=network.target mysql.service [Service] WorkingDirectory=/opt/wvp ExecStart=/usr/bin/java -jar /opt/wvp/wvp.jar --spring.config.location=/opt/wvp/application.yml Restart=on-failure RestartSec=5 User=wvp [Install] WantedBy=multi-user.target EOFZLMediaKit的服务文件类似,ExecStart换成它的启动程序。启动顺序上,脚本统一先起ZLMediaKit,再起wvp。原因不是wvp依赖ZLMediaKit才能启动,而是设备点播放是随时可能发生的,你希望媒体服务先就位。两者都起来后,脚本做一次端口探测:
systemctl daemon-reload systemctl enable --now zlm.service systemctl enable --now wvp.service sleep 5 ss -lunp | grep -E "(:5060|:10000)" || echo "端口未监听,请检查失败原因"这里的ss命令是验证脚本有没有真的把端口拉起来,比用网页登录靠谱得多。wvp启动慢,Java进程要JVM预热,注册信令要等它打印“server started”之类日志才算真起来,页面打不开不一定代表进程没启动,别急着反复重启,先看日志。
3.5 第五步:升级与回滚,脚本里必须留的后悔药
部署脚本只解决首次安装,但交付现场迟早要升级。我建议脚本里直接带一个升级函数。动作很简单:停服务、备份旧版本、覆盖新版本、起服务。关键是备份这一步别省,而且备份的除了程序包,还有当前配置文件。
upgrade() { local app=$1 local pkg=$2 systemctl stop ${app} cp -r /opt/${app}/conf /opt/${app}/conf.bak.$(date +%s) cp /opt/${app}/${app}.jar /opt/${app}/${app}.jar.bak.$(date +%s) cp ${pkg} /opt/${app}/${app}.jar systemctl start ${app} }版本升级最容易翻车的地方是ZLMediaKit的config.ini跨大版本改格式。旧配置直接覆盖到新版本上,启动时报“unknown option”,所以升级脚本里ZLMediaKit的配置不自动合并,而是保留旧配置,新版本起来后按提示逐项确认。这个逻辑用三行注释写死在脚本里,防止三个月后你自己也忘了当初为什么要这么写。
4. 部署中最常见的五个坑:现象、原因、解决
4.1 设备注册不上:SIP端口是UDP的,防火墙只放行了TCP
现象:wvp页面设备列表一直空,SIP日志里没有任何REGISTER请求。
原因:现场防火墙默认只放TCP端口,而GB28181的SIP注册走的是UDP 5060。另一个常见原因是设备端填的SIP服务器ID和wvp配置的SIP域不一致,注册包到了,被wvp判定为非法来源不回应。
解决:先给防火墙加UDP 5060放行,再登录摄像头核对SIP服务器ID、密码、端口三项。判断依据用tcpdump抓包最直接:
tcpdump -i any udp port 5060 -n抓到的包里要能看到设备IP发REGISTER。如果包出去了没回应,查wvp日志里有没有鉴权失败的记录;如果压根没包过来,问题在设备配置或网络,和平台无关。
4.2 设备在线但拉流报401:ZLMediaKit的secret和wvp不一致
现象:设备注册正常,列表也看得到,点播放时前端返回401,wvp日志里出现secret错误。
原因:wvp配置里的媒体服务密钥和ZLMediaKit config.ini里的secret不一致,ZLMediaKit的hook回调或鉴权被拒绝。
解决:把两边改成同一个随机字符串。改完先重启ZLMediaKit,再重启wvp,顺序反了会看到wvp还在用旧连接。这个secret默认值在生产环境一定要换掉——ZLMediaKit没有内置的访问控制,secret就相当于它的钥匙,默认值等于把钥匙挂门上。还有一层要留意:ZLMediaKit的API端口反向代理到公网时,secret会在请求头里明文传递,务必给API端口做访问控制。
4.3 播放黑屏:媒体端口段没有放行,设备推流被丢
现象:SIP的INVITE流程已经走完,设备也回了200 OK,但播放端一直黑屏或转圈,ZLMediaKit看不到流。
原因:设备按INVITE里的地址往某个媒体端口推UDP流,但那段端口没放行,或者中间路由丢包。wvp没收到hook,所以页面状态也刷不出来。
解决:把10000-20000的UDP段加入放行列表。判断时注意,telnet测端口只能测TCP,UDP要用nc -u测,或者直接在ZLMediaKit日志里看有没有收到RTP包。这个翻车点最容易伪装成“设备不支持”或者“编码不对”,实际只是端口被挡。
4.4 播放结束后流状态不恢复:hook回调地址写错
现象:录像文件其实已经生成,但平台的录像列表拿不到;播放挂断后,页面上的状态不恢复成空闲。
原因:ZLMediaKit把“流变化”“录像文件生成”等事件POST到了错误的地址,wvp压根没收到通知。最常见的是部署时把hook地址写成了127.0.0.1,而wvp在另一台机器上。
解决:在ZLMediaKit配置里找hook相关设置,把地址改成wvp的HTTP访问地址。ZLMediaKit日志里搜“hook”或“notify”,能看到POST失败的记录。这个坑我踩过一整晚,纯属玄学,信令和媒体都正常,就是业务状态对不上,最后发现是回调地址少写了一个端口。
4.5 Nginx反代后页面能开、播放器起不来:WebSocket握手失败
现象:用Nginx反代wvp的8080端口后,页面正常打开,但播放器反复重连。
原因:wvp的播放器通过WebSocket拉取流状态和播放地址,Nginx反代默认不转发Upgrade头,握手直接失败。
解决:在Nginx的location里补两个头:
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";加上之后重载Nginx,再刷新页面。反代方案里还容易漏掉超时配置,播放长走后端没有读操作,默认60秒超时就把连接断了,长连接场景要把proxy_read_timeout调大。
5. 装完怎么证明它能用:从注册到出流的验证
5.1 信令在线:看日志和抓包,别只看页面
页面显示“在线”有可能是假象。设备掉线后,SIP的超时机制要等几个周期才判离线,页面在这段时间里还显示绿色。可靠的判断是看wvp日志,设备每注册一次都会打一条记录,再严格一点就抓包。
看注册状态用一条命令:
journalctl -u wvp -f | grep -iE "register|REGISTER"抓包看完整握手:
tcpdump -i any udp port 5060 -n -v正常的注册交互是设备发REGISTER,wvp回401(带挑战),设备重新带鉴权信息再发REGISTER,wvp回200 OK。如果你看到的交互停在401,说明密码不对或SIP域不匹配,这比看页面直观得多。
5.2 媒体链路:用ffplay绕过页面直连ZLMediaKit
页面播放涉及wvp鉴权、WebSocket、浏览器解码等多层,出错时不好定位。验证媒体链路最直接的办法,是绕过wvp直连ZLMediaKit拉流。在wvp的播放接口返回里能找到流地址,通常形如rtsp://IP:554/live/streamId,直接用ffplay拉:
ffplay -i "rtsp://127.0.0.1:554/live/your_stream_id"如果ffplay能出画面,问题不在ZLMediaKit,往上查wvp到ZLMediaKit之间的地址配置;如果ffplay黑屏,再看设备有没有把流推到ZLMediaKit。登录ZLMediaKit的API,调用getMediaList接口能列出当前所有存活流,有流说明ZLMediaKit收到了,没流说明设备根本没推上来,问题又回到了SIP INVITE。
5.3 录像和回放:验证的是设备能力和平台调度的配合
录像分两段。设备端录像由设备自己存,平台通过SIP INVITE带回放参数(时间范围、流ID)去调;平台录像由ZLMediaKit按计划录制,落盘在ZLMediaKit的录像目录。验证时先做一轮平台录像:配一个计划任务,等约5分钟,去ZLMediaKit的rec目录确认文件增长。再验证回放:页面上选一个时间点,发起回放,看是否出画面和进度条是否可拖。
录像文件时间戳对不上是时区坑。wvp的JVM、MySQL、ZLMediaKit如果用了不同时区,录出来的文件名和检索时间会整体偏移。脚本里应该在三个服务启动前统一TZ=Asia/Shanghai,减少这种查不明白的时间问题。
6. 把安装脚本改造成运维基线:版本锁定、健康检查和升级纪律
脚本跑通了只是个起点,我更推荐把它当运维基线来维护。血泪经验是版本锁定:脚本里所有下载的包都要锁版本号或SHA,别用latest。有一次部署时ZLMediaKit用了最新release,配置格式变了,老脚本生成的config直接不识别,排查了一个下午。原因不复杂,自动拉新版本,配置格式向前不兼容。锁版本后,每次升级走单独流程,问题就好控制了。
再补一个健康检查函数。运维不是装完就跑,要能随时知道两个服务活着:
health_check() { curl -sf http://127.0.0.1:80/index/api/getServerConfig -H "secret: ${ZLM_SECRET}" >/dev/null && echo "zlm ok" curl -sf http://127.0.0.1:8080/ >/dev/null && echo "wvp http ok" }如果后面还要扩展,比如把AI识别(yolo那类事件检测)接进平台,ZLMediaKit的流事件hook和wvp的录像回调都能作为事件源,把视频帧交给算法容器做异步分析。但那是后话,前提是当前这套部署基线够稳、升级可复现。我曾经图省事跳过版本锁定,后来在线环境出问题想回滚,才发现旧包没留。从那以后我每次改脚本都先写一行备份注释。这套安装脚本现在给我的价值,不是省那一次安装的时间,而是让每次交付都长一个样。希望帮到你。
本文还有配套的精品资源,点击获取