Linux|麒麟操作系统实现多路RTMP|RTSP播放

简介: 无论是Windows平台还是Linux,多路播放诉求非常普遍,比如针对智慧工地、展馆、教育等宏观场景下的摄像头展示,关于RTSP或RTMP直播播放器开发需要注意的点,可参考之前博客,总的来说有以下一些点:

技术背景

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

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
78 1
|
3天前
|
缓存 安全 Linux
Linux系统查看操作系统版本信息、CPU信息、模块信息
在Linux系统中,常用命令可帮助用户查看操作系统版本、CPU信息和模块信息
43 23
|
9天前
|
弹性计算 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot是由阿里云推出的操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行等功能,极大提升了Linux系统的使用效率。用户只需通过简单的命令或自然语言描述问题,OS Copilot即可快速提供解决方案并执行相应操作。例如,查询磁盘使用量等常见任务变得轻松快捷。此外,它还支持从文件读取复杂任务定义,进一步简化了操作流程。虽然在某些模式下可能存在小问题,但总体上大大节省了学习和操作时间,提高了工作效率。
74 2
OS Copilot-操作系统智能助手-Linux新手小白的福音
|
2天前
|
存储 运维 安全
深入解析操作系统控制台:阿里云Alibaba Cloud Linux(Alinux)的运维利器
本文将详细介绍阿里云的Alibaba Cloud Linux操作系统控制台的功能和优势。
21 5
|
2天前
|
安全 大数据 Linux
云上体验最佳的服务器操作系统 - Alibaba Cloud Linux | 飞天技术沙龙-CentOS 迁移替换专场
本次方案的主题是云上体验最佳的服务器操作系统 - Alibaba Cloud Linux ,从 Alibaba Cloud Linux 的产生背景、产品优势以及云上用户使用它享受的技术红利等方面详细进行了介绍。同时,通过国内某社交平台、某快递企业、某手机客户大数据业务 3 大案例,成功助力客户实现弹性扩容能力提升、性能提升、降本增效。 1. 背景介绍 2. 产品介绍 3. 案例分享
|
1月前
|
安全 Linux 数据安全/隐私保护
深入Linux操作系统:文件系统和权限管理
在数字世界的海洋中,操作系统是连接用户与硬件的桥梁,而Linux作为其中的佼佼者,其文件系统和权限管理则是这座桥梁上不可或缺的结构。本文将带你探索Linux的文件系统结构,理解文件权限的重要性,并通过实际案例揭示如何有效地管理和控制这些权限。我们将一起航行在Linux的命令行海洋中,解锁文件系统的奥秘,并学习如何保护你的数据免受不必要的访问。
|
1月前
|
搜索推荐 Linux
深入理解Linux操作系统的启动过程
本文旨在揭示Linux操作系统从开机到完全启动的神秘面纱,通过逐步解析BIOS、引导加载程序、内核初始化等关键步骤,帮助读者建立对Linux启动流程的清晰认识。我们将探讨如何自定义和优化这一过程,以实现更高效、更稳定的系统运行。
|
1月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
1月前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####

热门文章

最新文章