交叉编译ZLMediaKit到RK3588与配置公网 WebRTC 播放
一、引言
这是一篇关于 RK3588、GStreamer、ZLMediaKit 与公网 WebRTC 配置的实践文章:先解释为什么 媒体服务器要部署在板端,再给出从 ARM64 运行包、WebRTC candidate 到公网端口映射的可验证落地方法。
先看安全边界:本文验证方案使用 HTTP、公网 IPv4 和路由器端口映射。 生产环境应使用 HTTPS、域名、Token 鉴权,限制管理端口。
已验证结论:播放器将 H.264/Opus WebRTC 流发布到同机 ZLMediaKit,ZLM 再向公网浏览器分发。 板内发布 ICE candidate 固定为 127.0.0.1,浏览器获得的 candidate 仍为公网 IP,可避免板内 RTP 绕行路由器 NAT 造成音频丢包和抖动。
二、背景:为什么RK3588需要部署ZLMediaKit
在我们开发的一个软件系统中,GStreamer 负责把素材或采集输入变成可播放的音视频管线,rk3588_gst_player 是运行在板端的我们开发的播放器进程,负责驱动这些管线并发布 WebRTC 流;RK3588 硬件负责高效完成视频采集/播放、H.264 编码和 Opus 音频。RK3588 很适合承担这些工作,但让浏览器直接连接播放器, 会把多客户端访问、公网 NAT、WebRTC 信令和 ICE candidate 管理全部压到嵌入式进程上。连接一多,资源 生命周期和公网可达性就很难统一处理。
ZLMediaKit 的角色不是播放器,而是板端的 WebRTC 媒体服务器:它接收播放器发布的流,统一维护媒体源, 再为每个浏览器建立播放连接。这样播放器负责“产生媒体”,ZLM 负责“分发媒体和管理连接”:
素材/采集输入
│ GStreamer 管线(H.264 + Opus)
▼
RK3588 播放器
│ WebRTC publish
▼
ZLMediaKit
│ WebRTC play
▼
公网浏览器
三、方案架构与数据流
视频素材
│
├─物理 HDMI 播放
│
└─rk3588_gst_player
└─H.264 + Opus + RTP + WebRTC publisher
│ 板内信令:127.0.0.1:ZLM_HTTP_PORT
│ 板内 ICE candidate:127.0.0.1
▼
ZLMediaKit
│ 公网 HTTP:WebRTC SDP API
│ 公网 UDP:DTLS/SRTP 音视频
▼
路由器 NAT 端口映射
▼
公网浏览器
| 路径 | 地址 | 用途 |
|---|---|---|
| 播放器→ZLM 信令 | http://127.0.0.1:HTTP_PORT/index/api/webrtc?...type=push | 不经过路由器。 |
| 播放器→ZLM 媒体 | zlm_publisher_ice_ip=127.0.0.1 | 避免公网 NAT hairpin。 |
| 浏览器→ZLM 信令 | http://PUBLIC_IP:HTTP_PORT/index/api/webrtc?...type=play | 前端向此 API POST offer SDP。 |
| 浏览器→ZLM 媒体 | PUBLIC_IP:RTC_PORT/udp | ZLM 通过 rtc.externIP 向浏览器公布。 |
四、一个系统中存在两段 WebRTC

两段连接彼此独立:candidate 不同、网络路径不同、配置目的不同。把 publisher 的公网配置和 browser 的 公网配置混在一起,是“板内音频卡顿”和“公网浏览器无画面”中最常见的排查错误。

