☰
GB28181视频平台搭建:WVP与ZLMediaKit部署联调
2026/9/30 5:11:44 网站建设 项目流程

GB28181 这套东西,第一次接触的人大概都会被"国标"两个字唬住,觉得是运营商的活儿,实际上把它拆开看,无非是一个 SIP 信令 + RTP 媒体流的组合,再配一套前端管理和一个流媒体转发服务。我自己第一次在 CentOS 7 上搭 wvp-GB28181-pro 加 ZLMediaKit 的时候,前后折腾了两天,踩的坑主要不在代码本身,而在依赖版本、端口占用和配置文件里几个不起眼的字段。这篇就把整个部署流程从环境准备到联调验收完整走一遍,顺带把那些文档里往往一笔带过、但实际会让服务起不来的细节讲清楚。适合有一定 Linux 基础、想自建国标视频接入平台的运维和开发看,新手照着做也能跑通,老手可以重点看第四章和第五章的配置字段与排查思路。

1. 先把组件分工理清:为什么是 WVP 加 ZLMediaKit 这套组合

很多人上手第一步是把两个项目都 clone 下来,结果发现配置文件里到处都是对方的地址,互相看不懂。根源在于没弄清这两个服务各自负责什么。国标协议规定了设备怎么注册、目录怎么查询、信令怎么交互,但它没规定视频流具体由谁来转发。WVP 负责的是"信令大脑",ZLMediaKit 负责的是"媒体搬运工",两者通过 HTTP Hook 和一串约定好的地址通信。

1.1 WVP 管信令,ZLMediaKit 管流

wvp-GB28181-pro 是基于 Spring Boot 的 Java 服务,它本身收发 SIP 消息:设备向它注册,它向设备发起 INVITE 邀约,设备把 RTP 流推给它指定的地址。但 WVP 自己并不擅长转发大流量媒体流,所以它把"收流地址"交给 ZLMediaKit。

具体流程是这样的:设备注册到 WVP 之后,用户在网页上点击"播放",WVP 向设备发送 INVITE,SDP 里写的接收地址其实是 ZLMediaKit 的 IP 和端口。设备把 RTP 推过来,ZLMediaKit 收流并转成 RTSP、HTTP-FLV、HLS、WebRTC 等格式,前端播放器再从 ZLMediaKit 拉流。WVP 通过配置的 hook 地址监听 ZLMediaKit 的事件(比如"流注册成功""流无人观看"),从而知道流什么时候该关。

理解这一层之后,配置里那些media.ip、media.stream-ip、hook地址就不会填错了——它们指向的都是 ZLMediaKit 那一端,而不是 WVP 自己。

1.2 部署顺序为什么建议先 ZLMediaKit 后 WVP

我试过反过来,先起 WVP 再装 ZLMediaKit,结果是 WVP 启动时尝试连 hook 地址失败,日志里一堆连接拒绝,虽然服务不会崩,但排查起来干扰很大。

ZLM 是底层依赖,先把它跑起来并且用curl确认 API 可访问,再启动 WVP,WVP 一启动就能连上,日志干净。这个顺序的好处在于,出问题时你能明确知道是哪一层的锅,而不是两个服务都没起来互相甩锅。

1.3 CentOS 7 上的版本选择要点

CentOS 7 自带的软件版本比较老,这是后面一堆编译问题的根源。几个关键点先摆出来:

组件CentOS 7 默认版本建议版本说明
GCC4.8.58 及以上ZLM 编译依赖 C++17
CMake2.8.123.1 以上低于 3.1 无法配置 ZLM
JDK通常无8 或 11以 WVP 版本要求为准
MySQL无5.7 或 8.0注意字符集配置
Redis无5 以上WVP 缓存与状态存储

提示:ZLM 的编译工具链用devtoolset系列升级最省事,不必冒险替换系统 GCC,避免影响其他已编译软件。

这一章的目的是让你心里有张地图。接下来进入真正的动手环节,从系统环境开始收拾。

2. CentOS 7 系统层的准备工作:把地基打平

系统环境这一步最容易被跳过,但后面 80% 的"服务起不来"都能追溯到这里。CentOS 7 默认的防火墙、SELinux、时钟、句柄数,都可能成为拦路虎。我一般会花十几分钟把这几项一次性处理掉,后面就省心了。

