1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qgstreamervideooutput_p.h>
5#include <qgstreamervideosink_p.h>
6#include <qgstsubtitlesink_p.h>
7#include <qvideosink.h>
8
9#include <QtCore/qloggingcategory.h>
10#include <qthread.h>
11
12static Q_LOGGING_CATEGORY(qLcMediaVideoOutput, "qt.multimedia.videooutput")
13
14QT_BEGIN_NAMESPACE
15
16QMaybe<QGstreamerVideoOutput *> QGstreamerVideoOutput::create(QObject *parent)
17{
18 QGstElement videoConvert("videoconvert", "videoConvert");
19 if (!videoConvert)
20 return errorMessageCannotFindElement(element: "videoconvert");
21
22 QGstElement videoSink("fakesink", "fakeVideoSink");
23 if (!videoSink)
24 return errorMessageCannotFindElement(element: "fakesink");
25
26 return new QGstreamerVideoOutput(videoConvert, videoSink, parent);
27}
28
29QGstreamerVideoOutput::QGstreamerVideoOutput(QGstElement convert, QGstElement sink,
30 QObject *parent)
31 : QObject(parent),
32 gstVideoOutput("videoOutput"),
33 videoConvert(std::move(convert)),
34 videoSink(std::move(sink))
35{
36 videoQueue = QGstElement("queue", "videoQueue");
37 videoSink.set(property: "sync", b: true);
38 gstVideoOutput.add(e1: videoQueue, e2: videoConvert, e3: videoSink);
39 if (!videoQueue.link(n1: videoConvert, n2: videoSink))
40 qCDebug(qLcMediaVideoOutput) << ">>>>>> linking failed";
41
42 gstVideoOutput.addGhostPad(child: videoQueue, name: "sink");
43}
44
45QGstreamerVideoOutput::~QGstreamerVideoOutput()
46{
47 gstVideoOutput.setStateSync(GST_STATE_NULL);
48}
49
50void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink)
51{
52 auto *gstVideoSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr;
53 if (gstVideoSink == m_videoSink)
54 return;
55
56 if (m_videoSink)
57 m_videoSink->setPipeline({});
58
59 m_videoSink = gstVideoSink;
60 if (m_videoSink)
61 m_videoSink->setPipeline(gstPipeline);
62
63 QGstElement gstSink;
64 if (m_videoSink) {
65 gstSink = m_videoSink->gstSink();
66 isFakeSink = false;
67 } else {
68 gstSink = QGstElement("fakesink", "fakevideosink");
69 Q_ASSERT(gstSink);
70 gstSink.set(property: "sync", b: true);
71 isFakeSink = true;
72 }
73
74 if (videoSink == gstSink)
75 return;
76
77 gstPipeline.beginConfig();
78 if (!videoSink.isNull()) {
79 gstVideoOutput.remove(element: videoSink);
80 videoSink.setStateSync(GST_STATE_NULL);
81 }
82 videoSink = gstSink;
83 gstVideoOutput.add(element: videoSink);
84
85 videoConvert.link(next: videoSink);
86 GstEvent *event = gst_event_new_reconfigure();
87 gst_element_send_event(element: videoSink.element(), event);
88 videoSink.syncStateWithParent();
89
90 doLinkSubtitleStream();
91
92 gstPipeline.endConfig();
93
94 qCDebug(qLcMediaVideoOutput) << "sinkChanged" << gstSink.name();
95
96 GST_DEBUG_BIN_TO_DOT_FILE(gstPipeline.bin(),
97 GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE |
98 GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES),
99 videoSink.name());
100
101}
102
103void QGstreamerVideoOutput::setPipeline(const QGstPipeline &pipeline)
104{
105 gstPipeline = pipeline;
106 if (m_videoSink)
107 m_videoSink->setPipeline(gstPipeline);
108}
109
110void QGstreamerVideoOutput::linkSubtitleStream(QGstElement src)
111{
112 qCDebug(qLcMediaVideoOutput) << "link subtitle stream" << src.isNull();
113 if (src == subtitleSrc)
114 return;
115
116 gstPipeline.beginConfig();
117 subtitleSrc = src;
118 doLinkSubtitleStream();
119 gstPipeline.endConfig();
120}
121
122void QGstreamerVideoOutput::unlinkSubtitleStream()
123{
124 if (subtitleSrc.isNull())
125 return;
126 qCDebug(qLcMediaVideoOutput) << "unlink subtitle stream";
127 subtitleSrc = {};
128 if (!subtitleSink.isNull()) {
129 gstPipeline.beginConfig();
130 gstPipeline.remove(element: subtitleSink);
131 gstPipeline.endConfig();
132 subtitleSink.setStateSync(GST_STATE_NULL);
133 subtitleSink = {};
134 }
135 if (m_videoSink)
136 m_videoSink->setSubtitleText({});
137}
138
139void QGstreamerVideoOutput::doLinkSubtitleStream()
140{
141 if (!subtitleSink.isNull()) {
142 gstPipeline.remove(element: subtitleSink);
143 subtitleSink.setStateSync(GST_STATE_NULL);
144 subtitleSink = {};
145 }
146 if (!m_videoSink || subtitleSrc.isNull())
147 return;
148 if (subtitleSink.isNull()) {
149 subtitleSink = m_videoSink->subtitleSink();
150 gstPipeline.add(element: subtitleSink);
151 }
152 if (!subtitleSrc.link(next: subtitleSink))
153 qCDebug(qLcMediaVideoOutput) << "link subtitle stream failed";
154}
155
156void QGstreamerVideoOutput::setIsPreview()
157{
158 // configures the queue to be fast and lightweight for camera preview
159 // also avoids blocking the queue in case we have an encodebin attached to the tee as well
160 videoQueue.set(property: "leaky", i: 2 /*downstream*/);
161 videoQueue.set(property: "silent", b: true);
162 videoQueue.set(property: "max-size-buffers", i: uint(1));
163 videoQueue.set(property: "max-size-bytes", i: uint(0));
164 videoQueue.set(property: "max-size-time", i: quint64(0));
165}
166
167void QGstreamerVideoOutput::flushSubtitles()
168{
169 if (!subtitleSink.isNull()) {
170 auto pad = subtitleSink.staticPad(name: "sink");
171 auto *event = gst_event_new_flush_start();
172 pad.sendEvent(event);
173 event = gst_event_new_flush_stop(reset_time: false);
174 pad.sendEvent(event);
175 }
176}
177
178QT_END_NAMESPACE
179
180#include "moc_qgstreamervideooutput_p.cpp"
181

source code of qtmultimedia/src/plugins/multimedia/gstreamer/common/qgstreamervideooutput.cpp