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