接自己的 ZLMediaKit:绿色安装包怎么换成外部流媒体
绿色安装包里带了一个 ZLMediaKit,端口占 51004–51013 加 51014–51063 的 RTP 段,secret 随包固定,配置文件在 vendor/zlm/config.ini。单机部署时它和平台在同一台机器上,装完即用。
换自己的 ZLM,理由通常更实际:
- 已有 ZLM 集群在跑,不想再多一套
- 流媒体要单独一台机器,甚至单独一个网段/机房
- 媒体并发高,内置节点扛不住,要加节点做调度
- 单位要求媒体组件统一版本、统一运维
平台本来就支持多节点、按权重调度、按设备指定节点。外部 ZLM 不用改造,登记进来就能用。麻烦的地方在于它是双向的:平台调 ZLM 是一个方向,ZLM 回调平台是另一个方向,一个方向配通不代表另一个通。

一、四条链路,各自的判据
| 链路 | 方向 | 端口(绿色包默认) | 不通的表现 |
|---|---|---|---|
| 平台 → ZLM API | 平台主机 → ZLM | 51004/tcp | 新增节点时「连接失败」,点播全挂,页面提示服务未装配 |
| ZLM → 平台 hook | ZLM 主机 → 平台 | 51002/tcp(后端 HTTP) | 点播能出流但状态不动、录像不出、无人观看不断流 |
| 设备 → ZLM RTP | 设备 → ZLM | 51014–51063/udp | SIP 返回成功,但画面一直黑,平台侧「收流超时」 |
| 浏览器 → ZLM | 浏览器 → ZLM | 51004(http-flv/ws-flv/HLS) | 后端一切正常,播放器转圈或报跨域/连接失败 |
先手工验一遍,再去动平台配置。四条命令对应上面四行:
# ① 平台主机上:能不能调通 ZLM 的 API
curl -s "http://192.168.10.220:51004/index/api/getServerConfig?secret=你的secret" | head -c 200
# ② 在 ZLM 主机上:能不能打到平台的 hook 端口
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.10.106:51002/index/hook
# ③ 设备侧:平台点播一台设备后,看 ZLM 有没有收到 RTP
# 在 ZLM 主机上抓包,端口段按自己的配置改
tcpdump -i any -n udp portrange 51014-51063 -c 20
# ④ 浏览器所在机器:直接打开拉流地址
# http://192.168.10.220:51004/live/<流ID>.live.flv四条都通过再接节点,能省掉一大半来回。
二、两种接法,先选一种
接法 A:还没启动过,首启就用外部 ZLM
绿色包首次启动时,如果节点表是空的,会按 config.yml 的 gb28181.zlm 段登记一个默认节点。把这一段直接改成你的 ZLM,首启出来的就是你的节点,不需要事后删内置节点。
gb28181:
zlm:
host: "192.168.10.220" # 平台访问 ZLM 的地址
sdpip: "192.168.10.220" # 写进 SDP 的 c= 行,设备往这里推 RTP
playbackhost: "192.168.10.220" # 浏览器拉流地址,NAT 时填公网地址
httpport: 51004 # ZLM 的 HTTP API 端口
secret: "你在 ZLM config.ini 里配的 api.secret"
rtpport: 51014 # 单端口收流时的端口
mediaserverid: "uvp-node-1" # 节点身份标识,建议给固定值同时把 hook 回调地址指对:
gb28181:
media:
# 推荐写完整基址:ZLM 在另一台机器时,127.0.0.1 是它自己,不是平台
hookbaseurl: "http://192.168.10.106:51002/index/hook"
hookhost: "192.168.10.106" # 不写 hookbaseurl 时才用这两项拼
hookport: 51002⛔ 跨机时 hookhost 必须是平台的地址,不能留 127.0.0.1。留着的后果很典型:ZLM 把事件回调给自己,连不上;平台这边只表现为流状态不更新,日志里一条报错都没有,因为请求根本没到平台。
gb28181.zlm 段只在节点表为空时生效。已经启动过的实例改它没用,节点已经在 meta_node 表里了,得走接法 B。
接法 B:已经在跑,事后加节点
登录平台,进 流媒体管理 → 节点管理 → 节点列表,点「新增节点」。表单分两步。
第一步填连接信息:
| 字段 | 填什么 |
|---|---|
| 节点别名 | 只用于平台内展示,不会写进 ZLM;留空显示「IP:端口」 |
| IP 地址 | 后端能访问到的 ZLM 地址(不是浏览器的地址) |
| API 端口 | ZLM 的 [http] port |
| API Secret | ZLM 的 [api] secret,只写不回显 |
点「连接并读取」,平台会实时去读一遍 ZLM 的真实配置,然后回显:
- 各协议端口(HTTP / HTTPS / RTSP / RTMP / RTC / ONVIF)与开关状态
- RTP 单端口,以及
[rtp_proxy] port_range的实际范围 - ZLM 自己的
general.mediaServerId(没有则显示「登记后由平台生成」)
端口以 ZLM 实测值为准,不以你填的为准。RTP 端口范围在表单里是只读的,真源在 ZLM 的 config.ini,平台这边只是镜像。
第二步确认后点「确认添加」,平台会做两件事:
- 通过 ZLM 的
setServerConfig写入general.mediaServerId,让 ZLM 后续的每次回调都带上这个身份; - 下发 hook:把平台该管的那些事件回调地址逐条写成
http://平台/index/hook/<事件>?node=<节点UUID>&cap=<凭据>,并打开hook.enable。
所以你不需要手工在 ZLM 的 config.ini 里配 hook。手工配也行,不过平台的规则是:
- 只刷新「带本节点凭据」或「路径就是本平台 hook 端点」的那些回调;
- 其他回调视为你在用,不覆盖;
hook.enable = 0被视为你的显式选择,平台不会强行打开。

