文档概述
本文介绍大牛直播SDK(SmartMediaKit)在 Windows 平台下 C++ 版 RTSP、RTMP 直播播放 SDK 的集成方法,适用于 MFC、Win32、C++ 桌面客户端等应用场景。内容涵盖 SDK 文件结构、Visual Studio 工程配置、SDK 初始化与销毁、RTSP/RTMP 播放流程、D3D/GDI 双渲染架构、事件回调、下载速度与丢包率监控、播放端 MP4 录像、截图、OSD 台标、RTSP 高级配置、多路播放和常见问题排查。
本文以 Windows C++ Demo 为基础进行说明。Demo 核心由 CSmartPlayerDlg、nt_wrapper_render_wnd、nt_render_wnd、nt_player_rtsp_config_dlg、nt_player_record_config_dlg 等模块组成。其中,CSmartPlayerDlg 负责主业务流程、SDK 初始化、播放控制、录像控制和事件分发;nt_wrapper_render_wnd 负责外层渲染窗口、全屏切换和窗口大小通知;nt_render_wnd 负责实际 D3D/GDI 渲染承载和 RGB32 图像绘制。上传文档也明确将该方案定位为面向 C++ / MFC / Win32 的 Windows Player SDK 集成说明,并以 WIN-PlayerSDK-CPP-Demo 为基础说明工程配置、API 调用流程、事件回调、渲染架构、录制、OSD 和网络质量监控等要点。
产品概述
大牛直播SDK(SmartMediaKit)Windows C++ 播放器 SDK 是面向专业级实时音视频系统设计的低延迟直播播放组件,适用于 RTSP 摄像头/NVR 预览、RTMP 直播拉流、工业视觉、安防监控、远程教育、应急指挥、无人机回传、设备巡检等需要稳定、超低延迟(100~200ms)播放能力的 Windows 客户端系统。

SDK 通过 SmartPlayerSDK.dll、SmartPlayerSDK.lib 以及 smart_player_sdk.h、smart_player_define.h 对外提供标准 C/C++ 接口。业务层通过 GetSmartPlayerSDKAPI() 获取 SmartPlayerSDKAPI 函数指针集合,后续通过该结构体调用 Init、Open、SetURL、StartPlay、StopPlay、StartRecorder、StopRecorder、CaptureImage 等接口完成播放器接入。上传文档中也将 SmartPlayerSDKAPI 定义为 C++ 封装结构体,通过 GetSmartPlayerSDKAPI() 获取函数指针集合后统一调用。
Windows平台毫秒级延迟RTSP播放器延迟测试
适用场景
| 场景 | 说明 |
|---|---|
| RTSP 实时预览 | 对接 IPC、NVR、DVR、编码器、无人机、执法记录仪等 RTSP 源 |
| RTMP 直播播放 | 对接 RTMP Server、直播平台、CDN 或自建流媒体服务 |
| 多路监控预览 | Windows 客户端多画面显示、轮巡、监控大屏 |
| 低延迟播放 | 用于远程控制、工业视觉、应急指挥等强实时场景 |
| 播放端 MP4 录像 | 拉流播放同时进行本地 MP4 录像 |
| 实时截图 | 播放过程中截取当前画面并保存为图片 |
| 网络质量监测 | 展示下载速度、丢包率、缓冲状态,辅助现场排查 |
| 视频帧回调 | 获取 RGB32/I420 视频帧,用于 GDI 绘制、算法分析或 AI 识别 |
| 音频 PCM 回调 | 获取 PCM 数据,用于音频分析、录音或二次处理 |
| 自定义数据接收 | 接收推流端携带的 UserData 或 SEI 数据 |
核心能力

