无人机系列-篇二-ZLMediaKit多路流管理与分发

ZLMediaKit 多路无人机流管理与分发架构

单架无人机推流到服务器很简单,调一个 API 就行。但当 10 架、50 架无人机同时接入,H265 转码把 CPU 吃满,WebRTC 连接数飙升,问题就来了。本文记录多路无人机流管理的完整架构设计,包含转码策略、协议分发和性能调优。

一、ZLMediaKit 在无人机平台中的定位

ZLMediaKit 的职责边界:

职责 说明
流接入 接收多路无人机 RTMP/RTSP 推流
转码调度 调用 FFmpeg 进行 H265 → H264 转码
协议转换 统一转出 WebRTC / HLS / HTTP-FLV
流管理 app/stream/vhost 三级管理
客户端管理 WebSocket 信令、播放会话管理

不负责:视频存储(交给对象存储)、AI 分析(交给独立 AI 服务)、业务逻辑(交给后端应用)。

二、多路无人机接入方案

2.1 流命名规范

多路无人机接入,首先要建立统一的命名规范:

bash 复制代码
rtmp://服务器IP:1935/{app}/{stream}

app 命名:uav(所有无人机统一 app)
stream 命名:{无人机ID}_{码流类型}

示例:
rtmp://server:1935/uav/uav_001_main    # 1号机主码流
rtmp://server:1935/uav/uav_001_sub     # 1号机辅码流
rtmp://server:1935/uav/uav_002_main    # 2号机主码流
rtmp://server:1935/uav/uav_002_sub     # 2号机辅码流

好处

  • 按无人机 ID 查询流地址,一目了然
  • 主码流和辅码流分开管理,按需拉取
  • 统一 app 前缀,方便 ZLMediaKit 配置鉴权

2.2 流注册与发现

无人机上线后推流,ZLMediaKit 自动注册流。后端通过 REST API 查询当前在线流:

bash 复制代码
# 查询所有媒体流列表
curl "http://服务器IP:8080/index/api/getMediaList?secret=你的secret"

# 返回示例
{
    "code": 0,
    "data": [
        {
            "app": "uav",
            "stream": "uav_001_main",
            "originType": "rtmp_push",
            "originUrl": "rtmp://192.168.1.100:1935/uav/uav_001_main",
            "createStamp": 1690838400,
            "aliveSecond": 3600,
            "readerCount": 3
        },
        {
            "app": "uav",
            "stream": "uav_001_sub",
            "originType": "rtmp_push",
            "originUrl": "rtmp://192.168.1.100:1935/uav/uav_001_sub",
            "createStamp": 1690838400,
            "aliveSecond": 3600,
            "readerCount": 1
        }
    ]
}

关键字段

字段 说明
originType 推流类型:rtmp_push / rtmp_pull / ffmpeg
readerCount 当前观看人数,用于判断是否需要转码
aliveSecond 流存活时间,用于超时清理

2.3 流上线/下线钩子

ZLMediaKit 支持 WebHook 通知,流上线和下线时自动回调后端:

ini 复制代码
// config.ini 中配置
[hook]
enable=1
on_stream_changed=http://后端服务/api/stream/changed

后端收到通知后更新流状态表:

less 复制代码
@PostMapping("/api/stream/changed")
public void onStreamChanged(@RequestBody StreamChangedHook hook) {
    if ("on_publish".equals(hook.getAction())) {
        // 无人机上线,注册流信息
        streamService.register(hook.getApp(), hook.getStream(), hook.getOriginUrl());
        // 检查是否需要启动转码
        transcodeService.checkAndStart(hook.getApp(), hook.getStream());
    } else if ("on_unpublish".equals(hook.getAction())) {
        // 无人机下线,清理资源
        streamService.unregister(hook.getApp(), hook.getStream());
        // 停止转码进程
        transcodeService.stop(hook.getApp(), hook.getStream());
    }
}

这样就能实现无人机推流后自动转码、下线后自动清理,全程无需人工干预。

三、H265 转 H264 转码策略

3.1 按需转码(核心优化)

50 架无人机同时在线,如果全部转码,服务器扛不住。但实际上不是每路流都有人看。

策略:只有当某路流有 Firefox/Safari 用户请求播放时,才启动转码;无人观看时停止转码进程。

graph TD A[用户请求 WebRTC 播放] --> B{检测浏览器类型} B -->|Chrome/Edge| C[直接播放 H265 流] B -->|Firefox/Safari| D{转码流是否已存在?} D -->|是| E[播放已转码的 H264 流] D -->|否| F[启动 FFmpeg 转码] F --> G[等待转码流就绪] G --> E