五、candidate 设计原则
(一)为什么 publisher 使用 127.0.0.1
播放器和 ZLMediaKit 位于同一块 RK3588,板内发布路径应当是:
rk3588_gst_player ── localhost ──> ZLMediaKit
zlm_publisher_ice_ip=127.0.0.1 把这条连接固定在本机。若误用公网 candidate,流量可能变成:
播放器 → 路由器 → 公网 IP → ZLMediaKit
这会引入 NAT hairpin、额外延迟和丢包风险;板内发布使用 127.0.0.1,公网播放则使用 rtc.externIP 公布的公网地址,二者不能混用。
(二)rtc.externIP 到底给谁用?
rtc.externIP 不是给播放器使用的,它告诉 ZLMediaKit:返回给外部浏览器的 ICE candidate 应该 使用哪个地址。配置项本身只填写 PUBLIC_IP,candidate 最终会与 rtc.port 组合成 PUBLIC_IP:RTC_PORT。若写成 127.0.0.1,公网浏览器会收到不可达地址;公网端口还必须与路由器映射保持一致。
六、前置条件
(一)开发机
- Ubuntu 开发机,项目路径为
/home/cuijc/xiangmu/RK3588-GStreamer-Player。 - 能通过 SSH 访问目标板,并已安装
sshpass、ssh、scp和tar。 - 使用仓库的
scripts/build.sh交叉编译播放器,不在 RK3588 板上原生编译播放器。
sudo apt-get update
sudo apt-get install sshpass openssh-client tar
(二)板端
- CPU 架构必须为
aarch64。 - 板子可访问公网 IP 检测站点,并能向浏览器发送 UDP。
- 目标板最好使用静态 LAN IP 或 DHCP 保留,否则路由器映射会失效。
- Buildroot 中已有 ZLM 依赖的
libssl.so.1.1、libcrypto.so.1.1、C/C++ 运行库。本项目的 ZLM 包额外携带libsrtp2.so.1。
uname -m
# 期望:aarch64
ip addr
df -h /root
(三)公网网络
- 路由器 WAN 口必须拥有真实、可入站的公网 IPv4。
- 若 WAN IP 与公网查询结果不同,通常是 CGNAT 或上级 NAT,仅配置家用路由器映射无法解决。
- 防火墙和运营商不得阻断所选 RTC UDP 端口。
(四)从零部署路线
下图中的 streamControl 是播放器的启流控制消息:调用它会启动指定通道的 WebRTC publisher,并等待媒体源达到可播放状态。
准备 ARM64 ZLMediaKit 运行包
│
▼
上传到 RK3588
│
▼
配置 config.ini
│
▼
启动 MediaServer
│
▼
启动 rk3588_gst_player
│
▼
发送 streamControl
│
▼
浏览器 WebRTC 播放
这条路线的关键不是命令顺序本身,而是每一步都为下一层提供前提:先有可运行的 ARM64 服务,再有正确 的 HTTP/RTC 配置,最后才验证浏览器播放。
七、交叉编译 ZLMediaKit ARM64 安装包
WebRTC 依赖 OpenSSL、libsrtp 和 SCTP;CMake 找不到依赖时 可能自动关闭 WebRTC;编译成功不代表 WebRTC 功能已开启,必须检查配置结果和运行时 codec。
需要升级或重建 ZLMediaKit 时,应以官方文档为准:
已验证编译基线: ZLMediaKit commit 0e9e59bf4382335bce22b2214877803f3beeeeca (版本时间 2026-08-06),libsrtp 2.5.0,GCC 10.3.1, Buildroot sysroot 中的 OpenSSL 1.1。当前已验证二进制内嵌的版本信息为 ZLMediaKit(git hash:0e9e59b/...),不建议在交付环境直接编译不固定的 master。
(一)安装开发机构建工具
以下命令只在 Ubuntu 开发机执行。ZLM 和 libsrtp 都使用本项目的 RK3588 Buildroot 交叉工具链,不在板子上原生编译。
sudo apt-get update
sudo apt-get install -y \
git curl ca-certificates cmake make \
autoconf automake libtool pkg-config \
file binutils
确认项目 SDK 完整:
cd /home/cuijc/xiangmu/RK3588-GStreamer-Player
test -x third_party/rk3588-sdk/host/bin/aarch64-none-linux-gnu-gcc
test -f third_party/rk3588-sdk/host/share/buildroot/toolchainfile.cmake
test -f third_party/rk3588-sdk/host/aarch64-buildroot-linux-gnu/sysroot/usr/lib/libssl.so.1.1
third_party/rk3588-sdk/host/bin/aarch64-none-linux-gnu-gcc --version
# 已验证环境:GCC 10.3.1
(二)建立隔离的构建目录
下列变量应在同一个 shell 中持续使用。mktemp 会创建独立目录,不污染项目源码和 SDK sysroot。
PROJECT_ROOT=/home/cuijc/xiangmu/RK3588-GStreamer-Player
SDK_ROOT=$PROJECT_ROOT/third_party/rk3588-sdk
SDK_HOST=$SDK_ROOT/host
SYSROOT=$SDK_HOST/aarch64-buildroot-linux-gnu/sysroot
TOOLCHAIN_FILE=$SDK_HOST/share/buildroot/toolchainfile.cmake
ZLM_WORK_ROOT=$(mktemp -d /tmp/zlm-rk3588-build.XXXXXX)
SRTP_SOURCE=$ZLM_WORK_ROOT/libsrtp-2.5.0
SRTP_PREFIX=$ZLM_WORK_ROOT/stage/libsrtp/usr
ZLM_SOURCE=$ZLM_WORK_ROOT/ZLMediaKit
ZLM_BUILD=$ZLM_WORK_ROOT/zlm-build
ZLM_STAGE=$ZLM_WORK_ROOT/stage/ZLMediaKit
printf 'build workspace: %s\n' "$ZLM_WORK_ROOT"
不要把 SRTP_PREFIX 直接指向 SDK sysroot;否则构建过程会修改项目共享的交叉编译环境。
(三)下载并固定 ZLMediaKit 源码
git clone --recursive \
https://github.com/ZLMediaKit/ZLMediaKit.git \
"$ZLM_SOURCE"
git -C "$ZLM_SOURCE" checkout \
0e9e59bf4382335bce22b2214877803f3beeeeca
git -C "$ZLM_SOURCE" submodule sync --recursive
git -C "$ZLM_SOURCE" submodule update --init --recursive
git -C "$ZLM_SOURCE" rev-parse HEAD
# 必须输出:
# 0e9e59bf4382335bce22b2214877803f3beeeeca
--recursive 和后续的 submodule update 不能省略。ZLM 使用多个子模块,只下载主仓库会导致配置或链接失败。
(四)交叉编译 libsrtp 2.5.0
WebRTC 必须使用 SRTP。本步通过 libsrtp 的 autotools shared_library 目标生成 soname 为 libsrtp2.so.1 的 ARM64 动态库,与已验证 MediaServer 一致。
curl -fL \
-o "$ZLM_WORK_ROOT/libsrtp-2.5.0.tar.gz" \
https://github.com/cisco/libsrtp/archive/refs/tags/v2.5.0.tar.gz
tar -xzf "$ZLM_WORK_ROOT/libsrtp-2.5.0.tar.gz" \
-C "$ZLM_WORK_ROOT"
cd "$SRTP_SOURCE"
autoreconf -fi
export PKG_CONFIG=$SDK_HOST/bin/pkg-config
export PKG_CONFIG_SYSROOT_DIR=$SYSROOT
export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig
CC=$SDK_HOST/bin/aarch64-none-linux-gnu-gcc \
AR=$SDK_HOST/bin/aarch64-none-linux-gnu-ar \
RANLIB=$SDK_HOST/bin/aarch64-none-linux-gnu-ranlib \
STRIP=$SDK_HOST/bin/aarch64-none-linux-gnu-strip \
./configure \
--host=aarch64-none-linux-gnu \
--prefix="$SRTP_PREFIX" \
--enable-openssl \
--with-openssl-dir="$SYSROOT/usr"
make -j"$(nproc)"
make -j"$(nproc)" shared_library
make install
检查产物:
file "$SRTP_PREFIX/lib/libsrtp2.so.1"
readelf -d "$SRTP_PREFIX/lib/libsrtp2.so.1" | \
grep -E 'SONAME|NEEDED'
strings "$SRTP_PREFIX/lib/libsrtp2.so.1" | grep 'libsrtp2 2.5.0'
# 期望:ARM aarch64,SONAME 为 libsrtp2.so.1
如果此处产物是 x86-64,说明 CC 未生效,必须清理本次临时目录并重新构建;不要把错误库拷到板子。
(五)配置 ZLMediaKit ARM64 构建
这里显式指定 OpenSSL 和 SRTP,防止 CMake 误用 Ubuntu 主机的 x86-64 库。CMAKE_SYSTEM_NAME=Linux 同时使 ZLM 使用 Linux 的链接分组逻辑,产物位于 release/linux/Release。
cmake -S "$ZLM_SOURCE" -B "$ZLM_BUILD" \
-DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_RPATH='$ORIGIN/lib' \
-DCMAKE_INSTALL_RPATH='$ORIGIN/lib' \
-DENABLE_SERVER=ON \
-DENABLE_API=ON \
-DENABLE_OPENSSL=ON \
-DENABLE_WEBRTC=ON \
-DENABLE_SCTP=ON \
-DENABLE_TESTS=OFF \
-DENABLE_SRT=OFF \
-DENABLE_FFMPEG=OFF \
-DENABLE_OBJCOPY=OFF \
-DOPENSSL_ROOT_DIR="$SYSROOT/usr" \
-DOPENSSL_INCLUDE_DIR="$SYSROOT/usr/include" \
-DOPENSSL_SSL_LIBRARY="$SYSROOT/usr/lib/libssl.so" \
-DOPENSSL_CRYPTO_LIBRARY="$SYSROOT/usr/lib/libcrypto.so" \
-DSRTP_INCLUDE_DIRS="$SRTP_PREFIX/include" \
-DSRTP_LIBRARIES="$SRTP_PREFIX/lib/libsrtp2.so" \
2>&1 | tee "$ZLM_WORK_ROOT/zlm-configure.log"
配置阶段必须确认 WebRTC 没有被 CMake 自动关闭:
grep -E \
'found library:.*ENABLE_(OPENSSL|WEBRTC) defined' \
"$ZLM_WORK_ROOT/zlm-configure.log"
# 至少应出现:
# found library: ... ENABLE_OPENSSL defined
# found library: ... ENABLE_WEBRTC defined
ZLM 在找不到 OpenSSL 或 SRTP 时可能只输出 warning,然后自动把 ENABLE_WEBRTC 设为 OFF,整体编译仍可能成功。因此不能只看 cmake --build 退出码,必须检查上述配置日志。
(六)编译 MediaServer
cmake --build "$ZLM_BUILD" \
--target MediaServer \
--parallel "$(nproc)"
ZLM_OUTPUT=$ZLM_SOURCE/release/linux/Release
test -x "$ZLM_OUTPUT/MediaServer"
test -f "$ZLM_OUTPUT/config.ini"
test -f "$ZLM_OUTPUT/default.pem"
test -f "$ZLM_OUTPUT/www/webrtc/index.html"
ZLM 的 CMake 脚本会自动把 conf/config.ini、default.pem 和 www/ 拷到产物目录。如果 CMAKE_SYSTEM_NAME 被其他工具链覆盖,可使用下列命令定位实际输出:
find "$ZLM_SOURCE/release" -type f -name MediaServer -print
(七)检查 ARM64、动态依赖和 RUNPATH
file "$ZLM_OUTPUT/MediaServer"
readelf -l "$ZLM_OUTPUT/MediaServer" | grep 'interpreter'
readelf -d "$ZLM_OUTPUT/MediaServer" | \
grep -E 'NEEDED|RPATH|RUNPATH'
strings "$ZLM_OUTPUT/MediaServer" | \
grep -m1 '^ZLMediaKit(git hash:'
期望的关键结果:
ELF 64-bit ... ARM aarch64
interpreter /lib/ld-linux-aarch64.so.1
RUNPATH: [$ORIGIN/lib]
NEEDED: libssl.so.1.1
NEEDED: libcrypto.so.1.1
NEEDED: libsrtp2.so.1
ZLMediaKit(git hash:0e9e59b/...)
Ubuntu 主机的 ldd 不能正确加载 ARM64 程序,此处使用 readelf。完整 ldd 检查应在 RK3588 板端执行。
(八)组装可部署运行目录
mkdir -p "$ZLM_STAGE/lib"
install -m 0755 \
"$ZLM_OUTPUT/MediaServer" \
"$ZLM_STAGE/MediaServer"
install -m 0600 \
"$ZLM_OUTPUT/config.ini" \
"$ZLM_STAGE/config.ini"
install -m 0600 \
"$ZLM_OUTPUT/default.pem" \
"$ZLM_STAGE/default.pem"
cp -a "$ZLM_OUTPUT/www" "$ZLM_STAGE/www"
# libsrtp2.so.1 不能省略
cp -a \
"$SRTP_PREFIX/lib/libsrtp2.so.1" \
"$ZLM_STAGE/lib/"
# 一键部署脚本会再安装最新 supervisor;
# 运行包内也可预置一份
install -m 0755 \
"$PROJECT_ROOT/scripts/run-zlmediakit.sh" \
"$ZLM_STAGE/run-zlmediakit.sh"
首次交付时应打开 $ZLM_STAGE/config.ini,把 [api].secret 替换为高强度随机值。不要在文档、日志或部署命令中回显实际 secret。公网端口和 rtc.externIP 可交由一键部署脚本写入。
(九)生成安装包
mkdir -p "$PROJECT_ROOT/output"
tar -czf \
"$PROJECT_ROOT/output/zlmediakit-rk3588.tar.gz" \
-C "$ZLM_WORK_ROOT/stage" \
ZLMediaKit
tar -tzf \
"$PROJECT_ROOT/output/zlmediakit-rk3588.tar.gz" | \
grep -E \
'MediaServer$|config.ini$|default.pem$|libsrtp2.so.1$|www/webrtc/index.html$'
sha256sum \
"$PROJECT_ROOT/output/zlmediakit-rk3588.tar.gz"
每次重新编译后的 SHA-256 都可能不同,应将当次 commit、构建时间和 SHA-256 一起记录在发布说明中。
(十)使用新包部署并做板端冒烟测试
先使用本文第七章的 --dry-run 检查包,然后通过 --zlm-package 部署。不要在已运行板上手工覆盖 MediaServer。
cd "$PROJECT_ROOT"
BOARD_PASSWORD='板子密码' \
bash scripts/deploy-rk3588-stack.sh \
--board 192.168.31.163 \
--public-ip PUBLIC_IP \
--http-port 65521 \
--rtc-port 65522 \
--player-port 8050 \
--zlm-package output/zlmediakit-rk3588.tar.gz \
--dry-run
去掉 --dry-run 才会实际上传、备份、安装和重启。部署后在板端检查:
LD_LIBRARY_PATH=/root/ZLMediaKit/lib \
ldd /root/ZLMediaKit/MediaServer
/etc/init.d/S96zlmediakit status
wget -S -O /dev/null http://127.0.0.1:65521/
ss -lnup | grep 65522
grep -E \
'Load codec: (H264|opus)|WebRtcSession.*listening' \
/root/ZLMediaKit/log/MediaServer.log | tail -n 20
(十一)编译完成条件
重新编译必须满足以下交付条件,否则不要替换已验证包:
- 产物是 ARM64 ELF,兼容板端 glibc 和 OpenSSL 1.1。
- 编译时找到 libsrtp2,
MediaServer动态依赖的 soname 与包中文件一致。 - 包含生成的
config.ini、default.pem、www/和非系统标准动态库。 - 在板端使用
LD_LIBRARY_PATH=/root/ZLMediaKit/lib ldd MediaServer时不得出现not found。 /index/api/webrtc可用,日志启动时加载 H264 和 Opus codec。- 板内 publisher ICE 选路是
127.0.0.1,公网浏览器 answer candidate 是当前公网 IP。 - 在 4G/5G 网络中完成至少 30 分钟的音视频播放验收。
八、ZLMediaKit WebRTC 配置
ZLM 配置位于 /root/ZLMediaKit/config.ini。以 192.168.31.163(我们的模块RK3588板子) 的已验证配置为例:
[api]
# 必须使用高强度随机值,不要暴露到公网
secret = 请使用板上实际密钥
[http]
port = 65521
allow_cross_domains = 1
allow_ip_range =
[rtc]
externIP = PUBLIC_IP
port = 65522
tcpPort = 65522
这些配置分别回答不同问题:HTTP 端口承载信令,CORS 决定浏览器能否读取 answer,RTC 端口承载 DTLS/SRTP, externIP 决定公网浏览器收到什么 candidate。不要把它们当成一个“WebRTC 端口”处理。
需要特别注意:CORS 导致的问题看起来像 WebRTC 网络失败,但实际上浏览器甚至没有进入 ICE 阶段;先确认响应头和跨域策略,再继续检查 candidate 与 RTC 媒体端口。