2.1 关掉 SELinux 和调整防火墙

SELinux 在容器化部署里经常是隐藏的坑,尤其是在 /usr/local 下自建目录、或者让 Nginx 反代本地端口的时候,会出现"权限明明对但就是访问不了"的诡异现象。图省事的做法是直接设成 permissive 模式:

# 临时生效 setenforce 0 # 永久生效 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

防火墙方面,国标部署涉及的端口挺多,与其一个个记,不如先统一放开内网,或者按下面这张表逐条放行。生产环境当然要精细化,测试环境可以先把 firewalld 停掉减少干扰:

systemctl stop firewalld systemctl disable firewalld

如果必须保留防火墙,需要放行的端口大致如下:

端口协议用途
8116UDP/TCPWVP 的 SIP 端口
5060UDP/TCP部分场景下的标准 SIP 端口
18080TCPWVP 的 Web 与 API
80TCPZLM 的 HTTP 与 FLV/HLS
554TCPZLM 的 RTSP
1935TCPZLM 的 RTMP
10000 起UDPZLM 收 RTP 流的端口段
30000 起UDP发送 RTP 流的端口段

注意:RTP 收流端口段和发流端口段一定要和后面配置文件里的port-range对得上,范围对不上设备推流会直接超时,这是非常隐蔽的一类故障。

2.2 时钟同步与句柄数调优

国标信令里带时间戳,如果服务器时间和设备时间偏差太大,个别设备会拒绝注册或者注册后立刻掉线。CentOS 7 装好后先确认时间:

timedatectl status # 若未同步,启用时间同步 timedatectl set-timezone Asia/Shanghai

句柄数这块,媒体服务在高并发收流时打开的文件描述符相当可观,默认的 1024 往往不够。修改/etc/security/limits.conf,追加:

* soft nofile 65535 * hard nofile 65535

改完之后要重新登录终端才生效,用ulimit -n确认。这一项在做压测或者接入几十路以上设备时尤其重要,否则会看到 ZLM 报 "too many open files" 然后无故断流。

2.3 编译工具链的安装

ZLM 需要 CMake 3.1 以上和较新的 GCC。CentOS 7 上装 devtoolset-8 是常用做法:

yum install -y centos-release-scl yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++ scl enable devtoolset-8 bash

scl enable只在当前会话生效,装完编译完就可以了。如果嫌每次都要敲,可以写进/etc/profile.d/里自动加载。

CMake 版本不够的话,直接从官网下个二进制包解压即可,不用编译:

cd /usr/local wget https://cmake.org/files/v3.20/cmake-3.20.0-linux-x86_64.tar.gz tar -zxvf cmake-3.20.0-linux-x86_64.tar.gz ln -s /usr/local/cmake-3.20.0-linux-x86_64/bin/cmake /usr/bin/cmake

这些准备工作看着琐碎,但每一项都对应后面一个潜在的坑。地基打平之后,编译和部署就顺多了。

3. 让 ZLMediaKit 先跑起来:编译、配置与自检

ZLMediaKit 是整个平台的媒体中枢,它的状态直接决定了画面能不能出来。这一章我会把编译方式的选择、配置文件里必须改的项、以及如何确认它真的健康都讲一遍。跑通这一章,后面 WVP 的联调会轻松很多。

3.1 编译方式怎么选

ZLM 官方提供了源码编译和预编译包两条路。我的经验是:如果只是测试,可以直接用带linux-x86_64字样的预编译包,解压就能跑;但生产环境还是建议源码编译,一来方便后续加功能,二来能避开预编译包对某些系统库版本的隐性依赖。

源码编译的大致流程:

git clone --depth 1 https://github.com/ZLMediaKit/ZLMediaKit.git cd ZLMediaKit git submodule update --init mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j4

编译耗时不短,四核机器大概十几分钟。完成后release/linux/Release/目录下会有MediaServer可执行文件。

有个细节值得说:cmake如果不带任何参数,默认会尝试构建测试用例,可能因为缺少一些可选依赖报错。加-DENABLE_TESTS=OFF能规避这类问题。编译失败时先看错误发生在哪个模块,多半是某个子模块没拉全,重新git submodule update --init再编译即可。

