大牛直播SDK(SmartMediaKit)Windows平台RTSP/RTMP直播播放SDK集成说明(C++版)

文档概述

本文介绍大牛直播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 核心由 CSmartPlayerDlgnt_wrapper_render_wndnt_render_wndnt_player_rtsp_config_dlgnt_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.dllSmartPlayerSDK.lib 以及 smart_player_sdk.hsmart_player_define.h 对外提供标准 C/C++ 接口。业务层通过 GetSmartPlayerSDKAPI() 获取 SmartPlayerSDKAPI 函数指针集合,后续通过该结构体调用 InitOpenSetURLStartPlayStopPlayStartRecorderStopRecorderCaptureImage 等接口完成播放器接入。上传文档中也将 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.dllSmartLog.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 设置是否只解码关键帧

上传文档的参数速查表中也列出了 SetBufferSetURLSetRtspTimeoutSetRTSPTcpModeSetRtspAutoSwitchTcpUdpSetFastStartupSetHWDecoderSetD3DRenderSetMuteSetReportDownloadSpeedStartPlayStopPlay 等关键播放接口。


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博客

相关推荐
Irene19911 小时前
Windows 11 WSL Ubuntu 环境:实际安装 Hive 踩坑实录
hive·windows·ubuntu
console.log('npc')2 小时前
Windows 11 → WSL2 → Ubuntu → Docker → Codex → Sub2API
windows·ubuntu·docker
ID_180079054733 小时前
企业级实战:淘宝铺货核心API接口说明(含JSON返回)
windows
EasyGBS3 小时前
1分钟讲清楚选EasyNVR还是国标GB28181视频平台EasyGBS:路线不同,别选错
音视频
遇印记4 小时前
软考知识点(windows系统管理与命令)
运维·服务器·网络·windows·ddos
日光明媚4 小时前
深度解析 SGLang 框架 Wan2.1 视频生成加速技术:从 49 分钟到 1 分钟的极致优化
人工智能·计算机视觉·aigc·音视频·sglang
小猿君4 小时前
谷歌I/O前夜Veo 4遭泄露,AI视频底层逻辑浮出水面
人工智能·音视频
南山有乔木7894 小时前
音频怎么转换MP3格式?M4A、WAV、FLAC转mp3实测有效的格式转换方法
音视频
不昀4 小时前
音频变压器Bourns SM-LP-5001国产替代选型指南
网络·音视频·以太网·网络通信·电子元器件