1. 前言
在安防监控、边缘网关、园区视频汇聚、工业视觉、无人值守站点、机器人、车载终端和云端视频接入等场景中,现场摄像头或 NVR 通常通过 RTSP 输出视频流,而上级平台、云直播平台、视频中台或 Web 播放链路往往更倾向于 RTMP 输入。
这就带来一个非常典型的工程需求:在 Linux arm64/aarch64 设备上部署一个轻量级转发模块,将多路 RTSP 摄像头流统一接入,然后按通道转推为 RTMP,同时支持本地录像、定时截图、下载速度监控和异常状态管理。
大牛直播SDK(SmartMediaKit)Linux arm64 版 relaydemo 展示的就是这样一套多路流媒体转发链路。它同时使用 SmartPlayer 拉流模块和 SmartPublisher 推流模块:
- SmartPlayer 负责 RTSP/RTMP 拉流、PullStream 压缩包回调、录像、截图和下载速度事件;
- SmartPublisher 负责接收外部编码音视频数据,并推送到 RTMP Server;
- SmartLog 负责 SDK 日志输出;
RelaySDKWrapper负责将拉流、推流、录像等能力封装成单路 relay 对象;- 主程序通过配置数组组织多路任务。
本文结合 Linux arm64 版本的 relaydemo,系统说明多路 RTSP 转 RTMP 推送模块的集成方式。
2. 模块定位
relaydemo 并不是一个单纯的播放器 Demo,也不是普通的 RTMP 推流 Demo,而是一个典型的 拉流转推模块。
它的核心价值在于:
cpp
RTSP/RTMP 源流
|
SmartPlayer 拉流
|
PullStream 获取压缩音视频包
|
RelaySDKWrapper 回调桥接
|
SmartPublisher 外部编码数据输入
|
RTMP 推送到平台
与"拉流解码 -> 重新编码 -> 推流"的转码方案不同,relaydemo 的常规转推链路主要处理压缩后的音视频包。视频数据可以不经过解码和重编码,直接通过 PostVideoEncodedDataV2() 投递给 SmartPublisher,再由 SmartPublisher 完成 RTMP 推送。
这种方式有几个明显优势:
- 不做视频二次编码,CPU 占用更低;
- 保留源流画质,避免二次编码损失;
- 链路更短,转推延迟更可控;
- 更适合 ARM 边缘设备上的多路并发;
- 同一路 SmartPlayer handle 还可以旁路做录像和截图;
- 适合构建轻量级视频协议转换网关。
3. 典型使用场景

