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 native GPU memory direct. buildGpuCapsString() also offers system memory, which
300 // qgcqvideosink maps through its CPU fallback instead of uploading software-decoded frames.
301 if (!chain.linkChain({capsf, videosink}) || !chain.ghostSink(capsf)) {
302 GST_ERROR_OBJECT(self, "Failed to link/ghost qgcqvideosink (GPU path)");
303 return FALSE;
304 }
305#endif
306
307 chain.commit();
308 self->glupload = glupload;
309 self->format_capsfilter = capsf;
310 self->videosink = videosink;
311
312#if defined(QGC_GST_BIN_USE_GLUPLOAD)
313 GST_INFO_OBJECT(self, "Using glupload→qgcqvideosink GPU path (DMABuf→GL EGLImage import)");
314#elif defined(QGC_GST_BIN_USE_DMABUF)
315 GST_INFO_OBJECT(self, "Using qgcqvideosink GPU path (direct DMABuf import, no glupload)");
316#else
317 GST_INFO_OBJECT(self, "Using qgcqvideosink GPU path (native memory passthrough)");
318#endif
319 return TRUE;
320}
321
322// Wire videoconvert -> (optional PAR=1/1) -> format-capsfilter -> qgcqvideosink; self pointers set only on commit.
323static gboolean wireCpuPath(GstQgcVideoSinkBin* self, GstElement* videosink, GstElement* capsf)
324{
325 BinChain chain(GST_BIN(self));
326 if (!chain.adopt(videosink)) {
327 gst_object_unref(capsf);
328 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink CPU sink element to bin");
329 return FALSE;
330 }
331 if (!chain.adopt(capsf)) {
332 GST_ERROR_OBJECT(self, "Failed to add qgcqvideosink CPU capsfilter element to bin");
333 return FALSE;
334 }
335
336 GstElement* videoconvert = chain.adopt(gst_qgc_video_sink_bin_make_conversion_element(self));
337 if (!videoconvert) {
338 GST_ERROR_OBJECT(self, "Failed to create video conversion element");
339 return FALSE;
340 }
341
342 // QVideoSink renders these natively; listing them avoids forcing videoconvert to BGRA.
343 GstCaps* caps = gst_caps_from_string(GstQgc::buildCpuCapsString().c_str());
344 if (!caps) {
345 GST_ERROR_OBJECT(self, "gst_caps_from_string() returned NULL for CPU caps");
346 return FALSE;
347 }
348 g_object_set(capsf, "caps", caps, NULL);
349 gst_caps_unref(caps);
350
351 // PAR=1/1 capsfilter normalizes non-square pixels; disable-par for v4l2 drivers without
352 // VIDIOC_CROPCAP that deadlock negotiation (GStreamer MR #6242).
353 GstElement* par = nullptr;
354 if (!self->disable_par) {
355 par = chain.adopt(gst_element_factory_make("capsfilter", "qgc-par-filter"));
356 if (!par) {
357 GST_WARNING_OBJECT(self, "capsfilter factory missing — PAR normalization disabled");
358 } else {
359 GstCaps* parCaps = gst_caps_from_string("video/x-raw, pixel-aspect-ratio=(fraction)1/1");
360 if (parCaps) {
361 g_object_set(par, "caps", parCaps, NULL);
362 gst_caps_unref(parCaps);
363 }
364 }
365 }
366
367 const bool linked = par ? chain.linkChain({videoconvert, par, capsf, videosink})
368 : chain.linkChain({videoconvert, capsf, videosink});
369 if (!linked || !chain.ghostSink(videoconvert)) {
370 GST_ERROR_OBJECT(self, "Failed to link/ghost CPU path");
371 return FALSE;
372 }
373
374 chain.commit();
375 self->videoconvert = videoconvert;
376 self->par_capsfilter = par;
377 self->format_capsfilter = capsf;
378 self->videosink = videosink;
379
380 GST_INFO_OBJECT(self, "Using qgcqvideosink CPU path (videoconvert%s → caps → qgcqvideosink → QVideoSink)",
381 par ? " → PAR=1/1" : "");
382 return TRUE;
383}
384
385static void gst_qgc_video_sink_bin_setup(GstQgcVideoSinkBin* self)
386{
387 // gpu-zerocopy is construct-only — pass it to construction; g_object_set after the fact is rejected.
388 GstElement* videosink = gst_element_factory_make_full("qgcqvideosink", "name", "qgcqvideosink", "gpu-zerocopy",
389 self->gpu_zerocopy, NULL);
390 if (!videosink) {
391 GST_ERROR_OBJECT(self, "Failed to create qgcqvideosink element");
392 return;
393 }
394 g_object_set(videosink, "active", (gboolean) TRUE, "sync", (gboolean) self->sync, "qos", (gboolean) self->qos,
395 "processing-deadline", self->processing_deadline, NULL);
396
397 GstElement* capsf = gst_element_factory_make("capsfilter", "qgc-format-filter");
398 if (!capsf) {
399 GST_ERROR_OBJECT(self, "Failed to create capsfilter for qgcqvideosink");
400 gst_clear_object(&videosink);
401 return;
402 }
403
404 // On failure the wire*Path() BinChain rolls the bin back and frees videosink/capsf; self
405 // pointers stay NULL so change_state surfaces the construction error.
406 if (!(self->gpu_zerocopy ? wireGpuPath(self, videosink, capsf) : wireCpuPath(self, videosink, capsf))) {
407 return;
408 }
409
410 // Probe the videosink sink pad so ALLOCATION/CONTEXT queries terminate here instead of racing
411 // the bus NEED_CONTEXT fallback. Installed post-commit so a wire failure leaves no orphan probe.
412 if (GstPad* videosinkPad = gst_element_get_static_pad(videosink, "sink")) {
413 const gulong probe_id = gst_pad_add_probe(videosinkPad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
414 GstQgc::videosinkQueryProbe, NULL, NULL);
415 if (probe_id == 0) {
416 GST_WARNING_OBJECT(
417 self, "gst_pad_add_probe(QUERY_DOWNSTREAM) returned 0 — qgcqvideosink query interception disabled");
418 }
419 gst_object_unref(videosinkPad);
420 }
421}
422
423static void gst_qgc_video_sink_bin_constructed(GObject* object)
424{
425 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->constructed(object);
426 gst_qgc_video_sink_bin_setup(GST_QGC_VIDEO_SINK_BIN(object));
427}
428
429// GstBin dispose unrefs children; NULL our cached pointers BEFORE chaining so a concurrent
430// property accessor sees NULL instead of freed memory.
431static void gst_qgc_video_sink_bin_dispose(GObject* object)
432{
433 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
434 self->videosink = NULL;
435 self->videoconvert = NULL;
436 self->par_capsfilter = NULL;
437 self->format_capsfilter = NULL;
438 self->glupload = NULL;
439 g_clear_pointer(&self->conversion_element, g_free);
440 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->dispose(object);
441}
442
443// Surfaces _setup() failures to the parent bus on NULL->READY; without it the bin sits without
444// a ghost pad and the parent reports a generic "no compatible pad" instead of the real cause.
445static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement* element, GstStateChange transition)
446{
447 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(element);
448 if (transition == GST_STATE_CHANGE_NULL_TO_READY && !self->videosink) {
449 GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
450 ("qgcvideosinkbin construction failed; cannot transition to READY"),
451 ("see prior GST_ERROR messages from this element for the underlying cause"));
452 return GST_STATE_CHANGE_FAILURE;
453 }
454 return GST_ELEMENT_CLASS(gst_qgc_video_sink_bin_parent_class)->change_state(element, transition);
455}
456
457static void gst_qgc_video_sink_bin_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
458{
459 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
460
461 switch (prop_id) {
463 self->gpu_zerocopy = g_value_get_boolean(value);
464 break;
466 GST_OBJECT_LOCK(self);
467 g_free(self->conversion_element);
468 self->conversion_element = g_value_dup_string(value);
469 GST_OBJECT_UNLOCK(self);
470 break;
471 case PROP_DISABLE_PAR:
472 self->disable_par = g_value_get_boolean(value);
473 break;
474 case PROP_SYNC:
475 self->sync = g_value_get_boolean(value);
476 if (self->videosink)
477 g_object_set(self->videosink, "sync", (gboolean) self->sync, NULL);
478 break;
479 case PROP_QOS:
480 self->qos = g_value_get_boolean(value);
481 if (self->videosink)
482 g_object_set(self->videosink, "qos", (gboolean) self->qos, NULL);
483 break;
485 self->processing_deadline = g_value_get_uint64(value);
486 if (self->videosink)
487 g_object_set(self->videosink, "processing-deadline", self->processing_deadline, NULL);
488 break;
489 default:
490 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
491 break;
492 }
493}
494
495static void gst_qgc_video_sink_bin_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
496{
497 GstQgcVideoSinkBin* self = GST_QGC_VIDEO_SINK_BIN(object);
498
499 switch (prop_id) {
501 g_value_set_boolean(value, self->gpu_zerocopy);
502 break;
504 GST_OBJECT_LOCK(self);
505 g_value_set_string(value, self->conversion_element);
506 GST_OBJECT_UNLOCK(self);
507 break;
508 case PROP_DISABLE_PAR:
509 g_value_set_boolean(value, self->disable_par);
510 break;
511 case PROP_SYNC:
512 g_value_set_boolean(value, self->sync);
513 break;
514 case PROP_QOS:
515 g_value_set_boolean(value, self->qos);
516 break;
518 g_value_set_uint64(value, self->processing_deadline);
519 break;
520 default:
521 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
522 break;
523 }
524}
525
527{
528 g_return_val_if_fail(GST_IS_QGC_VIDEO_SINK_BIN(self), NULL);
529 return self->videosink ? GST_ELEMENT(gst_object_ref(self->videosink)) : NULL;
530}
531
533{
534 if (!bin || !GST_IS_QGC_VIDEO_SINK_BIN(bin)) {
535 return FALSE;
536 }
537 return GST_QGC_VIDEO_SINK_BIN(bin)->gpu_zerocopy;
538}
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