3.2 config.ini 里必须改的几项

ZLM 第一次运行会在当前目录生成config.ini。这个文件很长,但真正需要动的不多。下面这几项不改,WVP 大概率连不上:

[api] # API 的密钥,WVP 里要填一致 secret=你的随机密钥 [general] # 媒体服务器唯一标识,WVP 靠它区分实例 mediaServerId=你的标识 [http] # HTTP 端口,前端拉 FLV/HLS 用 port=80 # 允许跨域,前端播放器常需要 allow_cross_domains=1 [rtp] # RTP 收流端口段,必须与 WVP 配置对应 port=10000 # 收流超时,设备迟迟不推流就释放 timeoutSec=15 [hook] # 开启 hook 才能让 WVP 感知流事件 enable=1 # 指向 WVP 的地址 on_publish=http://127.0.0.1:18080/index/hook/on_publish on_play=http://127.0.0.1:18080/index/hook/on_play on_stream_changed=http://127.0.0.1:18080/index/hook/on_stream_changed # hook 鉴权参数,要与 WVP 端一致 admin_params=secret=你的hook密钥

secret和admin_params这两处是重灾区。很多人改了secret忘了改 hook 里的admin_params,导致 ZLM 调 WVP 的 hook 时被拒绝,表现为"流上了但 WVP 显示离线",排查半天。

提示:port段和send-port-range段落要一起规划。收流和发流用不同的端口段,可以避免端口冲突,方便在 iptables 里分别做策略。

3.3 用 API 自检确认服务真的活着

服务启动后,别急着配 WVP,先用 API 确认 ZLM 本身没问题。启动命令:

cd release/linux/Release ./MediaServer -d &

带-d是后台运行。然后 curl 一下:

curl "http://127.0.0.1/index/api/getServerConfig?secret=你的随机密钥"

能返回一大段 JSON,说明 API 正常。再查一下线程和媒体列表:

curl "http://127.0.0.1/index/api/getStatistic?secret=你的随机密钥"

如果 API 报 "secret 错误",说明密钥不对;如果直接连不上,检查端口是否被占用、进程是否真的起来了。这一步确认通过之后,再进入 WVP 的部署,心里就有底了。

4. WVP-GB28181-pro 落地:数据库、配置字段与启动

WVP 是 Java 服务,配置项多,但真正需要改的字段集中在application.yml里。这一章我会把数据库准备、关键字段逐个拆解,以及启动时怎么通过日志判断成败。DS 层配置错了,WVP 根本起不来;SIP 层配错了,设备注册不上;media 层配错了,画面出不来。三个层面的问题定位方式完全不同。

4.1 数据库与缓存的准备

WVP 需要 MySQL 和 Redis。MySQL 建库时字符集务必用utf8mb4,否则设备名称里的特殊字符会出问题:

CREATE DATABASE wvp CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

建好库之后,WVP 启动时通常会自动建表(部分版本通过 Flyway 执行迁移脚本)。Redis 主要用来存 SIP 会话状态和临时缓存,默认连本机 6379 即可,如果有密码记得在第 4.2 节的配置里填。

一个容易忽略的点是 MySQL 8 的驱动和时区。CentOS 7 上如果用 MySQL 8,连接串里要带时区参数,否则 WVP 启动时会报时区识别失败:

url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai

4.2 application.yml 关键字段逐个拆

这份配置里,我把它分成三段来看。第一段是数据库,第二段是 SIP,第三段是 media。下面只列要点:

spring: datasource: url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai username: root password: 你的密码 redis: host: 127.0.0.1 port: 6379 password: sip: # WVP 监听 SIP 的地址,公网部署要写内网可达 IP ip: 你的服务器IP port: 8116 # SIP 域,一般与设备端配置的 SIP 域一致 domain: 3402000000 # 平台自身国标编号 id: 34020000002000000001 password: 平台注册密码 media: # ZLM 的 ID,要和 config.ini 里 mediaServerId 一致 id: 你的标识 # ZLM 的 IP ip: 你的服务器IP # 流媒体对外发布的 IP,供前端和外部设备访问 stream-ip: 你的服务器IP # SDP 中告知设备的收流 IP sdp-ip: 你的服务器IP # hook 地址,指向 WVP 自己 hook-ip: 127.0.0.1 rtp: enable: true # 收流端口段,要和 config.ini 的 rtp.port 对应 port-range: 10000,10500 send-port-range: 30000,30500