3.1 安防摄像头 RTSP 汇聚上云
在安防和视频监控项目中,IPC、NVR 或国标网关常以 RTSP 形式提供视频流,而云直播平台、视频中台、Web 播放系统或业务调度平台常要求 RTMP 输入。
此时可将 relaydemo 改造成边缘转发网关:
cpp
IPC / NVR / RTSP 摄像头
|
Linux arm64 边缘网关
|
多路 RTSP 转 RTMP
|
云直播平台 / 企业视频平台 / 调度平台
适合小区、园区、工地、门店、仓储、厂区、校园等多摄像头汇聚上云场景。
3.2 边缘网关多路协议转换
在很多项目中,边缘网关既要接入局域网摄像头,又要把部分关键通道转推到上级平台。SmartMediaKit relay 模块采用多实例 handle 方式,每一路独立拉流、独立推流、独立录像,适合构建轻量级协议转换网关。
典型场景包括:
- 工业边缘盒子接入多路 IPC;
- 机器人多摄像头回传;
- 车载或移动布控设备转发;
- 无人值守站点远程视频接入;
- 内网 RTSP 流转为公网 RTMP 流。
3.3 设备端录像与云端直播并行
很多项目不仅要将视频推送到平台,还要在设备侧本地留档。relaydemo 支持按通道开启录像能力:
cpp
同一路拉流 handle
|
|-- PullStream 压缩包转 RTMP
|-- SmartPlayer 本地录像
这种方式适合:
- 关键通道本地备份;
- 弱网环境下本地补录;
- 告警场景留证;
- 现场故障复盘;
- 设备端长期录像和云端实时观看并存。
3.4 定时截图与巡检取证
对于部分场景,业务并不需要持续录像,只需要定时生成图片用于巡检、取证或状态记录。relaydemo 支持通过 SmartPlayer 播放链路定时截图。
适合:
- 巡检画面定时抓拍;
- 远程值守状态留存;
- AI 轻量分析前置截图;
- 设备异常时取证;
- 低频预览类业务。
3.5 RTSP/RTMP 源统一接入
relaydemo 中的 pull_url_ 既可以是 RTSP,也可以是 RTMP。这意味着业务侧可以将不同来源统一纳入同一套 relay 逻辑:
- IPC RTSP;
- NVR RTSP;
- 边缘流媒体 RTSP;
- 第三方 RTSP 服务;
- 已有 RTMP 流;
- 本地轻量级 RTSP 服务输出的流。
4. SmartMediaKit 技术优势
4.1 SmartPlayer 与 SmartPublisher 协同
relay 模块同时使用 SmartPlayer 和 SmartPublisher:
| 模块 | 作用 |
|---|---|
libSmartPlayerSDK.so |
负责 RTSP/RTMP 拉流、PullStream 压缩包回调、录像、截图、下载速度事件 |
libSmartPublisherSDK.so |
负责接收外部编码数据并推送 RTMP |
libSmartLog.so |
负责 SDK 日志输出 |
业务侧无需自行实现 RTSP 拉流、RTMP 推流、码流封装、录像封装和截图逻辑,只需要按 SDK 生命周期组织通道即可。
4.2 不重编码转推
relay 的关键设计是"压缩包转发"。视频回调中收到的压缩视频包通过以下接口投递给推流端:
cpp
push_api_->PostVideoEncodedDataV2(push_handle,
video_codec_id,
data,
size,
info->is_key_frame_,
info->timestamp_,
info->presentation_timestamp_);
音频回调中收到的压缩音频包通过以下接口投递:
cpp
push_api_->PostAudioEncodedData(push_handle,
audio_codec_id,
data,
size,
info->is_key_frame_,
info->timestamp_,
info->parameter_info_,
info->parameter_info_size_);
这种方式保留源流编码数据,减少 CPU 开销,降低画质损失,也更适合多路并发转推。
4.3 多路实例模型清晰
Demo 使用 RelayConfigItem 描述每一路任务:
cpp
struct RelayConfigItem {
const char* pull_url_;
bool is_push_rtmp_;
bool is_recorder_;
bool is_capture_image_;
};
每一路可以独立配置:
- 拉流 URL;
- 是否转推 RTMP;
- 是否本地录像;
- 是否定时截图;
- 是否转发视频;
- 是否转发音频;
- 录像目录;
- 录像文件大小;
- 文件名前缀;
- 是否追加日期和时间。
这类配置模型非常适合后续改造成 JSON、数据库或平台下发的通道配置。
4.4 RTSP 兼容性和网络可观测性
拉流 handle 打开时,Demo 默认设置:
cpp
sdk_api_->SetBuffer(handle, buffer);
sdk_api_->SetRtspAutoSwitchTcpUdp(handle, 1);
sdk_api_->SetReportDownloadSpeed(handle, 1, 3);
其中:
SetBuffer()用于设置拉流缓冲;SetRtspAutoSwitchTcpUdp()用于 RTSP TCP/UDP 自动切换;SetReportDownloadSpeed()用于定时上报下载速度。
这对不同品牌 IPC、NVR、RTSP Server 的兼容性和生产运维都很重要。
4.5 录像和截图能力内建
SmartPlayer SDK 本身提供录像和截图能力。relay 模块可在转推链路之外,直接对同一路源流进行录像或截图:
StartRecorder()/StopRecorder();SetRecorderDirectory();SetRecorderFileMaxSize();SetRecorderFileNameRuler();SetRecorderAudioTranscodeAAC();CaptureImage()。
业务侧无需额外集成 MP4 封装或图片编码模块。
5. Demo 工程结构说明

