麒麟操作系统|Linux下低延时RTMP|RTSP直播播放实现

简介: 国产操作系统多为以Linux为基础二次开发的操作系统。2014年4月8日起,美国微软公司停止了对Windows XP SP3操作系统提供服务支持,这引起了社会和广大用户的广泛关注和对信息安全的担忧。而2020年对Windows7服务支持的终止再一次推动了国产系统的发展。

背景

       国产操作系统多为以Linux为基础二次开发的操作系统。2014年4月8日起,美国微软公司停止了对Windows XP SP3操作系统提供服务支持,这引起了社会和广大用户的广泛关注和对信息安全的担忧。而2020年对Windows7服务支持的终止再一次推动了国产系统的发展。

      工信部对此表示,将继续加大力度,支持Linux的国产操作系统的研发和应用,并希望用户可以使用国产操作系统。随着信息技术和互联网的快速发展普及,电子商务已经成为不可抗拒的现代商业潮流,云计算、大数据应用日趋成熟,但随之带来了许多问题和挑战。为全面响应国家“互联网+”战略的提出和深入贯彻落实国家“十二五”规划纲要,帮助传统企业开展“商务智慧转型”,加强电子商务深入应用,特别是移动电子商务发展中的环境保障建设,促进电子商务行业健康有序发展,使电子商务相关的技术和经济、法律和规则、诚信和信誉及如何建立一个安全、可靠、可信的电子商务环境,保障电子商务活动中系统、交易的安全性,信息的保密性,已经成为当前亟待需要探讨和解决的重要课题。

国产操作系统|Linux下RTMP|RTSP直播播放

      在发布国产操作系统|Linux平台的RTMP|RTSP直播播放SDK之前,大牛直播SDK(官方)的直播播放SDK无需赘述,采用自研内核框架,功能齐全、高稳定、超低延迟、超低资源占用,覆盖Windows、Android和iOS平台。


      本次发布的可用于国产操作系统和Linux上的的RTMP|RTSP直播播放SDK, 视频绘制使用XLib相关库实现, 音频输出使用PulseAudio和Alsa Lib实现,除了常规功能如实时静音、快照、buffer time设定、网络自动重连等,RTMP支持扩展H265播放, RTSP也支持H265播放。


      播放器接口和调用都比较简单,集成复杂度低,且不依赖于QT。

相关DEMO

       大牛直播SDK发布的Linux平台播放器SDK支持多实例播放,以单个窗体播放为例,相关代码如下:

const char* player_url_ = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream";
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;
  }
  display_ = display;
  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);
  main_wid_ = XCreateSimpleWindow(display, root, 0, 0, main_w, main_h, 0, white_pixel, black_pixel);
  if (!main_wid_)
  {
    fprintf(stderr, "Cannot Create Main Window\n");
    player_api.UnInit();
    XCloseDisplay(display);
    return 0;
  }
  XSelectInput(display, main_wid_, StructureNotifyMask | KeyPressMask);
  auto sub_wid = CreateSubWindow(display, screen, main_wid_);
  if (!sub_wid)
  {
    fprintf(stderr, "Cannot Create Render Window\n");
    player_api.UnInit();
    XDestroyWindow(display, main_wid_);
    XCloseDisplay(display);
    return 0;
  }
  XMapWindow(display, main_wid_);
  XStoreName(display, main_wid_, win_base_title);
  XMapWindow(display, sub_wid);
  NT_HANDLE handle = nullptr;
  // 打开一个播放实例,可以Open多个播放实例, 然后播放多路
  if (NT_ERC_OK != player_api.Open(&handle, 0, nullptr))
  {
    player_api.UnInit();
    fprintf(stderr, "player_api.Open failed!\n");
    XDestroyWindow(display, sub_wid);
    XDestroyWindow(display, main_wid_);
    XCloseDisplay(display);
    return 0;
  }
  player_api.SetEventCallBack(handle, nullptr, &NT_OnSDKEventHandle);
  player_api.SetVideoSizeCallBack(handle, nullptr, &NT_SDKVideoSizeHandle);
  player_api.SetReportDownloadSpeed(handle, 1, 5); // 5秒上报一次下载速度
  player_api.SetRtspTimeout(handle, 15);
  player_api.SetRtspAutoSwitchTcpUdp(handle, 1);
  player_api.SetBuffer(handle, 0); // 设置缓存
  player_api.SetIsOutputAudioDevice(handle, 1);
  player_api.SetAudioOutputLayer(handle, 0); // 使用pluse 或者 alsa播放, 两个可以选择一个
  //player_api.SetAudioVolume(handle, 100);
  player_api.SetURL(handle, player_url_); // 设置播放地址, rtsp或者rtmp地址
  player_api.SetXDisplay(handle, display);
  player_api.SetXScreenNumber(handle, screen);
  player_api.SetRenderXWindow(handle, sub_wid); // 设置绘制的X窗口
  player_api.SetRenderScaleMode(handle, 1); // 按比例绘制或者全填充
  player_api.SetRenderTextureScaleFilterMode(handle, 3); 
  player_api.SetFastStartup(handle, 1);
  player_api.SetLowLatencyMode(handle, 0);
  if (NT_ERC_OK != player_api.StartPlay(handle))
  {
    player_api.Close(handle);
    handle = nullptr;
    player_api.UnInit();
    fprintf(stderr, "player_api.StartPlay failed!\n");
    XDestroyWindow(display, sub_wid);
    XDestroyWindow(display, main_wid_);
    XCloseDisplay(display);
    return 0;
  }
  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;
            XMoveResizeWindow(display, sub_wid, 0, 0, main_w-4, main_h-4);
          }
        }
        else
        {
          if (sub_wid == xev.xconfigure.window)
          {
            player_api.OnWindowSize(handle, 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");
          if (handle != nullptr)
          {
            player_api.StopPlay(handle); // 停止播放
            player_api.Close(handle);
            handle = nullptr;
          }
          XDestroyWindow(display, sub_wid);
          XDestroyWindow(display, main_wid_);
          XCloseDisplay(display);
          player_api.UnInit();
          fprintf(stdout, "Close Player....\n");
          return 0;
        }
      }
    }
  }
}

