一、引言


这是一篇关于 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/udpZLM 通过 rtc.externIP 向浏览器公布。

两段连接彼此独立: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

(十一)编译完成条件

重新编译必须满足以下交付条件,否则不要替换已验证包:

  1. 产物是 ARM64 ELF,兼容板端 glibc 和 OpenSSL 1.1。
  2. 编译时找到 libsrtp2,MediaServer 动态依赖的 soname 与包中文件一致。
  3. 包含生成的 config.ini、default.pem、www/ 和非系统标准动态库。
  4. 在板端使用 LD_LIBRARY_PATH=/root/ZLMediaKit/lib ldd MediaServer 时不得出现 not found。
  5. /index/api/webrtc 可用,日志启动时加载 H264 和 Opus codec。
  6. 板内 publisher ICE 选路是 127.0.0.1,公网浏览器 answer candidate 是当前公网 IP。
  7. 在 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.portWebRTC UDP/DTLS/SRTP 端口。音视频无法通过 UDP 传输。
rtc.tcpPortUDP 不通时的 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 自动检测

启用后,播放器启动时立即检测,之后按配置周期复查。检测到变化时:

  1. 通过 ZLM setServerConfig API 更新 rtc.externIP。
  2. 当 zlm_public_base_url 使用裸 IPv4 时,同步替换 URL 主机部分。
  3. 检测失败时保留上一个有效地址,不中断 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的板子的映射

公网端口内网目标协议用途必需
65521192.168.31.163:65521TCPHTTP 页面和 WebRTC SDP API是
65522192.168.31.163:65522UDPWebRTC DTLS/SRTP 媒体是
65522192.168.31.163:65522TCPWebRTC 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> 的媒体文件,也不是完整播放页。

前端需要:

  1. 创建 RTCPeerConnection,加入 recvonly 音频和视频 transceiver。
  2. 创建 offer,等待 ICE gathering 完成。
  3. 向 streamUrl POST JSON,其中包含 offer SDP。
  4. 取出 ZLM 返回的 answer SDP,调用 setRemoteDescription。
  5. 在 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 failedHTTP 只是信令。重点检查 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 建立受控备份。

十六、部署检查清单

  1. ZLM 包是 ARM64,包含 MediaServer、config.ini、www/和 libsrtp2.so.1。
  2. ldd MediaServer 没有 not found。
  3. S96zlmediakit 和 S97rk3588-player 均在运行。
  4. ZLM HTTP 和 RTC UDP 端口均已监听。
  5. /root/config.ini 使用 backend=zlmediakit。
  6. 板内使用 zlm_api_base_url=127.0.0.1 和 zlm_publisher_ice_ip=127.0.0.1。
  7. ZLM rtc.externIP 是当前公网 IP,不是板子 LAN IP。
  8. 路由器已映射 HTTP TCP、RTC UDP,建议同时映射 RTC TCP。
  9. streamControlReply.streamUrl 使用公网 IP 和正确 HTTP 端口。
  10. ZLM publisher selected pair 不经过路由器网关。
  11. 使用 4G/5G 完成公网播放、长时间音视频和断线恢复验证。

相关文档:doc/一键部署与公网HTTP配置说明.html、doc/CONFIG_INI_REFERENCE.html、README.md。

十七、结论:我们应该记住什么

  1. ZLMediaKit 不是播放器,而是 WebRTC 媒体服务器。它接收 RK3588 发布的流,再为浏览器分发。
  2. 系统中存在两段 WebRTC:publisher → ZLM 和 ZLM → browser,必须分别配置和排查。
  3. 板内发布使用 127.0.0.1,公网播放使用 externIP。二者不能混用。
  4. 公网 WebRTC 成功需要完整链路:HTTP 信令、正确 SDP、ICE candidate、RTC 端口,以及可达的 UDP/TCP 媒体链路。
  5. 排障不要只测试 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 对应的公网入口。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