| 配置 | 作用 | 错误后果 |
|---|---|---|
http.port | 承载网页、API 和 WebRTC SDP 交换。 | 前端无法访问 /index/api/webrtc。 |
allow_cross_domains=1 | 允许前端站点跨域请求 WebRTC API。 | 浏览器 CORS 报错。 |
allow_ip_range= | 允许公网客户端访问 HTTP 端口。 | 只有本机或局域网可访问。 |
rtc.externIP | 放入 ZLM 返回给浏览器的 ICE candidate。 | 信令成功但无画面或 ICE failed。 |
rtc.port | WebRTC UDP/DTLS/SRTP 端口。 | 音视频无法通过 UDP 传输。 |
rtc.tcpPort | UDP 不通时的 WebRTC TCP 回退端口。 | 受限网络中没有 TCP 备用路径。 |
修改静态端口后重启 ZLM:
/etc/init.d/S96zlmediakit restart
/etc/init.d/S96zlmediakit status
ss -lntp | grep 65521
ss -lnup | grep 65522
ss -lntp | grep 65522
九、播放器 ZLM 后端配置
播放器(rk3588_gst_player )是我们开发的应用程序,启动WebRTC推流后会查询 ZLM 媒体列表,只有目标流的 H.264 与 Opus 轨道均为 ready 且已收到帧时才返回 code=200。zlm_stream_ready_timeout_seconds 的范围为 1-30 秒,默认 10 秒;超时返回 code=500 并清理本次新启动的流资源。因此成功回复的含义是“此时可播放”,而不只是“SDP 已协商”。
(一)公网 IP 自动检测
启用后,播放器启动时立即检测,之后按配置周期复查。检测到变化时:
- 通过 ZLM
setServerConfigAPI 更新rtc.externIP。 - 当
zlm_public_base_url使用裸 IPv4 时,同步替换 URL 主机部分。 - 检测失败时保留上一个有效地址,不中断 HDMI 和已有流。
正常日志:
[WebRTC-ZLM] Public IP synchronized: PUBLIC_IP,
streamBaseUrl=http://PUBLIC_IP:65521
(二)板内 publisher ICE 与公网 ICE 分离
播放器向 ZLM 发布时,会把 ZLM push answer 中的 candidate 改写为 zlm_publisher_ice_ip。成功日志:
[WebRTC-1] ZLMediaKit SDP answer: ...
internal ICE IP=127.0.0.1, rewritten candidates=4
selected_pair 是 ICE 最终选中的本地/远端候选地址对,它比“候选列表里出现过什么”更能说明媒体实际走哪条路径。ZLM 的选路日志应类似:
Initial selected_pair:
tcp 127.0.0.1:65522 <-> 192.168.31.163:临时端口
不应再出现 publisher 连接到网关 192.168.31.1 的选中对。浏览器播放 SDP 仍由 ZLM 直接生成,其 candidate 保持公网地址。这是两段 WebRTC 的配置边界:publisher 看本机,browser 看公网。
(三)无人观看时回收资源
播放器使用 ZLM on_stream_none_reader hook 识别无观众状态;日志或状态接口中的 readerCount 表示当前正在读取该媒体源的播放连接数量,降为 0 才进入无人观看处理:
viewer_idle_action=stop:彻底停流,下次必须重新下发streamControl。viewer_idle_action=suspend:挂起编码资源,通道保持逻辑可用;新观众触发on_stream_not_found后自动恢复。viewer_idle_timeout_seconds=0:禁用空闲回收。
这两个 hook 只接受 ZLM 从 127.0.0.1 或 ::1 发起的回调,不需要把播放器 8050 端口映射到公网。
十、路由器映射与公网条件
(一)192.168.31.163的板子的映射
| 公网端口 | 内网目标 | 协议 | 用途 | 必需 |
|---|---|---|---|---|
65521 | 192.168.31.163:65521 | TCP | HTTP 页面和 WebRTC SDP API | 是 |
65522 | 192.168.31.163:65522 | UDP | WebRTC DTLS/SRTP 媒体 | 是 |
65522 | 192.168.31.163:65522 | TCP | WebRTC TCP 回退 | 可选,建议 |
(二)端口映射原则
- ZLM
rtc.port与公网映射端口应保持一致,不要只做端口号转换。 - 同一个公网 IP 下部署多块板时,每块板必须使用独立的 HTTP 和 RTC 公网端口。
- 不要映射 SSH、播放器
8050、DEP 或其他管理端口。 - 验收时使用手机 4G/5G,不要只在同一 LAN 通过公网 IP 测试;后者依赖路由器 NAT loopback 能力。
十一、启动实时流与前端播放
(一)启动服务
/etc/init.d/S96zlmediakit start
/etc/init.d/S97rk3588-player start
/etc/init.d/S96zlmediakit status
/etc/init.d/S97rk3588-player status
wget -qO- http://127.0.0.1:8050/health
(二)下发 streamControl
streamControl 是播放器的启流控制接口:它启动指定通道的 publisher,并在媒体轨道真正就绪后返回播放地址。指令只负责启动指定通道,不再使用 controlType:
{
"messageId": "8a7b3f2c4d5e6f7a8b9c0d1e2f3a4b5c",
"messageType": "streamControl",
"payload": "{\"channelIndex\":1}",
"datetime": "2026-08-19 10:00:00",
"reply": true,
"client": "clientId",
"messageModel": "HTTP",
"address": "http://192.168.31.163:8050/message/receipt",
"deviceId": 1001
}
backend=zlmediakit 时,响应的 streamUrl 应为:
http://PUBLIC_IP:65521/index/api/webrtc?app=live&stream=rk3588-channel-1&type=play
通道 2 的流名为 rk3588-channel-2。
(三)前端如何使用 streamUrl
/index/api/webrtc?...type=play 是 WebRTC SDP API,不是可直接当作 <video src> 的媒体文件,也不是完整播放页。
前端需要:
- 创建
RTCPeerConnection,加入recvonly音频和视频 transceiver。 - 创建 offer,等待 ICE gathering 完成。
- 向
streamUrlPOST JSON,其中包含 offer SDP。 - 取出 ZLM 返回的 answer SDP,调用
setRemoteDescription。 - 在
ontrack中把MediaStream设置到video.srcObject。
ZLM 包中的测试页位于:
http://192.168.31.163:65521/webrtc/index.html
该页适合验证 ZLM 本身;业务前端应按相同协商流程集成到自己的页面。
十二、分层验收
WebRTC 不能只测试 HTTP:
HTTP 成功 ≠ SDP 成功 ≠ ICE 成功 ≠ DTLS 成功 ≠ 视频播放成功。
每一层都要有自己的证据,不能用 HTTP 200 代替后面的媒体验证。
(一)进程和端口
ps -ef | grep '[M]ediaServer'
ps -ef | grep '[r]k3588_gst_player'
ss -lntp | grep 65521
ss -lnup | grep 65522
wget -qO- http://127.0.0.1:8050/health
(二)ZLM HTTP 与公网入口
# 板端本机
wget -S -O /dev/null http://127.0.0.1:65521/
# 开发机或外网主机
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
http://PUBLIC_IP:65521/
返回 HTTP 200 只说明 TCP 信令可达,不代表 RTC UDP 媒体已可达。
(三)确认 ZLM 加载 WebRTC 和 Opus
grep -E 'Load codec: (H264|opus)|WebRtcSession.*listening' \
/root/ZLMediaKit/log/MediaServer.log | tail -n 20
(四)确认板内发布不绕行 NAT
grep 'internal ICE IP=' /root/Test/gst-test/player.log | tail -n 1
grep 'setSelectedPair' /root/ZLMediaKit/log/MediaServer.log | tail -n 5
期望结果:
internal ICE IP=127.0.0.1, rewritten candidates=4
selected_pair: ... 127.0.0.1:RTC_PORT <-> 192.168.31.x:临时端口
(五)确认公网 candidate
在浏览器 WebRTC internals 或 ZLM 返回的 answer SDP 中,candidate 应包含公网 IP 和 RTC 端口,不应是 127.0.0.1。同时检查:
awk 'BEGIN{p=0} /^\[rtc\]/{p=1} /^\[/ && !/^\[rtc\]/{if(p)exit} p{print}' \
/root/ZLMediaKit/config.ini | grep -E 'externIP|port|tcpPort'
(六)音视频稳定性
连续播放至少 30 分钟,重点检查:
- 播放器日志的
Audio RTP continuity中missingPackets=0、maxPtsStepMs=20。 - ZLM 不持续出现
RtpReceiver packet dropped,抖动缓冲不增长到数秒。 - 浏览器 FPS 稳定,丢帧不持续增长,jitter 没有长时间高位。
- 分别在 LAN 和 4G/5G 验证,避免把 NAT loopback 问题误判为 ZLM 问题。
十三、日常更新、启停和日志
(一)ZLM 已安装后的播放器日常更新
不传 --zlm-package 时,脚本保留板上的 ZLM 主程序和配置,只校验安装并更新 supervisor:
BOARD_PASSWORD='板子密码' bash scripts/deploy-rk3588-stack.sh \
--board 192.168.31.163 \
--public-ip PUBLIC_IP \
--http-port 65521 \
--rtc-port 65522 \
--player-port 8050
(二)服务命令
# ZLMediaKit
/etc/init.d/S96zlmediakit start
/etc/init.d/S96zlmediakit stop
/etc/init.d/S96zlmediakit restart
/etc/init.d/S96zlmediakit status
# rk3588_gst_player
/etc/init.d/S97rk3588-player start
/etc/init.d/S97rk3588-player stop
/etc/init.d/S97rk3588-player restart
/etc/init.d/S97rk3588-player status
播放器重启后,实时流启用状态不保留,需要重新下发 streamControl。
(三)日志位置
| 组件 | 日志或状态文件 |
|---|---|
| ZLMediaKit | /root/ZLMediaKit/log/MediaServer.log |
| ZLM 最近退出码 | /root/ZLMediaKit/log/last-exit-code |
| 播放器普通日志 | /root/Test/gst-test/player.log |
| 播放器 supervisor | /root/Test/gst-test/player-supervisor.log |
| 播放器最近退出 | /root/Test/gst-test/last-exit |
| 播放器 core dump | /root/Test/gst-test/crash/ |
tail -f /root/ZLMediaKit/log/MediaServer.log
tail -f /root/Test/gst-test/player.log
(四)备份
每次一键部署会创建:
/root/deployment-backups/YYYYMMDD-HHMMSS/
更新 ZLM 包时,旧的 config.ini和 default.pem 会保留到新目录,旧日志也会保留。回退会中断播放,执行前应先确认恢复整个组件还是只恢复配置。
十四、常见故障排查
(一)公网 WebRTC 最容易踩的坑