sip.ip和media.ip是最容易填混的两处。前者是 WVP 自己收 SIP 的地址,后者是 ZLM 的地址。如果 WVP 和 ZLM 在同一台机器,两者一样;如果分开部署,就要各填各的。

sdp-ip指的是设备推流的目标 IP,也就是设备要往哪个地址发 RTP。这个字段如果填成了内网地址而设备在外网,设备就会推到一个它无法到达的地址,表现为"信令成功但一直没画面"。

4.3 启动顺序与日志判读

配置改完后,启动顺序是 Redis、MySQL、ZLM、WVP。WVP 的启动用 java 命令或打包后的脚本:

java -jar wvp-pro-xxx.jar --spring.config.location=application.yml

启动日志里,重点看三行:一是 Spring 容器是否正常启动完成,二是 SIP 监听端口是否成功绑定,三是是否成功连上 ZLM 的 API。看到类似"媒体服务器连接成功"的字样,就说明后端链路通了。

如果日志里出现 hook 连接失败,八成是 ZLM 没起或者端口不对;如果出现数据库连接失败,检查账号密码和网络;如果 SIP 端口绑定失败,多半是端口被占用。这三类问题覆盖了绝大多数启动失败的情况。

4.4 前端访问与初始账号

WVP 起来了之后,浏览器访问http://服务器IP:18080,默认账号密码通常是admin/admin(以你使用的版本为准)。第一次登录建议立刻改密码,因为平台管理权限很大。

登录后进入平台配置页,重点核对"媒体服务器"列表里是否已经出现 ZLM 记录,状态是不是在线。如果显示离线,回到第 3.3 节的 API 自检再确认一遍,多数是secret或 IP 填错。

到这里,后台环境就算搭好了,接下来是最有成就感的环节:让真实设备接进来。

5. 联调验收:设备注册、点播与常见故障定位

环境搭好了,但"服务能跑"和"设备能接"是两回事。这一章聚焦联调,把设备接入的完整链路、点播失败时的排查顺序、以及语音对讲和级联这两个进阶场景讲清楚。这部分是我踩坑最多的地方,也是文档里讲得最少的地方。

5.1 设备注册链路的排查方法

设备注册本质上是一次 SIP REGISTER 交互。设备向 WVP 的 SIP 地址发送注册请求,WVP 校验编号和密码后返回响应。判断注册成功与否,最直接的是看 WVP 的"设备管理"列表里设备状态是否在线。

如果设备一直不在线,按这个顺序查:

  1. 设备端配置的 SIP 服务器地址是不是 WVP 所在服务器的可达 IP。
  2. 设备端的 SIP 端口是不是 8116(或你配置的端口),传输协议 UDP 还是 TCP 要和 WVP 一致。
  3. 设备端的 SIP 域、编号、密码是不是和 WVP 里添加的设备信息完全一致,注意编号位数。
  4. 服务器防火墙有没有放行 8116。
  5. WVP 日志里有没有收到 REGISTER 消息。

我遇到过一次,设备端填的 SIP 域少了一位,导致 WVP 收到注册请求但校验失败,日志里能看到请求进来但被拒绝。这种问题不看日志很难定位,所以在排查注册问题时,养成先看 WVP 日志的习惯。

5.2 点播失败时按这个顺序查

点播失败的表现是页面黑屏或者一直转圈,原因可能出现在链路的任何一环。我总结的排查顺序是从后往前:先看 ZLM 有没有收到流,再看 WVP 有没有发出 INVITE,最后看设备有没有响应。

排查点观察方式常见原因
ZLM 是否收流查 ZLM API 的媒体列表收流端口段不对
WVP 是否发 INVITE看 WVP 日志设备编号或域配置错
设备是否响应抓包或看设备日志设备不支持该编码
前端能否拉流直接访问 ZLM 的流地址跨域或端口未放行

一个很典型的坑是编码协商。有些设备默认推 H.265,而浏览器播放器只支持 H.264,结果就是信令全成功,ZLM 也收到了流,但前端播不出来。这时候要么在设备端把编码改成 H.264,要么在前端使用支持 H.265 的播放方案。定位这类问题,直接拿ffplay或 VLC 去拉 ZLM 的 RTSP 地址,能播说明是前端问题,不能播说明是流本身的问题。