| 能力项 | 说明 |
|---|---|
| 协议支持 | 支持 RTSP、RTMP 等直播流播放 |
| RTSP 传输模式 | 支持 TCP、UDP 以及 TCP/UDP 自动切换 |
| 视频解码 | 支持 H.264、H.265,支持软解和硬解能力检测 |
| 音频解码 | 支持 AAC、PCMA、PCMU 等常见音频格式 |
| D3D 渲染 | 系统支持 D3D 时优先使用 D3D 渲染,降低 CPU 占用 |
| GDI 渲染 | 不支持 D3D 或需要 RGB32 帧处理时,可通过 GDI 绘制 |
| 低延迟播放 | 支持低延迟模式、缓冲时间控制、秒开设置 |
| 播放控制 | 支持开始、停止、暂停、恢复、Seek、快速切换 URL |
| 图像控制 | 支持等比例缩放、旋转、水平翻转、垂直翻转 |
| 音频控制 | 支持静音、音量调节、是否输出到音频设备 |
| 截图 | 支持播放过程中截图,异步回调截图结果 |
| 播放端录像 | 支持音视频选择、录像目录、文件名前缀、文件大小、AAC 转码等配置 |
| OSD 台标 | D3D 模式支持 ARGB 台标叠加,可用于时间戳、业务标识等 |
| 状态回调 | 支持连接状态、缓冲状态、RTSP 状态码、下载速度、EOS 等事件 |
| 网络质量指标 | 支持通过下载速度事件获取 param1 下载速度和 param2 网络质量指标 |
| 多实例 | 可基于多个 player handle 扩展多路播放 |
开发环境
建议使用如下环境进行 Windows C++ Demo 集成:
| 项目 | 建议配置 |
|---|---|
| 操作系统 | Windows 7 及以上 |
| 开发工具 | Visual Studio 2013+ |
| 开发语言 | C++ |
| UI 框架 | MFC / Win32 均可,Demo 以 MFC Dialog 为例 |
| 平台架构 | x86 或 x64,与 SDK 库目录保持一致 |
| 渲染能力 | 推荐支持 D3D;不支持时可回退 GDI |
| 运行依赖 | SmartPlayerSDK.dll、SmartLog.dll 及相关依赖库 |
注意:工程平台必须与 SDK 库架构一致。使用 x64 SDK 时,C++ 工程也应设置为 x64;使用 x86 SDK 时,工程应设置为 Win32。上传文档也强调 SDK 同时提供 x86 和 x64 版本,编译目标平台需与 lib 目录保持一致。
SDK 文件结构
| 文件 | 说明 |
|---|---|
SmartPlayerSDK.dll |
播放器核心动态库 |
SmartPlayerSDK.lib |
C++ 工程链接用导入库 |
SmartLog.dll |
日志模块动态库 |
smart_player_sdk.h |
C/C++ SDK 接口声明 |
smart_player_define.h |
事件、结构体、错误码、回调类型定义 |
SmartPlayerDlg.cpp/.h |
MFC Demo 主对话框,负责播放、录像、截图、事件处理 |
nt_wrapper_render_wnd.cpp/.h |
外层渲染窗口,负责全屏、窗口大小通知、消息转发 |
nt_render_wnd.cpp/.h |
内层真实渲染窗口,负责 GDI/D3D 绘制承载 |
nt_player_rtsp_config_dlg.cpp/.h |
RTSP 参数配置对话框 |
nt_player_record_config_dlg.cpp/.h |
录像参数配置对话框 |
nt_sdk_player_dm_utilities.cpp/.h |
辅助工具,如 RGBX 缓冲区管理、字节对齐等 |
Visual Studio 工程配置
头文件路径
项目属性 → C/C++ → 常规 → 附加包含目录,添加:
$(ProjectDir)..\SmartPlayerSDK\include
链接器配置
项目属性 → 链接器 → 常规 → 附加库目录,添加:
$(ProjectDir)..\SmartPlayerSDK\lib\x64
如果是 32 位工程,则改为:
$(ProjectDir)..\SmartPlayerSDK\lib\x86
项目属性 → 链接器 → 输入 → 附加依赖项,添加:
SmartPlayerSDK.lib
DLL 部署
将以下文件复制到程序输出目录:
SmartPlayerSDK.dll
SmartLog.dll
其他依赖库
建议将 DLL 与 EXE 放在同一目录,避免运行时加载失败。
Unicode 与 GDI+
建议工程使用 Unicode 字符集。若使用 OSD 台标或 GDI+ 文字渲染,需要在预编译头中引入:
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
#include "smart_player_sdk.h"
上传文档也建议工程使用 Unicode 字符集,并在 stdafx.h 中引入 Windows、GDI+ 和 SDK 头文件。
推荐工程分层

建议 C++ 工程按以下方式拆分:
业务层 CSmartPlayerDlg
↓
SmartPlayerSDKAPI 接口表
- Init / UnInit
- Open / Close
- SetURL
- StartPlay / StopPlay
- StartRecorder / StopRecorder
- CaptureImage
- SetEventCallBack
- SetVideoSizeCallBack
- SetVideoFrameCallBack
- SetRecorderCallBack
↓
nt_wrapper_render_wnd
- 外层渲染窗口
- 全屏切换
- 窗口大小变化
- 键盘 / 鼠标事件
- OnWindowSize 通知 SDK
↓
nt_render_wnd
- 实际渲染窗口
- D3D / GDI 绘制承载
- RGB32 帧绘制
- 清屏与重绘管理
CSmartPlayerDlg 是业务主控层,负责按钮事件、播放状态、录像状态、RTSP 配置、播放地址、事件分发和 UI 展示。nt_wrapper_render_wnd 是渲染窗口封装层,负责外层窗口、全屏切换、窗口尺寸变化和消息转发。nt_render_wnd 是实际渲染窗口,负责 D3D/GDI 绘制承载、RGB32 图像绘制、ARGB Logo 叠加和清屏重绘。
SDK 初始化与销毁

