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平台可参考的资料比较少,可选的方案比较少,感兴趣的可酌情参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/553037.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TIM提示“个人文件夹被占用,请稍候再登录”怎么解决

在登录TIM的时候&#xff0c;弹出提示“个人文件夹被占用&#xff0c;请稍后再登录”的提示&#xff0c;无法正常登录TIM TIM提示“个人文件夹被占用&#xff0c;请稍候再登录”怎么解决&#xff1f; 这个时候&#xff0c;我们可以直接关闭上面的登录窗口&#xff0c;然后过一…

数据推送选择GB28181、RTSP还是RTMP?

GB/T28181 国标GB/T28181协议全称《安全防范视频监控联网系统信息传输、交换、控制技术要求》&#xff0c;是一个定义视频联网传输和设备控制标准的白皮书&#xff0c;由公安部科技信息化局提出&#xff0c;该标准规定了城市监控报警联网系统中信息传输、交换、控制的互联结构…

Windows平台RTMP推送摄像头对接介绍

背景 好多开发者在对接大牛直播SDK&#xff08;官方&#xff09;的Windows平台RTMP推送时&#xff0c;不熟悉摄像头调用&#xff0c;实际上&#xff0c;摄像头调用逻辑并不复杂&#xff0c;以下是大概流程&#xff1a; 首先调用我们sdk接口获取摄像头个数&#xff0c;调用接口…

Outlook2016未读邮件怎么设置字体颜色

Outlook2016中想要设置未读邮件的字体颜色&#xff0c;该怎么设置呢?下面我们就来看看详细的教程。 Outlook2016未读邮件怎么设置字体颜色? 1、下载安装outlook软件。 Outlook2016未读邮件怎么设置字体颜色? 2、双击打开outlook软件&#xff0c;登入邮箱账户。 Outlook…

Android平台RTMP推送模块如何对接NV21、YV12、RGB、YUV等编码前数据

前言 我们在对接Android平台摄像头或者屏幕采集、编码打包推送场景的时候&#xff0c;随着采集设备的不同&#xff0c;出来的数据也是多样化的&#xff0c;比如NV21、YV12、RGB、YUV等&#xff0c;更有图像数据甚至是翻转或者倒置的&#xff0c;如果上层处理&#xff0c;效率低…

庖丁解牛之-Android平台RTSP|RTMP播放器设计

背景 我们在做Android平台RTSP或者RTMP播放器开发的时候&#xff0c;需要注意的点非常多&#xff0c;以下&#xff0c;以大牛直播SDK(官方)的接口为例&#xff0c;大概介绍下相关接口设计&#xff1a; 接口设计 1. Open() 接口 Open接口的目的&#xff0c;主要是创建实例&a…

如何在Android端实现轻量级RTSP服务(类似于IPC)

为什么要设计轻量级RTSP服务 首先声明一点&#xff0c;本blog提到的轻量级RTSP服务&#xff0c;类似于网络摄像头&#xff08;IPC&#xff09;&#xff0c;而非传统意义的接受外部推流的RTSP服务器。 轻量级RTSP服务解决的核心痛点&#xff1a;避免用户单独部署RTSP或者RTMP服…

Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨

背景 随着智能门禁等物联网产品的普及&#xff0c;越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC&#xff0c;优点不再赘述&#xff0c;我们这里先说说可能需要面临的问题&#xff1a;WebRTC的服务器部署非常复杂&#xff0c;可…

微信公众号怎么发送模板消息 微信公众平台模板消息免费发送的技巧

想要发免费的模板消息&#xff0c;该怎么发布?下面我们就来看看详细的教程。 1、首先我们需要在微信公众号里面开通模板消息功能&#xff0c;没有开通的需要去申请&#xff0c;不然就用不了&#xff0c;如下图所示。 微信公众号怎么发送模板消息?微信公众平台模板消息免费发…

如何理解面向过程和面向对象?

一句话理解面向对象 有人说&#xff1a;“如果上帝是程序员&#xff0c;他怎么创造世界上的所有动物。”&#xff0c;理解这个问题就理解了面向对像。 面向过程和面向对象区别&#xff1f; 面向过程的思路&#xff1a;什么事都自己做&#xff1b;分析解决问题所需的步骤&…

