1// Copyright (C) 2016 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 <QtCore/qcoreapplication.h>
5#include <QtCore/qloggingcategory.h>
6
7#include "qgstpipeline_p.h"
8#include "qgst_bus_observer_p.h"
9
10#include <thread>
11
12QT_BEGIN_NAMESPACE
13
14static Q_LOGGING_CATEGORY(qLcGstPipeline, "qt.multimedia.gstpipeline");
15
16struct QGstPipelinePrivate
17{
18 explicit QGstPipelinePrivate(QGstBusHandle);
19 ~QGstPipelinePrivate();
20
21 std::chrono::nanoseconds m_position{};
22 double m_rate = 1.;
23 std::unique_ptr<QGstBusObserver> m_busObserver;
24};
25
26QGstPipelinePrivate::QGstPipelinePrivate(QGstBusHandle bus)
27 : m_busObserver{
28 std::make_unique<QGstBusObserver>(args: std::move(bus)),
29 }
30{
31 Q_ASSERT(QThread::isMainThread());
32}
33
34QGstPipelinePrivate::~QGstPipelinePrivate()
35{
36 m_busObserver->close();
37
38 if (m_busObserver->currentThreadIsNotifierThread())
39 return;
40
41 // The QGstPipelinePrivate is owned the the GstPipeline and can be destroyed from a gstreamer
42 // thread. In this case we cannot destroy the object immediately, but need to marshall it
43 // through the event loop of the main thread
44 QMetaObject::invokeMethod(qApp, function: [bus = std::move(m_busObserver)] {
45 // nothing to do, we just extend the lifetime of the bus
46 });
47}
48
49// QGstPipeline
50
51QGstPipeline QGstPipeline::create(const char *name)
52{
53 GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(arg: gst_pipeline_new(name));
54 return adopt(pipeline);
55}
56
57QGstPipeline QGstPipeline::createFromFactory(const char *factory, const char *name)
58{
59 QGstElement playbin3 = QGstElement::createFromFactory(factory, name);
60 GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(arg: playbin3.element());
61
62 return QGstPipeline::adopt(pipeline);
63}
64
65QGstPipeline QGstPipeline::adopt(GstPipeline *pipeline)
66{
67 QGstPipeline wrappedObject{
68 pipeline,
69 QGstPipeline::NeedsRef,
70 };
71
72 QGstBusHandle bus{
73 gst_pipeline_get_bus(pipeline),
74 QGstBusHandle::HasRef,
75 };
76
77 auto d = std::make_unique<QGstPipelinePrivate>(args: std::move(bus));
78 wrappedObject.set(property: "pipeline-private", object: std::move(d));
79
80 return wrappedObject;
81}
82
83QGstPipeline::QGstPipeline(GstPipeline *p, RefMode mode) : QGstBin(qGstCheckedCast<GstBin>(arg: p), mode)
84{
85}
86
87QGstPipeline::~QGstPipeline() = default;
88
89void QGstPipeline::installMessageFilter(QGstreamerSyncMessageFilter *filter)
90{
91 QGstPipelinePrivate *d = getPrivate();
92 d->m_busObserver->installMessageFilter(filter);
93}
94
95void QGstPipeline::removeMessageFilter(QGstreamerSyncMessageFilter *filter)
96{
97 QGstPipelinePrivate *d = getPrivate();
98 d->m_busObserver->removeMessageFilter(filter);
99}
100
101void QGstPipeline::installMessageFilter(QGstreamerBusMessageFilter *filter)
102{
103 QGstPipelinePrivate *d = getPrivate();
104 d->m_busObserver->installMessageFilter(filter);
105}
106
107void QGstPipeline::removeMessageFilter(QGstreamerBusMessageFilter *filter)
108{
109 QGstPipelinePrivate *d = getPrivate();
110 d->m_busObserver->removeMessageFilter(filter);
111}
112
113GstStateChangeReturn QGstPipeline::setState(GstState state)
114{
115 return gst_element_set_state(element: element(), state);
116}
117
118bool QGstPipeline::processNextPendingMessage(GstMessageType types, std::chrono::nanoseconds timeout)
119{
120 QGstPipelinePrivate *d = getPrivate();
121 return d->m_busObserver->processNextPendingMessage(type: types, timeout);
122}
123
124bool QGstPipeline::processNextPendingMessage(std::chrono::nanoseconds timeout)
125{
126 return processNextPendingMessage(types: GST_MESSAGE_ANY, timeout);
127}
128
129void QGstPipeline::flush()
130{
131 seek(pos: position(), /*flush=*/true);
132}
133
134void QGstPipeline::seek(std::chrono::nanoseconds pos, double rate, bool flush)
135{
136 using namespace std::chrono_literals;
137
138 QGstPipelinePrivate *d = getPrivate();
139 // always adjust the rate, so it can be set before playback starts
140 // setting position needs a loaded media file that's seekable
141
142 qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos << "rate:" << rate
143 << (flush ? "flushing" : "not flushing");
144
145 GstSeekFlags seekFlags = flush ? GST_SEEK_FLAG_FLUSH : GST_SEEK_FLAG_NONE;
146 seekFlags = GstSeekFlags(seekFlags | GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_ACCURATE);
147
148 bool success = (rate > 0)
149 ? gst_element_seek(element: element(), rate, format: GST_FORMAT_TIME, flags: seekFlags, start_type: GST_SEEK_TYPE_SET,
150 start: pos.count(), stop_type: GST_SEEK_TYPE_END, stop: 0)
151 : gst_element_seek(element: element(), rate, format: GST_FORMAT_TIME, flags: seekFlags, start_type: GST_SEEK_TYPE_SET, start: 0,
152 stop_type: GST_SEEK_TYPE_SET, stop: pos.count());
153
154 if (!success) {
155 qDebug() << "seek: gst_element_seek failed" << pos;
156 dumpGraph(fileNamePrefix: "seekSeekFailed");
157 return;
158 }
159
160 d->m_position = pos;
161}
162
163void QGstPipeline::seek(std::chrono::nanoseconds pos, bool flush)
164{
165 qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos;
166 seek(pos, rate: getPrivate()->m_rate, flush);
167}
168
169void QGstPipeline::setPlaybackRate(double rate, bool forceFlushingSeek)
170{
171 QGstPipelinePrivate *d = getPrivate();
172 if (rate == d->m_rate)
173 return;
174
175 d->m_rate = rate;
176
177 qCDebug(qLcGstPipeline) << "QGstPipeline::setPlaybackRate to" << rate;
178
179 applyPlaybackRate(forceFlushingSeek);
180}
181
182double QGstPipeline::playbackRate() const
183{
184 QGstPipelinePrivate *d = getPrivate();
185 return d->m_rate;
186}
187
188void QGstPipeline::applyPlaybackRate(bool forceFlushingSeek)
189{
190 QGstPipelinePrivate *d = getPrivate();
191
192 // do not GST_SEEK_FLAG_FLUSH with GST_SEEK_TYPE_NONE
193 // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3604
194 if (!forceFlushingSeek) {
195 bool asyncChangeSuccess = waitForAsyncStateChangeComplete();
196 if (!asyncChangeSuccess) {
197 qWarning()
198 << "QGstPipeline::seek: async pipeline change in progress. Seeking impossible";
199 return;
200 }
201
202 qCDebug(qLcGstPipeline) << "QGstPipeline::applyPlaybackRate instantly";
203 bool success = gst_element_seek(
204 element: element(), rate: d->m_rate, format: GST_FORMAT_UNDEFINED, flags: GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
205 start_type: GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, stop_type: GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
206 if (!success) {
207 qDebug() << "setPlaybackRate: gst_element_seek failed";
208 dumpGraph(fileNamePrefix: "applyPlaybackRateSeekFailed");
209 }
210 } else {
211 seek(pos: position(), flush: d->m_rate);
212 }
213}
214
215void QGstPipeline::setPosition(std::chrono::nanoseconds pos, bool flush)
216{
217 seek(pos, flush);
218}
219
220std::chrono::nanoseconds QGstPipeline::position() const
221{
222 QGstPipelinePrivate *d = getPrivate();
223 std::optional<std::chrono::nanoseconds> pos = QGstElement::position();
224 if (pos) {
225 d->m_position = *pos;
226 qCDebug(qLcGstPipeline) << "QGstPipeline::position:"
227 << std::chrono::round<std::chrono::milliseconds>(d: *pos);
228 } else {
229 qDebug() << "QGstPipeline: failed to query position, using previous position";
230 dumpGraph(fileNamePrefix: "positionQueryFailed");
231 }
232
233 return d->m_position;
234}
235
236std::chrono::milliseconds QGstPipeline::positionInMs() const
237{
238 using namespace std::chrono;
239 return round<milliseconds>(d: position());
240}
241
242void QGstPipeline::setPositionAndRate(std::chrono::nanoseconds pos, double rate)
243{
244 QGstPipelinePrivate *d = getPrivate();
245 d->m_rate = rate;
246 seek(pos, flush: rate);
247}
248
249std::optional<std::chrono::nanoseconds>
250QGstPipeline::queryPosition(std::chrono::nanoseconds timeout) const
251{
252 using namespace std::chrono_literals;
253 using namespace std::chrono;
254
255 std::chrono::nanoseconds totalSleepTime{};
256
257 for (;;) {
258 std::optional<nanoseconds> dur = QGstElement::duration();
259 if (dur)
260 return dur;
261
262 if (totalSleepTime >= timeout)
263 return std::nullopt;
264 std::this_thread::sleep_for(rtime: 20ms);
265 totalSleepTime += 20ms;
266 }
267}
268
269std::optional<std::chrono::nanoseconds>
270QGstPipeline::queryDuration(std::chrono::nanoseconds timeout) const
271{
272 using namespace std::chrono_literals;
273 using namespace std::chrono;
274
275 std::chrono::nanoseconds totalSleepTime{};
276
277 for (;;) {
278 std::optional<nanoseconds> dur = QGstElement::duration();
279 if (dur)
280 return dur;
281
282 if (totalSleepTime >= timeout)
283 return std::nullopt;
284
285 std::this_thread::sleep_for(rtime: 20ms);
286 totalSleepTime += 20ms;
287 }
288}
289
290std::optional<std::pair<std::chrono::nanoseconds, std::chrono::nanoseconds>>
291QGstPipeline::queryPositionAndDuration(std::chrono::nanoseconds timeout) const
292{
293 using namespace std::chrono_literals;
294 using namespace std::chrono;
295
296 std::chrono::nanoseconds totalSleepTime{};
297
298 std::optional<nanoseconds> dur;
299 std::optional<nanoseconds> pos;
300
301 for (;;) {
302 if (!dur)
303 dur = QGstElement::duration();
304 if (!pos)
305 pos = QGstElement::position();
306
307 if (dur && pos)
308 return std::pair{ *dur, *pos };
309
310 if (totalSleepTime >= timeout)
311 return std::nullopt;
312
313 std::this_thread::sleep_for(rtime: 20ms);
314 totalSleepTime += 20ms;
315 }
316}
317
318void QGstPipeline::seekToEndWithEOS()
319{
320 QGstPipelinePrivate *d = getPrivate();
321
322 gst_element_seek(element: element(), rate: d->m_rate, format: GST_FORMAT_TIME, flags: GST_SEEK_FLAG_NONE, start_type: GST_SEEK_TYPE_END,
323 start: 0, stop_type: GST_SEEK_TYPE_END, stop: 0);
324}
325
326QGstPipelinePrivate *QGstPipeline::getPrivate() const
327{
328 QGstPipelinePrivate *ret = getObject<QGstPipelinePrivate>(property: "pipeline-private");
329 Q_ASSERT(ret);
330 return ret;
331}
332
333QT_END_NAMESPACE
334

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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