获取 SDK API 结构体
SDK 以 C 接口导出,通过 GetSmartPlayerSDKAPI() 将所有函数指针填充到 SmartPlayerSDKAPI 结构体中,之后所有调用均通过该结构体完成。
cpp
#include "smart_player_sdk.h"
class CSmartPlayerDlg : public CDialogEx
{
private:
SmartPlayerSDKAPI player_api_; // SDK 函数指针集合
NT_HANDLE player_handle_; // 播放器句柄
};
初始化流程
建议在 MFC 对话框的 OnInitDialog() 中完成 SDK 初始化、播放器句柄创建、事件回调注册和渲染窗口初始化。
cpp
BOOL CSmartPlayerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 获取 SDK API
memset(&player_api_, 0, sizeof(player_api_));
GetSmartPlayerSDKAPI(&player_api_);
// SDK 全局初始化
if (NT_ERC_OK != player_api_.Init(0, NULL))
{
AfxMessageBox(_T("SmartPlayerSDK 初始化失败"));
return FALSE;
}
// 创建播放器句柄
if (NT_ERC_OK != player_api_.Open(&player_handle_, NULL, 0, NULL))
{
AfxMessageBox(_T("创建播放器实例失败"));
return FALSE;
}
// 注册事件回调
player_api_.SetEventCallBack(
player_handle_,
GetSafeHwnd(),
&NT_SP_SDKEventHandle);
// 初始化渲染窗口
wrapper_render_wnd_.SetPlayerSDKAPI(&player_api_);
return TRUE;
}
上传文档中的初始化流程同样包括:GDI+ 启动、加载 SDK、调用 Init()、调用 Open() 创建句柄、注册事件回调、初始化渲染窗口和启动定时器。
销毁流程
在窗口销毁时,应按照"先停止业务,再释放资源"的顺序清理:
cpp
void CSmartPlayerDlg::OnDestroy()
{
if (is_recording_)
StopRecorder();
if (is_playing_)
StopPlayback();
if (player_handle_ != NULL)
{
player_api_.Close(player_handle_);
player_handle_ = NULL;
}
player_api_.UnInit();
CDialogEx::OnDestroy();
}
注意:
| 项目 | 说明 |
|---|---|
Init / UnInit |
进程级操作,建议整个进程生命周期只调用一次 |
Open / Close |
实例级操作,每个播放器实例对应一个 handle |
StartPlay / StopPlay |
播放级操作,启动和停止播放链路 |
StartRecorder / StopRecorder |
录像级操作,启动和停止 MP4 录像 |
| 回调线程 | SDK 回调运行在内部线程,不应直接操作 UI |
| UI 更新 | 建议通过 PostMessage() 派发到 UI 线程 |
公共播放参数配置
播放前建议统一调用公共参数配置函数,例如 Demo 中的 InitCommonSDKParam()。
cpp
bool CSmartPlayerDlg::InitCommonSDKParam()
{
if (player_handle_ == NULL)
return false;
// 设置播放缓冲时间
player_api_.SetBuffer(player_handle_, buffer_ms_);
// RTSP 超时
player_api_.SetRtspTimeout(player_handle_, rtsp_conf_info_.timeout_);
// RTSP TCP 模式
player_api_.SetRTSPTcpMode(
player_handle_,
rtsp_conf_info_.is_tcp_ ? 1 : 0);
// RTSP TCP/UDP 自动切换
player_api_.SetRtspAutoSwitchTcpUdp(
player_handle_,
rtsp_conf_info_.is_tcp_udp_auto_switch_ ? 1 : 0);
// 秒开
player_api_.SetFastStartup(
player_handle_,
is_fast_startup_ ? 1 : 0);
// 下载速度与网络质量上报
player_api_.SetReportDownloadSpeed(player_handle_, 1, 1);
// 设置播放地址
if (NT_ERC_OK != player_api_.SetURL(player_handle_, GetURL().c_str()))
return false;
// 重置状态
connection_status_ = 0;
buffer_status_ = 0;
buffer_percent_ = 0;
download_speed_ = -1;
net_quality_metrics_ = 0;
return true;
}
关键参数说明:
| 接口 | 说明 |
|---|---|
SetURL |
设置 RTSP / RTMP 播放地址 |
SetBuffer |
设置播放缓冲时间,超低延迟场景可设为 0 |
SetRtspTimeout |
设置 RTSP 连接超时时间 |
SetRTSPTcpMode |
设置 RTSP TCP/UDP 模式 |
SetRtspAutoSwitchTcpUdp |
设置 RTSP TCP/UDP 自动切换 |
SetFastStartup |
设置快速启动 |
SetReportDownloadSpeed |
开启下载速度上报 |
SetMute |
设置静音 |
SetAudioVolume |
设置音量 |
SetRenderScaleMode |
设置等比例缩放或拉伸显示 |
SetOnlyDecodeVideoKeyFrame |
设置是否只解码关键帧 |
上传文档的参数速查表中也列出了 SetBuffer、SetURL、SetRtspTimeout、SetRTSPTcpMode、SetRtspAutoSwitchTcpUdp、SetFastStartup、SetHWDecoder、SetD3DRender、SetMute、SetReportDownloadSpeed、StartPlay 和 StopPlay 等关键播放接口。
RTSP / RTMP 播放流程

开始播放
播放入口通常位于 CSmartPlayerDlg::OnBnClickedButtonPlay()。推荐流程如下:
cpp
void CSmartPlayerDlg::OnBnClickedButtonPlay()
{
if (player_handle_ == NULL)
return;
if (is_playing_)
{
StopPlayback();
return;
}
if (!InitCommonSDKParam())
{
AfxMessageBox(_T("设置播放参数失败"));
return;
}
// 设置视频尺寸回调
player_api_.SetVideoSizeCallBack(
player_handle_,
GetSafeHwnd(),
SP_SDKVideoSizeHandle);
// 检测 D3D 渲染能力
bool is_support_d3d_render = false;
NT_INT32 support_d3d = 0;
if (NT_ERC_OK == player_api_.IsSupportD3DRender(
player_handle_,
wrapper_render_wnd_.RenderWnd(),
&support_d3d))
{
is_support_d3d_render = (support_d3d == 1);
}
if (is_support_d3d_render)
{
is_gdi_render_ = false;
player_api_.SetRenderWindow(
player_handle_,
wrapper_render_wnd_.RenderWnd());
player_api_.SetRenderScaleMode(
player_handle_,
btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);
}
else
{
is_gdi_render_ = true;
wrapper_render_wnd_.SetRenderScaleMode(
btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);
player_api_.SetVideoFrameCallBack(
player_handle_,
NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,
GetSafeHwnd(),
SM_SDKVideoFrameHandle);
}
// 启动播放
if (NT_ERC_OK != player_api_.StartPlay(player_handle_))
{
AfxMessageBox(_T("播放失败"));
return;
}
is_playing_ = true;
btn_play_.SetWindowTextW(_T("停止"));
}
C++ Demo 中也是先初始化公共参数,再注册视频尺寸回调,然后检测是否支持 D3D 渲染;支持时使用 SetRenderWindow() 走 D3D 渲染,不支持时注册 RGB32 视频帧回调走 GDI 绘制。
停止播放
cpp
void CSmartPlayerDlg::StopPlayback()
{
if (player_handle_ == NULL || !is_playing_)
return;
player_api_.StopPlay(player_handle_);
is_playing_ = false;
is_gdi_render_ = false;
wrapper_render_wnd_.ClearVideoSize();
wrapper_render_wnd_.CleanRender();
connection_status_ = 0;
buffer_status_ = 0;
buffer_percent_ = 0;
download_speed_ = -1;
net_quality_metrics_ = 0;
btn_play_.SetWindowTextW(_T("播放"));
btn_full_screen_.EnableWindow(FALSE);
}
停止播放时,建议同步清理渲染状态、视频尺寸、缓冲状态、下载速度和丢包率等状态,避免 UI 残留。
快速切换 URL
在监控轮巡、主子码流切换、备用流切换等场景下,可以使用快速切换 URL:
cpp
if (NT_ERC_OK == player_api_.SwitchURL(
player_handle_,
GetURL().c_str(),
0,
0))
{
OutputDebugStringA("Switch url ok\r\n");
}
else
{
OutputDebugStringA("Switch url failed\r\n");
}
D3D / GDI 双渲染架构