如何实现Android平台GB28181前端设备接入

技术背景 在实现Android平台GB28181前端设备接入之前&#xff0c;我们几年前就有了非常成熟的RTMP推送、RTSP推送和轻量级RTSP服务等模块&#xff0c;特别是RTMP推送&#xff0c;行业内应用非常广泛&#xff0c;好多开发者可能会问&#xff0c;既然有了以上模块&#xff0c;干…

foxmail怎么加入黑名单 foxmail导入黑名单邮箱地址的教程

1、首先&#xff0c;先进入到了foxmail的窗口的界面当中&#xff0c;进行点击菜单中工具&#xff0c;弹出了下拉菜单中&#xff0c;进行选中为“系统工具” foxmail怎么加入黑名单? foxmail导入黑名单邮箱地址的教程 2、进入到了的系统的设置的界面中&#xff0c;进行选中反…

Android前端音视频数据接入GB28181平台意义

技术背景 在我们研发Android平台GB28181前端音视频接入模块之前&#xff0c;业内听到最多的是&#xff0c;如何用Android或者Windows端&#xff0c;在没有国标IPC设备的前提下&#xff0c;模拟GB28181的信令和媒体流交互流程&#xff0c;实现GB28181整体方案的测试&#xff1f…

QQ浏览器怎样在首页显示优先推荐的网站

QQ浏览器怎样在首页显示优先推荐的网站&#xff1f;QQ浏览器显示优先推荐的网站的方法 1&#xff0c;在手机桌面上找到QQ浏览器的图标&#xff0c;点击打开。 QQ浏览器怎样在首页显示优先推荐的网站&#xff1f;QQ浏览器显示优先推荐的网站的方法[多图] 2&#xff0c;点击下…

push_back还是emplace_back?

背景和区别 emplace_back() 是 C11 之后&#xff0c;vector容器中添加的新方法&#xff0c;和 push_back()一样&#xff0c;都是在容器末尾添加一个新的元素&#xff0c;相对于push_back函数&#xff0c;它减少了一次类的构造。不同的是emplace_back() 在效率上相比较于 push_…

std::tuple还是struct?

std::tuple是C11提供的新模板类&#xff0c;可以翻译为“元组”&#xff0c;可把多个不同类型的变量组合成一个对象。std::tuple可看做std::pair的泛化实现&#xff0c;std::pair包含两个元素&#xff0c;std::tuple 可以同时包含多个元素&#xff0c;它拥有 struct 的表现&…

微软发动图明示新一代操作系统Windows 11

可能感到关注度不够&#xff0c;微软再次出面预热所谓的新一代Windows操作系统&#xff0c;阿拉伯数字“11”图形赫然出现在预热动图中&#xff0c;可以说暗示得不要再明显了。 值得注意的是&#xff0c;动图中11的两个1中间还有一位正打坐的小人&#xff0c;配合微软关注大脑…

std::atomic和std::mutex区别

​std::atomic介绍​ ​模板类std::atomic是C11提供的原子操作类型&#xff0c;头文件 #include<atomic>。​在多线程调用下&#xff0c;利用std::atomic可实现数据结构的无锁设计。​​ ​和互斥量的不同之处在于&#xff0c;std::atomic原子操作&#xff0c;主要是保…

C++ std::remove/std::remove_if/erase用法探讨

​std::remove 不会改变输入vector/string的长度。其过程相当于去除指定的字符&#xff0c;剩余字符往前靠。后面的和原始字符保持一致。​ 需要注意的是&#xff0c;remove函数是通过覆盖移去的&#xff0c;如果容器最后一个值刚好是需要删除的&#xff0c;则它无法覆盖掉容器…

深度技术Win11 64位最新旗舰版镜像V2021.08

深度技术Win11 64位最新旗舰版镜像V2021.08是微软最新版本的电脑操作系统&#xff0c;系统稳定性的进一步优化和提升&#xff0c;可以更好的获得整个纯版本系统的稳定性。支持系统智能激活服务&#xff0c;用户可以快速激活系统&#xff0c;提高系统使用率&#xff0c;有需要的…