使用 Gstreamer 在 embedded-linux 上的 Qt 小部件中嵌入单独的视频流

Embed separate video streams in Qt widgets on embedded-linux using Gstreamer

我正在寻找关于这个问题的任何提示:

我有一个 i.MX6 设备 运行 嵌入了 linux buildroot OS,还有一个基于 Qt5 Widget 的应用 运行 在那个屏幕上.我还有一个自定义 SDK,我无法更改并且仅限于 Qt 5.5.1 库,我需要在 iMX 上构建它。由于它是嵌入式的,我无法在其上使用 'dpkg' 或 'ldconfig' 等命令。

Objective: 我的目标是向 Qt 5.5 应用程序添加一项功能,在单独的小部件中显示来自多个摄像头(大约 4 到 6 个)的实时视频流。需要硬件加速。

没有用于测试的摄像头,我使用 VLC 流式传输 3 个视频(它们 运行 在本地后台播放),我的应用程序通过 RTSP 读取这些流。

我尝试过的:我一直在学习 Qt 和 Gstreamer 以找到解决方案。我尝试了很多不同的东西,使用了屏幕上已经安装的所有有前途的视频接收器,目前正在尝试基于 QtQuick 的解决方案。

我制作了一个简单的 Qt 小部件应用程序进行测试,当我 运行 它在我的 x86 系统 (Ubuntu 16.04) 上运行时,它运行良好。正如您在这些屏幕截图中所见:

选项卡 1

选项卡 2

流很好地集成在单独的选项卡中,我可以通过单击选项卡在它们之间切换。 但是,运行在目标设备(具有不同的接收器,例如 imxipuvideosink)上使用相同的应用程序会显示 window“上方”的所有流并彼此叠加。它们没有嵌入到 Tab 小部件中,可能是因为设备不能依赖 X。

我尝试了许多不同的方法来将流嵌入到小部件中,这里是其中的一些:

  1. autovideosink、imxipuvideosink、imxeglvivsink:这些是我在目标上部署时用上面的示例测试的接收器,我得到了相互重叠的未嵌入流。
  2. impxpvideosink : 我无法获得输出。
  3. qtvideosink、qtglvideosink:根据this documentation,这些需要连接“更新”和“绘画”信号。我尝试了以下行:QGlib::connect(sink, "update", widget, &QWidget::update); 但它抛出“没有匹配的函数调用...”错误,更新槽标记为“<未解析的重载函数类型>”。 QObject::connect 做同样的事情。我可能在这里做错了。
  4. qwidgetvideosink:使用此管道:rtspsrc location=rtsp://10.0.1.1:8554/stream ! videoparse width=400 height=300 format=i420 ! videoconvert ! qwidget5videosink 我得到一个输出,但它在 x86 上已损坏,并且目标设备没有安装 videoparse 插件。

因为我不得不使用 QWidget 应用程序,所以使用 QML 的唯一方法是在基于小部件的应用程序中创建一个 QQuickWidget。我将此 QQuickWidget 的源设置为一个简单的 .qml 文件并尝试了以下操作:

  1. MediaPlayer + VideoOutput :这实际上在两个系统上都按照我希望的方式工作,即视频显示并嵌入到单独的小部件中。但是 AFAIK MediaPlayer 并没有从硬件加速中受益,所以一些实例在开始时崩溃,其余的开始播放,但帧抖动很强。因为我的SDK仅限于Qt和QtMultimedia 5.5,所以我不能使用5.12中引入的MediaPlayer's pipeline definition feature
  2. qmlglsink :据说是这项工作最有前途的接收器,这个接收器只在包 gstreamer1.0-qt5 中可用,所以我需要将它安装在嵌入式设备上,这在技术上是可行的,但困难且有风险。
  3. VideoSurface + VideoItem : 我没有安装 lib QtGStreamer 1.0。
  4. GstGLVideoItem : 我没有安装 lib org.freedesktop.gstreamer.GLVideoItem 1.0。
  5. 我安装了 qtquick2videosink 但我不知道如何使用它。 The documentation 不清楚它应该用在什么 QML 元素上,我也没有找到任何用法示例。是 VideoSurface 吗?图形视频表面 ?

我知道这是非常受限的,但由于这是嵌入式的,所以我想保留安装更多包和库作为最后的手段。而且,如果我必须安装一些东西,我应该按照什么顺序从最有前途的到不太有前途的尝试它们?感谢任何建议或示例链接,我正在寻找一般指导以了解我错过了什么或知道我应该如何处理这个问题。

