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 <QtMultimedia/qvideosink.h> |
5 | |
6 | #include <QtCore/qloggingcategory.h> |
7 | |
8 | #include <common/qgstreamervideooutput_p.h> |
9 | #include <common/qgstreamervideosink_p.h> |
10 | #include <common/qgstsubtitlesink_p.h> |
11 | |
12 | static Q_LOGGING_CATEGORY(qLcMediaVideoOutput, "qt.multimedia.videooutput" ) |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | static QGstElement makeVideoConvertScale(const char *name) |
17 | { |
18 | QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale" ); |
19 | if (factory) // videoconvertscale is only available in gstreamer 1.20 |
20 | return QGstElement::createFromFactory(factory, name); |
21 | |
22 | return QGstBin::createFromPipelineDescription(pipelineDescription: "videoconvert ! videoscale" , name, |
23 | /*ghostUnlinkedPads=*/true); |
24 | } |
25 | |
26 | QMaybe<QGstreamerVideoOutput *> QGstreamerVideoOutput::create(QObject *parent) |
27 | { |
28 | QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale" ); |
29 | |
30 | static std::optional<QString> elementCheck = []() -> std::optional<QString> { |
31 | std::optional<QString> error = qGstErrorMessageIfElementsNotAvailable(arg: "fakesink" , args: "queue" ); |
32 | if (error) |
33 | return error; |
34 | |
35 | QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale" ); |
36 | if (factory) |
37 | return std::nullopt; |
38 | |
39 | return qGstErrorMessageIfElementsNotAvailable(arg: "videoconvert" , args: "videoscale" ); |
40 | }(); |
41 | |
42 | if (elementCheck) |
43 | return *elementCheck; |
44 | |
45 | return new QGstreamerVideoOutput(parent); |
46 | } |
47 | |
48 | QGstreamerVideoOutput::QGstreamerVideoOutput(QObject *parent) |
49 | : QObject(parent), |
50 | m_outputBin{ |
51 | QGstBin::create(name: "videoOutput" ), |
52 | }, |
53 | m_videoQueue{ |
54 | QGstElement::createFromFactory(factory: "queue" , name: "videoQueue" ), |
55 | }, |
56 | m_videoConvertScale{ |
57 | makeVideoConvertScale(name: "videoConvertScale" ), |
58 | }, |
59 | m_videoSink{ |
60 | QGstElement::createFromFactory(factory: "fakesink" , name: "fakeVideoSink" ), |
61 | } |
62 | { |
63 | m_videoSink.set(property: "sync" , b: true); |
64 | |
65 | m_outputBin.add(ts: m_videoQueue, ts: m_videoConvertScale, ts: m_videoSink); |
66 | qLinkGstElements(ts: m_videoQueue, ts: m_videoConvertScale, ts: m_videoSink); |
67 | |
68 | m_subtitleSink = QGstSubtitleSink::createSink(observer: this); |
69 | |
70 | m_outputBin.addGhostPad(child: m_videoQueue, name: "sink" ); |
71 | } |
72 | |
73 | QGstreamerVideoOutput::~QGstreamerVideoOutput() |
74 | { |
75 | QObject::disconnect(m_subtitleConnection); |
76 | m_outputBin.setStateSync(state: GST_STATE_NULL); |
77 | } |
78 | |
79 | void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink) |
80 | { |
81 | using namespace std::chrono_literals; |
82 | |
83 | auto *gstSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr; |
84 | if (gstSink == m_platformVideoSink) |
85 | return; |
86 | |
87 | m_platformVideoSink = gstSink; |
88 | if (m_platformVideoSink) { |
89 | m_platformVideoSink->setActive(m_isActive); |
90 | if (m_nativeSize.isValid()) |
91 | m_platformVideoSink->setNativeSize(m_nativeSize); |
92 | } |
93 | QGstElement videoSink; |
94 | if (m_platformVideoSink) { |
95 | videoSink = m_platformVideoSink->gstSink(); |
96 | } else { |
97 | videoSink = QGstElement::createFromFactory(factory: "fakesink" , name: "fakevideosink" ); |
98 | Q_ASSERT(videoSink); |
99 | videoSink.set(property: "sync" , b: true); |
100 | } |
101 | |
102 | QObject::disconnect(m_subtitleConnection); |
103 | if (sink) { |
104 | m_subtitleConnection = QObject::connect(sender: this, signal: &QGstreamerVideoOutput::subtitleChanged, context: sink, |
105 | slot: [sink](const QString &subtitle) { |
106 | sink->setSubtitleText(subtitle); |
107 | }); |
108 | sink->setSubtitleText(m_lastSubtitleString); |
109 | } |
110 | |
111 | if (m_videoSink == videoSink) |
112 | return; |
113 | |
114 | m_videoConvertScale.src().modifyPipelineInIdleProbe(f: [&] { |
115 | if (m_videoSink) |
116 | m_outputBin.stopAndRemoveElements(ts&: m_videoSink); |
117 | |
118 | m_videoSink = std::move(videoSink); |
119 | m_outputBin.add(ts: m_videoSink); |
120 | |
121 | qLinkGstElements(ts: m_videoConvertScale, ts: m_videoSink); |
122 | |
123 | GstEvent *event = gst_event_new_reconfigure(); |
124 | gst_element_send_event(element: m_videoSink.element(), event); |
125 | m_videoSink.syncStateWithParent(); |
126 | }); |
127 | |
128 | qCDebug(qLcMediaVideoOutput) << "sinkChanged" << m_videoSink.name(); |
129 | m_videoConvertScale.dumpPipelineGraph(filename: m_videoSink.name().constData()); |
130 | } |
131 | |
132 | void QGstreamerVideoOutput::setActive(bool isActive) |
133 | { |
134 | if (m_isActive == isActive) |
135 | return; |
136 | |
137 | m_isActive = isActive; |
138 | if (m_platformVideoSink) |
139 | m_platformVideoSink->setActive(isActive); |
140 | } |
141 | |
142 | void QGstreamerVideoOutput::updateNativeSize() |
143 | { |
144 | if (!m_platformVideoSink) |
145 | return; |
146 | |
147 | m_platformVideoSink->setNativeSize(qRotatedFrameSize(size: m_nativeSize, rotation: m_rotation)); |
148 | } |
149 | |
150 | void QGstreamerVideoOutput::setIsPreview() |
151 | { |
152 | // configures the queue to be fast and lightweight for camera preview |
153 | // also avoids blocking the queue in case we have an encodebin attached to the tee as well |
154 | m_videoQueue.set(property: "leaky" , i: 2 /*downstream*/); |
155 | m_videoQueue.set(property: "silent" , b: true); |
156 | m_videoQueue.set(property: "max-size-buffers" , i: int(1)); |
157 | m_videoQueue.set(property: "max-size-bytes" , i: unsigned(0)); |
158 | m_videoQueue.set(property: "max-size-time" , i: uint64_t(0)); |
159 | } |
160 | |
161 | void QGstreamerVideoOutput::flushSubtitles() |
162 | { |
163 | if (!m_subtitleSink.isNull()) { |
164 | auto pad = m_subtitleSink.staticPad(name: "sink" ); |
165 | auto *event = gst_event_new_flush_start(); |
166 | pad.sendEvent(event); |
167 | event = gst_event_new_flush_stop(reset_time: false); |
168 | pad.sendEvent(event); |
169 | } |
170 | } |
171 | |
172 | void QGstreamerVideoOutput::setNativeSize(QSize sz) |
173 | { |
174 | m_nativeSize = sz; |
175 | updateNativeSize(); |
176 | } |
177 | |
178 | void QGstreamerVideoOutput::setRotation(QtVideo::Rotation rot) |
179 | { |
180 | m_rotation = rot; |
181 | updateNativeSize(); |
182 | } |
183 | |
184 | void QGstreamerVideoOutput::updateSubtitle(QString string) |
185 | { |
186 | // GStreamer thread |
187 | |
188 | QMetaObject::invokeMethod(object: this, function: [this, string = std::move(string)]() mutable { |
189 | m_lastSubtitleString = string; |
190 | Q_EMIT subtitleChanged(std::move(string)); |
191 | }); |
192 | } |
193 | |
194 | QT_END_NAMESPACE |
195 | |
196 | #include "moc_qgstreamervideooutput_p.cpp" |
197 | |