relaydemo 目录结构如下:
cpp
relaydemo/
├── relaydemo.cpp
├── Makefile
├── readme.txt
├── libSmartPlayerSDK.so
├── libSmartPublisherSDK.so
├── libSmartLog.so
├── nt_smart_sdk/
│ └── linux/include/
│ ├── common/
│ ├── smart_player_sdk/
│ │ ├── nt_linux_smart_player_sdk.h
│ │ └── smart_player_define.h
│ └── smart_publisher_sdk/
│ ├── nt_linux_smart_publisher_sdk.h
│ └── nt_smart_publisher_define.h
├── nt_smart_sdk_wrapper/
│ ├── nt_app_sdk_handle_wrapper.h
│ ├── nt_app_sdk_handle_wrapper.cpp
│ ├── nt_app_relay_sdk_wrapper.h
│ └── nt_app_relay_sdk_wrapper.cpp
└── aarch64/
├── include/X11/
└── lib/
关键文件说明:
|----------------------------------|----------------------------------------------|
| 文件 | 说明 |
| relaydemo.cpp | Demo 主程序,包含任务配置、SDK 初始化、多路任务启动/停止、定时截图 |
| nt_app_sdk_handle_wrapper.* | SmartPlayer / SmartPublisher handle 的 C++ 封装 |
| nt_app_relay_sdk_wrapper.* | 单路 relay 封装,负责拉流、转推、录像和回调桥接 |
| nt_linux_smart_player_sdk.h | SmartPlayer 拉流、PullStream、录像、截图接口定义 |
| nt_linux_smart_publisher_sdk.h | SmartPublisher 外部编码数据输入和 RTMP 推流接口定义 |
| smart_player_define.h | 拉流事件、PullStream 回调结构、录像规则等定义 |
| nt_smart_publisher_define.h | 发布模块的视频/音频源类型、事件和编码数据输入定义 |
| Makefile | arm64 本地编译和 x86_64 交叉编译配置 |
| readme.txt | Demo 运行说明 |
从工程组织看,Demo 将 SDK 原始 C 接口封装成 C++ wrapper,业务主程序只需要关注多路配置、任务启动、主循环和退出释放。
6. 环境要求与编译运行
6.1 环境要求
|---------|-------------------------------------------------------------------|
| 项目 | 要求 |
| CPU 架构 | arm64 / aarch64 |
| 操作系统 | Linux |
| 编译器 | gcc / g++ |
| 图形相关 | Makefile 链接 X11,截图/播放相关能力依赖对应环境 |
| 基础库 | glib-2.0、gio-2.0、pthread、libstdc++、glibc |
| SDK 动态库 | libSmartPlayerSDK.so、libSmartPublisherSDK.so、libSmartLog.so |
上线前建议检查:
cpp
ldd ./SmartStreamRelayDemo
ldd ./libSmartPlayerSDK.so
ldd ./libSmartPublisherSDK.so
ldd ./libSmartLog.so
重点确认不存在:
cpp
not found
如果存在依赖缺失,需要检查 SDK 动态库路径、系统运行库、glib/gio、X11、libstdc++ 和 glibc 版本。
6.2 Makefile 链接项
Demo 的 Makefile 主要链接以下库:
NT_LINK_LIBS = -L. -lSmartPlayerSDK -L. -lSmartPublisherSDK -L. -lSmartLog
NT_LINK_LIBS += -lX11 -lglib-2.0 -lgio-2.0
默认交叉编译配置:
cpp
NT_ARCH = aarch64
NT_CROSS_COMPILE_PREFIX = aarch64-linux-gnu-
NT_ENABLE_EXTERNAL_LIB = yes
NT_EXTERNAL_LIB_PATH = ./aarch64
6.3 arm64 设备本地编译
如果直接在 ARM Linux 设备上编译:
cpp
cd relaydemo
make NT_ENABLE_EXTERNAL_LIB=no NT_CROSS_COMPILE_PREFIX=
参数说明:
|-----------------------------|-----------------------|
| 参数 | 说明 |
| NT_ENABLE_EXTERNAL_LIB=no | 使用目标系统已安装的依赖库 |
| NT_CROSS_COMPILE_PREFIX= | 置空后使用目标机本地 gcc/g++ 编译 |
6.4 x86_64 主机交叉编译
如果在 x86_64 Linux 主机上交叉编译:
cpp
cd relaydemo
make
需要提前安装:
cpp
aarch64-linux-gnu-gcc
aarch64-linux-gnu-g++
6.5 运行 Demo
编译产物为:
cpp
SmartStreamRelayDemo
前台运行:
./SmartStreamRelayDemo
后台运行:
cpp
nohup ./SmartStreamRelayDemo >/dev/null 2>&1 &
正常终止后台进程:
cpp
kill -s SIGINT <pid>
Demo 中注册了 SIGINT 处理函数,收到信号后会设置退出标记,主循环退出后停止截图、转推和录像任务,并释放 SDK 资源。
6.6 运行前目录准备
Demo 默认录像目录和截图目录为:
const char* rec_dir = "./testrec";
const char* capture_image_dir = "./testcapture";
运行前建议创建目录:
mkdir -p testrec testcapture
chmod 755 testrec testcapture
如果录像或截图失败,优先检查目录是否存在、路径是否正确、运行用户是否具备写权限。
7. 整体集成架构
7.1 单路转推链路

单路 RTSP 转 RTMP 的核心链路如下:
cpp
RTSP / RTMP 源
|
SmartPlayer Open + SetURL
|
StartPullStream()
|
压缩视频包 / 压缩音频包回调
|
RelaySDKWrapper
|
PostVideoEncodedDataV2()
PostAudioEncodedData()
|
SmartPublisher
|
StartPublisher()
|
RTMP Server / 云直播平台 / 企业视频平台
在这个链路中,SmartPlayer 不负责显示画面,主要用于拉流和输出压缩码流;SmartPublisher 不采集摄像头或麦克风,而是以外部编码数据模式接收码流并推送 RTMP。
7.2 多路任务模型

