Parallel pipelines with qml6glsink

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.


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
        width: 720
        height: 576

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


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

#include <gst/gst.h>

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

    void run();

    GstElement* m_pipeline = nullptr;

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

    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;
            []() { QCoreApplication::exit(-1); },
        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);

    return ret;


cmake_minimum_required(VERSION 3.16)

project(qmlSinkQt6 VERSION 0.1 LANGUAGES CXX)

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)


    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

    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

    PRIVATE Qt6::Quick
    -lglib-2.0 -lgstreamer-1.0 -lgobject-2.0

install(TARGETS appqmlSinkQt6

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 ( file) in the qt6 plugin of gstreamer. Insted of simply calling

delete m_texture;

one should call


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.