双层渲染窗口
SDK Demo 采用双层渲染窗口设计:
| 层级 | 类 | 职责 |
|---|---|---|
| 外层窗口 | nt_wrapper_render_wnd |
负责窗口创建、全屏切换、窗口大小通知、消息转发 |
| 内层窗口 | nt_render_wnd |
负责实际渲染承载、GDI 绘制、RGB32 图像显示 |
上传文档也将该结构概括为:外层 nt_wrapper_render_wnd 负责布局与 GDI 软渲染分发,内层 nt_render_wnd 承载实际的 D3D Surface 或 GDI DC。
D3D 渲染路径
支持 D3D 时,推荐使用 D3D 渲染:
cpp
player_api_.SetRenderWindow(
player_handle_,
wrapper_render_wnd_.RenderWnd());
player_api_.SetRenderScaleMode(player_handle_, 1);
D3D 渲染优势:
| 优势 | 说明 |
|---|---|
| CPU 占用低 | 由 GPU 承担主要渲染工作 |
| 适合多路播放 | 多画面预览时更具性能优势 |
| 支持 OSD 台标 | 可通过 ARGB Logo 叠加台标或时间戳 |
| 渲染链路更短 | 业务层无需逐帧处理 RGB 数据 |
GDI 渲染路径
如果系统不支持 D3D,或业务需要 RGB32 数据自行处理,可使用 GDI 路径:
cpp
player_api_.SetVideoFrameCallBack(
player_handle_,
NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,
GetSafeHwnd(),
SM_SDKVideoFrameHandle);
视频帧回调中复制 RGB32 数据,然后通过 PostMessage() 发送到 UI 线程:
cpp
extern "C" NT_VOID NT_CALLBACK SM_SDKVideoFrameHandle(
NT_HANDLE handle,
NT_PVOID userData,
NT_UINT32 status,
const NT_SP_VideoFrame* frame)
{
if (!frame)
return;
if (NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 == frame->format_)
{
std::unique_ptr<nt_rgb32_image> pImage(new nt_rgb32_image());
pImage->size_ = frame->stride0_ * frame->height_;
pImage->data_ = new NT_BYTE[pImage->size_];
memcpy(pImage->data_, frame->plane0_, pImage->size_);
pImage->width_ = frame->width_;
pImage->height_ = frame->height_;
pImage->stride_ = frame->stride0_;
HWND hwnd = (HWND)userData;
if (hwnd != NULL && ::IsWindow(hwnd))
{
::PostMessage(
hwnd,
WM_USER_SDK_RGB32_IMAGE,
(WPARAM)handle,
(LPARAM)pImage.release());
}
}
}
截图接口和 GDI 绘制接口在头文件中也有明确说明,例如 CaptureImage() 用于播放状态下截图,并通过回调通知完成状态;GDI 绘制接口要求 RGB32/RGBX 数据步长与 Windows 位图兼容。
事件回调机制

