QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
gstqgcvideosinkbin.cc
Go to the documentation of this file.
2
3#include "gstqgcelements.h"
4
5#include <array>
6#include <initializer_list>
7#include <string>
8
9#include "GstQgcAllocation.h"
10#include "GstQgcCaps.h"
11
12#define GST_CAT_DEFAULT gst_qgc_debug
13
14enum
15{
24};
25
26static GParamSpec* properties[PROP_LAST];
27
28// video/x-raw(ANY) accepts every memory feature plus system catch-all; narrows from CAPS_ANY so
29// non-raw links fail at link time (not PAUSED negotiation) without dropping any zero-copy path.
30static GstStaticPadTemplate sink_factory =
31 GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-raw(ANY)"));
32
33namespace {
34
35// Linked element chain with automatic rollback: adopt() elements (bin takes the floating ref),
36// linkChain() in flow order, ghostSink() the head, commit() on success; ~BinChain unwinds the rest.
37class BinChain
38{
39public:
40 explicit BinChain(GstBin* bin) : m_bin(bin) {}
41
42 ~BinChain()
43 {
44 if (m_committed) {
45 return;
46 }
47 if (m_ghost) {
48 gst_element_remove_pad(GST_ELEMENT(m_bin), m_ghost);
49 }
50 while (m_ownedCount > 0) {
51 gst_bin_remove(m_bin, m_owned[--m_ownedCount]);
52 }
53 }
54
55 BinChain(const BinChain&) = delete;
56 BinChain& operator=(const BinChain&) = delete;
57
58 GstElement* adopt(GstElement* element)
59 {
60 if (!element) {
61 return nullptr;
62 }
63 if (m_ownedCount == m_owned.size()) {
64 gst_object_unref(element);
65 return nullptr;
66 }
67 if (!gst_bin_add(m_bin, element)) {
68 gst_object_unref(element);
69 return nullptr;
70 }
71 m_owned[m_ownedCount++] = element;
72 return element;
73 }
74
75 bool linkChain(std::initializer_list<GstElement*> chain)
76 {
77 GstElement* prev = nullptr;
78 for (GstElement* element : chain) {
79 if (!element) {
80 return false;
81 }
82 if (prev && !gst_element_link(prev, element)) {
83 return false;
84 }
85 prev = element;
86 }
87 return true;
88 }
89
90 bool ghostSink(GstElement* head)
91 {
92 GstPad* sinkpad = gst_element_get_static_pad(head, "sink");
93 if (!sinkpad) {
94 return false;
95 }
96 m_ghost = gst_ghost_pad_new("sink", sinkpad);
97 gst_object_unref(sinkpad);
98 if (!m_ghost) {
99 return false;
100 }
101 if (!gst_element_add_pad(GST_ELEMENT(m_bin), m_ghost)) {
102 gst_object_unref(m_ghost);
103 m_ghost = nullptr;
104 return false;
105 }
106 return true;
107 }
108
109 void commit() { m_committed = true; }
110
111private:
112 GstBin* m_bin;
113 std::array<GstElement*, 5> m_owned{};
114 std::size_t m_ownedCount = 0;
115 GstPad* m_ghost = nullptr;
116 bool m_committed = false;
117};
118
119} // namespace
120
121#ifdef QGC_GST_BUILD_TESTING
122gboolean gst_qgc_video_sink_bin_rejects_failed_adopt_for_test()
123{
124 GstElement* target = gst_bin_new("qgc-adopt-target");
125 GstElement* parent = gst_bin_new("qgc-adopt-existing-parent");
126 GstElement* element = gst_element_factory_make("identity", "preparented");
127 if (!target || !parent || !element) {
128 gst_clear_object(&element);
129 gst_clear_object(&parent);
130 gst_clear_object(&target);
131 return FALSE;
132 }
133
134 if (!gst_bin_add(GST_BIN(parent), element)) {
135 gst_object_unref(parent);
136 gst_object_unref(target);
137 return FALSE;
138 }
139
140 auto* chain = new BinChain(GST_BIN(target));
141 gst_object_ref(element);
142 GstElement* adopted = chain->adopt(element);
143 chain->commit();
144 delete chain;
145
146 GstObject* currentParent = gst_object_get_parent(GST_OBJECT(element));
147 const gboolean rejected = (adopted == nullptr) && (currentParent == GST_OBJECT(parent));
148 if (currentParent) {
149 gst_object_unref(currentParent);
150 }
151 gst_object_unref(parent);
152 gst_object_unref(target);
153 return rejected;
154}
155#endif
156
157#define gst_qgc_video_sink_bin_parent_class parent_class
158G_DEFINE_FINAL_TYPE(GstQgcVideoSinkBin, gst_qgc_video_sink_bin, GST_TYPE_BIN);
159
160GST_ELEMENT_REGISTER_DEFINE_WITH_CODE(qgcvideosinkbin, "qgcvideosinkbin", GST_RANK_NONE, GST_TYPE_QGC_VIDEO_SINK_BIN,
161 qgc_element_init(plugin));
162
163static void gst_qgc_video_sink_bin_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec);
164static void gst_qgc_video_sink_bin_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec);
165static void gst_qgc_video_sink_bin_constructed(GObject* object);
166static void gst_qgc_video_sink_bin_dispose(GObject* object);
167static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement* element, GstStateChange transition);
168
169static void gst_qgc_video_sink_bin_class_init(GstQgcVideoSinkBinClass* klass)
170{
171 GObjectClass* object_class = G_OBJECT_CLASS(klass);
172 GstElementClass* element_class = GST_ELEMENT_CLASS(klass);
173
174 object_class->set_property = gst_qgc_video_sink_bin_set_property;
175 object_class->get_property = gst_qgc_video_sink_bin_get_property;
176 object_class->constructed = gst_qgc_video_sink_bin_constructed;
177 object_class->dispose = gst_qgc_video_sink_bin_dispose;
178 element_class->change_state = gst_qgc_video_sink_bin_change_state;
179
180 properties[PROP_GPU_ZEROCOPY] = g_param_spec_boolean(
181 "gpu-zerocopy", "GPU zero-copy", "Enable the platform GPU zero-copy path. Construct-only.", FALSE,
182 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
183
185 g_param_spec_string("conversion-element", "Conversion element factory",
186 "Factory name to use for the CPU branch's color conversion. Empty/NULL = auto-probe.", NULL,
187 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
188
189 properties[PROP_DISABLE_PAR] = g_param_spec_boolean(
190 "disable-par", "Disable PAR=1/1 capsfilter",
191 "Skip the pixel-aspect-ratio=1/1 capsfilter on the CPU branch (workaround for "
192 "v4l2 drivers without VIDIOC_CROPCAP). Construct-only.",
193 FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
194
196 g_param_spec_boolean("sync", "Sync", "Proxied to the inner basesink: sync rendering to the clock.", FALSE,
197 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
198
199 // Opt-in QoS: default FALSE leaves the existing no-QoS behavior unchanged (sync=FALSE means the
200 // basesink generates no QoS events anyway until a caller enables it).
202 g_param_spec_boolean("qos", "QoS", "Proxied to the inner basesink: generate QoS events upstream.", FALSE,
203 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
204
205 properties[PROP_PROCESSING_DEADLINE] = g_param_spec_uint64(
206 "processing-deadline", "Processing deadline",
207 "Proxied to the inner basesink: maximum buffer processing time in nanoseconds.", 0, G_MAXUINT64,
208 G_GUINT64_CONSTANT(20000000), (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
209
210 g_object_class_install_properties(object_class, PROP_LAST, properties);
211
212 gst_element_class_add_static_pad_template(GST_ELEMENT_CLASS(klass), &sink_factory);
213
214 gst_element_class_set_static_metadata(element_class, "QGC Video Sink Bin", "Sink/Video/Bin",
215 "QVideoSink-based video sink for QGroundControl", "QGroundControl team");
216}
217
218static void gst_qgc_video_sink_bin_init(GstQgcVideoSinkBin* self)
219{
220 self->videoconvert = NULL;
221 self->glupload = NULL;
222 self->videosink = NULL;
223 self->format_capsfilter = NULL;
224 self->par_capsfilter = NULL;
225 self->gpu_zerocopy = FALSE;
226 self->conversion_element = NULL;
227 self->disable_par = FALSE;
228 self->sync = FALSE;
229 self->qos = FALSE;
230 self->processing_deadline = G_GUINT64_CONSTANT(20000000);
231}
232
233// Probe: caller override → SoC-native (imxvideoconvert_g2d/nvvidconv) → videoconvert.
235{
236 if (self->conversion_element && self->conversion_element[0] != '\0') {
237 GstElement* e = gst_element_factory_make(self->conversion_element, NULL);
238 if (e) {
239 GST_INFO_OBJECT(self, "Using conversion-element override '%s'", self->conversion_element);
240 return e;
241 }
242 GST_WARNING_OBJECT(self, "conversion-element='%s' factory missing — falling through to defaults",
243 self->conversion_element);
244 }
245 static const char* kSoCFactories[] = {"imxvideoconvert_g2d", "nvvidconv", NULL};
246 for (int i = 0; kSoCFactories[i] != NULL; ++i) {
247 if (GstElement* e = gst_element_factory_make(kSoCFactories[i], NULL)) {
248 GST_INFO_OBJECT(self, "Using SoC conversion element '%s'", kSoCFactories[i]);
249 return e;
250 }
251 }
252 return gst_element_factory_make("videoconvert", NULL);
253}
254
255// Wire format-capsfilter -> qgcqvideosink (optional glupload prefix); self pointers set only on commit.
256static gboolean wireGpuPath(GstQgcVideoSinkBin* self, GstElement* videosink, GstElement* capsf)
257{
258 BinChain chain(GST_BIN(self));
259 if (!chain.adopt(videosink)) {
260 gst_object_unref(capsf);
261 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink GPU sink element to bin");
262 return FALSE;
263 }
264 if (!chain.adopt(capsf)) {
265 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink GPU capsfilter element to bin");
266 return FALSE;
267 }
268
269 const std::string capsStr = GstQgc::buildGpuCapsString();
270 GstCaps* caps = gst_caps_from_string(capsStr.c_str());
271 if (!caps) {
272 GST_ERROR_OBJECT(self, "gst_caps_from_string() returned NULL for GPU caps");
273 return FALSE;
274 }
275 // format_capsfilter restricts negotiation to Qt-renderable formats; qgcqvideosink's pad
276 // template is CAPS_ANY, so without it upstream could pick a format that fails in set_caps.
277 g_object_set(capsf, "caps", caps, NULL);
278 gst_caps_unref(caps);
279
280#if defined(QGC_GST_BIN_USE_GLUPLOAD)
281 GstElement* glupload = chain.adopt(gst_element_factory_make("glupload", NULL));
282 if (!glupload) {
283 GST_ERROR_OBJECT(self, "Failed to create glupload element");
284 return FALSE;
285 }
286 // Converts amcvideodec's external-OES Surface textures to GL_TEXTURE_2D (else the Qt RHI 2D sink
287 // samples them black); no-op passthrough when upstream is already 2D (Linux va/DMABuf).
288 GstElement* glcolorconvert = chain.adopt(gst_element_factory_make("glcolorconvert", NULL));
289 if (!glcolorconvert) {
290 GST_ERROR_OBJECT(self, "Failed to create glcolorconvert element");
291 return FALSE;
292 }
293 if (!chain.linkChain({glupload, glcolorconvert, capsf, videosink}) || !chain.ghostSink(glupload)) {
294 GST_ERROR_OBJECT(self, "Failed to link/ghost glupload→glcolorconvert→capsfilter→qgcqvideosink GPU path");
295 return FALSE;
296 }
297#else
298 GstElement* glupload = nullptr;
299 // Keep advertised native GPU formats direct. Formats outside buildGpuCapsString() must
300 // renegotiate upstream; converting here would also upload software frames instead of
301 // letting qgcqvideosink use its CPU mapping fallback.
302 if (!chain.linkChain({capsf, videosink}) || !chain.ghostSink(capsf)) {
303 GST_ERROR_OBJECT(self, "Failed to link/ghost qgcqvideosink (GPU path)");
304 return FALSE;
305 }
306#endif
307
308 chain.commit();
309 self->glupload = glupload;
310 self->format_capsfilter = capsf;
311 self->videosink = videosink;
312
313#if defined(QGC_GST_BIN_USE_GLUPLOAD)
314 GST_INFO_OBJECT(self, "Using glupload→qgcqvideosink GPU path (DMABuf→GL EGLImage import)");
315#elif defined(QGC_GST_BIN_USE_DMABUF)
316 GST_INFO_OBJECT(self, "Using qgcqvideosink GPU path (direct DMABuf import, no glupload)");
317#else
318 GST_INFO_OBJECT(self, "Using qgcqvideosink GPU path (native memory passthrough)");
319#endif
320 return TRUE;
321}
322
323// Wire videoconvert -> (optional PAR=1/1) -> format-capsfilter -> qgcqvideosink; self pointers set only on commit.
324static gboolean wireCpuPath(GstQgcVideoSinkBin* self, GstElement* videosink, GstElement* capsf)
325{
326 BinChain chain(GST_BIN(self));
327 if (!chain.adopt(videosink)) {
328 gst_object_unref(capsf);
329 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink CPU sink element to bin");
330 return FALSE;
331 }
332 if (!chain.adopt(capsf)) {
333 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink CPU capsfilter element to bin");
334 return FALSE;
335 }
336
337 GstElement* videoconvert = chain.adopt(gst_qgc_video_sink_bin_make_conversion_element(self));
338 if (!videoconvert) {
339 GST_ERROR_OBJECT(self, "Failed to create video conversion element");
340 return FALSE;
341 }
342
343 // QVideoSink renders these natively; listing them avoids forcing videoconvert to BGRA.
344 GstCaps* caps = gst_caps_from_string(GstQgc::buildCpuCapsString().c_str());
345 if (!caps) {
346 GST_ERROR_OBJECT(self, "gst_caps_from_string() returned NULL for CPU caps");
347 return FALSE;
348 }
349 g_object_set(capsf, "caps", caps, NULL);
350 gst_caps_unref(caps);
351
352 // PAR=1/1 capsfilter normalizes non-square pixels; disable-par for v4l2 drivers without
353 // VIDIOC_CROPCAP that deadlock negotiation (GStreamer MR #6242).
354 GstElement* par = nullptr;
355 if (!self->disable_par) {
356 par = chain.adopt(gst_element_factory_make("capsfilter", "qgc-par-filter"));
357 if (!par) {
358 GST_WARNING_OBJECT(self, "capsfilter factory missing — PAR normalization disabled");
359 } else {
360 GstCaps* parCaps = gst_caps_from_string("video/x-raw, pixel-aspect-ratio=(fraction)1/1");
361 if (parCaps) {
362 g_object_set(par, "caps", parCaps, NULL);
363 gst_caps_unref(parCaps);
364 }
365 }
366 }
367
368 const bool linked = par ? chain.linkChain({videoconvert, par, capsf, videosink})
369 : chain.linkChain({videoconvert, capsf, videosink});
370 if (!linked || !chain.ghostSink(videoconvert)) {
371 GST_ERROR_OBJECT(self, "Failed to link/ghost CPU path");
372 return FALSE;
373 }
374
375 chain.commit();
376 self->videoconvert = videoconvert;
377 self->par_capsfilter = par;
378 self->format_capsfilter = capsf;
379 self->videosink = videosink;
380
381 GST_INFO_OBJECT(self, "Using qgcqvideosink CPU path (videoconvert%s → caps → qgcqvideosink → QVideoSink)",
382 par ? " → PAR=1/1" : "");
383 return TRUE;
384}
385
386static void gst_qgc_video_sink_bin_setup(GstQgcVideoSinkBin* self)
387{
388 // gpu-zerocopy is construct-only — pass it to construction; g_object_set after the fact is rejected.
389 GstElement* videosink = gst_element_factory_make_full("qgcqvideosink", "name", "qgcqvideosink", "gpu-zerocopy",
390 self->gpu_zerocopy, NULL);
391 if (!videosink) {
392 GST_ERROR_OBJECT(self, "Failed to create qgcqvideosink element");
393 return;
394 }
395 g_object_set(videosink, "active", (gboolean) TRUE, "sync", (gboolean) self->sync, "qos", (gboolean) self->qos,
396 "processing-deadline", self->processing_deadline, NULL);
397
398 GstElement* capsf = gst_element_factory_make("capsfilter", "qgc-format-filter");
399 if (!capsf) {
400 GST_ERROR_OBJECT(self, "Failed to create capsfilter for qgcqvideosink");
401 gst_clear_object(&videosink);
402 return;
403 }
404
405 // On failure the wire*Path() BinChain rolls the bin back and frees videosink/capsf; self
406 // pointers stay NULL so change_state surfaces the construction error.
407 if (!(self->gpu_zerocopy ? wireGpuPath(self, videosink, capsf) : wireCpuPath(self, videosink, capsf))) {
408 return;
409 }
410
411 // Probe the videosink sink pad so ALLOCATION/CONTEXT queries terminate here instead of racing
412 // the bus NEED_CONTEXT fallback. Installed post-commit so a wire failure leaves no orphan probe.
413 if (GstPad* videosinkPad = gst_element_get_static_pad(videosink, "sink")) {
414 const gulong probe_id = gst_pad_add_probe(videosinkPad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
415 GstQgc::videosinkQueryProbe, NULL, NULL);
416 if (probe_id == 0) {
417 GST_WARNING_OBJECT(
418 self, "gst_pad_add_probe(QUERY_DOWNSTREAM) returned 0 — qgcqvideosink query interception disabled");
419 }
420 gst_object_unref(videosinkPad);
421 }
422}
423
424static void gst_qgc_video_sink_bin_constructed(GObject* object)
425{
426 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->constructed(object);
427 gst_qgc_video_sink_bin_setup(GST_QGC_VIDEO_SINK_BIN(object));
428}
429
430// GstBin dispose unrefs children; NULL our cached pointers BEFORE chaining so a concurrent
431// property accessor sees NULL instead of freed memory.
432static void gst_qgc_video_sink_bin_dispose(GObject* object)
433{
434 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
435 self->videosink = NULL;
436 self->videoconvert = NULL;
437 self->par_capsfilter = NULL;
438 self->format_capsfilter = NULL;
439 self->glupload = NULL;
440 g_clear_pointer(&self->conversion_element, g_free);
441 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->dispose(object);
442}
443
444// Surfaces _setup() failures to the parent bus on NULL->READY; without it the bin sits without
445// a ghost pad and the parent reports a generic "no compatible pad" instead of the real cause.
446static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement* element, GstStateChange transition)
447{
448 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(element);
449 if (transition == GST_STATE_CHANGE_NULL_TO_READY && !self->videosink) {
450 GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
451 ("qgcvideosinkbin construction failed; cannot transition to READY"),
452 ("see prior GST_ERROR messages from this element for the underlying cause"));
453 return GST_STATE_CHANGE_FAILURE;
454 }
455 return GST_ELEMENT_CLASS(gst_qgc_video_sink_bin_parent_class)->change_state(element, transition);
456}
457
458static void gst_qgc_video_sink_bin_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
459{
460 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
461
462 switch (prop_id) {
464 self->gpu_zerocopy = g_value_get_boolean(value);
465 break;
467 GST_OBJECT_LOCK(self);
468 g_free(self->conversion_element);
469 self->conversion_element = g_value_dup_string(value);
470 GST_OBJECT_UNLOCK(self);
471 break;
472 case PROP_DISABLE_PAR:
473 self->disable_par = g_value_get_boolean(value);
474 break;
475 case PROP_SYNC:
476 self->sync = g_value_get_boolean(value);
477 if (self->videosink)
478 g_object_set(self->videosink, "sync", (gboolean) self->sync, NULL);
479 break;
480 case PROP_QOS:
481 self->qos = g_value_get_boolean(value);
482 if (self->videosink)
483 g_object_set(self->videosink, "qos", (gboolean) self->qos, NULL);
484 break;
486 self->processing_deadline = g_value_get_uint64(value);
487 if (self->videosink)
488 g_object_set(self->videosink, "processing-deadline", self->processing_deadline, NULL);
489 break;
490 default:
491 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
492 break;
493 }
494}
495
496static void gst_qgc_video_sink_bin_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
497{
498 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
499
500 switch (prop_id) {
502 g_value_set_boolean(value, self->gpu_zerocopy);
503 break;
505 GST_OBJECT_LOCK(self);
506 g_value_set_string(value, self->conversion_element);
507 GST_OBJECT_UNLOCK(self);
508 break;
509 case PROP_DISABLE_PAR:
510 g_value_set_boolean(value, self->disable_par);
511 break;
512 case PROP_SYNC:
513 g_value_set_boolean(value, self->sync);
514 break;
515 case PROP_QOS:
516 g_value_set_boolean(value, self->qos);
517 break;
519 g_value_set_uint64(value, self->processing_deadline);
520 break;
521 default:
522 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
523 break;
524 }
525}
526
528{
529 g_return_val_if_fail(GST_IS_QGC_VIDEO_SINK_BIN(self), NULL);
530 return self->videosink ? GST_ELEMENT(gst_object_ref(self->videosink)) : NULL;
531}
532
534{
535 if (!bin || !GST_IS_QGC_VIDEO_SINK_BIN(bin)) {
536 return FALSE;
537 }
538 return GST_QGC_VIDEO_SINK_BIN(bin)->gpu_zerocopy;
539}
struct _GstElement GstElement
void qgc_element_init(GstPlugin *plugin)
static GstElement * gst_qgc_video_sink_bin_make_conversion_element(GstQgcVideoSinkBin *self)
static void gst_qgc_video_sink_bin_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
static gboolean wireCpuPath(GstQgcVideoSinkBin *self, GstElement *videosink, GstElement *capsf)
static void gst_qgc_video_sink_bin_setup(GstQgcVideoSinkBin *self)
@ PROP_DISABLE_PAR
@ PROP_GPU_ZEROCOPY
@ PROP_CONVERSION_ELEMENT
@ PROP_PROCESSING_DEADLINE
@ PROP_LAST
@ PROP_SYNC
static GParamSpec * properties[PROP_LAST]
#define gst_qgc_video_sink_bin_parent_class
static void gst_qgc_video_sink_bin_class_init(GstQgcVideoSinkBinClass *klass)
static GstStaticPadTemplate sink_factory
G_DEFINE_FINAL_TYPE(GstQgcVideoSinkBin, gst_qgc_video_sink_bin, GST_TYPE_BIN)
GstElement * gst_qgc_video_sink_bin_get_qvideosink(GstQgcVideoSinkBin *self)
Returns the internal qgcqvideosink element, transfer-full (caller unrefs); NULL if not yet constructe...
static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement *element, GstStateChange transition)
static void gst_qgc_video_sink_bin_constructed(GObject *object)
static gboolean wireGpuPath(GstQgcVideoSinkBin *self, GstElement *videosink, GstElement *capsf)
static void gst_qgc_video_sink_bin_dispose(GObject *object)
static void gst_qgc_video_sink_bin_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE(qgcvideosinkbin, "qgcvideosinkbin", GST_RANK_NONE, GST_TYPE_QGC_VIDEO_SINK_BIN, qgc_element_init(plugin))
gboolean gst_qgc_video_sink_bin_get_gpu_zerocopy(GstElement *bin)
Whether the bin built its GPU zero-copy pipeline (mirrors "gpu-zerocopy"); NULL-safe (FALSE).
static void gst_qgc_video_sink_bin_init(GstQgcVideoSinkBin *self)
#define GST_TYPE_QGC_VIDEO_SINK_BIN
GstPadProbeReturn videosinkQueryProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
std::string buildGpuCapsString()
Definition GstQgcCaps.cc:16
std::string buildCpuCapsString()
Definition GstQgcCaps.cc:11
bool rejected(const Fields &reply, std::string_view command)