3.2 转码 API 调用

arduino 复制代码
curl -X POST "http://服务器IP:8080/index/api/addFFmpegSource" \
  -d "secret=你的secret" \
  -d "src_url=rtmp://127.0.0.1:1935/uav/uav_001_main" \
  -d "dst_url=rtmp://127.0.0.1/uav/uav_001_main_h264" \
  -d "timeout_ms=10000" \
  -d "enable_hls=0" \
  -d "enable_mp4=0" \
  -d "ffmpeg_cmd_key=live"

转码后的流命名规范:原流名 + _h264 后缀,方便查找。

3.3 FFmpeg 命令模板配置

在 ZLMediaKit 的 config.ini 中定义转码命令模板:

ini 复制代码
[ffmpeg]
cmd=%s -re -i %s -c:v libx264 -preset ultrafast -tune zerolatency -g 8 -bf 0 -b:v 4M -maxrate 4M -minrate 4M -bufsize 8M -c:a aac -b:a 128k -f flv %s

如果使用 GPU 加速:

ini 复制代码
[ffmpeg_nvenc]
cmd=%s -re -i %s -c:v h264_nvenc -preset p1 -tune ull -g 8 -bf 0 -b:v 4M -maxrate 4M -minrate 4M -bufsize 8M -c:a aac -b:a 128k -f flv %s

3.4 转码资源规划

并发路数 CPU 软编码 GPU 硬编码 (NVENC) 推荐
1 路 1080p 1 核满载 GPU 8% 均可
5 路 1080p 5 核满载 GPU 40% 必须 GPU
10 路 1080p 10 核满载 GPU 80% 必须 GPU + 多卡
20 路 1080p 不可行 2 张 GPU 多卡 + 按需转码

实际经验:配合「按需转码」策略,一台 4 核服务器 + 1 张 RTX 3060 可支撑 20 路无人机接入,同时最多 5 路并发转码,日常运行 CPU 占用 30%。

四、输出协议选择与分发

4.1 三种输出协议对比

协议 延迟 浏览器原生支持 并发能力 适用场景
WebRTC <500ms 需 JS 库 中(每连接占资源) 指挥中心、实时操控
HTTP-FLV 1-3s 需 flv.js 高(HTTP 无状态) Web 直播、多路预览
HLS 3-10s 原生支持 极高(CDN 友好) 大规模分发、回看

4.2 按场景选择输出协议

graph TD A[播放请求] --> B{什么场景?} B -->|指挥中心大屏| C[WebRTC<br/>延迟 < 500ms<br/>需要实时操控] B -->|Web 端多路预览| D[HTTP-FLV<br/>延迟 1-3s<br/>flv.js 支持] B -->|手机端播放| E[WebRTC<br/>延迟 < 500ms<br/>移动端友好] B -->|大规模分发| F[HLS<br/>延迟 3-10s<br/>CDN 加速] B -->|录像回看| G[HLS<br/>支持 seek<br/>CDN 缓存]

4.3 多路同屏优化

指挥中心常见需求:同时看 8-16 路无人机画面。

问题:8 路 WebRTC 连接 = 8 路独立的视频流传输,浏览器和服务器压力都很大。

解决方案

  1. 小流预览:多路同屏时用辅码流(640x360),单台手机也能看 8 路
  2. HTTP-FLV 替代 WebRTC:多路预览不需要超低延迟,FLV 的 HTTP 无状态特性更适合高并发
  3. 点击放大切 WebRTC:默认 FLV 预览,点击某路切 WebRTC 看实时画面
ini 复制代码
// 前端多路预览切换逻辑
function playStream(streamId, protocol) {
    if (protocol === 'flv') {
        // 多路预览用 FLV
        const url = `http://server:8080/uav/${streamId}.live.flv`;
        playWithFlvJs(url);
    } else if (protocol === 'webrtc') {
        // 单路放大用 WebRTC
        const url = `http://server:8080/index/api/webrtc?app=uav&stream=${streamId}&type=play`;
        playWithWebRTC(url);
    }
}

五、流媒体服务器集群部署

单台 ZLMediaKit 的承载能力有限,路数多了必须水平扩展。

5.1 集群架构

graph TD A[无人机集群] --> B[负载均衡<br/>Nginx / LVS] B --> C[ZLMediaKit 节点 1] B --> D[ZLMediaKit 节点 2] B --> E[ZLMediaKit 节点 N] C --> F[Redis<br/>流注册中心] D --> F E --> F C --> G[共享存储<br/>录像/截图] D --> G E --> G F --> H[后端服务<br/>流管理与调度]