日志设置和SDK Init相关

void NT_SDKLogInit()
{
  SmartLogAPI log_api;
  memset(&log_api, 0, sizeof(log_api));
  GetSmartLogAPI(&log_api);
  log_api.SetLevel(SL_INFO_LEVEL);
  log_api.SetPath((NT_PVOID)"./");
}
bool NT_PlayerSDKInit(SmartPlayerSDKAPI& player_api)
{
  memset(&player_api, 0, sizeof(player_api));
  GetSmartPlayerSDKAPI(&player_api);
  auto ret = player_api.Init(0, nullptr);
  if (NT_ERC_OK != ret)
  {
    fprintf(stderr, "player_api.Init failed!\n");
    return false;
  }
  else
  {
    fprintf(stdout, "player_api.Init ok!\n");
  }
  return true;
}


窗体相关

Display* display_ = nullptr;
Window   main_wid_ = None;
const char* win_base_title = "Rtmp/Rtsp Live Player Demo";
int EventPoll(int fd, bool is_write, int timeout_ms)
{
  int result;
  do
  {
    struct pollfd info;
    info.fd = fd;
    if (is_write)
    {
      info.events = POLLOUT;
    }
    else
    {
      info.events = POLLIN | POLLPRI;
    }
    result = poll(&info, 1, timeout_ms);
  } while (result < 0 && errno == EINTR);
  return result;
}
bool MY_X11_Pending(Display* display, int timeout_ms)
{
  XFlush(display);
  if (XEventsQueued(display, QueuedAlready) > 0)
  {
    return true;
  }
  if (EventPoll(ConnectionNumber(display), false, timeout_ms))
  {
    if (XPending(display) > 0)
    {
      return true;
    }
  }
  return false;
}
Window CreateSubWindow(Display* display, int screen, Window parent)
{
  XWindowAttributes  parent_win_att;
  XGetWindowAttributes(display, parent, &parent_win_att);
  fprintf(stdout, "parent w:%d, h:%d\n", parent_win_att.width, parent_win_att.height);
  XSetWindowAttributes swa;
  swa.border_pixel = WhitePixel(display, screen);
  swa.event_mask = KeyPressMask | StructureNotifyMask;
  return XCreateWindow(display, parent, 0, 0, parent_win_att.width-4, parent_win_att.height-4,
    2, parent_win_att.depth, InputOutput, parent_win_att.visual, CWEventMask | CWBorderPixel, &swa);
}


Event回调

void NT_OnSDKEventHandle(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 (NT_SP_E_EVENT_ID_DOWNLOAD_SPEED == event_id)
  {
    fprintf(stdout, "OnSDKEventHandle handle:%p speed:%lldkbps, %lldKB/s. \r", handle, (param1 * 8) / 1000, param1 / 1024);
    fflush(stdout);
  }
}


视频分辨率回调