mediaServerId、下发带 node 与 cap 的 hook 回调。添加完成后,还有三个地址要按需确认(新建第二步或事后的「编辑」里都能填):
- 设备推流地址(sdpIp):设备往哪推 RTP。跨网段、平台在 NAT 后面时必填,不能填
127.0.0.1 - 播放地址(playbackHost):浏览器从哪拉流。经过 NAT 时填公网地址或域名
- 平台回调地址(hookIp):ZLM 回调平台时连的地址,跨机时必填
这三个地址配错的表现各不相同:sdpIp 错 → 信令成功、没画面;playbackHost 错 → 平台里能看到流,播放器打不开;hookIp 错 → 能出流,但状态与录像不对。
三、内置那个 ZLM 怎么办
启动脚本不管你接没接外部节点,每次 start 都会把包内 ZLM 拉起来,它不知道你在用别人的。两种处置:
方案一(省事):让它空转。 什么都不用改。包内 ZLM 在 51004–51013 上跑着,平台不用它,只是多占一份内存。缺点是两套流媒体同时在跑,运维看着会疑惑。
方案二(干净):把内置的端口挪开,或者干脆停掉它。 如果你的 ZLM 也要用 51004,不挪端口会直接启动失败——脚本会报「ZLM 端口 51004 已被其他进程占用」,然后整个 start 中止。改 config/config.env 让内置的让开:
UVP_ZLM_HTTP_PORT=52004
UVP_ZLM_SSL_PORT=52007
UVP_ZLM_RTSP_PORT=52005
UVP_ZLM_RTMP_PORT=52006
UVP_ZLM_RTC_PORT=52008
UVP_ZLM_SIGNALING_PORT=52009
UVP_ZLM_SIGNALING_SSL_PORT=52010
UVP_ZLM_SRT_PORT=52011
UVP_ZLM_ONVIF_PORT=52012
UVP_ZLM_ICE_PORT=52013
UVP_ZLM_RTP_PROXY_PORT=52014
UVP_ZLM_RTP_RANGE=52014-52063改完 restart。UVP_ZLM_RTP_RANGE 和 UVP_ZLM_RTP_PROXY_PORT 必须同一起点,两个都改又互相矛盾时脚本会拒绝启动。
如果连节点列表里的内置节点都不想要:先把它切到「维护态」,确认上面没有流和会话,再删除。删除会走影响预检,有流、有会话、有录像时会被拒绝,这是预检在拦你,不是坏了。
四、三个容易卡住的地方
节点删不掉:先看是哪一种
平台的删除有四道关卡:权限、影响预检(实时读一次该节点的流/会话/录像)、必须处于维护态、影响必须为空。所以:
- 提示「节点影响预检失败」= 连不上这个节点(ZLM 挂了、IP 变了、secret 错了)
- 提示「请先把节点切到维护态」= 状态还是 active/offline
- 提示「节点仍有活动影响」= 上面真的还有流或会话
第一种最难受:ZLM 已经不可达,预检读不通,既切不了维护态也删不掉,死锁。这时用强制移除(purge=unreachable)。平台会先真探一次,如果发现节点其实可达,直接拒绝(409),所以它当不了绕过前两道关卡的万能删除键。强制移除只给真的够不着的节点用。
删节点是单向的,对端不知道
平台删掉节点之后不会通知那台 ZLM。ZLM 继续按自己的周期往平台回调,平台已经不认识它了,日志里就持续出现 node_unknown 的拒绝记录。on_server_keepalive 这类事件每 10 秒一条,很快能刷满日志。
现在的版本在退役节点时会尝试把平台自己写过的 hook 逐条撤掉,带退避重试,撤不掉会留一条显式的失配记录。但你要是手工在 ZLM 侧配过 hook,那部分平台不碰,得自己去清。
mediaServerId 不要随手改
它不只是个名字。平台靠它判断这条回调是哪个节点发来的,而 ZLM 会把 setServerConfig 的结果持久化回自己的 config.ini。一旦改成和平台登记不一致的值,hook 归属校验就永远对不上,症状是节点在线、设备也能点播,但状态和录像全乱。
要么让平台写,要么在登记前就固定好(接法 A 里的 mediaserverid),别中途手工改。
五、多节点时怎么用
节点登记好以后,平台按权重把新流分到各节点,也可以给单台设备指定节点(zlm_node_id)。用起来有三个约定:
- 状态三值:
active参与调度;maintenance不接新流、旧流自然结束;offline是心跳超时后的判定。把一个活着的节点手工改成 offline 没有意义,下一轮心跳会把它翻回 active。 - 权重 0 表示不参与加权调度,适合「备用节点」。
- 端口容量按登记的 RTP 段算。登记段比 ZLM 实际段大时,调度会一直以为这个节点很空,直到端口真的耗尽、推流开始失败。所以新增节点时让平台去读实测值,别手填。
六、验收清单
接完按这张表走一遍,比「感觉能播了」可靠:
- [ ]
curl能拿到 ZLM 的getServerConfig(说明 API 通、secret 对) - [ ] 节点列表状态是
active,且持续超过 1 分钟(心跳正常) - [ ] ZLM 的
config.ini里general.mediaServerId与平台登记的一致 - [ ] ZLM 的 hook 回调地址里带
node=与cap=(说明是平台下发的,不是手写的死 hook) - [ ] 点播一台设备:有画面、ZLM 侧能看到会话、平台侧状态变为播放中
- [ ] 停止点播:会话按预期回收(无人观看超时能断流)
- [ ] 录一段像:录像文件落在预期的位置,能在平台里回放
- [ ] 拔掉 ZLM 网线 30 秒:节点变离线;恢复后自动回来
七、回退
把外部节点切维护态、确认无流后删除,然后:
- 如果内置 ZLM 只是被挪了端口,把
config.env里的UVP_ZLM_*改回 51004 段,restart - 如果内置节点已经被删了,节点表空了以后平台会重新按
gb28181.zlmseed 一个默认节点——把这段改回127.0.0.1与包内 secret 即可
包内 ZLM 的 config.ini 和证书一直都在 vendor/zlm/ 与 data/secrets/,没有被动过,回退不涉及重新初始化。
三篇走下来,三个内置组件都能换。真正花时间的从来不是填写配置项,而是搞清两件事:启动的时候谁在改写你的配置,以及这两个组件之间是不是每个方向都能通。想明白这两点,剩下的照着表查就行。