5.2 流路由问题

集群模式下,无人机 A 推流到节点 1,用户请求播放时可能被分配到节点 2。节点 2 上没有这路流,播放失败。

解决方案

方案 说明 优缺点
Redis 注册中心 每个节点将流信息写入 Redis,播放请求查 Redis 后转发到源节点 简单可靠,推荐
ZLMediaKit 内置集群 配置 cluster.originUrl 实现节点间流转发 原生支持,但配置复杂
固定路由 同一无人机的流固定推到同一节点 简单但负载不均

推荐 Redis 方案,后端服务统一调度:

kotlin 复制代码
// 无人机上线时,记录流到节点的映射
redisTemplate.opsForValue().set(
    "stream:" + streamId, 
    nodeIp,
    30, TimeUnit.MINUTES  // 30分钟无心跳自动过期
);

// 用户请求播放时,查询流所在节点
String nodeIp = redisTemplate.opsForValue().get("stream:" + streamId);
if (nodeIp == null) {
    // 流不存在或已过期
    return error("stream not found");
}
// 返回对应节点的播放地址
return playUrl(nodeIp, streamId);

5.3 节点健康检查

bash 复制代码
# ZLMediaKit 健康检查接口
curl "http://节点IP:8080/index/api/getServerConfig"

# 检查关键指标
{
    "code": 0,
    "data": [{
        "Buffer.AlwaysUseRange": "1",
        "Media.heartBeatInterval": "30",
        "Rtmp.port": "1935",
        "Rtsp.port": "554",
        "Http.port": "8080"
    }]
}

配合负载均衡的健康检查,自动剔除故障节点。

六、监控与告警

6.1 关键监控指标

指标 告警阈值 说明
在线流数量 下降 >50% 可能无人机批量掉线
转码进程数 接近上限 需要扩容或启动 GPU
CPU 使用率 >85% 转码瓶颈
GPU 使用率 >90% NVENC 接近满载
推流断连次数 单机 >3次/分钟 网络不稳定
WebRTC 连接数 接近上限 并发瓶颈

6.2 流断线检测

无人机在野外飞行,4G 信号不稳定,推流断连是常态。需要自动检测和重连:

less 复制代码
@Scheduled(fixedRate = 10000)  // 每10秒检查一次
public void checkStreamHealth() {
    List<StreamInfo> onlineStreams = streamService.getOnlineStreams();
    for (StreamInfo stream : onlineStreams) {
        // 检查流最后活跃时间
        if (stream.getLastActiveTime() < System.currentTimeMillis() - 30000) {
            // 30秒无数据,判定断连
            streamService.markOffline(stream.getStreamId());
            // 通知前端该路画面已断开
            websocketNotify(stream.getStreamId(), "stream_offline");
            // 清理对应的转码进程
            transcodeService.stop(stream.getApp(), stream.getStreamId());
        }
    }
}

七、总结

问题 方案
多路流命名混乱 统一规范:uav/uav_{ID}_{main/sub}
转码 CPU 不够 按需转码 + GPU 加速
浏览器 H265 不兼容 FFmpeg 实时转 H264
多路同屏卡顿 辅码流 + HTTP-FLV
集群流路由 Redis 注册中心
推流断连 心跳检测 + 自动重连

核心原则:按需分配资源。不是每路流都需要转码,不是每个用户都需要 WebRTC,不是每路都需要大流。根据实际场景动态调度,才能在有限服务器资源下支撑更多无人机。


上一篇# 无人机系列-篇一图传链路选型与视频编码

下一篇无人机系列-篇三-AI视频识别接入架构与优化

觉得有用点个赞 👍,有问题评论区交流。

相关推荐
geovindu1 小时前
java: Gale-Shapley Algorithm
java·开发语言·后端·算法
环境栈笔记2 小时前
指纹浏览器安全评测方法:核对环境、数据与权限后再选型
前端·人工智能·后端·自动化
卷无止境2 小时前
LangExtract:让LLM从杂乱文本中"抠"出结构化数据的开源工具
后端·python
卷无止境3 小时前
软件复杂度:那些让代码变得难以理解的东西,到底能不能量化?
后端·python
mldong13 小时前
从 mldong 到 jeeflow:一个工作流引擎的独立进化
后端
陈随易13 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
Aaron - Wistron13 小时前
Web API C# (Furion版)带 单元测试
开发语言·后端·c#
卷无止境14 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境14 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python