多路任务可以抽象为:
cpp
进程
|
|-- SmartPlayerSDKAPI.Init()
|-- NT_SmartPublisherSDKAPI.Init()
|
|-- Relay 1: rtsp://camera-1 -> rtmp://server/live/stream1
|-- Relay 2: rtsp://camera-2 -> rtmp://server/live/stream2
|-- Relay 3: rtsp://camera-3 -> recorder only
|-- Relay 4: rtsp://camera-4 -> capture image only
|
|-- SIGINT / 服务停止
|
|-- StopPublisher / StopPullStream / StopRecorder / StopPlay
|-- Publisher.UnInit()
|-- Player.UnInit()
每一路 relay 都应具备独立状态、独立 URL、独立录像配置和独立异常处理。
7.3 录像与截图旁路
同一路 SmartPlayer handle 可以用于不同旁路能力:
cpp
同一路 SmartPlayer pull handle
|
|-- StartPullStream() 压缩包转 RTMP
|-- StartRecorder() 本地录像
|-- StartPlay() + CaptureImage() 定时截图
Demo 在截图场景下会先创建一个 PullStreamSDKHandleWrapper 并 StartPlay(),如果该路同时需要转推或录像,会通过 AttachPullHandle() 复用该 handle,避免同一路源重复拉流。
8. 核心 API 集中说明

为了避免接口分散,本节按集成流程集中说明主要 API。
8.1 日志和 SDK 初始化
cpp
SmartLogAPI log_api;
memset(&log_api, 0, sizeof(log_api));
GetSmartLogAPI(&log_api);
log_api.SetLevel(SL_INFO_LEVEL);
log_api.SetPath((NT_PVOID)"./");
初始化 Player 和 Publisher:
cpp
SmartPlayerSDKAPI pull_api;
NT_SmartPublisherSDKAPI push_api;
memset(&pull_api, 0, sizeof(pull_api));
GetSmartPlayerSDKAPI(&pull_api);
if (NT_ERC_OK != pull_api.Init(0, nullptr)) {
fprintf(stderr, "pull_api.Init failed!\n");
return false;
}
memset(&push_api, 0, sizeof(push_api));
NT_GetSmartPublisherSDKAPI(&push_api);
if (NT_ERC_OK != push_api.Init(0, nullptr)) {
pull_api.UnInit();
fprintf(stderr, "push_api.Init failed!\n");
return false;
}
需要注意:
- Player SDK 和 Publisher SDK 都需要初始化;
- 如果 Publisher 初始化失败,应释放已经初始化成功的 Player;
- 进程退出时建议先停止所有任务,再
push_api.UnInit(),最后pull_api.UnInit()。
8.2 打开拉流 handle
PullStreamSDKHandleWrapper::Open() 的核心流程:
cpp
NT_HANDLE handle = nullptr;
sdk_api_->Open(&handle, 0, nullptr);
sdk_api_->SetEventCallBack(handle, this, OnSDKEvent);
sdk_api_->SetBuffer(handle, buffer);
sdk_api_->SetRtspAutoSwitchTcpUdp(handle, 1);
sdk_api_->SetReportDownloadSpeed(handle, 1, 3);
sdk_api_->SetURL(handle, url.c_str());
说明:
|-----------------------------|----------------------|
| 接口 | 说明 |
| Open() | 创建 SmartPlayer 实例 |
| SetEventCallBack() | 注册事件回调 |
| SetBuffer() | 设置缓冲,Demo 默认传 0 |
| SetRtspAutoSwitchTcpUdp() | 开启 RTSP TCP/UDP 自动切换 |
| SetReportDownloadSpeed() | 每 3 秒上报下载速度 |
| SetURL() | 设置 RTSP/RTMP 源地址 |
8.3 启动 PullStream
转推链路通过 PullStream 获取压缩音视频数据:
cpp
pull_api_->SetPullStreamVideoDataCallBack(handle,
this,
OnPullVideoData);
pull_api_->SetPullStreamAudioDataCallBack(handle,
this,
OnPullAudioData);
pull_api_->SetPullStreamAudioTranscodeAAC(handle, 1);
pull_api_->StartPullStream(handle);
说明:
StartPullStream()用于获取未解码的压缩音视频数据;- 视频回调中包含 codec id、关键帧标记、DTS、PTS;
- 音频回调中包含 codec id、timestamp 和 parameter info;
SetPullStreamAudioTranscodeAAC()可将音频转为 AAC,提高 RTMP 和 MP4 兼容性。
8.4 打开推流 handle
转推场景下,Publisher 不采集本地摄像头和麦克风,而是接收外部编码数据:
cpp
push_api_->Open(&handle,
NT_PB_E_VIDEO_OPTION_ENCODED_DATA,
NT_PB_E_AUDIO_OPTION_ENCODED_DATA,
0,
nullptr);
这一步非常关键:
- 视频源设置为
NT_PB_E_VIDEO_OPTION_ENCODED_DATA; - 音频源设置为
NT_PB_E_AUDIO_OPTION_ENCODED_DATA; - 后续通过
PostVideoEncodedDataV2()和PostAudioEncodedData()输入数据。
8.5 设置 RTMP URL 并启动推流
cpp
push_api_->SetURL(push_handle, rtmp_url.c_str(), nullptr);
push_api_->StartPublisher(push_handle, nullptr);
停止推流:
cpp
push_api_->StopPublisher(push_handle);
生产环境中,RTMP URL 建议从配置文件或平台下发,不建议硬编码。
8.6 视频包转发
cpp
push_api_->PostVideoEncodedDataV2(push_handle,
video_codec_id,
data,
size,
info->is_key_frame_,
info->timestamp_,
info->presentation_timestamp_);
字段说明:
|---------------------------|-----------------|
| 字段 | 说明 |
| video_codec_id | 视频编码类型,例如 H.264 |
| data / size | 压缩视频包 |
| is_key_frame_ | 是否关键帧 |
| timestamp_ | 解码时间戳 |
| presentation_timestamp_ | 显示时间戳 |
8.7 音频包转发
cpp
push_api_->PostAudioEncodedData(push_handle,
audio_codec_id,
data,
size,
info->is_key_frame_,
info->timestamp_,
info->parameter_info_,
info->parameter_info_size_);
注意事项:
- 如果源音频是 AAC,
parameter_info_通常用于传递 AudioSpecificConfig; - 如果业务不需要音频,建议关闭音频转发;
- 如果源音频类型不确定,可启用音频转 AAC;
- 推流有画面无声音时,优先检查音频转发开关、源流音频和编码格式。
8.8 停止与释放
推荐释放顺序:
cpp
StopPlay() // 如果启用了截图播放链路
StopPublisher()
StopPullStream()
StopRecorder()
Close()
push_api.UnInit()
pull_api.UnInit()
Demo 中 RelaySDKWrapper 析构会兜底调用:
cpp
StopPushRtmp();
StopPull();
StopRecorder();
生产环境建议仍然显式停止每一路任务,便于状态记录和问题排查。
9. 多路任务配置与管理

