大牛直播SDK(SmartMediaKit)Windows平台多路RTSP转RTMP推流集成说明 文档概述在安防监控、智慧园区、应急指挥、工业视觉、低空经济、无人机回传和多路摄像头上云等场景中现场设备通常以 RTSP 方式输出视频流而云端平台、直播分发平台或业务中台往往更倾向于接收 RTMP 流。此时系统需要在边缘侧或 Windows 工控机侧部署一个稳定、低延迟、可长期运行的流媒体转发模块将多路 RTSP 摄像头流实时转推到 RTMP Server、CDN 或业务直播平台。大牛直播SDKSmartMediaKitWindows 平台多路 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 FormsDemo 以 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 version1.0 encodingutf-8 ? StreamRelays Relay id0/id AudioOption4/AudioOption PullUrlrtsp://admin:password192.168.0.120:554/h264/ch1/main/av_stream/PullUrl PushUrlrtmp://192.168.0.103:1935/live/stream00/PushUrl /Relay Relay id1/id AudioOption0/AudioOption PullUrlrtsp://admin:password192.168.0.121:554/cam/realmonitor?channel1amp;subtype0/PullUrl PushUrlrtmp://192.168.0.103:1935/live/stream01/PushUrl /Relay /StreamRelays字段说明字段说明id路序号便于标识AudioOption音频模式PullUrlRTSP/RTMP 拉流地址PushUrlRTMP 推流地址需要注意的是XML 中如果 RTSP 地址包含必须写成amp;否则 XML 解析会失败。StreamRelayConfig.cs中对应的数据模型很简单只包含Id、AudioOption、PullUrl和PushUrl四个字段。音频模式说明Demo 中AudioOption用于控制每一路推流的音频来源AudioOption含义适用场景0无音频只需要视频监控画面1采集本机麦克风需要本地讲解、语音注入2采集本机扬声器需要转发本机播放声音3麦克风 扬声器混音需要本地混音4使用拉流端编码后音频数据摄像头原始音频随视频一起转发在 RTSP 摄像头转 RTMP 的典型场景中推荐优先使用AudioOption4。这样可以直接使用拉流端输出的编码音频数据。如果摄像头输出的是 PCMA、PCMU、Speex 等格式也可以通过拉流端音频转 AAC 能力转成 RTMP 更常用的 AAC 后推送。nt_player_wrapper中可以看到订阅音频时会设置音频数据回调并开启拉流音频转 AAC。如果现场没有音频需求建议配置为AudioOption0减少无意义的音频处理和带宽占用。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_wrappernt_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 SDKC# 工程必须设置为 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 中包含需要写成amp;。这是 XML 语法要求不是 SDK 限制。2. 为什么程序启动后某些路按钮不可用Demo 会根据configure.xml中实际Relay数量计算配置路数未配置的路不会创建 relay 实例对应 UI 控件也会禁用。3. 为什么建议转发时设置 Buffer 为 0转发场景追求尽可能低的链路延迟不需要播放器为了平滑播放额外缓存数据。Demo 在拉流前调用SetBuffer(0)目的就是减少不必要的内部缓冲。4. 为什么有的视频可以转推有的音频没有声音需要检查AudioOption。如果希望转发摄像头自带音频建议使用AudioOption4。如果摄像头没有音频或业务不需要音频可设置为 0。5. 为什么不建议所有路都打开预览预览意味着解码和渲染会增加 CPU/GPU 消耗。多路转发时建议默认不预览只在调试时打开指定一路。6. 推流失败如何定位先看拉流状态是否连接成功再看下载速度是否持续更新。如果拉流正常但推流失败重点检查 RTMP 地址、服务器状态、网络出口、防火墙和鉴权配置。总结大牛直播SDKSmartMediaKitWindows 平台多路 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博客