技术背景

无论是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播放相关推荐

  1. RTMP/RTSP推送端和RTMP/RTSP播放端录像设计探讨

    好多开发者认为,无论是RTSP/RTMP推送端还是RTSP/RTMP播放端,涉及到录像,只要2个接口足矣:开始录像.停止录像. 实际场景下,一个好的录像模块,2个接口远远不够, 本文以大牛直播SDK( ...

  2. Android平台RTMP/RTSP播放器开发系列之解码和绘制

    本文主要抛砖引玉,粗略介绍下Android平台RTMP/RTSP播放器中解码和绘制相关的部分(Github). 解码 提到解码,大家都知道软硬解,甚至一些公司觉得硬解码已经足够通用,慢慢抛弃软解了,如 ...

  3. android 直播流服务器,视频-Android上的实时流RTMP / RTSP播放器,无需使用webview(WOWZA服务器)...

    视频-Android上的实时流RTMP / RTSP播放器,无需使用webview(WOWZA服务器) 我正在开发一个Android应用程序,我想在其中发布和播放视频... 我想要的是: 我的应用记录 ...

  4. 基于RTMP实现Linux|麒麟操作系统下屏幕|系统声音采集推送

    背景 Windows操作系统自问世以来,以其简单易用的图形化界面操作受到大众追捧,为计算机的普及.科技的发展做出了不可磨灭的功绩,也慢慢的成为人们最依赖的操作系统.在中国,90%以上的办公环境都是Wi ...

  5. [轻量级RTSP服务]Linux|麒麟操作系统下实现屏幕|系统声音采集

    背景 随着国产操作系统的推进,传统行业对Linux平台的呼声和需求越来越大,之前几年,我们发布了Linux平台运营商级的RTSP转RTMP推送模块.RTMP推送模块和RTSP.RTMP播放模块,前段时 ...

  6. Windows平台RTMP|RTSP播放器为什么要兼容GDI绘制

    为什么要支持GDI 先说结论,Windows平台播放渲染这块,一般来说99%以上的机器都是支持D3D的,实现GDI模式绘制,除了为了好的兼容性外,在远程连接的场景下,D3D创建不成功,需要使用GDI模 ...

  7. Android、iOS平台RTMP/RTSP播放器实现实时音量调节

    介绍移动端RTMP.RTSP播放器实时音量调节之前,我们之前也写过,为什么windows播放端加这样的接口,windows端播放器在多窗口大屏显示的场景下尤其需要,尽管我们老早就有了实时静音接口,相对 ...

  8. Windows平台RTMP/RTSP播放器如何实现实时音量调节

    为什么要做实时音量调节 RTMP或RTSP直播播放音量调节,主要用于多实例(多窗口)播放场景下,比如同时播放4路RTMP或RTSP流,如果音频全部打开,几路audio同时打开,可能会影响用户体验,我们 ...

  9. Windows平台RTMP|RTSP播放器实现画面全屏功能

    我们在Windows平台实现RTSP或者RTMP播放的时候,有个功能是绕不开的,那就是播放窗口全屏.本文就以大牛直播SDK(官方)的Windows播放器为例,大概讲下大概实现: 全屏播放需要考虑的点不 ...

最新文章

  1. 微软2014校园招聘笔试试题
  2. Linux Mint无法打开系统设置,以及很多系统图标
  3. gitlab更新配置无效_GitMaster 发布 v1.11.0 版本,支持 GitLab 多级分组,Gist支持文件列表...
  4. 操作系统:哲学家进餐问题
  5. oracle指令df,怎么使用df命令查看Linux磁盘空间?
  6. c#中的long类型示例_C#中带示例的带符号字节数组
  7. zoj 1406 Jungle Roads
  8. Delphi 的绘图功能[9] - TextRect
  9. Eclipse用法和技巧十一:分栏显示
  10. 服务器linux simsun.ttc is not a valid ttf file
  11. Gps经纬度转化关系
  12. krc转换lrc java_win7将酷狗音乐krc歌词转换成lrc歌词文件的方法
  13. 对话改写论文笔记(2021年初 )
  14. 一级域名是什么?和二级域名有什么区别?
  15. 【电器识别】基于AlexNet网络实现电线杆、绝缘子、发电机和电容器等电器设备识别附matlab代码
  16. Travel(SPFA+思维)
  17. 服务器win系统更新如何设置,Windows服务器更新服务的配置
  18. android之图片选择器ImageSelector的使用
  19. 获两大A股龙头加持,掌上辅材能否成为中国版“家得宝”?
  20. R数据分析:生存分析的列线图的理解与绘制详细教程

热门文章

  1. 启动spark shell
  2. 在JavaScript中以Hours24:Minutes:Seconds格式获取当前时间
  3. can收发器 rx_CANOpen系列教程03 _CAN收发器功能、原理及作用
  4. php 查看扩展 代码,[扩展推荐] 使用 PHP Insights 在终端查看 PHP 项目代码质量
  5. 大一python编程题_请教python编程问题(作业就剩这几道题了)
  6. 恒生估值系统_恒生指数和恒生国企指数投资价值分析
  7. 检查列表中的所有元素在Python中是否相同
  8. 面试官:重写 equals 时为什么一定要重写 hashCode?
  9. 最囧的国庆,是一种怎样的体验?
  10. Java14发布!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!​