9.1 Demo 配置方式
Demo 中通过数组配置多路任务:
cpp
RelayConfigItem relay_conf_items[] = {
{
"rtsp://admin:password@192.168.0.120:554/h264/ch1/main/av_stream",
true, // is_push_rtmp
false, // is_recorder
false, // is_capture_image
},
};
字段说明:
|---------------------|----------------------|
| 字段 | 说明 |
| pull_url_ | 拉流地址,可以是 RTSP 或 RTMP |
| is_push_rtmp_ | 是否将该路转推 RTMP |
| is_recorder_ | 是否对该路本地录像 |
| is_capture_image_ | 是否对该路定时截图 |
RTMP 推送基地址:
cpp
const char* rtmp_push_base_url = "rtmp://192.168.0.107/live/";
Demo 根据通道序号生成目标推送 URL:
cpp
rtmp://192.168.0.107/live/linuxrelaytest1
rtmp://192.168.0.107/live/linuxrelaytest2
...
9.2 生产环境配置建议
实际项目中,建议将硬编码数组改为 JSON、数据库或平台下发配置,例如:
cpp
[
{
"channelId": "cam-001",
"pullUrl": "rtsp://user:pass@192.168.1.10:554/stream1",
"pushRtmpUrl": "rtmp://live.example.com/live/cam001",
"enablePush": true,
"enableRecorder": true,
"enableSnapshot": false,
"relayVideo": true,
"relayAudio": true,
"recorderFileMaxSizeMB": 200,
"snapshotIntervalSec": 30
}
]
每一路通道建议维护以下信息:
|-------------------|--------|
| 字段 | 说明 |
| channel_id | 通道 ID |
| pull_url | 拉流地址 |
| push_url | 推流地址 |
| enable_push | 是否转推 |
| enable_recorder | 是否录像 |
| enable_snapshot | 是否截图 |
| relay_video | 是否转发视频 |
| relay_audio | 是否转发音频 |
| state | 当前状态 |
| download_speed | 当前下载速度 |
| recorder_file | 当前录像文件 |
| last_error | 最近错误 |
| reconnect_count | 重连次数 |
9.3 启动任务流程
StartTasks() 的核心逻辑可以概括为:
cpp
遍历 RelayConfigItem
|
检查 pull_url 是否有效
|
如果开启截图:
创建 PullStreamSDKHandleWrapper
SetVideoFrameCallBack(I420)
StartPlay()
保存 player handle
|
如果需要推流或录像:
创建 RelaySDKWrapper
SetPullURL()
如果已有截图 handle,则 AttachPullHandle()
|
如果开启 RTMP:
SetRelayVideo(true)
SetRelayAudio(true/false)
StartPull()
StartPushRtmp()
|
如果开启录像:
设置录像目录、大小、文件名前缀、日期时间规则
StartRecorder()
|
如果 relay 正在工作:
加入 relays 容器
9.4 停止任务流程
推荐停止流程:
cpp
停止截图播放 handle
|
停止 RTMP 推送
|
停止 PullStream
|
停止录像
|
释放 relay 对象
|
Publisher UnInit
|
Player UnInit
需要注意,停止顺序应避免以下问题:
- 推流 handle 已释放但回调仍在投递数据;
- PullStream 未停止导致回调继续触发;
- 录像文件未正常关闭;
- 截图播放 handle 未释放;
- 重连过程中重复创建 handle。
10. 本地录像与定时截图

