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