注册事件回调
cpp
player_api_.SetEventCallBack(
player_handle_,
GetSafeHwnd(),
&NT_SP_SDKEventHandle);
回调函数
SDK 回调运行在内部工作线程,不建议直接操作 UI。推荐在回调中只做轻量处理,然后通过 PostMessage() 投递到 UI 线程。
cpp
extern "C" NT_VOID NT_CALLBACK NT_SP_SDKEventHandle(
NT_HANDLE handle,
NT_PVOID user_data,
NT_UINT32 event_id,
NT_INT64 param1,
NT_INT64 param2,
NT_UINT64 param3,
NT_PCSTR param4,
NT_PCSTR param5,
NT_PVOID param6)
{
if (user_data == NULL)
return;
if (NT_SP_E_EVENT_ID_DOWNLOAD_SPEED == event_id)
{
g_nt_player_sdk_net_metrics.set_speed_and_metrics(
handle,
param1,
param2);
}
HWND hwnd = (HWND)user_data;
if (::IsWindow(hwnd))
{
::PostMessage(
hwnd,
WM_USER_SDK_SP_EVENT,
event_id,
param1);
}
}
UI 线程事件处理
cpp
LRESULT CSmartPlayerDlg::OnSDKEvent(WPARAM wParam, LPARAM lParam)
{
NT_UINT32 event_id = (NT_UINT32)wParam;
if (event_id == NT_SP_E_EVENT_ID_CONNECTING ||
event_id == NT_SP_E_EVENT_ID_CONNECTED ||
event_id == NT_SP_E_EVENT_ID_CONNECTION_FAILED ||
event_id == NT_SP_E_EVENT_ID_DISCONNECTED ||
event_id == NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED)
{
connection_status_ = event_id;
}
if (event_id == NT_SP_E_EVENT_ID_START_BUFFERING ||
event_id == NT_SP_E_EVENT_ID_BUFFERING ||
event_id == NT_SP_E_EVENT_ID_STOP_BUFFERING)
{
buffer_status_ = event_id;
if (event_id == NT_SP_E_EVENT_ID_BUFFERING)
buffer_percent_ = (NT_INT32)lParam;
}
if (event_id == NT_SP_E_EVENT_ID_DOWNLOAD_SPEED)
{
int64_t speed = 0;
int64_t metrics = 0;
if (g_nt_player_sdk_net_metrics.test_fetch_speed_and_metrics(
player_handle_,
speed,
metrics))
{
download_speed_ = speed;
net_quality_metrics_ = metrics;
}
}
UpdateTitleBar();
return S_OK;
}
上传文档也明确说明 SDK 回调运行在内部工作线程,必须使用 PostMessage() 将事件派发到 UI 线程处理,避免在回调中直接操作 UI 控件。
常用事件
| 事件 ID | 说明 |
|---|---|
NT_SP_E_EVENT_ID_CONNECTING |
正在连接 |
NT_SP_E_EVENT_ID_CONNECTED |
连接成功 |
NT_SP_E_EVENT_ID_CONNECTION_FAILED |
连接失败 |
NT_SP_E_EVENT_ID_DISCONNECTED |
连接断开 |
NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED |
超时未收到媒体数据 |
NT_SP_E_EVENT_ID_START_BUFFERING |
开始缓冲 |
NT_SP_E_EVENT_ID_BUFFERING |
缓冲中,param1 表示百分比 |
NT_SP_E_EVENT_ID_STOP_BUFFERING |
缓冲结束 |
NT_SP_E_EVENT_ID_DOWNLOAD_SPEED |
下载速度上报,param1 表示 Byte/s |
NT_SP_E_EVENT_ID_RTSP_STATUS_CODE |
RTSP 状态码,例如 401 |
NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS |
点播播放结束 |
NT_SP_E_EVENT_ID_RECORDER_REACH_EOS |
点播录像结束 |
NT_SP_E_EVENT_ID_DURATION |
点播时长,单位毫秒 |
下载速度与丢包率监控
数据中转
NT_SP_E_EVENT_ID_DOWNLOAD_SPEED 事件在 SDK 内部线程触发,param1 表示下载速度,单位 Byte/s;param2 表示网络质量指标。推荐使用线程安全容器将回调线程的数据转交给 UI 线程处理。上传文档中也说明 param1 是下载速度,param2 是网络质量指标,并使用互斥锁容器在回调线程和 UI 线程之间传递数据。
cpp
class nt_player_sdk_net_quality_metrics
{
public:
void set_speed_and_metrics(
NT_HANDLE handle,
int64_t speed,
int64_t metrics)
{
const std::lock_guard<std::mutex> lock(mutex_);
handle_ = handle;
speed_ = speed;
metrics_ = metrics;
}
bool test_fetch_speed_and_metrics(
NT_HANDLE handle,
int64_t& speed,
int64_t& metrics)
{
if (!handle)
return false;
const std::lock_guard<std::mutex> lock(mutex_);
if (handle != handle_)
return false;
speed = speed_;
metrics = metrics_;
reset();
return true;
}
private:
void reset()
{
handle_ = nullptr;
speed_ = 0;
metrics_ = 0;
}
private:
std::mutex mutex_;
NT_HANDLE handle_{ nullptr };
int64_t speed_{ 0 };
int64_t metrics_{ 0 };
};
丢包率解析规则
cpp
param1:下载速度,单位 Byte/s
param2:网络质量指标
当 param2 的 bit15 置位时,表示丢包率有效;
低 15 位为丢包率原始值;
丢包率百分比 = (param2 & 0x7FFF) / 256.0
示例:
cpp
if (net_quality_metrics_ != 0)
{
if ((net_quality_metrics_ >> 15) & 1LL)
{
int64_t raw = net_quality_metrics_ & 0x7FFFLL;
float loss_rate = static_cast<float>(raw) / 256.0f;
wchar_t buf[64] = { 0 };
_snwprintf_s(buf, 64, L" 丢包率: %.2f%%", loss_rate);
show_str += buf;
}
}
下载速度展示
cpp
if (download_speed_ >= 0)
{
wchar_t buf[128] = { 0 };
_snwprintf_s(
buf,
128,
L" 速度: %lldkbps (%lldKB/s)",
download_speed_ * 8 / 1000,
download_speed_ / 1024);
show_str += buf;
}
使用建议
| 状态 | 说明 |
|---|---|
| 下载速度稳定,丢包率低 | 网络状态较好 |
| 下载速度低,频繁缓冲 | 可能是带宽不足、服务器转发异常或上游码率不足 |
| 下载速度正常,丢包率高 | 可能是 UDP 丢包、无线抖动、链路瞬时拥塞 |
| 丢包率高且缓冲频繁 | 建议优先切换 RTSP TCP 或开启 TCP/UDP 自动切换 |
| 丢包率不显示 | 当前回调未携带有效网络质量指标,可结合下载速度和缓冲事件判断 |
RTSP 高级配置

C++ Demo 使用 nt_player_rtsp_config_dlg 管理 RTSP 参数。
cpp
struct nt_player_rtsp_conf_info
{
int timeout_;
bool is_tcp_;
bool is_tcp_udp_auto_switch_;
};
默认配置:
cpp
timeout_ = 12;
is_tcp_ = false;
is_tcp_udp_auto_switch_ = true;
参数说明:
| 参数 | 说明 |
|---|---|
timeout_ |
RTSP 连接超时时间,单位秒 |
is_tcp_ |
是否强制 RTSP 使用 TCP |
is_tcp_udp_auto_switch_ |
是否开启 TCP/UDP 自动切换 |
推荐配置:
| 场景 | 推荐策略 |
|---|---|
| 局域网摄像头,网络稳定 | 可使用 UDP 或自动切换 |
| 公网、无线、弱网环境 | 优先使用 TCP |
| 设备响应慢 | 适当增加 RTSP timeout |
| UDP 丢包明显 | 切换 TCP 模式 |
| 现场网络复杂 | 开启 TCP/UDP 自动切换 |
RTSP 401 鉴权处理
当 SDK 上报 NT_SP_E_EVENT_ID_RTSP_STATUS_CODE 且状态码为 401 时,说明 RTSP 服务端需要鉴权。业务层可弹出用户名密码输入框,设置鉴权信息后重新播放。
cpp
else if (NT_SP_E_EVENT_ID_RTSP_STATUS_CODE == event_id)
{
int status_code = (int)lParam;
if (401 == status_code)
{
HandleVerification();
}
return S_OK;
}
推荐处理流程:
cpp
收到 RTSP 401
↓
弹出用户名 / 密码输入框
↓
设置 RTSP 鉴权信息
↓
重新设置 URL 和播放参数
↓
恢复播放或录像状态
播放端 MP4 录像