10.1 本地录像
Demo 使用 SmartPlayer 的录像能力,录像配置示例:
cpp
relay_item->SetRecorderDirectory("./testrec");
relay_item->SetRecorderFileMaxSize(200);
relay_item->SetRecorderAudioTranscodeAAC(true);
relay_item->SetRecorderAppendDate(true);
relay_item->SetRecorderAppendTime(true);
relay_item->SetRecorderVideo(true);
relay_item->SetRecorderAudio(false);
relay_item->SetRecorderFileNamePrefix("rt-1-");
relay_item->StartRecorder();
内部会调用:
cpp
pull_api_->SetRecorderVideo(handle, 1);
pull_api_->SetRecorderAudio(handle, 0);
pull_api_->SetRecorderDirectory(handle, recoder_directory_.c_str());
pull_api_->SetRecorderFileMaxSize(handle, recorder_file_max_size_ * 1024);
pull_api_->SetRecorderFileNameRuler(handle, &rec_name_ruler);
pull_api_->SetRecorderCallBack(handle, this, OnRecorderHandle);
pull_api_->SetRecorderAudioTranscodeAAC(handle, 1);
pull_api_->StartRecorder(handle);
录像文件命名规则:
cpp
NT_SP_RecorderFileNameRuler rec_name_ruler = {0, nullptr, 0, 0};
rec_name_ruler.file_name_prefix_ = "rt-1-";
rec_name_ruler.append_date_ = 1;
rec_name_ruler.append_time_ = 1;
建议:
- 录像目录提前创建并赋予写权限;
- 文件名前缀包含通道 ID;
- 按需开启音频录像;
- 如果音频格式不确定,可开启音频转 AAC;
- 录像文件完成后可异步上传、入库或生成索引。
10.2 定时截图
Demo 通过 SmartPlayer 播放链路实现截图:
cpp
pull_api->SetVideoFrameCallBack(handle->Handle(),
NT_SP_E_VIDEO_FRAME_FROMAT_I420,
nullptr,
OnPlayerSDKVideoFrameHandle);
pull_api->StartPlay(handle->Handle());
截图间隔:
cpp
const time_t capture_image_interval = 30;
截图调用:
cpp
player_api->CaptureImage(handle,
MakeCaptureImageFileName(i).c_str(),
nullptr,
OnPlayerSDKCaptureImageCallBack);
截图文件名类似:
./testcapture/tc-1-06-26-14-30-00.png
建议:
- 截图目录提前创建;
- 多路截图避免同一秒同时触发过多请求;
- 截图频率不要过高;
- 视频帧回调优先使用 I420,避免 RGB 转换增加性能开销;
- 定时截图适合巡检和状态留证,不建议替代录像。
11. 生产环境集成建议

11.1 部署目录建议
建议生产环境采用清晰目录结构:
cpp
/opt/smartrelay/
├── bin/
│ └── smartrelay
├── lib/
│ ├── libSmartPlayerSDK.so
│ ├── libSmartPublisherSDK.so
│ └── libSmartLog.so
├── conf/
│ └── relay_channels.json
├── logs/
├── rec/
└── capture/
启动脚本示例:
cpp
#!/bin/bash
APP_HOME=/opt/smartrelay
export LD_LIBRARY_PATH=$APP_HOME/lib:$LD_LIBRARY_PATH
cd $APP_HOME/bin
./smartrelay --config $APP_HOME/conf/relay_channels.json
11.2 URL 与凭据安全
RTSP URL 经常包含用户名和密码,日志中应做脱敏:
rtsp://admin:******@192.168.0.120:554/...
RTMP URL 如果包含 token、鉴权串或业务身份,也应避免明文输出到普通日志。
建议:
- URL 从配置文件或平台接口读取;
- 配置文件权限限制为业务进程可读;
- 日志中只保留必要定位信息;
- 通道 ID 与 URL 分开记录;
- 异常日志中避免打印完整密码和 token。
11.3 状态机建议

