文档概述
在安防监控、智慧园区、应急指挥、工业视觉、低空经济、无人机回传和多路摄像头上云等场景中,现场设备通常以 RTSP 方式输出视频流,而云端平台、直播分发平台或业务中台往往更倾向于接收 RTMP 流。此时,系统需要在边缘侧或 Windows 工控机侧部署一个稳定、低延迟、可长期运行的流媒体转发模块,将多路 RTSP 摄像头流实时转推到 RTMP Server、CDN 或业务直播平台。
大牛直播SDK(SmartMediaKit)Windows 平台多路 RTSP 转 RTMP 推流方案,正是面向这类场景设计的协议转换与转发能力。它并不是简单的播放器叠加推流工具,而是基于 SmartPlayerSDK 和 SmartPublisherSDK 的组合能力,通过“拉流端编码数据回调 + 推流端编码数据输入”的方式,实现 RTSP 到 RTMP 的低延迟转发。
本文以 Windows C# DemoSmartRelayDemo为基础,介绍多路 RTSP 转 RTMP 推流的工程结构、配置方式、SDK 初始化、拉流流程、推流流程、音频策略、事件回调、单路预览、资源释放和部署注意事项,帮助开发者快速完成 Windows 平台下的多路转发集成。
产品定位
多路 RTSP 转 RTMP 推流模块主要解决的问题是:把来自 IPC、NVR、编码器、无人机、车载终端或其他 RTSP 设备的实时流,批量转发到 RTMP 服务器或直播平台。
在这一过程中,SDK 更强调三个工程价值:
第一,低延迟转发。转发链路优先采用编码后数据直通方式,避免不必要的解码、重编码和画面合成处理。
第二,多路并发与稳定运行。每一路转发独立维护拉流端和推流端实例,便于单路异常定位,也便于后续扩展为服务进程、边缘网关或无人值守转发程序。
第三,状态可观测。拉流端和推流端均提供事件回调,能够区分“摄像头拉不到流”“RTMP 服务器连接失败”“网络抖动”“缓冲异常”等不同问题,便于现场排查。
官网资料中也将该转发 SDK 定位为跨平台 RTSP/RTMP 转 RTMP 转发能力,强调低延迟、稳定性、状态反馈、资源占用和跨平台 SDK 交付能力。
适用场景
| 场景 | 说明 |
|---|---|
| 安防视频上云 | 将内网 IPC/NVR 的 RTSP 流批量转推到云端 RTMP 平台 |
| 园区/工厂视频汇聚 | 多品牌摄像头统一接入,输出标准 RTMP 地址 |
| 边缘节点转发 | 在 Windows 工控机、边缘网关上完成 RTSP 到 RTMP 的协议适配 |
| 应急指挥/无人机回传 | 将现场实时视频转推到调度平台或指挥大屏 |
| 直播平台接入 | 将 RTSP 源转换为 RTMP 后接入自建流媒体服务器、CDN 或业务平台 |
| 无人值守转发 | 配合配置文件和开机自启动,实现多路自动拉流和自动转推 |
能力概览
| 能力项 | 说明 |
|---|---|
| 多路转发 | 支持多路RTSP转RTMP推送,可根据授权和工程需要扩展 |
| 拉流协议 | 支持 RTSP、RTMP 等流媒体输入,Windows 平台也可扩展本地 FLV 转发 |
| 推流协议 | 支持 RTMP 推送 |
| 视频编码 | 支持 H.264、H.265 编码数据转发 |
| 音频处理 | 支持 AAC,并可将 PCMA、PCMU、Speex 等音频转 AAC 后推送 |
| 本地预览 | 转发过程中可按需预览任意一路输入流 |
| 事件回调 | 支持拉流连接状态、下载速度、缓冲状态、推流连接状态等反馈 |
| 配置化管理 | 通过configure.xml配置每一路 PullUrl、PushUrl 和音频模式 |
| 开机自启动 | Demo 提供注册表方式实现程序开机自启动 |
官网能力列表中也提到支持本地预览、URL 切换、录像扩展、内网 RTSP 网关扩展、音频转 AAC、H.264/H.265 转发和整体网络状态反馈等能力。
开发环境
建议使用如下环境集成:
| 项目 | 建议配置 |
|---|---|
| 操作系统 | Windows 7 / Windows 10 / Windows 11 |
| 开发语言 | C# |
| UI 框架 | Windows Forms,Demo 以 WinForms 为例 |
| 开发工具 | Visual Studio 2013 及以上 |
| 目标平台 | x64 或 x86,需与 SDK DLL 位数保持一致 |
| 核心库 | SmartPlayerSDK.dll、SmartPublisherSDK.dll |
| 可选库 | SmartLog.dll,用于日志输出和现场问题定位 |
C# 工程平台架构必须与 SDK 动态库保持一致。例如使用 x64 版本 SDK 时,项目平台建议明确设置为 x64,不建议使用 Any CPU,避免运行时出现 DLL 加载失败。
工程文件结构
Demo 可按如下结构理解:
SmartRelayDemo ├── SmartStreamRelayDemo.cs # 主窗口业务逻辑 ├── StreamRelayConfig.cs # 单路转发配置模型 ├── nt_relay_wrapper.cs # 单路转发协调层 ├── nt_player_wrapper.cs # 拉流端封装,基于 SmartPlayerSDK ├── nt_publisher_wrapper.cs # 推流端封装,基于 SmartPublisherSDK ├── smart_player_sdk.cs # SmartPlayerSDK P/Invoke 接口声明 ├── nt_smart_publisher_sdk.cs # SmartPublisherSDK P/Invoke 接口声明 └── configure.xml # 多路转发配置文件其中,SmartStreamRelayDemo.cs负责 UI、配置读取、SDK 初始化、8 路 relay 实例创建、一键拉流和一键转推;nt_relay_wrapper.cs负责将单路拉流端和推流端组合起来;nt_player_wrapper.cs负责拉流、事件、视频数据回调和音频数据回调;nt_publisher_wrapper.cs负责 RTMP 推送、编码数据输入和推流状态回调。
推荐工程分层
建议业务工程不要把所有 SDK 调用直接写在 Form 按钮事件中,而是保持 Demo 中这种分层方式:
这种分层的好处是:
| 层级 | 职责 |
|---|---|
| SmartStreamRelayDemo | 读取配置、管理多路实例、处理按钮和状态显示 |
| nt_relay_wrapper | 每一路转发的协调器,连接拉流端和推流端 |
| nt_player_wrapper | 拉取 RTSP/RTMP 流,并通过回调输出编码音视频数据 |
| nt_publisher_wrapper | 创建推流实例,将编码数据投递给 RTMP 推送模块 |
| SDK DLL | 提供底层拉流、推流、事件、音视频处理能力 |
后续如果要改造成 Windows Service、控制台程序、边缘网关进程或后台无人值守程序,也可以复用nt_relay_wrapper、nt_player_wrapper和nt_publisher_wrapper这几层。
configure.xml 多路配置
Demo 启动后会读取 exe 同级目录下的configure.xml。每一个<Relay>节点对应一路转发配置。
示例:
<?xml version="1.0" encoding="utf-8" ?> <StreamRelays> <Relay> <id>0</id> <AudioOption>4</AudioOption> <PullUrl>rtsp://admin:password@192.168.0.120:554/h264/ch1/main/av_stream</PullUrl> <PushUrl>rtmp://192.168.0.103:1935/live/stream00</PushUrl> </Relay> <Relay> <id>1</id> <AudioOption>0</AudioOption> <PullUrl>rtsp://admin:password@192.168.0.121:554/cam/realmonitor?channel=1&subtype=0</PullUrl> <PushUrl>rtmp://192.168.0.103:1935/live/stream01</PushUrl> </Relay> </StreamRelays>字段说明:
| 字段 | 说明 |
|---|---|
| id | 路序号,便于标识 |
| AudioOption | 音频模式 |
| PullUrl | RTSP/RTMP 拉流地址 |
| PushUrl | RTMP 推流地址 |
需要注意的是,XML 中如果 RTSP 地址包含&,必须写成&,否则 XML 解析会失败。
StreamRelayConfig.cs中对应的数据模型很简单,只包含Id、AudioOption、PullUrl和PushUrl四个字段。
音频模式说明
Demo 中AudioOption用于控制每一路推流的音频来源:
| AudioOption | 含义 | 适用场景 |
|---|---|---|
| 0 | 无音频 | 只需要视频监控画面 |
| 1 | 采集本机麦克风 | 需要本地讲解、语音注入 |
| 2 | 采集本机扬声器 | 需要转发本机播放声音 |
| 3 | 麦克风 + 扬声器混音 | 需要本地混音 |
| 4 | 使用拉流端编码后音频数据 | 摄像头原始音频随视频一起转发 |
在 RTSP 摄像头转 RTMP 的典型场景中,推荐优先使用AudioOption=4。这样可以直接使用拉流端输出的编码音频数据。如果摄像头输出的是 PCMA、PCMU、Speex 等格式,也可以通过拉流端音频转 AAC 能力,转成 RTMP 更常用的 AAC 后推送。nt_player_wrapper中可以看到,订阅音频时会设置音频数据回调,并开启拉流音频转 AAC。
如果现场没有音频需求,建议配置为AudioOption=0,减少无意义的音频处理和带宽占用。
SDK 初始化与生命周期
Windows C# 工程中,SmartPlayerSDK 和 SmartPublisherSDK 都属于进程级 SDK 初始化。通常建议在程序启动后只初始化一次,在程序退出前统一反初始化。
Demo 中初始化逻辑大致如下:
private bool InitSDK() { UInt32 pub_init_ret = NTSmartPublisherSDK.NT_PB_Init(0, IntPtr.Zero); UInt32 pull_init_ret = NTSmartPlayerSDK.NT_SP_Init(0, IntPtr.Zero); if (NTBaseCodeDefine.NT_ERC_OK == pub_init_ret && NTBaseCodeDefine.NT_ERC_OK == pull_init_ret) { is_sdk_has_inited_ = true; return true; } if (NTBaseCodeDefine.NT_ERC_OK == pub_init_ret) NTSmartPublisherSDK.NT_PB_UnInit(); if (NTBaseCodeDefine.NT_ERC_OK == pull_init_ret) NTSmartPlayerSDK.NT_SP_UnInit(); is_sdk_has_inited_ = false; return false; }退出时按反向顺序释放:
private bool UnInitSDK() { if (is_sdk_has_inited_) { NTSmartPlayerSDK.NT_SP_UnInit(); NTSmartPublisherSDK.NT_PB_UnInit(); } return true; }这里要注意:不能在某一路转发仍在运行时直接调用 SDK 反初始化。正确顺序应为:先停止预览、停止拉流、停止推流、释放 wrapper,再调用 SDK UnInit。Demo 的FormClosingHandling()中已经按这个思路做了统一释放。
多路实例创建
Demo 默认定义了 8 路:
const int PULL_PUSH_GROUP_NUM = 8;同时维护 8 组拉流地址、推流地址、播放按钮、拉流状态、推流状态和音频模式显示。程序读取configure.xml后,会根据实际配置数量计算stream_relay_instance_count_,只创建实际配置的 relay 实例,未配置的 UI 控件会被自动禁用。
核心逻辑可以理解为:
读取 configure.xml ↓ 计算实际配置路数 stream_relay_instance_count_ ↓ 为每一路创建 nt_relay_wrapper ↓ 绑定拉流状态回调、分辨率回调、推流状态回调 ↓ 设置每一路 AudioOption ↓ 启动拉流 ↓ 启动 RTMP 转推这种方式便于做成“配置即运行”的无人值守程序。现场只需要修改配置文件,即可调整拉流源和推流目标。
单路转发核心:nt_relay_wrapper
nt_relay_wrapper是整个 Demo 中最关键的协调层。每一路转发都有一个独立的nt_relay_wrapper实例,内部同时持有一个拉流端nt_player_wrapper和一个推流端nt_publisher_wrapper。
结构可以概括为:
nt_relay_wrapper ├── nt_player_wrapper # 拉流端 ├── nt_publisher_wrapper # 推流端 ├── StartPull() # 启动拉流 ├── StopPull() # 停止拉流 ├── StartPublisher() # 启动推流 ├── StopPublisher() # 停止推流 ├── StartPlayer() # 本地预览 └── StopPlayer() # 停止预览它的核心价值不是做复杂业务,而是把“拉到的数据”转交给“推流模块”。视频数据和音频数据的路由逻辑如下:
private void OnVideoDataHandle(IntPtr handle, IntPtr user_data, UInt32 video_codec_id, IntPtr data, UInt32 size, IntPtr info, IntPtr reserve) { if (publisher_wrapper_.is_rtmp_publishing()) { publisher_wrapper_.OnVideoDataHandle( handle, user_data, video_codec_id, data, size, info, reserve); } } private void OnAudioDataHandle(IntPtr handle, IntPtr user_data, UInt32 audio_codec_id, IntPtr data, UInt32 size, IntPtr info, IntPtr reserve) { if (publisher_wrapper_.is_rtmp_publishing()) { publisher_wrapper_.OnAudioDataHandle( handle, user_data, audio_codec_id, data, size, info, reserve); } }这就是 RTSP 转 RTMP 的核心链路:拉流端通过回调吐出编码后的音视频数据,推流端再将这些编码数据输入到 RTMP 推送模块。
拉流流程
拉流由nt_player_wrapper负责。启动拉流时,建议按以下顺序处理:
Demo 中nt_relay_wrapper.StartPull()会先设置SetBuffer(0),再订阅视频和音频回调,然后调用player_wrapper_.StartPull()。这里“先订阅事件,再启动 SDK”非常重要,因为 SDK 启动后可能很快在后台线程触发数据回调。
nt_player_wrapper.StartPull()中会设置:
NTSmartPlayerSDK.NT_SP_SetPullStreamVideoDataCallBack(...); if (subscribe_audio) { NTSmartPlayerSDK.NT_SP_SetPullStreamAudioDataCallBack(...); NTSmartPlayerSDK.NT_SP_SetPullStreamAudioTranscodeAAC(...); } NTSmartPlayerSDK.NT_SP_StartPullStream(player_handle_);也就是说,拉流端并不是为了播放而播放,而是为了从 RTSP/RTMP 源中拿到可用于后续转推的音视频数据。
推流流程
推流由nt_publisher_wrapper负责。启动推流时,nt_relay_wrapper.StartPublisher()会先打开推流 handle,再设置 RTMP URL,最后调用推流启动接口。
流程如下:
nt_publisher_wrapper中,编码后视频数据最终通过:
NTSmartPublisherSDK.NT_PB_PostVideoEncodedDataV2(...)投递给 SDK;编码后音频数据则通过:
NTSmartPublisherSDK.NT_PB_PostAudioEncodedData(...)投递给 SDK。底层接口声明中也可以看到,这两个接口分别用于投递编码后视频数据和编码后音频数据。
这类方式的优势是明显的:如果原始 RTSP 源已经是 H.264/H.265 视频编码,就不需要在转发端重新编码视频,CPU 占用和转发延迟都会更可控。
一键拉流与一键转推
Demo 中提供了两个关键入口:
PullStream() PushStream()PullStream()负责遍历所有已配置的 relay,逐路启动拉流;如果当前已经在拉流,则停止所有推流和拉流。
PushStream()负责遍历所有已配置的 relay,逐路启动 RTMP 推送;如果当前已经在推流,则停止所有推送。
简化后的逻辑如下:
for (int i = 0; i < stream_relay_instance_count_; i++) { stream_relay_config_[i].PullUrl = edit_pull_urls_[i].Text; relays[i].StartPull(stream_relay_config_[i].PullUrl); }for (int i = 0; i < stream_relay_instance_count_; i++) { stream_relay_config_[i].PushUrl = edit_push_urls_[i].Text; relays[i].SetPusherOption(video_option_, (uint)stream_relay_config_[i].AudioOption); relays[i].StartPublisher(stream_relay_config_[i].PushUrl); }这种设计适合 Demo 展示,也适合很多项目中的“批量转发”场景。如果业务侧需要更细粒度控制,也可以改造成单路启动、单路停止、单路重连或异常单路恢复。
本地预览
多路转发不一定需要始终预览画面。为了降低资源消耗,建议默认只做拉流和转推,现场调试或运维时再按需打开某一路预览。
Demo 中每一路都有一个预览按钮,点击后调用:
relays[index].StartPlayer( stream_relay_config_[index].PullUrl, is_rtsp_tcp_mode, is_mute);预览能力复用了nt_player_wrapper的播放能力。也就是说,一路 RTSP 流既可以作为“拉流转推”的输入,也可以在需要时做本地播放预览。这样可以方便现场确认摄像头画面、码流分辨率、网络连接状态和转发效果。
工程上建议:多路并发转发时不要默认打开全部预览,尤其是 4 路、8 路甚至更多路摄像头同时转发时,解码和渲染会显著增加 CPU/GPU 占用。只有在调试、巡检或人工查看时,再打开指定路预览。
状态回调与问题定位
多路转发系统最怕的问题不是“某一路失败”,而是失败后不知道问题发生在哪一段。因此,推拉流状态回调非常重要。
拉流端重点关注:
| 状态 | 说明 |
|---|---|
| 连接中 | 正在连接 RTSP/RTMP 源 |
| 连接成功 | 摄像头或流媒体源可访问 |
| 连接失败 | URL、鉴权、网络或设备异常 |
| 断开连接 | 源端断流或网络断开 |
| 下载速度 | 当前拉流带宽,可用于判断是否有数据持续到达 |
| 缓冲状态 | 网络抖动或源端不稳定时用于辅助判断 |
推流端重点关注:
| 状态 | 说明 |
|---|---|
| 连接中 | 正在连接 RTMP Server |
| 已连接 | RTMP 推送连接建立 |
| 连接失败 | 推流地址、服务器、网络或鉴权异常 |
| 断开连接 | RTMP 服务器断开或网络异常 |
Demo 中GetPlayerEventMsgInfo()和GetPublisherEventMsgInfo()会将事件信息显示到对应路的 Label 上,便于现场观察每一路状态。
实际项目中建议将这些状态进一步写入日志系统,例如:
[Relay-03] Pull connecting: rtsp://... [Relay-03] Pull connected [Relay-03] Push connecting: rtmp://... [Relay-03] Push connected [Relay-03] DownloadSpeed: 3200kbps [Relay-03] Pull disconnected这样可以快速判断故障属于摄像头侧、网络侧、转发程序侧,还是 RTMP 服务端侧。
资源释放与退出处理
多路 SDK 程序一定要重视释放顺序。尤其是 C# WinForms 程序中,SDK 回调可能来自后台线程,UI 控件释放后,如果回调仍然访问 Label、Button 或窗口句柄,就可能导致异常。
建议退出顺序如下:
解绑事件回调 ↓ 停止本地预览 ↓ 停止拉流 ↓ 释放播放器 wrapper ↓ 停止推流 ↓ 释放推流 wrapper ↓ SDK UnInitDemo 中退出处理已经体现了这个思路:
relays[i].GetPlayerWrapper().EventGetPlayerEventMsg -= GetPlayerEventMsgInfo; relays[i].GetPlayerWrapper().EventGetVideoSize -= GetVideoSize; relays[i].GetPublisherWrapper().EventGetPublisherEventMsg -= GetPublisherEventMsgInfo; relays[i].StopPlayer(); relays[i].StopPull(); relays[i].PlayerDispose(); relays[i].StopPublisher(); relays[i].PublisherDispose(); UnInitSDK();这个顺序比简单粗暴地直接关闭窗口更可靠,适合长期运行的工程项目。
开机自启动
对于无人值守转发场景,Windows 程序常见部署方式是开机后自动启动。Demo 中提供了写入注册表的方式,将程序加入:
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run实现当前用户登录后自动启动。
实现时要注意两点:
第一,路径需要加双引号,避免安装目录中包含空格时启动失败。
第二,删除自启动项时,最好先根据 exe 路径查找实际键名,再删除对应键值,避免键名和程序名不一致时删除失败。
Demo 中SetAutoStart()已经考虑了这类细节。
部署建议
1. 明确 SDK DLL 位数
如果使用 x64 SDK,C# 工程必须设置为 x64。不要使用 Any CPU,否则可能出现运行时找不到 DLL 或加载失败。
2. 配置文件和 exe 放同级目录
configure.xml建议放在 exe 同级目录,方便运维人员直接修改,不需要重新编译程序。
3. 日志目录提前创建
如果启用 SmartLog,确保日志目录存在,并对运行账号有写入权限。
4. 多路转发尽量关闭默认预览
批量转发时,预览会引入解码和绘制开销。转发服务默认只做拉流和推流,需要看画面时再打开单路预览。
5. 按路记录状态
建议日志中带上relay index、PullUrl、PushUrl、拉流状态、推流状态和下载速度,便于多路场景定位问题。
6. 区分“拉不到”和“推不上”
现场排查时要分两段看:
RTSP 源 → 拉流端 拉流端 → RTMP 推流端 → RTMP Server如果拉流失败,重点检查摄像头地址、账号密码、RTSP 端口、网络连通性;如果推流失败,重点检查 RTMP Server 地址、应用名、流名、防火墙和服务端状态。
常见问题
1. 为什么 RTSP 地址在 XML 中配置后读取失败?
如果 URL 中包含&,需要写成&。这是 XML 语法要求,不是 SDK 限制。
2. 为什么程序启动后某些路按钮不可用?
Demo 会根据configure.xml中实际<Relay>数量计算配置路数,未配置的路不会创建 relay 实例,对应 UI 控件也会禁用。
3. 为什么建议转发时设置 Buffer 为 0?
转发场景追求尽可能低的链路延迟,不需要播放器为了平滑播放额外缓存数据。Demo 在拉流前调用SetBuffer(0),目的就是减少不必要的内部缓冲。
4. 为什么有的视频可以转推,有的音频没有声音?
需要检查AudioOption。如果希望转发摄像头自带音频,建议使用AudioOption=4。如果摄像头没有音频或业务不需要音频,可设置为 0。
5. 为什么不建议所有路都打开预览?
预览意味着解码和渲染,会增加 CPU/GPU 消耗。多路转发时,建议默认不预览,只在调试时打开指定一路。
6. 推流失败如何定位?
先看拉流状态是否连接成功,再看下载速度是否持续更新。如果拉流正常但推流失败,重点检查 RTMP 地址、服务器状态、网络出口、防火墙和鉴权配置。
总结
大牛直播SDK(SmartMediaKit)Windows 平台多路 RTSP 转 RTMP 推流方案,适合需要在边缘侧、工控机、Windows 客户端或后台服务中批量接入 RTSP 摄像头,并统一转发到 RTMP 平台的项目。
从工程结构看,Demo 采用了比较清晰的分层方式:SmartStreamRelayDemo负责业务控制和多路管理,nt_relay_wrapper负责单路协调,nt_player_wrapper负责拉流和数据回调,nt_publisher_wrapper负责 RTMP 推送和编码数据输入。这样的设计既便于理解,也便于后续扩展为更稳定的后台服务或边缘网关程序。
从实际集成角度看,开发者重点关注几件事即可:配置好每一路 PullUrl 和 PushUrl,正确设置 AudioOption,确保 SDK 初始化和释放顺序正确,默认关闭多路预览以降低资源占用,并通过事件回调和日志区分拉流侧问题与推流侧问题。
对于安防视频上云、园区视频汇聚、无人值守转发、应急指挥回传等场景,这种“多路 RTSP 输入 + RTMP 标准输出”的方式,能够在不改造前端摄像头和后端平台的前提下,快速完成协议适配和实时视频转发。
📎 CSDN官方博客:音视频牛哥-CSDN博客