播放端录像适用于监控取证、课程录制、巡检留档、远程指挥留存等场景。C++ Demo 使用 nt_player_record_config_dlg 管理录像配置。
cpp
struct nt_player_rec_conf_info
{
bool is_record_video_;
bool is_record_audio_;
std::wstring dir_;
std::string file_name_prefix_;
int file_max_size_;
bool is_append_date_;
bool is_append_time_;
bool is_audio_transcode_aac_;
};
录像配置说明:
| 参数 | 说明 |
|---|---|
is_record_video_ |
是否录制视频 |
is_record_audio_ |
是否录制音频 |
dir_ |
录像保存目录 |
file_name_prefix_ |
录像文件名前缀 |
file_max_size_ |
单个录像文件最大大小 |
is_append_date_ |
文件名是否追加日期 |
is_append_time_ |
文件名是否追加时间 |
is_audio_transcode_aac_ |
是否将音频转 AAC |
启动录像示例:
cpp
player_api_.SetRecorderVideo(
player_handle_,
rec_conf_info_.is_record_video_ ? 1 : 0);
player_api_.SetRecorderAudio(
player_handle_,
rec_conf_info_.is_record_audio_ ? 1 : 0);
player_api_.SetRecorderDirectoryW(
player_handle_,
rec_conf_info_.dir_.c_str());
player_api_.SetRecorderFileMaxSize(
player_handle_,
rec_conf_info_.file_max_size_);
NT_SP_RecorderFileNameRuler ruler = { 0 };
ruler.type_ = 0;
ruler.file_name_prefix_ = rec_conf_info_.file_name_prefix_.c_str();
ruler.append_date_ = rec_conf_info_.is_append_date_ ? 1 : 0;
ruler.append_time_ = rec_conf_info_.is_append_time_ ? 1 : 0;
player_api_.SetRecorderFileNameRuler(player_handle_, &ruler);
player_api_.SetRecorderCallBack(
player_handle_,
GetSafeHwnd(),
SP_SDKRecorderHandle);
player_api_.SetRecorderAudioTranscodeAAC(
player_handle_,
rec_conf_info_.is_audio_transcode_aac_ ? 1 : 0);
if (NT_ERC_OK != player_api_.StartRecorder(player_handle_))
{
AfxMessageBox(_T("录像失败"));
return;
}
is_recording_ = true;
停止录像:
cpp
void CSmartPlayerDlg::StopRecorder()
{
if (!is_recording_)
return;
player_api_.StopRecorder(player_handle_);
is_recording_ = false;
btn_record_.SetWindowTextW(_T("录像"));
}
注意:
| 项目 | 建议 |
|---|---|
| 录像目录 | 必须存在,建议使用宽字符接口支持中文路径 |
| 文件大小 | 超过限制后自动切片 |
| 音频格式 | 建议开启 AAC 转码,提升兼容性 |
| 退出程序 | 退出前必须先调用 StopRecorder() |
| 播放与录像 | 可以边播边录,也可以仅录像 |
| 录像格式 | 对外文档建议统一表述为本地 MP4 文件 |
截图功能
截图应在播放状态下调用。SDK 生成图片可能需要一定时间,因此不建议高频连续截图。
cpp
void CSmartPlayerDlg::OnBnClickedButtonCaptureImage()
{
if (!is_playing_ || player_handle_ == NULL)
return;
std::string file_name_utf8 = MakeCaptureImageFileName();
NT_UINT32 ret = player_api_.CaptureImage(
player_handle_,
file_name_utf8.c_str(),
GetSafeHwnd(),
SM_SDKCaptureImageHandle);
if (NT_ERC_OK != ret)
{
AfxMessageBox(_T("截图失败"));
}
}
截图回调:
cpp
extern "C" NT_VOID NT_CALLBACK SM_SDKCaptureImageHandle(
NT_HANDLE handle,
NT_PVOID userData,
NT_UINT32 result,
NT_PCSTR file_name)
{
std::ostringstream ss;
ss << "CaptureImage result:" << result;
if (result == NT_ERC_OK)
ss << " ok, ";
else
ss << " failed, ";
if (file_name != NULL)
ss << " file_name:" << file_name;
OutputDebugStringA(ss.str().c_str());
}
注意事项:
| 项目 | 建议 |
|---|---|
| 调用状态 | 仅在播放状态下截图 |
| 文件格式 | 建议保存 PNG |
| 保存路径 | 路径需存在且可写 |
| 请求频率 | 不建议高频连续截图 |
| 异步处理 | 以截图完成回调为准 |
OSD 台标叠加
SDK 支持在 D3D 渲染模式下通过 ARGB Logo 将文字或图片叠加到视频画面上,适合显示时间戳、设备名称、业务标识等信息。
注意:OSD 台标仅建议在 D3D 渲染模式下使用。GDI 模式下如需叠加文字,应在视频帧回调中自行绘制。
GDI+ 生成 ARGB 位图
cpp
void CSmartPlayerDlg::RefreshLogo()
{
if (is_gdi_render_ || player_handle_ == NULL)
return;
std::wstring text = MakeCurTimerStr();
Gdiplus::Bitmap tmpBmp(1, 1, PixelFormat32bppARGB);
Gdiplus::Graphics tmpG(&tmpBmp);
Gdiplus::Font font(L"Arial", 14.0f);
...
player_api_.SetRenderARGBLogo(
player_handle_,
(NT_PVOID)bmpData.Scan0,
stride,
w,
h,
6,
6,
w,
h);
bmp.UnlockBits(&bmpData);
}
重点注意:
| 项目 | 说明 |
|---|---|
| 生效模式 | D3D 渲染模式 |
| 图像格式 | ARGB |
| stride | GDI+ 可能返回负 stride,必须取绝对值 |
| 刷新方式 | 可使用定时器刷新时间戳 |
| 清除台标 | SetRenderARGBLogo(handle, NULL, 0, 0, 0, 0, 0, 0, 0) |
上传文档也特别提示:BitmapData.Stride 可能为负值,传入 SDK 前必须取绝对值。
音频与画面控制
静音
cpp
void CSmartPlayerDlg::OnBnClickedCheckMute()
{
if (player_handle_ == NULL)
return;
int mute = (btn_mute_.GetCheck() == BST_CHECKED) ? 1 : 0;
player_api_.SetMute(player_handle_, mute);
}
音量
cpp
void CSmartPlayerDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if (player_handle_ == NULL)
return;
int volume = slider_audio_volume_.GetPos();
player_api_.SetAudioVolume(player_handle_, volume);
}
旋转
cpp
rotate_degrees_ += 90;
rotate_degrees_ %= 360;
player_api_.SetRotation(player_handle_, rotate_degrees_);
水平 / 垂直翻转
cpp
player_api_.SetFlipHorizontal(
player_handle_,
btn_check_flip_horizontal_.GetCheck() == BST_CHECKED ? 1 : 0);
player_api_.SetFlipVertical(
player_handle_,
btn_check_flip_vertical_.GetCheck() == BST_CHECKED ? 1 : 0);
等比例缩放
cpp
player_api_.SetRenderScaleMode(
player_handle_,
btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);
只解码关键帧
cpp
player_api_.SetOnlyDecodeVideoKeyFrame(
player_handle_,
btn_check_only_decode_video_key_frame_.GetCheck() == BST_CHECKED ? 1 : 0);
适用场景:
| 能力 | 适用场景 |
|---|---|
| 静音 / 音量 | 监控预览、会议播放、无人值守播放 |
| 旋转 | 摄像头倒装、移动设备横竖屏回传 |
| 水平 / 垂直翻转 | 镜像纠正、设备安装方向修正 |
| 等比例缩放 | 保持真实画面比例,适合监控和工业视觉 |
| 只解码关键帧 | 多路小窗口预览,降低 CPU 占用 |
点播控制
对于本地文件、部分 HTTP 点播流或具备点播属性的流,SDK 可支持时长、播放位置、Seek、暂停/恢复等能力。
| 能力 | 说明 |
|---|---|
| 获取时长 | metadata 解析完成后可通过事件或接口获取 |
| 获取播放位置 | 定时器轮询当前播放位置 |
| Seek | 设置目标播放位置 |
| 暂停 / 恢复 | 暂停或恢复播放 |
| EOS | 点播结束后上报播放完成事件 |
直播流一般不支持时长、Seek 和播放位置控制,业务层应根据流类型区分处理。
多路播放建议

