QT实现低延迟的RTSP、RTMP播放器

简介: 好多开发者在QT环境下实现RTMP或RTSP播放时,首先考虑到的是集成VLC,集成后,却发现VLC在延迟、断网重连、稳定性等各个方面不尽人意,无法满足上线环境需求。本文以调用大牛直播SDK(官方)的Windows平台播放端SDK为例,介绍下如何在QT下实现低延迟的RTMP|RTSP播放器,废话不多说,先上图:

好多开发者在QT环境下实现RTMP或RTSP播放时,首先考虑到的是集成VLC,集成后,却发现VLC在延迟、断网重连、稳定性等各个方面不尽人意,无法满足上线环境需求。本文以调用大牛直播SDK(官方)的Windows平台播放端SDK为例,介绍下如何在QT下实现低延迟的RTMP|RTSP播放器,废话不多说,先上图:

20210430102852455.png

大牛直播SDK有MFC的demo,所以在QT上实现播放轻车熟路,如果需要多窗口播放,也可以参考转发的demo,转发的那个4窗口预览的demo做了二次封装,调用更方便。


窗体布局不再赘述,就是个普通的6窗口布局,不得不说,QT在窗体布局这块,相对MFC真的太方便了。


考虑到大多场景下,开发者有多路播放诉求,针对这种情况,我们对player做个简单的封装:


开始播放:

bool player_wrapper::StartPlay(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
{
  if (is_playing_)
    return false;
  if (!OpenPlayerHandle(url, is_rtsp_tcp_mode, is_mute))
    return false;
  player_api_->SetBuffer(player_handle_, 100);
  player_api_->SetMute(player_handle_, is_mute ? 1 : 0);
  player_api_->SetRtspAutoSwitchTcpUdp(player_handle_, true);
  player_api_->SetRtspTimeout(player_handle_, 10);
  player_api_->SetRenderWindow(player_handle_, render_wnd_);
  player_api_->SetRenderScaleMode(player_handle_, 1);
  auto ret = player_api_->StartPlay(player_handle_);
  if (NT_ERC_OK != ret)
  {
    if (!is_recording_)
    {
      player_api_->Close(player_handle_);
      player_handle_ = NULL;
    }
    return false;
  }
  is_playing_ = true;
  return true;
}

开始播放封装,调用了OpenPlayerHandle(),检查系统是不是支持特定机型硬解码,通过调用Open()接口,获取播放实例,然后进行播放前的参数设置,比如网络状态event回调、视频宽高回调、设置buffer time、RTSP的TCP-UDP模式,默认播放音量等,具体实现如下:

bool player_wrapper::OpenPlayerHandle(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
{
  if (player_handle_ != NULL)
    return true;
  if (url.empty())
    return false;
  bool is_support_h264_hardware_decoder_ = NT_ERC_OK == player_api_->IsSupportH264HardwareDecoder();
  bool is_support_h265_hardware_decoder_ = NT_ERC_OK == player_api_->IsSupportH265HardwareDecoder();
  NT_HANDLE player_handle = NULL;
  Q_ASSERT(player_api_ != NULL);
  if (NT_ERC_OK != player_api_->Open(&player_handle, render_wnd_, 0, NULL))
  {
    return false;
  }
  Q_ASSERT(player_handle != NULL);
  player_api_->SetEventCallBack(player_handle, this, &NT_Player_SDKEventHandle);
  player_api_->SetVideoSizeCallBack(player_handle, this, SP_SDKVideoSizeHandle);
  player_api_->SetH264HardwareDecoder(player_handle, is_support_h264_hardware_decoder_ ? 1 : 0, 0);
  player_api_->SetH265HardwareDecoder(player_handle, is_support_h265_hardware_decoder_ ? 1 : 0, 0);
  player_api_->SetBuffer(player_handle, 0);
  player_api_->SetFastStartup(player_handle, 1);
  player_api_->SetRTSPTcpMode(player_handle, is_rtsp_tcp_mode ? 1 : 0);
  player_api_->SetMute(player_handle, is_mute ? 1 : 0);
  int audio_volume = 100;
  player_api_->SetAudioVolume(player_handle, audio_volume);
  if (NT_ERC_OK != player_api_->SetURL(player_handle, url.c_str()))
  {
    if (!is_recording_)
    {
      player_api_->Close(player_handle_);
      player_handle_ = NULL;
    }
    return false;
  }
  player_handle_ = player_handle;
  return true;
}

停止播放:

void player_wrapper::StopPlay()
{
  if (!is_playing_)
    return;
  player_api_->StopPlay(player_handle_);
  if (!is_recording_)
  {
    player_api_->Close(player_handle_);
    player_handle_ = NULL;
  }
  is_playing_ = false;
}

Event回调:

extern "C" NT_VOID NT_CALLBACK NT_Player_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;
  auto wrapper = reinterpret_cast<player_wrapper*>(user_data);
  if (wrapper == NULL)
    return;
  wrapper->OnPlayerStatus(event_id, param1);
}
void player_wrapper::OnPlayerStatus(NT_UINT32 event_id, NT_INT64 param1)
{
  if (player_handle_ == NULL)
    return;
  if (!is_playing_ && !is_recording_)
  {
    return;
  }
  if (NT_SP_E_EVENT_ID_RTSP_STATUS_CODE == event_id)
  {
    int status_code = (int)param1;
    if (401 == status_code)
    {
      //HandleVerification();
    }
    return;
  }
  if (NT_SP_E_EVENT_ID_CONNECTING == event_id
    || NT_SP_E_EVENT_ID_CONNECTION_FAILED == event_id
    || NT_SP_E_EVENT_ID_CONNECTED == event_id
    || NT_SP_E_EVENT_ID_DISCONNECTED == event_id
    || NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED == event_id)
  {
    if (NT_SP_E_EVENT_ID_CONNECTING == event_id)
    {
      OutputDebugStringA("connection status: connecting\r\n");
    }
    else if (NT_SP_E_EVENT_ID_CONNECTION_FAILED == event_id)
    {
      OutputDebugStringA("connection status: connection failed\r\n");
    }
    else if (NT_SP_E_EVENT_ID_CONNECTED == event_id)
    {
      OutputDebugStringA("connection status: connected\r\n");
    }
    else if (NT_SP_E_EVENT_ID_DISCONNECTED == event_id)
    {
      OutputDebugStringA("connection status: disconnected\r\n");
    }
    else if (NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED == event_id)
    {
      OutputDebugStringA("connection status: no mediadata received\r\n");
    }
  }
  std::unique_lock<std::recursive_mutex> lock(player_handle_mutex_);
  player_status_ = event_id;
}

调用封装后的播放接口,记得多实例播放环境下,Init()和UnInit()接口仅需要调用一次,测试URL可自行设置。

void frmMain::startplay()
{
    for (int i = 0; i < widgets.size(); ++i)
    {
        if (!plays[i])
        {
            plays[i] = std::make_shared<player_wrapper>(&player_api_, (HWND)NULL,
                (HWND)widgets.at(i)->winId());
        }
        auto& play = plays[i];
        if (!play->IsPlaying())
        {
            QString play_url = "rtsp://admin:admin123456@192.168.0.121:554/cam/realmonitor?channel=1&subtype=0";
            if (!play->StartPlay(play_url.toStdString(), true, false))
            {
                QMessageBox::information(NULL, "播放失败!", play_url);
            }
        }
    }
}
void frmMain::stopplay()
{
    for (int i = 0; i < widgets.size(); ++i)
    {
        if (!plays[i])
        {
            plays[i] = std::make_shared<player_wrapper>(&player_api_, (HWND)NULL,
                (HWND)widgets.at(i)->winId());
        }
        auto& play = plays[i];
        if (play->IsPlaying())
        {
            play->StopPlay();
        }
        widgets.at(i)->setText(QString("通道 %1").arg(i + 1));
        widgets.at(i)->update();
    }
}

窗体大小发生改变时:

void frmMain::resizeEvent(QResizeEvent* event)
{
    for (int i = 0; i < widgets.size(); ++i)
    {
        if (!plays[i])
        {
            plays[i] = std::make_shared<player_wrapper>(&player_api_, (HWND)NULL,
                (HWND)widgets.at(i)->winId());
        }
        auto& play = plays[i];
        if (play->IsPlaying())
        {
            play->OnWindowSize(widgets.at(i)->width(), widgets.at(i)->height());
        }
    }
}

以上是QT环境下集成个低延迟的RTMP、RTSP播放的基本流程,感兴趣的开发者可酌情参考。相对MFC,QT环境下在实现更酷炫和实用的逻辑显然体验更好一些。

相关文章
|
2月前
|
C++
基于Qt的简易音乐播放器设计与实现
基于Qt的简易音乐播放器设计与实现
127 0
|
2月前
|
算法 Linux API
【Qt 延迟手段】Qt中实现延迟和休眠的多种方法
【Qt 延迟手段】Qt中实现延迟和休眠的多种方法
208 0
|
8月前
|
JSON 搜索推荐 数据库
基于Qt框架实战:MP3音乐播放器搜索引擎
基于Qt框架实战:MP3音乐播放器搜索引擎
基于Qt框架实战:MP3音乐播放器搜索引擎
|
9月前
|
Linux
linux系统中利用QT实现音乐播放器的功能
linux系统中利用QT实现音乐播放器的功能
159 0
|
11月前
Qt图片定时滚动播放器+透明过渡动画
解决:[QWidget::paintEngine: Should no longer be called QPainter::begin: Paint device returned engine == 0, type: 1] 需要在哪个控件上绘制,就要在哪个控件类中重写 paintEvent() ,所以本项目 需要使用自定义的MyQLabel继承QLabel
90 0
|
2月前
|
人机交互 C++
QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器
QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器
132 0
|
9月前
嵌入式 QT 基于mplayer的音乐播放器
嵌入式 QT 基于mplayer的音乐播放器
|
10月前
|
C++ 计算机视觉 Python
C++ QT视频音乐播放器
C++ QT视频音乐播放器
125 0
C++ QT视频音乐播放器
|
11月前
Qt图片定时滚动播放器
可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开文件选择,定时滚动图片 重载实现dragEnterEvent(拖拽)、dropEvent(拖拽放下)、resizeEvent(窗口大小改变)
83 0
|
11天前
|
关系型数据库 MySQL 项目管理
数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面
数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面