编辑: 我用 qwidgetvideosink 搜索了更多内容,并找到了使其工作的方法。以下管道是我发现的最佳结果:rtspsrc location=rtsp://10.0.1.1:8554/stream ! decodebin ! imxg2dvideotransform ! clockoverlay ! qwidget5videosink sync=false。不幸的是,此处的性能与使用 MediaPlayer 时一样差,甚至更差。但至少使用这种方法我可以自定义管道。我尝试了几种不同的方法,但找不到更好的解决方案。我也找不到用更精确的插件替换 decodebin 的方法。任何帮助也将不胜感激。

你要的是imxeglvivsink。这个有一个临时传递 window id。 您可以将您的代码基于: https://thiblahute.github.io/GStreamer-doc/gst-plugins-base-video-1.0/videooverlay.html?gi-language=c

有 gtk+ 和 qt 的示例。现在我看了一下,我在 qt 中使用了 gtk+ 方式(使用总线同步处理程序)... 我在 imx6 上用 imxeglvivsink

这样做了

我想分享我们在这个特定问题上寻求的解决方案,希望它能帮助遇到类似问题的任何人。

在所有需要缺少库(QtMultimedia、qmlglsink 等)的解决方案都失败后,或者由于未知原因而无法工作后,我了解了 framebuffers——它们基本上只是 GPU 的层就我而言 - 以及如何在这种情况下使用它们。

事实证明,我一直在使用的 linux 嵌入式设备有 3 个帧缓冲区,这使我们能够将应用程序拆分为一个用于视频流播放的“后台”帧缓冲区和一个“前景”帧缓冲区用于叠加显示。每当我们希望背景中的视频变得可见时,叠加层(Qt MainWindow)就需要透明。为此,我们使用了 alpha 混合和颜色键。

在测试了这个解决方案的各个部分之后,我们最终得到了一个启动两个管道的应用程序(因为我想同时在屏幕上显示 2 个摄像头,并且每个摄像头都可以使用输入切换到另一个流-选择器)。管道结构如下所示,例如:

input-selector name=selector ! decodebin ! textoverlay name=text0 ! queue !
imxg2dvideosink framebuffer=/dev/fb0 name=end0 force-aspect-ratio=false
    window-x-coord=0 window-y-coord=0 window-width=512 window-height=473
rtspsrc location=rtsp://10.0.1.1:8554/stream name=src0 ! queue name=qs_0 ! selector.sink_0
rtspsrc location=rtsp://10.0.1.1:8556/stream name=src2 ! queue name=qs_2 ! selector.sink_1
rtspsrc location=rtsp://10.0.1.1:8558/stream name=src4 ! queue name=qs_4 ! selector.sink_2

我们将帧缓冲区 属性 传递给接收器,以便它将视频发送到帧缓冲区 0,而应用程序本身显示在出现在 fb0 顶部的帧缓冲区 1 上。为实现这一点,我们只需在调用应用程序可执行文件之前将 QT_QPA_EGLFS_FB env 变量设置为 /dev/fb1,因为我们的设备使用 EGLFS plugin.

运行

对于 alpha 混合和颜色键控部分,我们必须在应用程序中执行此操作:

#include <fcntl.h>
#include <linux/mxcfb.h>
#include <sys/ioctl.h>

...

// Read overlay framebuffer fb1
int fb = 0;
fb = open("/dev/fb1", O_RDWR);
if (fb < 0)
    qWarning() << "Error, framebuffer cannot be opened";

// Enable alpha
struct mxcfb_gbl_alpha alphaStruct;
alphaStruct.enable = 1;
alphaStruct.alpha = 255;
if (ioctl(fb, MXCFB_SET_GBL_ALPHA, &alphaStruct) < 0)
    qWarning() << "Error, framebuffer alpha cannot be set";

// Set color key to pure blue
struct mxcfb_color_key colorKeyStruct;
guint32 colorKeyValue = g_ascii_strtoull("0x0000FF", NULL, 16);
colorKeyStruct.color_key = colorKeyValue;
colorKeyStruct.enable = 1;
if (ioctl(fb, MXCFB_SET_CLR_KEY, &colorKeyStruct) < 0)
    qWarning() << "Error, framebuffer color key cannot be set";

...

基本上,这会打开覆盖应用 运行 所在的帧缓冲区,在其上启用 alpha,然后将一种颜色(蓝色)设置为透明色。因此,具有此确切颜色值的每个像素都将在背景中显示 运行 的视频。

现在我们有一个应用程序可以通过使用硬件加速视频接收器的自定义 Gstreamer 管道播放视频流。