summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp')
-rw-r--r--src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp221
1 files changed, 221 insertions, 0 deletions
diff --git a/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
new file mode 100644
index 000000000..2f3197e92
--- /dev/null
+++ b/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp
@@ -0,0 +1,221 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtMultimedia/qvideosink.h>
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qthread.h>
+
+#include <common/qgstreamervideooutput_p.h>
+#include <common/qgstreamervideosink_p.h>
+#include <common/qgstsubtitlesink_p.h>
+
+static Q_LOGGING_CATEGORY(qLcMediaVideoOutput, "qt.multimedia.videooutput")
+
+QT_BEGIN_NAMESPACE
+
+QMaybe<QGstreamerVideoOutput *> QGstreamerVideoOutput::create(QObject *parent)
+{
+ QGstElement videoConvert;
+ QGstElement videoScale;
+
+ QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale");
+
+ if (factory) { // videoconvertscale is only available in gstreamer 1.20
+ videoConvert = QGstElement::createFromFactory(factory, "videoConvertScale");
+ } else {
+ videoConvert = QGstElement::createFromFactory("videoconvert", "videoConvert");
+ if (!videoConvert)
+ return qGstErrorMessageCannotFindElement("videoconvert");
+
+ videoScale = QGstElement::createFromFactory("videoscale", "videoScale");
+ if (!videoScale)
+ return qGstErrorMessageCannotFindElement("videoscale");
+ }
+
+ if (!QGstElement::findFactory("fakesink"))
+ return qGstErrorMessageCannotFindElement("fakesink");
+
+ return new QGstreamerVideoOutput(videoConvert, videoScale, parent);
+}
+
+QGstreamerVideoOutput::QGstreamerVideoOutput(QGstElement convert, QGstElement scale,
+ QObject *parent)
+ : QObject(parent),
+ gstVideoOutput(QGstBin::create("videoOutput")),
+ videoQueue{
+ QGstElement::createFromFactory("queue", "videoQueue"),
+ },
+ videoConvert(std::move(convert)),
+ videoScale(std::move(scale)),
+ videoSink{
+ QGstElement::createFromFactory("fakesink", "fakeVideoSink"),
+ }
+{
+ videoSink.set("sync", true);
+ videoSink.set("async", false); // no asynchronous state changes
+
+ if (videoScale) {
+ gstVideoOutput.add(videoQueue, videoConvert, videoScale, videoSink);
+ qLinkGstElements(videoQueue, videoConvert, videoScale, videoSink);
+ } else {
+ gstVideoOutput.add(videoQueue, videoConvert, videoSink);
+ qLinkGstElements(videoQueue, videoConvert, videoSink);
+ }
+
+ gstVideoOutput.addGhostPad(videoQueue, "sink");
+}
+
+QGstreamerVideoOutput::~QGstreamerVideoOutput()
+{
+ gstVideoOutput.setStateSync(GST_STATE_NULL);
+}
+
+void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink)
+{
+ auto *gstVideoSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr;
+ if (gstVideoSink == m_videoSink)
+ return;
+
+ if (m_videoSink)
+ m_videoSink->setPipeline({});
+
+ m_videoSink = gstVideoSink;
+ if (m_videoSink) {
+ m_videoSink->setPipeline(gstPipeline);
+ if (nativeSize.isValid())
+ m_videoSink->setNativeSize(nativeSize);
+ }
+ QGstElement gstSink;
+ if (m_videoSink) {
+ gstSink = m_videoSink->gstSink();
+ } else {
+ gstSink = QGstElement::createFromFactory("fakesink", "fakevideosink");
+ Q_ASSERT(gstSink);
+ gstSink.set("sync", true);
+ gstSink.set("async", false); // no asynchronous state changes
+ }
+
+ if (videoSink == gstSink)
+ return;
+
+ gstPipeline.modifyPipelineWhileNotRunning([&] {
+ if (!videoSink.isNull())
+ gstVideoOutput.stopAndRemoveElements(videoSink);
+
+ videoSink = gstSink;
+ gstVideoOutput.add(videoSink);
+
+ if (videoScale)
+ qLinkGstElements(videoScale, videoSink);
+ else
+ qLinkGstElements(videoConvert, videoSink);
+
+ GstEvent *event = gst_event_new_reconfigure();
+ gst_element_send_event(videoSink.element(), event);
+ videoSink.syncStateWithParent();
+
+ doLinkSubtitleStream();
+ });
+
+ qCDebug(qLcMediaVideoOutput) << "sinkChanged" << gstSink.name();
+
+ gstPipeline.dumpGraph(videoSink.name().constData());
+}
+
+void QGstreamerVideoOutput::setPipeline(const QGstPipeline &pipeline)
+{
+ gstPipeline = pipeline;
+ if (m_videoSink)
+ m_videoSink->setPipeline(gstPipeline);
+}
+
+void QGstreamerVideoOutput::linkSubtitleStream(QGstElement src)
+{
+ qCDebug(qLcMediaVideoOutput) << "link subtitle stream" << src.isNull();
+ if (src == subtitleSrc)
+ return;
+
+ gstPipeline.modifyPipelineWhileNotRunning([&] {
+ subtitleSrc = src;
+ doLinkSubtitleStream();
+ });
+}
+
+void QGstreamerVideoOutput::unlinkSubtitleStream()
+{
+ if (subtitleSrc.isNull())
+ return;
+ qCDebug(qLcMediaVideoOutput) << "unlink subtitle stream";
+ subtitleSrc = {};
+ if (!subtitleSink.isNull()) {
+ gstPipeline.modifyPipelineWhileNotRunning([&] {
+ gstPipeline.stopAndRemoveElements(subtitleSink);
+ return;
+ });
+ subtitleSink = {};
+ }
+ if (m_videoSink)
+ m_videoSink->setSubtitleText({});
+}
+
+void QGstreamerVideoOutput::doLinkSubtitleStream()
+{
+ if (!subtitleSink.isNull()) {
+ gstPipeline.stopAndRemoveElements(subtitleSink);
+ subtitleSink = {};
+ }
+ if (!m_videoSink || subtitleSrc.isNull())
+ return;
+ if (subtitleSink.isNull()) {
+ subtitleSink = m_videoSink->subtitleSink();
+ gstPipeline.add(subtitleSink);
+ }
+ qLinkGstElements(subtitleSrc, subtitleSink);
+}
+
+void QGstreamerVideoOutput::updateNativeSize()
+{
+ if (!m_videoSink)
+ return;
+
+ m_videoSink->setNativeSize(qRotatedFrameSize(nativeSize, rotation));
+}
+
+void QGstreamerVideoOutput::setIsPreview()
+{
+ // configures the queue to be fast and lightweight for camera preview
+ // also avoids blocking the queue in case we have an encodebin attached to the tee as well
+ videoQueue.set("leaky", 2 /*downstream*/);
+ videoQueue.set("silent", true);
+ videoQueue.set("max-size-buffers", uint(1));
+ videoQueue.set("max-size-bytes", uint(0));
+ videoQueue.set("max-size-time", quint64(0));
+}
+
+void QGstreamerVideoOutput::flushSubtitles()
+{
+ if (!subtitleSink.isNull()) {
+ auto pad = subtitleSink.staticPad("sink");
+ auto *event = gst_event_new_flush_start();
+ pad.sendEvent(event);
+ event = gst_event_new_flush_stop(false);
+ pad.sendEvent(event);
+ }
+}
+
+void QGstreamerVideoOutput::setNativeSize(QSize sz)
+{
+ nativeSize = sz;
+ updateNativeSize();
+}
+
+void QGstreamerVideoOutput::setRotation(QtVideo::Rotation rot)
+{
+ rotation = rot;
+ updateNativeSize();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qgstreamervideooutput_p.cpp"