首页 > 解决方案 > 使用 Gstreamer 在嵌入式 Linux 上的 Qt 小部件中嵌入单独的视频流

问题描述

我正在寻找可以解决此问题的任何提示:

我有一个运行嵌入式 linux buildroot 操作系统的 i.MX6 设备,以及一个在该屏幕上运行的基于 Qt5 Widget 的应用程序。我还有一个自定义 SDK,我无法更改,并且仅限于 Qt 5.5.1 库,我需要在 iMX 上构建它。而且由于它是嵌入式的,因此我不能在其上使用诸如“dpkg”或“ldconfig”之类的命令。

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

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

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

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

选项卡 1

选项卡 2

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

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

  1. autovideosink、imxipuvideosink、imxeglvivsink:这些是我在目标上部署时使用上面的示例测试的接收器,并且我得到了未嵌入的流相互重叠。
  2. impxpvideosink :我无法获得输出。
  3. qtvideosink, qtglvideosink :根据本文档,这些需要连接“更新”和“绘制”信号。我尝试了以下行: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 的管道定义功能。
  2. qmlglsink : 据说是这项工作最有前途的接收器,这个只在包中可用,gstreamer1.0-qt5所以我需要将它安装在嵌入式设备上,这在技术上是可行的,但困难且有风险。
  3. VideoSurface + VideoItem:我没有安装 lib QtGStreamer 1.0。
  4. GstGLVideoItem:我没有安装 lib org.freedesktop.gstreamer.GLVideoItem 1.0。
  5. 我安装了 qtquick2videosink 但我不知道如何使用它。该文档不清楚应该在哪个 QML 元素上使用它,我也没有找到任何使用示例。是 VideoSurface 吗?图形视频表面?

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

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

标签: qt5gstreamerembedded-linuxqtquick2qtwidgets

解决方案


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

在所有需要缺少库(QtMultimedia、qmlglsink 等)的解决方案失败后,或者由于未知原因而无法工作后,我了解了帧缓冲区——就我而言,它们基本上只是 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,而应用程序本身正在显示在帧缓冲区 1 上,它出现在 fb0 的顶部。为了实现这一点,我们只需在调用应用程序可执行文件之前将 QT_QPA_EGLFS_FB 环境变量设置为/dev/fb1,因为我们的设备使用 EGLFS插件运行。

对于 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 管道播放视频流,该管道使用硬件加速的视频接收器。


推荐阅读