C++ 多路播放建议每一路流使用独立的 player handle 和独立渲染窗口:
cpp
Player 1
- NT_HANDLE handle_1
- nt_wrapper_render_wnd render_wnd_1
- 独立事件上下文
Player 2
- NT_HANDLE handle_2
- nt_wrapper_render_wnd render_wnd_2
- 独立事件上下文
Player N
- NT_HANDLE handle_n
- nt_wrapper_render_wnd render_wnd_n
- 独立事件上下文
建议:
| 场景 | 推荐策略 |
|---|---|
| 少量高清播放 | 优先 D3D 渲染 |
| 多路 1080P 预览 | 建议启用硬解并控制窗口数量 |
| 小窗口多画面 | 可考虑只解码关键帧 |
| 弱网 RTSP | 开启 TCP 或 TCP/UDP 自动切换 |
| 监控轮巡 | 使用 SwitchURL() 降低重建成本 |
| 网络排查 | 开启下载速度与丢包率展示 |
| AI 分析 | 按需开启视频帧回调,避免所有路同时回调造成 CPU 压力 |
最小可运行示例
以下示例展示从初始化到播放的基本调用流程。实际工程中,建议参考 Demo 中 CSmartPlayerDlg::OnInitDialog()、OnBnClickedButtonPlay()、StopPlayback()、OnDestroy() 的调用顺序。
cpp
#include <windows.h>
#include "smart_player_sdk.h"
static SmartPlayerSDKAPI g_api;
static NT_HANDLE g_handle = NULL;
extern "C" NT_VOID NT_CALLBACK EventCallback(
NT_HANDLE handle,
NT_PVOID user_data,
NT_UINT32 event_id,
NT_INT64 param1,
NT_INT64 param2,
NT_UINT64 param3,
NT_PCSTR param4,
NT_PCSTR param5,
NT_PVOID param6)
{
if (event_id == NT_SP_E_EVENT_ID_CONNECTED)
{
OutputDebugStringW(L"[SmartPlayerSDK] Connected\n");
}
else if (event_id == NT_SP_E_EVENT_ID_CONNECTION_FAILED)
{
OutputDebugStringW(L"[SmartPlayerSDK] Connection failed\n");
}
}
bool StartSmartPlayer(HWND hVideoWnd, const char* url)
{
memset(&g_api, 0, sizeof(g_api));
GetSmartPlayerSDKAPI(&g_api);
if (NT_ERC_OK != g_api.Init(0, NULL))
return false;
if (NT_ERC_OK != g_api.Open(&g_handle, NULL, 0, NULL))
return false;
g_api.SetEventCallBack(g_handle, hVideoWnd, EventCallback);
g_api.SetURL(g_handle, url);
g_api.SetBuffer(g_handle, 0);
g_api.SetFastStartup(g_handle, 1);
g_api.SetReportDownloadSpeed(g_handle, 1, 1);
g_api.SetRenderWindow(g_handle, hVideoWnd);
g_api.SetRenderScaleMode(g_handle, 1);
if (NT_ERC_OK != g_api.StartPlay(g_handle))
return false;
return true;
}
void StopSmartPlayer()
{
if (g_handle != NULL)
{
g_api.StopPlay(g_handle);
g_api.Close(g_handle);
g_handle = NULL;
}
g_api.UnInit();
}
常见问题排查
| 问题 | 可能原因 | 建议处理 |
|---|---|---|
GetSmartPlayerSDKAPI 失败 |
DLL 不存在或位数不匹配 | 确认 DLL 与 EXE 同目录,x86/x64 一致 |
| SDK 初始化失败 | 依赖库缺失或加载失败 | 检查运行目录和依赖库 |
Open 失败 |
SDK 未初始化或参数异常 | 确认 Init() 成功后再调用 Open() |
StartPlay 失败 |
URL 错误、网络不通、上游无流 | 检查播放地址和网络连通性 |
| 画面不显示 | 渲染窗口句柄无效 | 确认传入有效子窗口 HWND |
| D3D 不显示 | D3D 不支持或窗口未创建 | 检测 D3D 支持,必要时回退 GDI |
| GDI 不刷新 | RGB32 帧回调未注册 | 注册 SetVideoFrameCallBack() 并在 UI 线程刷新 |
| 有画面无声音 | 流无音频、静音或音量为 0 | 检查流音频轨、静音和音量 |
| 频繁缓冲 | 网络不稳定、带宽不足、上游码率异常 | 查看下载速度、丢包率和缓冲事件 |
| 丢包率高 | UDP 丢包、无线抖动、链路拥塞 | 尝试 RTSP TCP 或 TCP/UDP 自动切换 |
| RTSP 连接超时 | UDP 被防火墙拦截或设备响应慢 | 改用 TCP 或增加超时时间 |
| 录像文件损坏 | 未调用 StopRecorder() 直接退出 |
程序退出前必须先停止录像 |
| 截图失败 | 非播放状态、路径无权限、请求过频 | 确认播放状态、路径权限,降低截图频率 |
| OSD 不显示 | 当前为 GDI 渲染模式 | OSD 台标建议仅在 D3D 模式使用 |
| OSD 错位 | GDI+ stride 未取绝对值 | 传入 SDK 前对 stride 取 abs() |
| 多路播放 CPU 高 | 全部软解、GDI 回调过多、分辨率过高 | 启用 D3D/硬解,减少视频帧回调 |
| 回调中操作 UI 崩溃 | 跨线程操作 UI 控件 | 使用 PostMessage() 转到 UI 线程处理 |
| 多次 Init/UnInit 异常 | 进程级接口重复调用 | 整个进程生命周期只调用一次 Init/UnInit |
上传文档 FAQ 中也特别强调:回调中操作 UI 可能导致崩溃,应通过 PostMessage() 派发到 UI 线程;多次 Init/UnInit 可能异常,建议按进程级单例管理;D3D 画面不显示时需检查渲染窗口句柄是否有效。
生产环境建议
| 项目 | 建议 |
|---|---|
| 生命周期 | 严格遵循 Init → Open → SetParam/Callback → StartPlay → StopPlay → Close → UnInit |
| 线程模型 | SDK 回调中不直接操作 UI,统一通过消息转发 |
| 渲染路径 | D3D 优先,GDI 兜底 |
| 多路播放 | 每路独立 handle 和渲染窗口 |
| 网络指标 | 展示下载速度、丢包率、缓冲状态 |
| RTSP 弱网 | 支持 TCP 和 TCP/UDP 自动切换 |
| 错误提示 | 对连接失败、RTSP 401、缓冲异常、录像失败做明确提示 |
| 录像路径 | 使用宽字符接口,确认目录存在且可写 |
| 截图频率 | 控制截图请求频率,避免过高 CPU |
| OSD 台标 | D3D 模式使用,GDI 模式避免调用 |
| 日志留存 | 客户现场问题建议开启 SDK 日志和业务日志 |
| 资源释放 | 关闭窗口前停止播放、停止录像、关闭句柄、反初始化 SDK |
小结
大牛直播SDK(SmartMediaKit)Windows 平台 C++ 版 RTSP、RTMP 直播播放 SDK,适合需要低延迟、高稳定、可工程化集成的 Windows 实时音视频客户端项目。C++ Demo 以 MFC Dialog 为基础,完整展示了 SDK 初始化、播放器句柄管理、RTSP/RTMP 播放、D3D/GDI 渲染、事件回调、下载速度与丢包率监控、截图、播放端 MP4 录像、OSD 台标、RTSP 鉴权、URL 切换和资源释放等核心流程。
从工程实践角度看,建议将主业务窗口、SDK 调用、渲染窗口、RTSP 配置、录像配置和状态回调进行模块化管理。对于安防监控、工业视觉、应急指挥、无人机回传、远程教育、低空经济等场景,C++ 版 SDK 更适合构建高性能、低延迟、可长期运行的 Windows 实时视频客户端。
📎 CSDN官方博客:音视频牛哥-CSDN博客