| 现象 | 原因与处理 |
|---|---|
libsrtp2.so.1: cannot open shared object file | 安装包缺少 ARM64 libsrtp2.so.1,或未通过 supervisor 设置 LD_LIBRARY_PATH。确认 /root/ZLMediaKit/lib/libsrtp2.so.1 存在,用 /etc/init.d/S96zlmediakit start 启动。 |
ZLM 报 Please login first | 播放器未取到正确的 [api].secret。检查 zlm_config_path、ZLM [api] 段和配置文件格式。zlm_api_secret 也可显式配置,但不要写入公开日志。 |
| 局域网能播,公网连不上 | 检查是否 CGNAT、TCP HTTP 映射、UDP RTC 映射、防火墙和 rtc.externIP。使用 4G/5G 测试。 |
| HTTP API 成功,但无画面或 ICE failed | HTTP 只是信令。重点检查 RTC UDP 映射、candidate 的公网 IP/端口、双向 UDP 和公网入站条件。 |
| 板内发布后音频严重卡顿 | 检查 ZLM setSelectedPair。如果 publisher 对端是网关 192.168.31.1,说明发布流绕行 NAT。确认 zlm_publisher_ice_ip=127.0.0.1 并使用支持 candidate 改写的新版播放器。 |
启流前几秒有 packet dropped | 可能是初始 GOP/轨道就绪的短暂缓存对齐。应从 All track ready 之后开始独立采样 30–60 秒;若仍持续增长,再查 RTP 连续性和 ICE 路径。 |
| 公网 IP 变化后无法播放 | 检查 zlm_public_ip_auto_detect=true、检测 URL 可访问、API secret 正确,以及日志是否出现 Public IP synchronized。路由器映射仍必须存在。 |
| HTTPS 前端请求 HTTP 流地址被拦截 | 这是浏览器 Mixed Content 限制。将 ZLM 接入域名 + HTTPS,或使用同源 HTTPS 反向代理,不能靠前端代码绕过。 |
| MediaServer 反复重启 | 查看 /root/ZLMediaKit/log/MediaServer.log 和 last-exit-code,然后用 ldd 检查库、用 df -h 检查空间,并检查 config.ini 是否有超长行、缺少 ] 或 =。 |
(二)快速采集排障信息
date
uname -a
ip addr
ip route
ps -ef | grep -E '[M]ediaServer|[r]k3588_gst_player'
ss -lntup | grep -E '65521|65522|8050'
LD_LIBRARY_PATH=/root/ZLMediaKit/lib ldd /root/ZLMediaKit/MediaServer
tail -n 300 /root/ZLMediaKit/log/MediaServer.log
tail -n 300 /root/Test/gst-test/player.log
十五、安全与生产化建议
- 纯 HTTP 方案已可用,但不加密。生产环境建议升级为域名 + HTTPS,并使用与证书匹配的
zlm_public_base_url。 - 不要在页面、指令响应或普通日志中暴露 ZLM
[api].secret。 - 对公网流增加业务鉴权、时效 token 或反向代理鉴权;一个可访问的
streamUrl本身不等于访问控制。 - 修改板子默认 SSH 密码,使用 SSH key 和管理网段限制。
- 公网只放行 ZLM HTTP/HTTPS 和 RTC 所需端口,不放行播放器控制端口
8050。 - 定期检查 ZLM 日志大小和磁盘使用率,并为配置、证书和 API secret 建立受控备份。
十六、部署检查清单
- ZLM 包是 ARM64,包含
MediaServer、config.ini、www/和libsrtp2.so.1。 ldd MediaServer没有not found。S96zlmediakit和S97rk3588-player均在运行。- ZLM HTTP 和 RTC UDP 端口均已监听。
/root/config.ini使用backend=zlmediakit。- 板内使用
zlm_api_base_url=127.0.0.1和zlm_publisher_ice_ip=127.0.0.1。 - ZLM
rtc.externIP是当前公网 IP,不是板子 LAN IP。 - 路由器已映射 HTTP TCP、RTC UDP,建议同时映射 RTC TCP。
streamControlReply.streamUrl使用公网 IP 和正确 HTTP 端口。- ZLM publisher selected pair 不经过路由器网关。
- 使用 4G/5G 完成公网播放、长时间音视频和断线恢复验证。
相关文档:doc/一键部署与公网HTTP配置说明.html、doc/CONFIG_INI_REFERENCE.html、README.md。
十七、结论:我们应该记住什么
- ZLMediaKit 不是播放器,而是 WebRTC 媒体服务器。它接收 RK3588 发布的流,再为浏览器分发。
- 系统中存在两段 WebRTC:
publisher → ZLM和ZLM → browser,必须分别配置和排查。 - 板内发布使用 127.0.0.1,公网播放使用 externIP。二者不能混用。
- 公网 WebRTC 成功需要完整链路:HTTP 信令、正确 SDP、ICE candidate、RTC 端口,以及可达的 UDP/TCP 媒体链路。
- 排障不要只测试 HTTP。沿 HTTP → SDP → ICE → DTLS → RTP → video 逐层确认。
十八、文件结构说明
理解各文件的职责,有助于快速判断问题属于运行包、ZLM 配置、播放器配置还是浏览器链路:
/root/ZLMediaKit/
├── MediaServer # ARM64 ZLMediaKit 主程序
├── config.ini # HTTP、RTC、API secret、externIP 等配置
├── default.pem # ZLM 默认或部署时替换的证书
├── lib/libsrtp2.so.1 # WebRTC SRTP 运行时依赖
├── www/webrtc/index.html # ZLM 内置 WebRTC 播放测试页
└── log/MediaServer.log # ZLM 运行与 ICE/媒体日志
/root/config.ini # rk3588_gst_player 的 backend、publisher candidate 和公网 URL
/root/Test/gst-test/player.log # 播放器启流、轨道 ready 与控制接口日志
播放器调用同机 ZLM 使用 127.0.0.1;浏览器播放地址和 ZLM 返回的公网 candidate 则使用 rtc.externIP 对应的公网入口。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)