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 | #ifndef QGST_P_H |
5 | #define QGST_P_H |
6 | |
7 | // |
8 | // W A R N I N G |
9 | // ------------- |
10 | // |
11 | // This file is not part of the Qt API. It exists purely as an |
12 | // implementation detail. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <QtCore/qdebug.h> |
19 | #include <QtCore/qlist.h> |
20 | #include <QtCore/qmutex.h> |
21 | #include <QtCore/qsemaphore.h> |
22 | |
23 | #include <QtMultimedia/qaudioformat.h> |
24 | #include <QtMultimedia/qvideoframe.h> |
25 | #include <QtMultimedia/private/qtmultimediaglobal_p.h> |
26 | #include <QtMultimedia/private/qmultimediautils_p.h> |
27 | #include <QtMultimedia/private/qplatformmediaplayer_p.h> |
28 | #include <QtMultimedia/private/qsharedhandle_p.h> |
29 | |
30 | #include <gst/gst.h> |
31 | #include <gst/app/gstappsink.h> |
32 | #include <gst/app/gstappsrc.h> |
33 | #include <gst/video/video-info.h> |
34 | |
35 | #include "qgst_handle_types_p.h" |
36 | |
37 | #include <type_traits> |
38 | #ifdef __cpp_lib_three_way_comparison |
39 | # include <compare> |
40 | #endif |
41 | |
42 | #if QT_CONFIG(gstreamer_photography) |
43 | # define GST_USE_UNSTABLE_API |
44 | # include <gst/interfaces/photography.h> |
45 | # undef GST_USE_UNSTABLE_API |
46 | #endif |
47 | |
48 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | namespace QGstImpl { |
52 | |
53 | template <typename T> |
54 | struct GstObjectTraits |
55 | { |
56 | // using Type = T; |
57 | // template <typename U> |
58 | // static bool isObjectOfType(U *); |
59 | // template <typename U> |
60 | // static T *cast(U *); |
61 | }; |
62 | |
63 | #define QGST_DEFINE_CAST_TRAITS(ClassName, MACRO_LABEL) \ |
64 | template <> \ |
65 | struct GstObjectTraits<ClassName> \ |
66 | { \ |
67 | using Type = ClassName; \ |
68 | template <typename U> \ |
69 | static bool isObjectOfType(U *arg) \ |
70 | { \ |
71 | return GST_IS_##MACRO_LABEL(arg); \ |
72 | } \ |
73 | template <typename U> \ |
74 | static Type *cast(U *arg) \ |
75 | { \ |
76 | return GST_##MACRO_LABEL##_CAST(arg); \ |
77 | } \ |
78 | template <typename U> \ |
79 | static Type *checked_cast(U *arg) \ |
80 | { \ |
81 | return GST_##MACRO_LABEL(arg); \ |
82 | } \ |
83 | }; \ |
84 | static_assert(true, "ensure semicolon") |
85 | |
86 | #define QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE(ClassName, MACRO_LABEL) \ |
87 | template <> \ |
88 | struct GstObjectTraits<ClassName> \ |
89 | { \ |
90 | using Type = ClassName; \ |
91 | template <typename U> \ |
92 | static bool isObjectOfType(U *arg) \ |
93 | { \ |
94 | return GST_IS_##MACRO_LABEL(arg); \ |
95 | } \ |
96 | template <typename U> \ |
97 | static Type *cast(U *arg) \ |
98 | { \ |
99 | return checked_cast(arg); \ |
100 | } \ |
101 | template <typename U> \ |
102 | static Type *checked_cast(U *arg) \ |
103 | { \ |
104 | return GST_##MACRO_LABEL(arg); \ |
105 | } \ |
106 | }; \ |
107 | static_assert(true, "ensure semicolon") |
108 | |
109 | QGST_DEFINE_CAST_TRAITS(GstBin, BIN); |
110 | QGST_DEFINE_CAST_TRAITS(GstClock, CLOCK); |
111 | QGST_DEFINE_CAST_TRAITS(GstElement, ELEMENT); |
112 | QGST_DEFINE_CAST_TRAITS(GstObject, OBJECT); |
113 | QGST_DEFINE_CAST_TRAITS(GstPad, PAD); |
114 | QGST_DEFINE_CAST_TRAITS(GstPipeline, PIPELINE); |
115 | QGST_DEFINE_CAST_TRAITS(GstBaseSink, BASE_SINK); |
116 | QGST_DEFINE_CAST_TRAITS(GstBaseSrc, BASE_SRC); |
117 | QGST_DEFINE_CAST_TRAITS(GstAppSink, APP_SINK); |
118 | QGST_DEFINE_CAST_TRAITS(GstAppSrc, APP_SRC); |
119 | |
120 | QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE(GstTagSetter, TAG_SETTER); |
121 | |
122 | |
123 | template <> |
124 | struct GstObjectTraits<GObject> |
125 | { |
126 | using Type = GObject; |
127 | template <typename U> |
128 | static bool isObjectOfType(U *arg) |
129 | { |
130 | return G_IS_OBJECT(arg); |
131 | } |
132 | template <typename U> |
133 | static Type *cast(U *arg) |
134 | { |
135 | return G_OBJECT(arg); |
136 | } |
137 | template <typename U> |
138 | static Type *checked_cast(U *arg) |
139 | { |
140 | return G_OBJECT(arg); |
141 | } |
142 | }; |
143 | |
144 | #undef QGST_DEFINE_CAST_TRAITS |
145 | #undef QGST_DEFINE_CAST_TRAITS_FOR_INTERFACE |
146 | |
147 | } // namespace QGstImpl |
148 | |
149 | template <typename DestinationType, typename SourceType> |
150 | bool qIsGstObjectOfType(SourceType *arg) |
151 | { |
152 | using Traits = QGstImpl::GstObjectTraits<DestinationType>; |
153 | return arg && Traits::isObjectOfType(arg); |
154 | } |
155 | |
156 | template <typename DestinationType, typename SourceType> |
157 | DestinationType *qGstSafeCast(SourceType *arg) |
158 | { |
159 | using Traits = QGstImpl::GstObjectTraits<DestinationType>; |
160 | if (arg && Traits::isObjectOfType(arg)) |
161 | return Traits::cast(arg); |
162 | return nullptr; |
163 | } |
164 | |
165 | template <typename DestinationType, typename SourceType> |
166 | DestinationType *qGstCheckedCast(SourceType *arg) |
167 | { |
168 | using Traits = QGstImpl::GstObjectTraits<DestinationType>; |
169 | if (arg) |
170 | Q_ASSERT(Traits::isObjectOfType(arg)); |
171 | return Traits::cast(arg); |
172 | } |
173 | |
174 | class QSize; |
175 | class QGstStructureView; |
176 | class QGstCaps; |
177 | class QCameraFormat; |
178 | |
179 | template <typename T> struct QGRange |
180 | { |
181 | T min; |
182 | T max; |
183 | |
184 | #ifdef __cpp_impl_three_way_comparison |
185 | auto operator<=> (const QGRange &) const = default; |
186 | #else |
187 | bool operator==(const QGRange &rhs) const |
188 | { |
189 | return std::tie(min, max) == std::tie(rhs.min, rhs.max); |
190 | } |
191 | #endif |
192 | }; |
193 | |
194 | struct QGString : QUniqueGStringHandle |
195 | { |
196 | using QUniqueGStringHandle::QUniqueGStringHandle; |
197 | |
198 | QLatin1StringView asStringView() const { return QLatin1StringView{ get() }; } |
199 | QByteArrayView asByteArrayView() const { return QByteArrayView{ get() }; } |
200 | QString toQString() const { return QString::fromUtf8(utf8: get()); } |
201 | |
202 | #ifdef __cpp_lib_three_way_comparison |
203 | // clang-format off |
204 | friend auto operator<=>(const QGString &lhs, const QGString &rhs) |
205 | { |
206 | return lhs.asStringView() <=> rhs.asStringView(); |
207 | } |
208 | friend auto operator<=>(const QGString &lhs, const QLatin1StringView rhs) |
209 | { |
210 | return lhs.asStringView() <=> rhs; |
211 | } |
212 | friend auto operator<=>(const QGString &lhs, const QByteArrayView rhs) |
213 | { |
214 | return lhs.asByteArrayView() <=> rhs; |
215 | } |
216 | friend auto operator<=>(const QLatin1StringView lhs, const QGString &rhs) |
217 | { |
218 | return lhs <=> rhs.asStringView(); |
219 | } |
220 | friend auto operator<=>(const QByteArrayView lhs, const QGString &rhs) |
221 | { |
222 | return lhs <=> rhs.asByteArrayView(); |
223 | } |
224 | // clang-format on |
225 | #else |
226 | // remove once we're on c++20 |
227 | bool operator==(const QGString &str) const { return asStringView() == str.asStringView(); } |
228 | bool operator==(const QLatin1StringView str) const { return asStringView() == str; } |
229 | bool operator==(const QByteArrayView str) const { return asByteArrayView() == str; } |
230 | |
231 | bool operator!=(const QGString &str) const { return asStringView() != str.asStringView(); } |
232 | bool operator!=(const QLatin1StringView str) const { return asStringView() != str; } |
233 | bool operator!=(const QByteArrayView str) const { return asByteArrayView() != str; } |
234 | |
235 | friend bool operator<(const QGString &lhs, const QGString &rhs) |
236 | { |
237 | return lhs.asStringView() < rhs.asStringView(); |
238 | } |
239 | friend bool operator<(const QGString &lhs, const QLatin1StringView rhs) |
240 | { |
241 | return lhs.asStringView() < rhs; |
242 | } |
243 | friend bool operator<(const QGString &lhs, const QByteArrayView rhs) |
244 | { |
245 | return lhs.asByteArrayView() < rhs; |
246 | } |
247 | friend bool operator<(const QLatin1StringView lhs, const QGString &rhs) |
248 | { |
249 | return lhs < rhs.asStringView(); |
250 | } |
251 | friend bool operator<(const QByteArrayView lhs, const QGString &rhs) |
252 | { |
253 | return lhs < rhs.asByteArrayView(); |
254 | } |
255 | #endif |
256 | |
257 | explicit operator QByteArrayView() const { return asByteArrayView(); } |
258 | explicit operator QByteArray() const { return QByteArray{ asByteArrayView() }; } |
259 | }; |
260 | |
261 | class QGValue |
262 | { |
263 | public: |
264 | explicit QGValue(const GValue *v); |
265 | const GValue *value; |
266 | |
267 | bool isNull() const; |
268 | |
269 | std::optional<bool> toBool() const; |
270 | std::optional<int> toInt() const; |
271 | std::optional<int> toInt64() const; |
272 | template<typename T> |
273 | T *getPointer() const |
274 | { |
275 | return value ? static_cast<T *>(g_value_get_pointer(value)) : nullptr; |
276 | } |
277 | |
278 | const char *toString() const; |
279 | std::optional<float> getFraction() const; |
280 | std::optional<QGRange<float>> getFractionRange() const; |
281 | std::optional<QGRange<int>> toIntRange() const; |
282 | |
283 | QGstStructureView toStructure() const; |
284 | QGstCaps toCaps() const; |
285 | |
286 | bool isList() const; |
287 | int listSize() const; |
288 | QGValue at(int index) const; |
289 | |
290 | QList<QAudioFormat::SampleFormat> getSampleFormats() const; |
291 | }; |
292 | |
293 | class QGstreamerMessage; |
294 | |
295 | class QGstStructureView |
296 | { |
297 | public: |
298 | const GstStructure *structure = nullptr; |
299 | explicit QGstStructureView(const GstStructure *); |
300 | explicit QGstStructureView(const QUniqueGstStructureHandle &); |
301 | |
302 | QUniqueGstStructureHandle clone() const; |
303 | |
304 | bool isNull() const; |
305 | QByteArrayView name() const; |
306 | QGValue operator[](const char *fieldname) const; |
307 | |
308 | QGstCaps caps() const; |
309 | QGstTagListHandle tags() const; |
310 | |
311 | QSize resolution() const; |
312 | QVideoFrameFormat::PixelFormat pixelFormat() const; |
313 | QGRange<float> frameRateRange() const; |
314 | std::optional<QGRange<QSize>> resolutionRange() const; |
315 | QGstreamerMessage getMessage(); |
316 | std::optional<Fraction> pixelAspectRatio() const; |
317 | QSize nativeSize() const; |
318 | }; |
319 | |
320 | struct QSharedGstCapsTraits |
321 | { |
322 | using Type = GstCaps *; |
323 | static constexpr Type invalidValue() noexcept { return nullptr; } |
324 | static GstCaps *ref(GstCaps *arg) noexcept { return gst_caps_ref(caps: arg); } |
325 | static bool unref(GstCaps *arg) noexcept |
326 | { |
327 | gst_caps_unref(caps: arg); |
328 | return true; |
329 | } |
330 | }; |
331 | |
332 | class QGstCaps : public QtPrivate::QSharedHandle<QSharedGstCapsTraits> |
333 | { |
334 | using BaseClass = QtPrivate::QSharedHandle<QSharedGstCapsTraits>; |
335 | |
336 | public: |
337 | using BaseClass::BaseClass; |
338 | QGstCaps(const QGstCaps &) = default; |
339 | QGstCaps(QGstCaps &&) noexcept = default; |
340 | QGstCaps &operator=(const QGstCaps &) = default; |
341 | QGstCaps &operator=(QGstCaps &&) noexcept = default; |
342 | |
343 | enum MemoryFormat { CpuMemory, GLTexture, DMABuf }; |
344 | |
345 | int size() const; |
346 | QGstStructureView at(int index) const; |
347 | GstCaps *caps() const; |
348 | |
349 | MemoryFormat memoryFormat() const; |
350 | std::optional<std::pair<QVideoFrameFormat, GstVideoInfo>> formatAndVideoInfo() const; |
351 | |
352 | void addPixelFormats(const QList<QVideoFrameFormat::PixelFormat> &formats, const char *modifier = nullptr); |
353 | void setResolution(QSize); |
354 | |
355 | static QGstCaps create(); |
356 | |
357 | static QGstCaps fromCameraFormat(const QCameraFormat &format); |
358 | |
359 | QGstCaps copy() const; |
360 | }; |
361 | |
362 | struct QSharedGstObjectTraits |
363 | { |
364 | using Type = GstObject *; |
365 | static constexpr Type invalidValue() noexcept { return nullptr; } |
366 | static GstObject *ref(GstObject *arg) noexcept |
367 | { |
368 | gst_object_ref_sink(object: arg); |
369 | return arg; |
370 | } |
371 | static bool unref(GstObject *arg) noexcept |
372 | { |
373 | gst_object_unref(object: arg); |
374 | return true; |
375 | } |
376 | }; |
377 | |
378 | class QGObjectHandlerConnection; |
379 | |
380 | class QGstObject : public QtPrivate::QSharedHandle<QSharedGstObjectTraits> |
381 | { |
382 | using BaseClass = QtPrivate::QSharedHandle<QSharedGstObjectTraits>; |
383 | |
384 | public: |
385 | using BaseClass::BaseClass; |
386 | QGstObject(const QGstObject &) = default; |
387 | QGstObject(QGstObject &&) noexcept = default; |
388 | |
389 | QGstObject &operator=(const QGstObject &) = default; |
390 | QGstObject &operator=(QGstObject &&) noexcept = default; |
391 | |
392 | void set(const char *property, const char *str); |
393 | void set(const char *property, bool b); |
394 | void set(const char *property, int32_t i); |
395 | void set(const char *property, uint32_t i); |
396 | void set(const char *property, int64_t i); |
397 | void set(const char *property, uint64_t i); |
398 | void set(const char *property, double d); |
399 | void set(const char *property, const QGstObject &o); |
400 | void set(const char *property, const QGstCaps &c); |
401 | void set(const char *property, void *object, GDestroyNotify destroyFunction); |
402 | |
403 | template <typename Object> |
404 | void set(const char *property, Object *object, GDestroyNotify destroyFunction) |
405 | { |
406 | set(property, object: static_cast<void *>(object), destroyFunction); |
407 | } |
408 | |
409 | template <typename Object> |
410 | void set(const char *property, std::unique_ptr<Object> object) |
411 | { |
412 | set(property, static_cast<void *>(object.release()), qDeleteFromVoidPointer<Object>); |
413 | } |
414 | |
415 | template <typename T> |
416 | static void qDeleteFromVoidPointer(void *ptr) |
417 | { |
418 | delete reinterpret_cast<T *>(ptr); |
419 | } |
420 | |
421 | QGString getString(const char *property) const; |
422 | QGstStructureView getStructure(const char *property) const; |
423 | bool getBool(const char *property) const; |
424 | uint getUInt(const char *property) const; |
425 | int getInt(const char *property) const; |
426 | quint64 getUInt64(const char *property) const; |
427 | qint64 getInt64(const char *property) const; |
428 | float getFloat(const char *property) const; |
429 | double getDouble(const char *property) const; |
430 | QGstObject getGstObject(const char *property) const; |
431 | void *getObject(const char *property) const; |
432 | |
433 | template <typename T> |
434 | T *getObject(const char *property) const |
435 | { |
436 | void *rawObject = getObject(property); |
437 | return reinterpret_cast<T *>(rawObject); |
438 | } |
439 | |
440 | QGObjectHandlerConnection connect(const char *name, GCallback callback, gpointer userData); |
441 | void disconnect(gulong handlerId); |
442 | |
443 | GType type() const; |
444 | QLatin1StringView typeName() const; |
445 | GstObject *object() const; |
446 | QLatin1StringView name() const; |
447 | }; |
448 | |
449 | class QGObjectHandlerConnection |
450 | { |
451 | public: |
452 | QGObjectHandlerConnection(QGstObject object, gulong handler); |
453 | |
454 | QGObjectHandlerConnection() = default; |
455 | QGObjectHandlerConnection(const QGObjectHandlerConnection &) = default; |
456 | QGObjectHandlerConnection(QGObjectHandlerConnection &&) = default; |
457 | QGObjectHandlerConnection &operator=(const QGObjectHandlerConnection &) = default; |
458 | QGObjectHandlerConnection &operator=(QGObjectHandlerConnection &&) = default; |
459 | |
460 | void disconnect(); |
461 | |
462 | private: |
463 | static constexpr gulong invalidHandlerId = std::numeric_limits<gulong>::max(); |
464 | |
465 | QGstObject object; |
466 | gulong handlerId = invalidHandlerId; |
467 | }; |
468 | |
469 | // disconnects in dtor |
470 | class QGObjectHandlerScopedConnection |
471 | { |
472 | public: |
473 | QGObjectHandlerScopedConnection(QGObjectHandlerConnection connection); |
474 | |
475 | QGObjectHandlerScopedConnection() = default; |
476 | QGObjectHandlerScopedConnection(const QGObjectHandlerScopedConnection &) = delete; |
477 | QGObjectHandlerScopedConnection &operator=(const QGObjectHandlerScopedConnection &) = delete; |
478 | QGObjectHandlerScopedConnection(QGObjectHandlerScopedConnection &&) = default; |
479 | QGObjectHandlerScopedConnection &operator=(QGObjectHandlerScopedConnection &&) = default; |
480 | |
481 | ~QGObjectHandlerScopedConnection(); |
482 | |
483 | void disconnect(); |
484 | |
485 | private: |
486 | QGObjectHandlerConnection connection; |
487 | }; |
488 | |
489 | class QGstElement; |
490 | |
491 | class QGstPad : public QGstObject |
492 | { |
493 | public: |
494 | using QGstObject::QGstObject; |
495 | QGstPad(const QGstPad &) = default; |
496 | QGstPad(QGstPad &&) noexcept = default; |
497 | |
498 | explicit QGstPad(const QGstObject &o); |
499 | explicit QGstPad(GstPad *pad, RefMode mode); |
500 | |
501 | QGstPad &operator=(const QGstPad &) = default; |
502 | QGstPad &operator=(QGstPad &&) noexcept = default; |
503 | |
504 | QGstCaps currentCaps() const; |
505 | QGstCaps queryCaps() const; |
506 | |
507 | QGstTagListHandle tags() const; |
508 | QGString streamId() const; |
509 | |
510 | std::optional<QPlatformMediaPlayer::TrackType> |
511 | inferTrackTypeFromName() const; // for decodebin3 etc |
512 | |
513 | bool isLinked() const; |
514 | bool link(const QGstPad &sink) const; |
515 | bool unlink(const QGstPad &sink) const; |
516 | bool unlinkPeer() const; |
517 | QGstPad peer() const; |
518 | QGstElement parent() const; |
519 | |
520 | GstPad *pad() const; |
521 | |
522 | GstEvent *stickyEvent(GstEventType type); |
523 | bool sendEvent(GstEvent *event); |
524 | void sendFlushStartStop(bool resetTime); |
525 | |
526 | template <auto Member, typename T> |
527 | void addProbe(T *instance, GstPadProbeType type); |
528 | |
529 | template <typename Functor> |
530 | void doInIdleProbe(Functor &&work); |
531 | |
532 | template <auto Member, typename T> |
533 | void addEosProbe(T *instance); |
534 | |
535 | template <typename Functor> |
536 | void modifyPipelineInIdleProbe(Functor &&f); |
537 | |
538 | void sendFlushIfPaused(); |
539 | }; |
540 | |
541 | class QGstClock : public QGstObject |
542 | { |
543 | public: |
544 | QGstClock() = default; |
545 | explicit QGstClock(const QGstObject &o); |
546 | explicit QGstClock(GstClock *clock, RefMode mode); |
547 | |
548 | GstClock *clock() const; |
549 | GstClockTime time() const; |
550 | }; |
551 | |
552 | class QGstBin; |
553 | class QGstPipeline; |
554 | |
555 | class QGstElement : public QGstObject |
556 | { |
557 | public: |
558 | using QGstObject::QGstObject; |
559 | |
560 | QGstElement(const QGstElement &) = default; |
561 | QGstElement(QGstElement &&) noexcept = default; |
562 | QGstElement &operator=(const QGstElement &) = default; |
563 | QGstElement &operator=(QGstElement &&) noexcept = default; |
564 | |
565 | explicit QGstElement(GstElement *element, RefMode mode); |
566 | static QGstElement createFromFactory(const char *factory, const char *name = nullptr); |
567 | static QGstElement createFromFactory(GstElementFactory *, const char *name = nullptr); |
568 | static QGstElement createFromFactory(const QGstElementFactoryHandle &, |
569 | const char *name = nullptr); |
570 | static QGstElement createFromDevice(const QGstDeviceHandle &, const char *name = nullptr); |
571 | static QGstElement createFromDevice(GstDevice *, const char *name = nullptr); |
572 | static QGstElement createFromPipelineDescription(const char *); |
573 | static QGstElement createFromPipelineDescription(const QByteArray &); |
574 | |
575 | static QGstElementFactoryHandle findFactory(const char *); |
576 | static QGstElementFactoryHandle findFactory(const QByteArray &name); |
577 | |
578 | QGstPad staticPad(const char *name) const; |
579 | QGstPad src() const; |
580 | QGstPad sink() const; |
581 | QGstPad getRequestPad(const char *name) const; |
582 | void releaseRequestPad(const QGstPad &pad) const; |
583 | |
584 | GstState state(std::chrono::nanoseconds timeout = std::chrono::seconds(0)) const; |
585 | GstStateChangeReturn setState(GstState state); |
586 | bool setStateSync(GstState state, std::chrono::nanoseconds timeout = std::chrono::seconds(1)); |
587 | bool syncStateWithParent(); |
588 | bool finishStateChange(std::chrono::nanoseconds timeout = std::chrono::seconds(5)); |
589 | bool hasAsyncStateChange(std::chrono::nanoseconds timeout = std::chrono::seconds(0)) const; |
590 | bool waitForAsyncStateChangeComplete( |
591 | std::chrono::nanoseconds timeout = std::chrono::seconds(5)) const; |
592 | |
593 | void lockState(bool locked); |
594 | bool isStateLocked() const; |
595 | |
596 | void sendEvent(GstEvent *event) const; |
597 | void sendEos() const; |
598 | |
599 | std::optional<std::chrono::nanoseconds> duration() const; |
600 | std::optional<std::chrono::milliseconds> durationInMs() const; |
601 | std::optional<std::chrono::nanoseconds> position() const; |
602 | std::optional<std::chrono::milliseconds> positionInMs() const; |
603 | std::optional<bool> canSeek() const; |
604 | |
605 | template <auto Member, typename T> |
606 | QGObjectHandlerConnection onPadAdded(T *instance) |
607 | { |
608 | struct Impl |
609 | { |
610 | static void callback(GstElement *e, GstPad *pad, gpointer userData) |
611 | { |
612 | (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef), |
613 | QGstPad(pad, NeedsRef)); |
614 | }; |
615 | }; |
616 | |
617 | return connect(name: "pad-added" , G_CALLBACK(Impl::callback), userData: instance); |
618 | } |
619 | template <auto Member, typename T> |
620 | QGObjectHandlerConnection onPadRemoved(T *instance) |
621 | { |
622 | struct Impl |
623 | { |
624 | static void callback(GstElement *e, GstPad *pad, gpointer userData) |
625 | { |
626 | (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef), |
627 | QGstPad(pad, NeedsRef)); |
628 | }; |
629 | }; |
630 | |
631 | return connect(name: "pad-removed" , G_CALLBACK(Impl::callback), userData: instance); |
632 | } |
633 | template <auto Member, typename T> |
634 | QGObjectHandlerConnection onNoMorePads(T *instance) |
635 | { |
636 | struct Impl |
637 | { |
638 | static void callback(GstElement *e, gpointer userData) |
639 | { |
640 | (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef)); |
641 | }; |
642 | }; |
643 | |
644 | return connect(name: "no-more-pads" , G_CALLBACK(Impl::callback), userData: instance); |
645 | } |
646 | |
647 | GstClockTime baseTime() const; |
648 | void setBaseTime(GstClockTime time) const; |
649 | |
650 | GstElement *element() const; |
651 | |
652 | QGstElement getParent() const; |
653 | QGstBin getParentBin() const; |
654 | QGstPipeline getPipeline() const; |
655 | |
656 | void removeFromParent(); |
657 | void dumpPipelineGraph(const char *filename) const; |
658 | |
659 | private: |
660 | QGstQueryHandle &positionQuery() const; |
661 | mutable QGstQueryHandle m_positionQuery; |
662 | }; |
663 | |
664 | // QGstPad implementations |
665 | |
666 | template <auto Member, typename T> |
667 | void QGstPad::addProbe(T *instance, GstPadProbeType type) |
668 | { |
669 | auto callback = [](GstPad *pad, GstPadProbeInfo *info, gpointer userData) { |
670 | return (static_cast<T *>(userData)->*Member)(QGstPad(pad, NeedsRef), info); |
671 | }; |
672 | |
673 | gst_pad_add_probe(pad(), type, callback, instance, nullptr); |
674 | } |
675 | |
676 | template <typename Functor> |
677 | void QGstPad::doInIdleProbe(Functor &&work) |
678 | { |
679 | using namespace std::chrono_literals; |
680 | |
681 | struct CallbackData |
682 | { |
683 | QSemaphore waitDone; |
684 | std::once_flag onceFlag; |
685 | Functor work; |
686 | |
687 | void run() |
688 | { |
689 | std::call_once(onceFlag, [&] { |
690 | work(); |
691 | }); |
692 | } |
693 | }; |
694 | |
695 | CallbackData cd{ QSemaphore{}, {}, std::forward<Functor>(work) }; |
696 | |
697 | auto callback = [](GstPad *, GstPadProbeInfo *, gpointer p) { |
698 | auto cd = reinterpret_cast<CallbackData *>(p); |
699 | cd->run(); |
700 | cd->waitDone.release(); |
701 | return GST_PAD_PROBE_REMOVE; |
702 | }; |
703 | |
704 | gulong probe = gst_pad_add_probe(pad(), GST_PAD_PROBE_TYPE_IDLE, callback, &cd, nullptr); |
705 | if (probe == 0) |
706 | return; // probe was executed |
707 | |
708 | bool success = cd.waitDone.try_acquire_for(250ms); |
709 | if (success) |
710 | return; |
711 | |
712 | // the probe has not been executed for 250ms, probably because the element has transitioned |
713 | // from playing to paused. Flushing the pad would unblock paused pads |
714 | sendFlushIfPaused(); |
715 | |
716 | success = cd.waitDone.try_acquire_for(1s); |
717 | if (success) |
718 | return; |
719 | |
720 | // if the idle probe still has not been called, we remove it and call it |
721 | // explicitly to avoid deadlocking the application. Probably not exactly safe, |
722 | // but better than deadlocking |
723 | qWarning() << "QGstPad::doInIdleProbe blocked for 1s. Executing the pad probe manually" ; |
724 | parent().dumpPipelineGraph(filename: "doInIdleProbeHang" ); |
725 | gst_pad_remove_probe(pad: pad(), id: probe); |
726 | cd.run(); |
727 | } |
728 | |
729 | template <auto Member, typename T> |
730 | void QGstPad::addEosProbe(T *instance) |
731 | { |
732 | auto callback = [](GstPad *, GstPadProbeInfo *info, gpointer userData) { |
733 | if (GST_EVENT_TYPE(GST_PAD_PROBE_INFO_DATA(info)) != GST_EVENT_EOS) |
734 | return GST_PAD_PROBE_PASS; |
735 | (static_cast<T *>(userData)->*Member)(); |
736 | return GST_PAD_PROBE_REMOVE; |
737 | }; |
738 | |
739 | gst_pad_add_probe(pad(), GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, callback, instance, nullptr); |
740 | } |
741 | template <typename Functor> |
742 | void QGstPad::modifyPipelineInIdleProbe(Functor &&f) |
743 | { |
744 | using namespace std::chrono_literals; |
745 | |
746 | GstPadDirection direction = gst_pad_get_direction(pad: pad()); |
747 | |
748 | switch (direction) { |
749 | case GstPadDirection::GST_PAD_SINK: { |
750 | // modifying a source: we need to flush the sink pad before we can modify downstream |
751 | // elements |
752 | sendFlushIfPaused(); |
753 | doInIdleProbe(f); |
754 | return; |
755 | } |
756 | case GstPadDirection::GST_PAD_SRC: { |
757 | // modifying a sink: we need to use the idle probes iff the pipeline is playing |
758 | if (parent().state(timeout: 1s) == GstState::GST_STATE_PLAYING) |
759 | doInIdleProbe(f); |
760 | else |
761 | f(); |
762 | return; |
763 | } |
764 | |
765 | default: |
766 | Q_UNREACHABLE(); |
767 | } |
768 | } |
769 | |
770 | template <typename... Ts> |
771 | std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> |
772 | qLinkGstElements(const Ts &...ts) |
773 | { |
774 | bool link_success = [&] { |
775 | if constexpr (sizeof...(Ts) == 2) |
776 | return gst_element_link(ts.element()...); |
777 | else |
778 | return gst_element_link_many(ts.element()..., nullptr); |
779 | }(); |
780 | |
781 | if (Q_UNLIKELY(!link_success)) { |
782 | qWarning() << "qLinkGstElements: could not link elements: " |
783 | << std::initializer_list<const char *>{ |
784 | (GST_ELEMENT_NAME(ts.element()))..., |
785 | }; |
786 | } |
787 | } |
788 | |
789 | template <typename... Ts> |
790 | std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> |
791 | qUnlinkGstElements(const Ts &...ts) |
792 | { |
793 | if constexpr (sizeof...(Ts) == 2) |
794 | gst_element_unlink(ts.element()...); |
795 | else |
796 | gst_element_unlink_many(ts.element()..., nullptr); |
797 | } |
798 | |
799 | class QGstBin : public QGstElement |
800 | { |
801 | public: |
802 | using QGstElement::QGstElement; |
803 | QGstBin(const QGstBin &) = default; |
804 | QGstBin(QGstBin &&) noexcept = default; |
805 | QGstBin &operator=(const QGstBin &) = default; |
806 | QGstBin &operator=(QGstBin &&) noexcept = default; |
807 | |
808 | explicit QGstBin(GstBin *bin, RefMode mode = NeedsRef); |
809 | static QGstBin create(const char *name); |
810 | static QGstBin createFromFactory(const char *factory, const char *name); |
811 | static QGstBin createFromPipelineDescription(const QByteArray &pipelineDescription, |
812 | const char *name = nullptr, |
813 | bool ghostUnlinkedPads = false); |
814 | static QGstBin createFromPipelineDescription(const char *pipelineDescription, |
815 | const char *name = nullptr, |
816 | bool ghostUnlinkedPads = false); |
817 | |
818 | template <typename... Ts> |
819 | std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> add(const Ts &...ts) |
820 | { |
821 | if constexpr (sizeof...(Ts) == 1) |
822 | gst_bin_add(bin(), ts.element()...); |
823 | else |
824 | gst_bin_add_many(bin(), ts.element()..., nullptr); |
825 | } |
826 | |
827 | template <typename... Ts> |
828 | std::enable_if_t<(std::is_base_of_v<QGstElement, Ts> && ...), void> remove(const Ts &...ts) |
829 | { |
830 | if constexpr (sizeof...(Ts) == 1) |
831 | gst_bin_remove(bin(), ts.element()...); |
832 | else |
833 | gst_bin_remove_many(bin(), ts.element()..., nullptr); |
834 | } |
835 | |
836 | template <typename... Ts> |
837 | std::enable_if_t<(std::is_base_of_v<QGstElement, std::remove_reference_t<Ts>> && ...), void> |
838 | stopAndRemoveElements(Ts &&...ts) |
839 | { |
840 | bool stateChangeSuccessful = (ts.setStateSync(GST_STATE_NULL) && ...); |
841 | Q_ASSERT(stateChangeSuccessful); |
842 | remove(ts...); |
843 | } |
844 | |
845 | GstBin *bin() const; |
846 | |
847 | void addGhostPad(const QGstElement &child, const char *name); |
848 | void addGhostPad(const char *name, const QGstPad &pad); |
849 | void addUnlinkedGhostPads(GstPadDirection); |
850 | |
851 | bool syncChildrenState(); |
852 | |
853 | void dumpGraph(const char *fileNamePrefix) const; |
854 | |
855 | QGstElement findByName(const char *); |
856 | |
857 | void recalculateLatency(); |
858 | }; |
859 | |
860 | class QGstBaseSink : public QGstElement |
861 | { |
862 | public: |
863 | using QGstElement::QGstElement; |
864 | |
865 | explicit QGstBaseSink(GstBaseSink *, RefMode); |
866 | |
867 | QGstBaseSink(const QGstBaseSink &) = default; |
868 | QGstBaseSink(QGstBaseSink &&) noexcept = default; |
869 | QGstBaseSink &operator=(const QGstBaseSink &) = default; |
870 | QGstBaseSink &operator=(QGstBaseSink &&) noexcept = default; |
871 | |
872 | void setSync(bool); |
873 | |
874 | GstBaseSink *baseSink() const; |
875 | }; |
876 | |
877 | class QGstBaseSrc : public QGstElement |
878 | { |
879 | public: |
880 | using QGstElement::QGstElement; |
881 | |
882 | explicit QGstBaseSrc(GstBaseSrc *, RefMode); |
883 | |
884 | QGstBaseSrc(const QGstBaseSrc &) = default; |
885 | QGstBaseSrc(QGstBaseSrc &&) noexcept = default; |
886 | QGstBaseSrc &operator=(const QGstBaseSrc &) = default; |
887 | QGstBaseSrc &operator=(QGstBaseSrc &&) noexcept = default; |
888 | |
889 | GstBaseSrc *baseSrc() const; |
890 | }; |
891 | |
892 | class QGstAppSink : public QGstBaseSink |
893 | { |
894 | public: |
895 | using QGstBaseSink::QGstBaseSink; |
896 | |
897 | explicit QGstAppSink(GstAppSink *, RefMode); |
898 | |
899 | QGstAppSink(const QGstAppSink &) = default; |
900 | QGstAppSink(QGstAppSink &&) noexcept = default; |
901 | QGstAppSink &operator=(const QGstAppSink &) = default; |
902 | QGstAppSink &operator=(QGstAppSink &&) noexcept = default; |
903 | |
904 | static QGstAppSink create(const char *name); |
905 | |
906 | GstAppSink *appSink() const; |
907 | |
908 | void setMaxBuffers(int); |
909 | # if GST_CHECK_VERSION(1, 24, 0) |
910 | void setMaxBufferTime(std::chrono::nanoseconds); |
911 | # endif |
912 | |
913 | void setCaps(const QGstCaps &caps); |
914 | void setCallbacks(GstAppSinkCallbacks &callbacks, gpointer user_data, GDestroyNotify notify); |
915 | |
916 | QGstSampleHandle pullSample(); |
917 | }; |
918 | |
919 | class QGstAppSrc : public QGstBaseSrc |
920 | { |
921 | public: |
922 | using QGstBaseSrc::QGstBaseSrc; |
923 | |
924 | explicit QGstAppSrc(GstAppSrc *, RefMode); |
925 | |
926 | QGstAppSrc(const QGstAppSrc &) = default; |
927 | QGstAppSrc(QGstAppSrc &&) noexcept = default; |
928 | QGstAppSrc &operator=(const QGstAppSrc &) = default; |
929 | QGstAppSrc &operator=(QGstAppSrc &&) noexcept = default; |
930 | |
931 | static QGstAppSrc create(const char *name); |
932 | |
933 | GstAppSrc *appSrc() const; |
934 | |
935 | void setCallbacks(GstAppSrcCallbacks &callbacks, gpointer user_data, GDestroyNotify notify); |
936 | |
937 | GstFlowReturn pushBuffer(GstBuffer *); // take ownership |
938 | }; |
939 | |
940 | inline GstClockTime qGstClockTimeFromChrono(std::chrono::nanoseconds ns) |
941 | { |
942 | return ns.count(); |
943 | } |
944 | |
945 | QString qGstErrorMessageCannotFindElement(std::string_view element); |
946 | |
947 | template <typename Arg, typename... Args> |
948 | std::optional<QString> qGstErrorMessageIfElementsNotAvailable(const Arg &arg, Args... args) |
949 | { |
950 | QGstElementFactoryHandle factory = QGstElement::findFactory(arg); |
951 | if (!factory) |
952 | return qGstErrorMessageCannotFindElement(arg); |
953 | |
954 | if constexpr (sizeof...(args) != 0) |
955 | return qGstErrorMessageIfElementsNotAvailable(args...); |
956 | else |
957 | return std::nullopt; |
958 | } |
959 | |
960 | template <typename Functor> |
961 | void qForeachStreamInCollection(GstStreamCollection *collection, Functor &&f) |
962 | { |
963 | guint size = gst_stream_collection_get_size(collection); |
964 | for (guint index = 0; index != size; ++index) |
965 | f(gst_stream_collection_get_stream(collection, index)); |
966 | } |
967 | |
968 | template <typename Functor> |
969 | void qForeachStreamInCollection(const QGstStreamCollectionHandle &collection, Functor &&f) |
970 | { |
971 | qForeachStreamInCollection(collection.get(), std::forward<Functor>(f)); |
972 | } |
973 | |
974 | QT_END_NAMESPACE |
975 | |
976 | namespace std { |
977 | |
978 | template <> |
979 | struct hash<QT_PREPEND_NAMESPACE(QGstElement)> |
980 | { |
981 | using argument_type = QT_PREPEND_NAMESPACE(QGstElement); |
982 | using result_type = size_t; |
983 | result_type operator()(const argument_type &e) const noexcept |
984 | { |
985 | return std::hash<void *>{}(e.element()); |
986 | } |
987 | }; |
988 | } // namespace std |
989 | |
990 | #endif |
991 | |