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 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | static Q_LOGGING_CATEGORY(qLcGstPipeline, "qt.multimedia.gstpipeline"); |
15 | |
16 | struct 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 | |
26 | QGstPipelinePrivate::QGstPipelinePrivate(QGstBusHandle bus) |
27 | : m_busObserver{ |
28 | std::make_unique<QGstBusObserver>(args: std::move(bus)), |
29 | } |
30 | { |
31 | Q_ASSERT(QThread::isMainThread()); |
32 | } |
33 | |
34 | QGstPipelinePrivate::~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 | |
51 | QGstPipeline QGstPipeline::create(const char *name) |
52 | { |
53 | GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(arg: gst_pipeline_new(name)); |
54 | return adopt(pipeline); |
55 | } |
56 | |
57 | QGstPipeline 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 | |
65 | QGstPipeline 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 | |
83 | QGstPipeline::QGstPipeline(GstPipeline *p, RefMode mode) : QGstBin(qGstCheckedCast<GstBin>(arg: p), mode) |
84 | { |
85 | } |
86 | |
87 | QGstPipeline::~QGstPipeline() = default; |
88 | |
89 | void QGstPipeline::installMessageFilter(QGstreamerSyncMessageFilter *filter) |
90 | { |
91 | QGstPipelinePrivate *d = getPrivate(); |
92 | d->m_busObserver->installMessageFilter(filter); |
93 | } |
94 | |
95 | void QGstPipeline::removeMessageFilter(QGstreamerSyncMessageFilter *filter) |
96 | { |
97 | QGstPipelinePrivate *d = getPrivate(); |
98 | d->m_busObserver->removeMessageFilter(filter); |
99 | } |
100 | |
101 | void QGstPipeline::installMessageFilter(QGstreamerBusMessageFilter *filter) |
102 | { |
103 | QGstPipelinePrivate *d = getPrivate(); |
104 | d->m_busObserver->installMessageFilter(filter); |
105 | } |
106 | |
107 | void QGstPipeline::removeMessageFilter(QGstreamerBusMessageFilter *filter) |
108 | { |
109 | QGstPipelinePrivate *d = getPrivate(); |
110 | d->m_busObserver->removeMessageFilter(filter); |
111 | } |
112 | |
113 | GstStateChangeReturn QGstPipeline::setState(GstState state) |
114 | { |
115 | return gst_element_set_state(element: element(), state); |
116 | } |
117 | |
118 | bool QGstPipeline::processNextPendingMessage(GstMessageType types, std::chrono::nanoseconds timeout) |
119 | { |
120 | QGstPipelinePrivate *d = getPrivate(); |
121 | return d->m_busObserver->processNextPendingMessage(type: types, timeout); |
122 | } |
123 | |
124 | bool QGstPipeline::processNextPendingMessage(std::chrono::nanoseconds timeout) |
125 | { |
126 | return processNextPendingMessage(types: GST_MESSAGE_ANY, timeout); |
127 | } |
128 | |
129 | void QGstPipeline::flush() |
130 | { |
131 | seek(pos: position(), /*flush=*/true); |
132 | } |
133 | |
134 | void 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 | |
163 | void 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 | |
169 | void 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 | |
182 | double QGstPipeline::playbackRate() const |
183 | { |
184 | QGstPipelinePrivate *d = getPrivate(); |
185 | return d->m_rate; |
186 | } |
187 | |
188 | void 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 | |
215 | void QGstPipeline::setPosition(std::chrono::nanoseconds pos, bool flush) |
216 | { |
217 | seek(pos, flush); |
218 | } |
219 | |
220 | std::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 | |
236 | std::chrono::milliseconds QGstPipeline::positionInMs() const |
237 | { |
238 | using namespace std::chrono; |
239 | return round<milliseconds>(d: position()); |
240 | } |
241 | |
242 | void 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 | |
249 | std::optional<std::chrono::nanoseconds> |
250 | QGstPipeline::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 | |
269 | std::optional<std::chrono::nanoseconds> |
270 | QGstPipeline::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 | |
290 | std::optional<std::pair<std::chrono::nanoseconds, std::chrono::nanoseconds>> |
291 | QGstPipeline::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 | |
318 | void 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 | |
326 | QGstPipelinePrivate *QGstPipeline::getPrivate() const |
327 | { |
328 | QGstPipelinePrivate *ret = getObject<QGstPipelinePrivate>(property: "pipeline-private"); |
329 | Q_ASSERT(ret); |
330 | return ret; |
331 | } |
332 | |
333 | QT_END_NAMESPACE |
334 |
Definitions
- qLcGstPipeline
- QGstPipelinePrivate
- QGstPipelinePrivate
- ~QGstPipelinePrivate
- create
- createFromFactory
- adopt
- QGstPipeline
- ~QGstPipeline
- installMessageFilter
- removeMessageFilter
- installMessageFilter
- removeMessageFilter
- setState
- processNextPendingMessage
- processNextPendingMessage
- flush
- seek
- seek
- setPlaybackRate
- playbackRate
- applyPlaybackRate
- setPosition
- position
- positionInMs
- setPositionAndRate
- queryPosition
- queryDuration
- queryPositionAndDuration
- seekToEndWithEOS
Start learning QML with our Intro Training
Find out more