Parallel pipelines with qml6glsink

Hello,
I have recently compiled the GStreamer 1.24 with the Qt6 and wayland support (I needed to deactivated qml6glmixer). I’m able to successfully capture and show the camera streams with the following commands (for camera 1 and 2, respectively):

gst-launch-1.0 v4l2src device=/dev/video7 ! waylandsink
gst-launch-1.0 v4l2src device=/dev/video0 ! waylandsink

This works perfectly fine.
Basing on the example I switched to Qt code, create a pipeline (v4l2src + glupload + qml6glsink) and the corresponding GstGLQt6VideoItem QML’s item (see below for code). This also works perfectly.
Now I want to show the streams of 2 cameras in parallel. For that I duplicated the pipeline and the QML item, but the 2nd GstGLQt6VideoItem show exactly the same stream as the 1st one. Why is it so? What am I doing wrong?
Note that once my application is running (showing duplicated stream of the 1st camera) I checked with the gst-launch commands and both devices are busy, so I suppose that the 2nd pipeline is reading the stream of the 2nd camera but it is not uploaded and shown…
Note also that this code but adapted to Qt5 framework works perfectly fine and streams of both cameras are displayed.

Main.qml

import QtQuick 2.12
import QtQuick.Window 2.12

import org.freedesktop.gstreamer.Qt6GLVideoItem 1.0