void NT_SDKVideoSizeHandle(NT_HANDLE handle, NT_PVOID userData,
  NT_INT32 width, NT_INT32 height)
{
  if (display_ && main_wid_)
  {
    std::ostringstream ss;
    ss << win_base_title << "  [Video Size: " << width << "*" << height << " ]";
    XStoreName(display_, main_wid_, ss.str().c_str());
  }
}


相关界面

20210721163705858.png

总结

国产操作系统|Linux下的RTMP、RTSP直播播放,经实际测试,延迟和Windows平台一样,毫秒级,随着国产操作系统在无纸化同屏等行业的推进,越来越多的场景需要这样一款稳定性高延迟低的RTMP|RTSP播放器,本文抛砖引玉,感兴趣的开发者可酌情参考。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
3天前
|
安全 Linux 数据安全/隐私保护
探索Linux操作系统的文件权限管理
【9月更文挑战第29天】在数字世界中,文件权限管理如同保护我们隐私的锁。本文将带你了解如何在Linux系统中设置和管理文件权限,确保你的数据安全。我们将一起学习如何通过命令行工具来控制文件访问,就像学习一门新语言一样有趣。准备好了吗?让我们一起开启这场技术之旅!
|
21天前
|
存储 安全 Linux
探索Linux操作系统的心脏:内核
在这篇文章中,我们将深入探讨Linux操作系统的核心—内核。通过简单易懂的语言和比喻,我们会发现内核是如何像心脏一样为系统提供动力,处理数据,并保持一切顺畅运行。从文件系统的管理到进程调度,再到设备驱动,我们将一探究竟,看看内核是怎样支撑起整个操作系统的大厦。无论你是计算机新手还是资深用户,这篇文章都将带你领略Linux内核的魅力,让你对这台复杂机器的内部运作有一个清晰的认识。
50 3
|
21天前
|
存储 数据挖掘 Linux
服务器数据恢复—Linux操作系统网站服务器数据恢复案例
服务器数据恢复环境: 一台linux操作系统服务器上跑了几十个网站,服务器上只有一块SATA硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区
|
8天前
|
编解码 Linux 开发工具
Linux平台x86_64|aarch64架构RTMP推送|轻量级RTSP服务模块集成说明
支持x64_64架构、aarch64架构(需要glibc-2.21及以上版本的Linux系统, 需要libX11.so.6, 需要GLib–2.0, 需安装 libstdc++.so.6.0.21、GLIBCXX_3.4.21、 CXXABI_1.3.9)。
|
2月前
|
安全 Linux 开发工具
探索Linux操作系统:从命令行到脚本编程
【8月更文挑战第31天】在这篇文章中,我们将一起潜入Linux操作系统的海洋,从最基础的命令行操作开始,逐步深入到编写实用的脚本。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和实用技能。我们将通过实际代码示例,展示如何在日常工作中利用Linux的强大功能来简化任务和提高效率。准备好了吗?让我们一起开启这段旅程,探索Linux的奥秘吧!
|
19天前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
22 0
|
2月前
|
网络协议 Linux Shell
探索Linux操作系统:从基础到高级编程
【8月更文挑战第31天】本文旨在为读者提供一条清晰的路径,从Linux操作系统的基础知识出发,逐步深入到高级编程技巧。我们将一起揭开Linux神秘的面纱,了解其内部工作原理,并通过实际代码示例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你带来新的视角和技能提升。
|
2月前
|
Linux
探索Linux操作系统:命令行与脚本编程基础
【8月更文挑战第31天】在这篇文章中,我们将一起踏上一段旅程,深入探索Linux操作系统的奥秘。通过学习命令行的使用和编写简单的脚本,你将能够更高效地与你的计算机进行交流。无论你是新手还是有经验的用户,本文都将为你打开一扇通往Linux世界的大门。准备好了吗?让我们开始吧!
|
2月前
|
Linux
探索Linux操作系统的启动过程
【8月更文挑战第31天】本文将深入探讨Linux系统从按下电源键到登录界面出现之间的神秘世界。我们将一步步揭开内核加载、初始化进程启动和系统服务运行的面纱,同时通过实际代码示例,揭示这一切是如何精妙地编织在一起的。无论你是系统管理员还是对操作系统感兴趣的爱好者,这篇文章都将给你带来新的视角和深刻的见解。
|
2月前
|
存储 Linux 调度
深入理解Linux操作系统的启动过程
【8月更文挑战第31天】本文将深入探讨Linux操作系统的启动过程,包括BIOS、内核、init进程等关键步骤。我们将通过实际代码示例,揭示Linux启动过程中的奥秘,帮助读者更好地理解和掌握Linux系统。
下一篇
无影云桌面