Linux|麒麟操作系统实现多路RTMP|RTSP播放
技術背景
無論是Windows平臺還是Linux,多路播放訴求非常普遍,比如針對智慧工地、展館、教育等宏觀場景下的攝像頭展示,關于RTSP或RTMP直播播放器開發需要注意的點,可參考之前博客,總的來說有以下一些點:
1. 低延遲:大多數RTSP的播放都面向直播場景,所以,如果延遲過大,比如監控行業,小偷都走了,客戶端才看到,或者別人已經按過門鈴幾秒,主人才看到圖像,嚴重影響體驗,所以,低延遲是衡量一個好的RTSP播放器非常重要的指標,目前大牛直播SDK的RTSP播放延遲控制在幾百毫秒,VLC在幾秒,這個延遲,是長時間的低延遲,比如運行1天、一周、一個月甚至更久;
2. 音視頻同步或跳轉:有些開發者為了追求低延遲體驗,甚至不做音視頻同步,拿到audio video直接播放,導致a/v不同步,還有就是時間戳亂跳;
3. 支持多實例:一個好的播放器,需要支持同時播放多路音視頻數據,比如4-8-9-16-32窗口;
4. 支持buffer time設置:在一些有網絡抖動的場景,播放器需要支持精準的buffer time設置,一般來說,以毫秒計;
5. H.265的播放和錄制:除了H.264,還需要支持H.265,目前市面上的RTSP H.265攝像頭越來越多,支持H.265的RTSP播放器迫在眉睫,此外,單純的播放H.265還不夠,還需要可以能把H.265的數據能錄制下來;
6. TCP/UDP模式切換:考慮到好多服務器僅支持TCP或UDP模式,一個好的RTSP播放器需要支持TCP/UDP模式自動切換;
7. 靜音支持:比如,多窗口播放RTSP流,如果每個audio都播放出來,體驗非常不好,所以實時靜音功能非常必要;
8. 視頻view旋轉:好多攝像頭由于安裝限制,導致圖像倒置,所以一個好的RTSP播放器應該支持如視頻view實時旋轉(0° 90° 180° 270°)、水平反轉、垂直反轉;
9. 支持解碼后audio/video數據輸出(可選):大牛直播SDK接觸到好多開發者,希望能在播放的同時,獲取到YUV或RGB數據,進行人臉匹配等算法分析,所以音視頻回調可選;
10. 快照:感興趣或重要的畫面,實時截取下來非常必要;
11. 網絡抖動處理(如斷網重連):基本功能,不再贅述;
12. 跨平臺:一個好的播放器,跨平臺(Windows/Android/iOS)很有必要,起碼為了后續擴展性考慮,開發的時候,有這方面的考慮,目前大牛直播SDK的RTSP播放器,完美支持以上平臺;
13. 長期運行穩定性:提到穩定性,好多開發者不以為然,實際上,一個好的產品,穩定是最基本的前提,不容忽視!
14. 可以錄像:播放的過程中,隨時錄制下來感興趣的視頻片斷,存檔或其他二次處理;
15. log信息記錄:整體流程機制實時反饋,不多打log,但是不能一些重要的log,如播放過程中出錯等;
16. download速度實時反饋:可以看到實時下載速度反饋,以此來監聽網絡狀態;
17. 異常狀態處理:如播放的過程中,斷網、網絡抖動、來電話、切后臺后返回等各種場景的處理。
代碼實現
本文以大牛直播SDK(官方)的Linux平臺為例,介紹下RTMP或RTSP流多路播放集成。
int main(int argc, char *argv[]) {XInitThreads(); // X支持多線程, 必須調用NT_SDKLogInit();// SDK初始化SmartPlayerSDKAPI player_api;if (!NT_PlayerSDKInit(player_api)){fprintf(stderr, "SDK init failed.\n");return 0;}auto display = XOpenDisplay(nullptr);if (!display){fprintf(stderr, "Cannot connect to X server\n");player_api.UnInit();return 0;}auto screen = DefaultScreen(display);auto root = XRootWindow(display, screen);XWindowAttributes root_win_att;if (!XGetWindowAttributes(display, root, &root_win_att)){fprintf(stderr, "Get Root window attri failed\n");player_api.UnInit();XCloseDisplay(display);return 0;}if (root_win_att.width < 100 || root_win_att.height < 100){fprintf(stderr, "Root window size error.\n");player_api.UnInit();XCloseDisplay(display);return 0;}fprintf(stdout, "Root Window Size:%d*%d\n", root_win_att.width, root_win_att.height);int main_w = root_win_att.width / 2, main_h = root_win_att.height/2;auto black_pixel = BlackPixel(display, screen);auto white_pixel = WhitePixel(display, screen);auto main_wid = XCreateSimpleWindow(display, root, 0, 0, main_w, main_h, 0, white_pixel, black_pixel);if (!main_wid){player_api.UnInit();XCloseDisplay(display);fprintf(stderr, "Cannot create main windows\n");return 0;}XSelectInput(display, main_wid, StructureNotifyMask | KeyPressMask);XMapWindow(display, main_wid);XStoreName(display, main_wid, win_base_title);std::vector<std::shared_ptr<NT_PlayerSDKWrapper> > players;for (auto url: players_url_){auto i = std::make_shared<NT_PlayerSDKWrapper>(&player_api);i->SetDisplay(display);i->SetScreen(screen);i->SetURL(url);players.push_back(i);if ( players.size() > 3 )break;}auto border_w = 2;std::vector<NT_LayoutRect> layout_rects;SubWindowsLayout(main_w, main_h, border_w, static_cast<int>(players.size()), layout_rects);for (auto i = 0; i < static_cast<int>(players.size()); ++i){assert(players[i]);players[i]->SetWindow(CreateSubWindow(display, screen, main_wid, layout_rects[i], border_w));}for (const auto& i : players){assert(i);if (i->GetWindow())XMapWindow(display, i->GetWindow());}for (auto i = 0; i < static_cast<int>(players.size()); ++i){assert(players[i]);// 第一路不靜音, 其他全部靜音players[i]->Start(0, i!=0, 1, false);//players[i]->Start(0, false, 1, false);}while (true){while (MY_X11_Pending(display, 10)){XEvent xev;memset(&xev, 0, sizeof(xev));XNextEvent(display, &xev);if (xev.type == ConfigureNotify){if (xev.xconfigure.window == main_wid){if (xev.xconfigure.width != main_w || xev.xconfigure.height != main_h){main_w = xev.xconfigure.width;main_h = xev.xconfigure.height;SubWindowsLayout(main_w, main_h, border_w, static_cast<int>(players.size()), layout_rects);for (auto i = 0; i < static_cast<int>(players.size()); ++i){if (players[i]->GetWindow()){XMoveResizeWindow(display, players[i]->GetWindow(), layout_rects[i].x_, layout_rects[i].y_, layout_rects[i].w_, layout_rects[i].h_);}}}}else{for (const auto& i: players){assert(i);if (i->GetWindow() && i->GetWindow() == xev.xconfigure.window){i->OnWindowSize(xev.xconfigure.width, xev.xconfigure.height);}}}}else if (xev.type == KeyPress){if (xev.xkey.keycode == XKeysymToKeycode(display, XK_Escape)){fprintf(stdout, "ESC Key Press\n");for (const auto& i : players){i->Stop();if (i->GetWindow()){XDestroyWindow(display, i->GetWindow());i->SetWindow(None);}}players.clear();XDestroyWindow(display, main_wid);XCloseDisplay(display);player_api.UnInit();fprintf(stdout, "Close Players....\n");return 0;}}}} }開始播放封裝
bool NT_PlayerSDKWrapper::Start(int buffer, bool is_mute, int render_scale_mode, bool is_only_dec_key_frame) {if (is_playing_)return false;if (url_.empty())return false;if (!OpenHandle(url_, buffer))return false;assert(handle_ && handle_->Handle());// 音頻參數player_api_->SetMute(handle_->Handle(), is_mute ? 1 : 0);player_api_->SetIsOutputAudioDevice(handle_->Handle(), 1);player_api_->SetAudioOutputLayer(handle_->Handle(), 0); // 使用pluse 或者 alsa播放, 兩個可以選擇一個// 視頻參數player_api_->SetVideoSizeCallBack(handle_->Handle(), this, &NT_Player_SDK_WRAPPER_OnVideoSizeHandle);player_api_->SetXDisplay(handle_->Handle(), display_);player_api_->SetXScreenNumber(handle_->Handle(),screen_);player_api_->SetRenderXWindow(handle_->Handle(), window_);player_api_->SetRenderScaleMode(handle_->Handle(), render_scale_mode);player_api_->SetRenderTextureScaleFilterMode(handle_->Handle(), 3);player_api_->SetOnlyDecodeVideoKeyFrame(handle_->Handle(), is_only_dec_key_frame ? 1 : 0);auto ret = player_api_->StartPlay(handle_->Handle());if (NT_ERC_OK != ret){ResetHandle();return false;}is_playing_ = true;return true; }停止播放
void NT_PlayerSDKWrapper::Stop() {if (!is_playing_)return;assert(handle_);player_api_->StopPlay(handle_->Handle());video_width_ = 0;video_height_ = 0;ResetHandle();is_playing_ = false; }視頻寬高回調
extern "C" NT_VOID NT_CALLBACK NT_Player_SDK_WRAPPER_OnVideoSizeHandle(NT_HANDLE handle, NT_PVOID user_data,NT_INT32 width, NT_INT32 height) {auto sdk_wrapper = reinterpret_cast<NT_PlayerSDKWrapper*>(user_data);if (nullptr == sdk_wrapper)return;sdk_wrapper->VideoSizeHandle(handle, width, height); }實時快照
extern "C" NT_VOID NT_CALLBACK NT_Player_SDK_WRAPPER_OnCaptureImageCallBack(NT_HANDLE handle, NT_PVOID user_data, NT_UINT32 result, NT_PCSTR file_name) {auto sdk_wrapper = reinterpret_cast<NT_PlayerSDKWrapper*>(user_data);if (nullptr == sdk_wrapper)return;sdk_wrapper->CaptureImageHandle(handle, result, file_name); }實時靜音
void NT_PlayerSDKWrapper::SetMute(bool is_mute) {if (is_playing_ && handle_){player_api_->SetMute(handle_->Handle(), is_mute?1:0);} }設置繪制模式
void NT_PlayerSDKWrapper::SetRenderScaleMode(int render_scale_mode) {if (is_playing_ && handle_){player_api_->SetRenderScaleMode(handle_->Handle(), render_scale_mode);} }設置只解關鍵幀
void NT_PlayerSDKWrapper::SetOnlyDecodeVideoKeyFrame(bool is_only_dec_key_frame) {if (is_playing_ && handle_){player_api_->SetOnlyDecodeVideoKeyFrame(handle_->Handle(), is_only_dec_key_frame ? 1 : 0);} }Handler管理
bool NT_PlayerSDKWrapper::OpenHandle(const std::string& url, int buffer) {if (handle_){if (handle_->IsOpened()&& handle_->URL() == url){return true;}}ResetHandle();auto handle = std::make_shared<NT_SDK_HandleWrapper>(player_api_);if (!handle->Open(url, buffer)){return false;}handle_ = handle;handle_->AddEventHandler(shared_from_this());return true; }void NT_PlayerSDKWrapper::ResetHandle() {if (handle_){handle_->RemoveHandler(this);handle_.reset();} }錄像等其他接口不再贅述,可Windows平臺一致。
總結
多路RTMP或RTSP播放,涉及到性能和多路之間音視頻同步、長時間播放穩定性等問題,Linux平臺可參考的資料比較少,可選的方案比較少,感興趣的可酌情參考。
總結
以上是生活随笔為你收集整理的Linux|麒麟操作系统实现多路RTMP|RTSP播放的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【深度学习】近几年,关于基于Imagen
- 下一篇: 【Python】pandas一行代码绘制