注意:拉流地址里通常带stream参数,格式随 WVP 版本变化,最稳妥的方法是打开浏览器开发者工具,看播放器实际请求的 URL,照着它去用 ffplay 验证。

5.3 语音对讲与级联场景的注意点

语音对讲(broadcast/talk)和级联是国标平台常见的进阶需求,但它们的信令流程比点播复杂,出问题时也更隐蔽。

语音对讲的方向和点播相反:点播是平台拉设备的流,对讲是平台把音频推给设备。它需要 ZLM 的 RTP 发流端口段可用,同时设备要支持对应的音频编码(通常是 G.711)。如果对讲按下去没声音,先确认设备的对讲功能是否开启,再检查发流端口段有没有被防火墙挡住。

级联方面,常见的需求是下级平台向上级平台注册,然后上级平台向下级查询目录、拉取资源。要注意的是,"上级主动向下级拉取资源"这种能力在不同版本上的支持程度不一样。有些版本需要下级平台主动上报目录,上级才能看到资源。如果你在做这种场景,建议先确认两端平台的版本和级联配置,把catalog订阅和响应的日志都打开,看目录请求是否发出、响应是否返回。这类问题的排查基本靠日志,光看界面状态是不够的。

联调通过之后,平台就算能用了。但要让它稳定跑下去,还有几件运维上的事要做。

6. 上线之后的稳定性维护:那些跑一段时间才暴露的问题

很多人搭好之后觉得万事大吉,结果运行一两周开始出现断流、内存上涨、注册掉线。这一章讲讲长期运行的维护经验,这部分内容基本不会写在部署文档里,但恰恰是生产环境的真实痛点。

6.1 内存、句柄与端口回收

ZLM 长时间运行,如果收流句柄没有及时释放,内存会缓慢上涨。config.ini 里的timeoutSec和流无人观看自动关闭的策略很关键。建议开启"无人观看自动关流",避免僵尸流占着资源:

[hook] # 流无人观看时通知并关闭,减轻服务器负担 on_stream_none_reader=http://127.0.0.1:18080/index/hook/on_stream_none_reader

WVP 端也要配置对应的无人观看处理逻辑。另外,RTP 收流端口是有限的,如果某个流异常退出而端口没释放,后续流就会因为端口被占而收不到。定期检查 ZLM 的媒体列表,清理那些没有读者的僵尸流,是个好习惯。

句柄方面,前面调过nofile之后,还要配合监控。用一条简单的命令就能看进程打开了多少描述符:

ls /proc/$(pgrep MediaServer)/fd | wc -l

如果这个数字持续增长不降,说明有资源没释放,需要进一步排查是哪个模块的问题。

6.2 升级与配置备份策略

WVP 和 ZLM 都在活跃迭代,升级是免不了的。我的做法是升级前先把配置文件单独备份,因为版本更新经常会调整配置字段,直接覆盖新包可能把配置带跑。备份至少包括application.yml、config.ini和数据库。数据库可以用mysqldump:

mysqldump -uroot -p wvp > wvp_backup_$(date +%F).sql

升级 ZLM 时,编译产物和配置文件分开管理,用软链接指向当前版本,回滚时改个软链接就行,不用重新编译。这套方式在多实例部署时尤其好用。

6.3 日常巡检的几个检查项

最后列几个我日常会看的项,形成习惯之后能提前发现大部分问题:

  • ZLM 的 API 是否正常响应,媒体列表是否出现异常增多的流。
  • WVP 的日志里是否有大量注册失败或者 hook 超时。
  • 磁盘空间,尤其是 HLS 切片和日志目录,容易被写满。
  • Redis 内存占用,会话状态堆积会导致内存上涨。
  • 服务器时间是否仍然同步,时钟漂移会引发信令异常。

把这些做成一个简单的巡检脚本,每天跑一次,比出事之后再救火从容得多。我在实际运维中最大的体会就是,国标平台这类服务,真正难的从来不是第一次搭起来,而是让它连续几个月不出幺蛾子——而后者靠的就是这些看似不起眼的日常维护动作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询