每一路 relay 建议维护状态机:
cpp
Idle
|
OpeningPull
|
Pulling
|
|-- Pushing
|-- Recording
|-- Snapshotting
|
Failed
|
Reconnecting
|
Stopping
|
Stopped
状态机中建议区分:
- 拉流状态;
- 推流状态;
- 录像状态;
- 截图状态;
- 网络状态;
- 最近错误事件。
11.4 重连与恢复策略
转推链路涉及拉流端和推流端两侧异常,建议分别处理:
|-----------|-----------------------|
| 异常类型 | 建议处理 |
| RTSP 拉流失败 | 重建 Pull handle |
| RTSP 源断开 | 停止当前 PullStream 后重新拉流 |
| RTMP 推流失败 | 重建 Push handle |
| RTMP 服务重启 | 按退避策略重推 |
| 录像失败 | 独立告警,不一定影响转推 |
| 截图失败 | 限频告警,不影响主链路 |
| URL 鉴权失败 | 不建议高频无限重试 |
| 目录无权限 | 明确告警,等待配置修复 |
11.5 监控指标建议
生产环境建议采集以下指标:
|-----------|--------------|
| 指标 | 说明 |
| 拉流状态 | 每路源是否正常 |
| 推流状态 | 每路 RTMP 是否在线 |
| 下载速度 | 判断源流码率和网络状态 |
| 推流 URL | 记录目标平台 |
| 录像文件名 | 当前或最近录像文件 |
| 录像目录剩余空间 | 避免磁盘写满 |
| 截图成功/失败次数 | 判断截图链路健康 |
| 重连次数 | 判断网络或源端稳定性 |
| CPU / 内存 | 判断设备负载 |
| 网络上下行 | 判断带宽是否充足 |
| 通道数 | 判断并发是否超出设备能力 |
11.6 并发性能建议
多路转推并发能力受以下因素影响:
- 源流分辨率;
- 源流码率;
- 是否转发音频;
- 是否开启音频转 AAC;
- 是否本地录像;
- 是否定时截图;
- 磁盘写入性能;
- 网络上行带宽;
- CPU 和内存规格;
- IPC/NVR 对并发拉流数量的限制。
由于视频链路不重编码,转推 CPU 压力通常明显低于转码方案。但录像、截图、音频转码和多路网络 IO 仍然需要做压测。
12. 常见问题排查
12.1 拉不到 RTSP 流
排查方向:
- RTSP URL 是否正确;
- 用户名和密码是否正确;
- 摄像头或 NVR 是否允许当前账号访问;
- 是否达到摄像头最大连接数;
- VLC、ffplay 或 SmartPlayer 是否可单独播放;
- 是否开启 RTSP TCP/UDP 自动切换;
- 网络、防火墙、路由是否可达;
- SDK 日志中是否有连接失败信息。
12.2 RTMP 推送失败
排查方向:
- RTMP Server 地址、端口、app、stream 是否正确;
- 防火墙是否允许访问;
- RTMP 服务是否允许发布;
- 推流 URL 是否被其他进程占用;
- Publisher handle 是否以
ENCODED_DATA模式打开; - 是否已经收到上游压缩视频包;
- 是否调用了
StartPublisher(); - SDK 日志是否有连接失败事件。
12.3 推流有画面无声音
排查方向:
- 是否调用
SetRelayAudio(true); - 源流是否包含音频;
- 源音频编码是否被 RTMP Server 或播放端支持;
- 是否需要开启
SetPullStreamAudioTranscodeAAC(true); PostAudioEncodedData()是否持续被调用;- 如果业务不需要音频,可明确关闭音频转发。
12.4 录像失败
排查方向:
- 录像目录是否存在;
- 录像目录是否可写;
- 磁盘空间是否充足;
- 是否开启
SetRecorderVideo(true)或SetRecorderAudio(true); - 单文件大小是否合理;
- 文件名前缀是否包含非法字符;
- 录像目录是否包含非英文字符。
12.5 截图失败
排查方向:
- 截图目录是否存在;
- 目录是否可写;
- 是否已经
StartPlay(); - 源流是否有视频;
- 截图频率是否过高;
- 回调
result是否为NT_ERC_OK; - 多路截图是否导致 CPU 峰值过高。
12.6 CPU 或内存占用高
优化方向:
- 关闭不必要的音频转发;
- 关闭不必要的截图;
- 降低截图频率;
- 减少录像路数;
- 关闭不必要的播放/解码链路;
- 避免同一路源重复拉流;
- 优先复用 Pull handle;
- 控制单设备并发通道数;
- 检查是否误走了解码或转码链路。
12.7 多路运行一段时间后不稳定
排查方向:
- 每路异常后是否正确
Stop/Close; - 断线重连是否创建了未释放的 handle;
- 录像目录是否写满;
- RTMP 服务是否限制连接数;
- IPC/NVR 是否限制并发拉流数;
- 日志是否过量写入磁盘;
- 是否存在频繁截图导致 CPU 峰值;
- 是否存在 URL 配置错误导致高频重连。
13. 上线验收 Checklist
13.1 环境验收
|---------------------------------|------|
| 检查项 | 是否完成 |
| 目标设备为 arm64/aarch64 | □ |
| SmartStreamRelayDemo 可正常启动 | □ |
| libSmartPlayerSDK.so 可正常加载 | □ |
| libSmartPublisherSDK.so 可正常加载 | □ |
| libSmartLog.so 可正常加载 | □ |
| ldd 检查无 not found | □ |
| glib/gio/X11 依赖满足 | □ |
| 录像目录可写 | □ |
| 截图目录可写 | □ |
| 网络可访问 RTSP 源和 RTMP 服务 | □ |
13.2 功能验收
|-------------------|------|
| 检查项 | 是否完成 |
| 单路 RTSP 转 RTMP 成功 | □ |
| 多路 RTSP 转 RTMP 成功 | □ |
| RTMP 播放端可正常播放 | □ |
| 开启音频转发后声音正常 | □ |
| 关闭音频转发后视频正常 | □ |
| 本地录像文件生成成功 | □ |
| 录像文件可播放 | □ |
| 定时截图成功 | □ |
| 下载速度事件正常上报 | □ |
| SIGINT 后进程可正常退出 | □ |
| 退出后资源释放完整 | □ |
13.3 稳定性验收
|-------------------|------|
| 检查项 | 是否完成 |
| 多路长时间运行无崩溃 | □ |
| 断网恢复后可按策略重连 | □ |
| RTMP 服务重启后可恢复推送 | □ |
| 摄像头重启后可恢复拉流 | □ |
| 录像切片稳定 | □ |
| 截图不会导致 CPU 峰值不可控 | □ |
| 内存无持续增长 | □ |
| 磁盘满、目录无权限等异常有明确告警 | □ |
| 多路停止/重启无资源泄漏 | □ |
14. 与当前 Demo 的对应关系
|--------------------------------------|---------------------------------------------------------------------------------|
| 文档内容 | 对应文件 |
| 多路任务配置、SDK 初始化、启动/停止任务、定时截图 | relaydemo.cpp |
| 拉流 handle 封装、事件分发 | nt_smart_sdk_wrapper/nt_app_sdk_handle_wrapper.cpp |
| 推流 handle 封装 | nt_smart_sdk_wrapper/nt_app_sdk_handle_wrapper.cpp |
| 单路 relay 逻辑、PullStream 回调、RTMP 推送、录像 | nt_smart_sdk_wrapper/nt_app_relay_sdk_wrapper.cpp |
| Relay 对外接口 | nt_smart_sdk_wrapper/nt_app_relay_sdk_wrapper.h |
| SmartPlayer 接口 | nt_smart_sdk/linux/include/smart_player_sdk/nt_linux_smart_player_sdk.h |
| SmartPublisher 接口 | nt_smart_sdk/linux/include/smart_publisher_sdk/nt_linux_smart_publisher_sdk.h |
| 编译配置 | Makefile |
| 运行说明 | readme.txt |
15. 总结
整体来看,大牛直播SDK(SmartMediaKit)Linux arm64 平台多路 RTSP 转 RTMP 推送模块,并不是一个简单的"拉一路流再推一路流"的示例,而是一套面向边缘网关、安防汇聚、工业视觉和云端视频接入场景的轻量级流媒体转发能力组件。
在实际项目中,RTSP 摄像头、NVR、边缘流媒体服务和云端 RTMP 平台之间经常存在协议不一致、接入方式不统一、设备资源有限、录像留证和运维监控要求较高等问题。SmartMediaKit relay 模块通过 SmartPlayer 拉流、PullStream 压缩包回调和 SmartPublisher 外部编码数据输入的组合方式,实现了不重编码的视频转推链路,在降低 CPU 占用的同时,也减少了画质损失和链路复杂度。
从 relaydemo 可以看出,该模块的集成流程比较清晰:进程级初始化 SmartPlayer 和 SmartPublisher,每一路创建独立的 relay wrapper,按通道设置拉流 URL、推流 URL、录像和截图策略,通过 PullStream 回调将压缩音视频包投递给 Publisher,运行中通过下载速度、录像回调和业务状态机监控每路状态,退出时再逐路停止推流、拉流、录像和截图链路。
对于多路 RTSP 摄像头汇聚上云场景,可以直接参考 relaydemo 的多路配置和 wrapper 封装方式,将其改造成边缘转发服务;对于需要设备端本地留证的项目,可以按通道开启录像;对于巡检和低频取证类场景,可以结合定时截图能力;对于需要长期运行的生产环境,则建议重点完善配置管理、URL 脱敏、状态机、异常重连、磁盘空间监控和通道级运维指标。
对于需要在 Linux arm64 设备上快速构建多路 RTSP/RTMP 拉流、RTSP 转 RTMP 推送、本地录像和定时截图能力的项目,大牛直播SDK(SmartMediaKit)可以作为底层转发能力组件,帮助开发者减少协议适配、码流转发、RTMP 封装、录像截图和异常处理等方面的重复开发工作,把更多精力放在设备管理、平台接入、业务规则和行业应用本身。
📎 CSDN官方博客:音视频牛哥-CSDN博客