Window {
    width: 1280
    height: 800
    visible: true
    title: qsTr("Hello World")

    GstGLQt6VideoItem {
        id: video1
        objectName: "cam1"
        anchors.left: parent.left
        anchors.top: parent.top
        width: 720
        height: 576
    }

    GstGLQt6VideoItem {
        id: video2
        objectName: "cam2"
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        width: 720
        height: 576
    }
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <QRunnable>

#include <gst/gst.h>

class SetPlaying : public QRunnable
{
public:
    SetPlaying(GstElement* pipeline);
    virtual ~SetPlaying();

    void run();

private:
    GstElement* m_pipeline = nullptr;
};

SetPlaying::SetPlaying(GstElement *pipeline)
{
    m_pipeline = pipeline ? static_cast<GstElement*>(pipeline) : nullptr;
}

SetPlaying::~SetPlaying()
{
    if (m_pipeline) gst_object_unref(m_pipeline);
}

void SetPlaying::run()
{
    if (m_pipeline) gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
}

int main(int argc, char *argv[])
{
    int ret = 0;
    gst_init(&argc, &argv);

    {
        QGuiApplication app(argc, argv);

        // Create pipelines before loading QML
        GstElement* pipeline1 = gst_pipeline_new(nullptr);
        GstElement* src1 = gst_element_factory_make("v4l2src", nullptr);
        GstElement* glupload1 = gst_element_factory_make("glupload", nullptr);
        GstElement* sink1 = gst_element_factory_make("qml6glsink", nullptr);
        g_assert(src1 && glupload1 && sink1);

        GstElement* pipeline2 = gst_pipeline_new(nullptr);
        GstElement* src2 = gst_element_factory_make("v4l2src", nullptr);
        GstElement* glupload2 = gst_element_factory_make("glupload", nullptr);
        GstElement* sink2 = gst_element_factory_make("qml6glsink", nullptr);
        g_assert(src2 && glupload2 && sink2);

        // set source cameras
        g_object_set(src1, "device", "/dev/video7", nullptr);
        g_object_set(src1, "io-mode", 2, nullptr);
        g_object_set(src2, "device", "/dev/video0", nullptr);
        g_object_set(src2, "io-mode", 2, nullptr);

        // bind pipeline
        gst_bin_add_many(GST_BIN(pipeline1), src1, glupload1, sink1, nullptr);
        gst_bin_add_many(GST_BIN(pipeline2), src2, glupload2, sink2, nullptr);
        gst_element_link_many(src1, glupload1, sink1, nullptr);
        gst_element_link_many(src2, glupload2, sink2, nullptr);

        QQmlApplicationEngine engine;
        QObject::connect(
            &engine,
            &QQmlApplicationEngine::objectCreationFailed,
            &app,
            []() { QCoreApplication::exit(-1); },
            Qt::QueuedConnection);
        engine.loadFromModule("qmlSinkQt6", "Main");

        // Set the sink
        QQuickWindow* rootObject = static_cast<QQuickWindow*>(engine.rootObjects().first());
        QQuickItem* cam1 = rootObject->findChild<QQuickItem*>("cam1");
        QQuickItem* cam2 = rootObject->findChild<QQuickItem*>("cam2");
        g_assert(cam1 && cam2 && cam1 != cam2);
        g_object_set(sink1, "widget", cam1, nullptr);
        g_object_set(sink2, "widget", cam2, nullptr);

        rootObject->scheduleRenderJob(new SetPlaying(pipeline1), QQuickWindow::BeforeSynchronizingStage);
        rootObject->scheduleRenderJob(new SetPlaying(pipeline2), QQuickWindow::BeforeSynchronizingStage);

        ret = app.exec();

        gst_element_set_state(pipeline1, GST_STATE_NULL);
        gst_element_set_state(pipeline2, GST_STATE_NULL);
        gst_object_unref(pipeline1);
        gst_object_unref(pipeline2);
    }

    gst_deinit();
    return ret;
}

CMakeList.txt:

cmake_minimum_required(VERSION 3.16)

project(qmlSinkQt6 VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(B_SYSROOT "/opt/we-wayland-qt5/2.7.4/sysroots/armv7at2hf-neon-poky-linux-gnueabi/")

find_package(Qt6 6.5 REQUIRED COMPONENTS Quick)

qt_standard_project_setup(REQUIRES 6.5)

qt_add_executable(appqmlSinkQt6
    main.cpp
)

qt_add_qml_module(appqmlSinkQt6
    URI qmlSinkQt6
    VERSION 1.0
    QML_FILES Main.qml
)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appqmlSinkQt6 PROPERTIES
#    MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appqmlSinkQt6
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_include_directories(appqmlSinkQt6
    PRIVATE ${B_SYSROOT}/usr/include/gstreamer-1.0
    PRIVATE ${B_SYSROOT}/usr/include/glib-2.0
    PRIVATE ${B_SYSROOT}/usr/lib/glib-2.0/include
)

target_link_libraries(appqmlSinkQt6
    PRIVATE Qt6::Quick
    -lglib-2.0 -lgstreamer-1.0 -lgobject-2.0
)

include(GNUInstallDirs)
install(TARGETS appqmlSinkQt6
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

Hello,
I made an additional observation: it seems that the stream that is being duplicated is always the one that is affected to the first GstGLQt6VideoItem component from the QML file.
To be more precise: if video1/cam1 is affected to sink1 and video2/cam2 to sink2 then:

  • if video1 is first in QML file both video1 and video2 will output sink1
  • if video2 is first in QML file both video1 and video2 will output sink2

Why is it so? Can I have multiple GstGLQt6VideoItem components in the same QML file/module?

Ok, I found the correction myself.
The problem comes from a dtor of GstQSGTexture (gstqsg6material.cc file) in the qt6 plugin of gstreamer. Insted of simply calling

delete m_texture;

one should call

m_texture->deleteLater();

as the texture may still be in use.
I filed an issue, I’ll file a patch request soon.

Good for you to find the issue. Is this part done differently in Qt5 sink?

I’m not an expert in gstreamer code, but if I understood correctly Qt has reworked their multimedia package and added an abstraction to rendering which made the qt5 plugin incompatible with Qt6.
I tried to compare the code qt5 vs qt6 plugins code, and for me qt6 was somewhat rewritten from scratch.

That is true. Qt multimedia module was pretty much rewritten and lower Qt6 versions has quite some bugs in this module. Also FFMpeg is now the default backend of Qt multimedia module.