QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
gstqgcvideosinkbin.cc
Go to the documentation of this file.
2#include "gstqgcelements.h"
3
4#include <gst/app/gstappsink.h>
5#include <gst/video/gstvideometa.h>
6#include <gst/video/video-info.h>
7#if GST_CHECK_VERSION(1, 24, 0)
8# include <gst/video/video-info-dma.h>
9#endif
10
11#include <string>
12
13#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
14# include "../HwBuffers/GstGlContextBridge.h"
15#endif
16
17#define GST_CAT_DEFAULT gst_qgc_debug
18
19#define DEFAULT_ENABLE_LAST_SAMPLE FALSE
20#define DEFAULT_SYNC FALSE
21#define DEFAULT_MAX_LATENESS G_GINT64_CONSTANT(-1)
22
23#define PROP_ENABLE_LAST_SAMPLE_NAME "enable-last-sample"
24#define PROP_LAST_SAMPLE_NAME "last-sample"
25#define PROP_SYNC_NAME "sync"
26#define PROP_MAX_LATENESS_NAME "max-lateness"
27
28enum
29{
39};
40
41static GParamSpec *properties[PROP_LAST];
42
43static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
44 "sink",
45 GST_PAD_SINK,
46 GST_PAD_ALWAYS,
47 GST_STATIC_CAPS_ANY);
48
49#define gst_qgc_video_sink_bin_parent_class parent_class
50G_DEFINE_TYPE(GstQgcVideoSinkBin, gst_qgc_video_sink_bin, GST_TYPE_BIN);
51
52GST_ELEMENT_REGISTER_DEFINE_WITH_CODE(qgcvideosinkbin,"qgcvideosinkbin",
53 GST_RANK_NONE,
55 qgc_element_init(plugin));
56
57static void gst_qgc_video_sink_bin_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
58static void gst_qgc_video_sink_bin_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
59static void gst_qgc_video_sink_bin_constructed(GObject *object);
60static void gst_qgc_video_sink_bin_dispose(GObject *object);
61static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement *element, GstStateChange transition);
62
63static void
64gst_qgc_video_sink_bin_class_init(GstQgcVideoSinkBinClass *klass)
65{
66 GObjectClass *object_class = G_OBJECT_CLASS(klass);
67 GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
68
69 object_class->set_property = gst_qgc_video_sink_bin_set_property;
70 object_class->get_property = gst_qgc_video_sink_bin_get_property;
71 object_class->constructed = gst_qgc_video_sink_bin_constructed;
72 object_class->dispose = gst_qgc_video_sink_bin_dispose;
73 element_class->change_state = gst_qgc_video_sink_bin_change_state;
74
75 properties[PROP_ENABLE_LAST_SAMPLE] = g_param_spec_boolean(
76 "enable-last-sample", "Enable last sample",
77 "Retain the most recent buffer for UI snapshotting",
79 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
80 );
81
82 properties[PROP_LAST_SAMPLE] = g_param_spec_boxed(
83 "last-sample", "Last sample",
84 "Last preroll/played sample held by the sink",
85 GST_TYPE_SAMPLE,
86 (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)
87 );
88
89 properties[PROP_SYNC] = g_param_spec_boolean(
90 "sync", "Sync",
91 "Synchronise frame presentation to the pipeline clock",
93 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
94 );
95
96 properties[PROP_MAX_LATENESS] = g_param_spec_int64(
97 "max-lateness", "Max lateness",
98 "Maximum number of nanoseconds a buffer can be late before it is dropped (-1 unlimited)",
99 -1, G_MAXINT64,
101 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
102 );
103
104 properties[PROP_GPU_ZEROCOPY] = g_param_spec_boolean(
105 "gpu-zerocopy",
106 "GPU zero-copy",
107 "Enable DMABuf zero-copy path (Linux only). Construct-only.",
108 FALSE,
109 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
110
111 properties[PROP_CONVERSION_ELEMENT] = g_param_spec_string(
112 "conversion-element",
113 "Conversion element factory",
114 "Factory name to use for the CPU branch's color conversion. Empty/NULL = auto-probe.",
115 NULL,
116 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
117
118 properties[PROP_DISABLE_PAR] = g_param_spec_boolean(
119 "disable-par",
120 "Disable PAR=1/1 capsfilter",
121 "Skip the pixel-aspect-ratio=1/1 capsfilter on the CPU branch (workaround for "
122 "v4l2 drivers without VIDIOC_CROPCAP). Construct-only.",
123 FALSE,
124 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
125
126 g_object_class_install_properties(object_class, PROP_LAST, properties);
127
128 gst_element_class_add_static_pad_template(GST_ELEMENT_CLASS(klass), &sink_factory);
129
130 gst_element_class_set_static_metadata(element_class,
131 "QGC Video Sink Bin", "Sink/Video/Bin",
132 "Appsink-based video sink for QGroundControl (frames pushed to a QVideoSink)",
133 "QGroundControl team"
134 );
135}
136
137// VAAPI/H.264 ref-frame queue typically 4–8; min=2 forced fallback allocations.
138constexpr guint kProposedMinBuffers = 4;
139
140static GstPadProbeReturn
142{
143 GstCaps *caps = nullptr;
144 gboolean need_pool = FALSE;
145 gst_query_parse_allocation(query, &caps, &need_pool);
146 if (!caps) {
147 return GST_PAD_PROBE_OK;
148 }
149
150 GstVideoInfo vinfo;
151 // DMA_DRM caps need the dma_drm parser; plain gst_video_info_from_caps fails and the
152 // min-buffer hint silently disappears, leaving va on its copy-threshold pool.
153#if GST_CHECK_VERSION(1, 24, 0)
154 if (gst_video_is_dma_drm_caps(caps)) {
155 GstVideoInfoDmaDrm drmInfo;
156 gst_video_info_dma_drm_init(&drmInfo);
157 if (!gst_video_info_dma_drm_from_caps(&drmInfo, caps)
158 || !gst_video_info_dma_drm_to_video_info(&drmInfo, &vinfo)) {
159 return GST_PAD_PROBE_OK;
160 }
161 } else
162#endif
163 if (!gst_video_info_from_caps(&vinfo, caps)) {
164 return GST_PAD_PROBE_OK;
165 }
166 const gsize size = GST_VIDEO_INFO_SIZE(&vinfo);
167
168 GstCapsFeatures *features = gst_caps_get_features(caps, 0);
169 const bool is_system_memory = !features
170 || gst_caps_features_is_any(features)
171 || gst_caps_features_is_equal(features, GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY);
172
173 if (!is_system_memory) {
174 // Advertise min-buffer hint for HW memory (DMABuf/GL/D3D/NVMM/AHB) so upstream v4l2/VA does not enable copy threshold.
175 gst_query_add_allocation_pool(query, NULL, size, kProposedMinBuffers, 0);
176 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);
177 return GST_PAD_PROBE_OK;
178 }
179
180 if (!need_pool) {
181 return GST_PAD_PROBE_OK;
182 }
183
184 GstBufferPool *pool = gst_buffer_pool_new();
185 if (!pool) {
186 return GST_PAD_PROBE_OK;
187 }
188 GstStructure *config = gst_buffer_pool_get_config(pool);
189 gst_buffer_pool_config_set_params(config, caps, size, kProposedMinBuffers, 0);
190 gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META);
191 if (gst_buffer_pool_set_config(pool, config)) {
192 gst_query_add_allocation_pool(query, pool, size, kProposedMinBuffers, 0);
193 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);
194 }
195 gst_object_unref(pool);
196 return GST_PAD_PROBE_OK;
197}
198
199static GstPadProbeReturn
200gst_qgc_appsink_query_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
201{
202 (void)pad;
203 (void)user_data;
204 GstQuery *query = GST_PAD_PROBE_INFO_QUERY(info);
205 if (!query) {
206 return GST_PAD_PROBE_OK;
207 }
208
209 switch (GST_QUERY_TYPE(query)) {
210 case GST_QUERY_ALLOCATION:
212 case GST_QUERY_CONTEXT:
213#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
214 // Synchronous answer for gst.gl.GLDisplay/app_context — bus NEED_CONTEXT fallback races state changes and can isolate glupload from Qt's RHI context.
215 if (GstGlContextBridge::answerContextQuery(query)) {
216 return GST_PAD_PROBE_HANDLED;
217 }
218#endif
219 return GST_PAD_PROBE_OK;
220 default:
221 return GST_PAD_PROBE_OK;
222 }
223}
224
225static gboolean
226gst_qgc_video_sink_bin_ghost_pad(GstQgcVideoSinkBin *self, GstElement *inner)
227{
228 GstPad *sinkpad = gst_element_get_static_pad(inner, "sink");
229 if (!sinkpad) {
230 GST_ERROR_OBJECT(self, "gst_element_get_static_pad('sink') failed");
231 return FALSE;
232 }
233
234 GstPad *ghostpad = gst_ghost_pad_new("sink", sinkpad);
235 gst_object_unref(sinkpad);
236 if (!ghostpad) {
237 GST_ERROR_OBJECT(self, "gst_ghost_pad_new('sink') failed");
238 return FALSE;
239 }
240
241 if (!gst_element_add_pad(GST_ELEMENT(self), ghostpad)) {
242 GST_ERROR_OBJECT(self, "gst_element_add_pad() failed");
243 gst_object_unref(ghostpad);
244 return FALSE;
245 }
246
247 return TRUE;
248}
249
250static void
251gst_qgc_video_sink_bin_init(GstQgcVideoSinkBin *self)
252{
253 self->videoconvert = NULL;
254 self->glupload = NULL;
255 self->appsink = NULL;
256 self->par_capsfilter = NULL;
257 self->gpu_zerocopy = FALSE;
258 self->conversion_element = NULL;
259 self->disable_par = FALSE;
260}
261
262// Probe: caller override → SoC-native (imxvideoconvert_g2d/nvvidconv) → videoconvert.
263static GstElement *
265{
266 if (self->conversion_element && self->conversion_element[0] != '\0') {
267 GstElement *e = gst_element_factory_make(self->conversion_element, NULL);
268 if (e) {
269 GST_INFO_OBJECT(self, "Using conversion-element override '%s'", self->conversion_element);
270 return e;
271 }
272 GST_WARNING_OBJECT(self,
273 "conversion-element='%s' factory missing — falling through to defaults",
274 self->conversion_element);
275 }
276 static const char *kSoCFactories[] = { "imxvideoconvert_g2d", "nvvidconv", NULL };
277 for (int i = 0; kSoCFactories[i] != NULL; ++i) {
278 if (GstElement *e = gst_element_factory_make(kSoCFactories[i], NULL)) {
279 GST_INFO_OBJECT(self, "Using SoC conversion element '%s'", kSoCFactories[i]);
280 return e;
281 }
282 }
283 return gst_element_factory_make("videoconvert", NULL);
284}
285
286static void
287gst_qgc_video_sink_bin_setup(GstQgcVideoSinkBin *self)
288{
289 self->appsink = gst_element_factory_make("appsink", "qgcappsink");
290 if (!self->appsink) {
291 GST_ERROR_OBJECT(self, "Failed to create appsink element");
292 return;
293 }
294
295 // Attach probe before the gpu/cpu branch so both paths share the same downstream query handler.
296 if (GstPad *appsinkPad = gst_element_get_static_pad(self->appsink, "sink")) {
297 // probe_id == 0 means add failed (e.g. pad in dispose) — without this probe, GL-context queries fall back to the slower bus NEED_CONTEXT path that races with state changes.
298 const gulong probe_id = gst_pad_add_probe(appsinkPad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
299 gst_qgc_appsink_query_probe, NULL, NULL);
300 if (probe_id == 0) {
301 GST_WARNING_OBJECT(self, "gst_pad_add_probe(QUERY_DOWNSTREAM) returned 0 — appsink query interception disabled");
302 }
303 gst_object_unref(appsinkPad);
304 }
305
306 if (self->gpu_zerocopy) {
307 // List only features the build can consume zero-copy; stale features waste a caps-intersection pass on every link.
308 // Y444 omitted: Qt 6.10 has no Format_YUV444* and toQtPixelFormat returns Invalid
309 // → onNewSample errors out. Re-add when Qt grows the enum.
310 static constexpr const char kFormats[] =
311 "{ NV12, NV21, I420, YV12, Y42B, P010_10LE, AYUV, YUY2, UYVY, "
312 "GRAY8, GRAY16_LE, BGRA, RGBA }";
313 std::string capsStr;
314#if defined(QGC_GST_BIN_USE_GLUPLOAD)
315 // Linux desktop: va decoder produces DMA_DRM DMABuf which appsink can't consume directly; routing through glupload imports DMABuf into GL textures (still zero-copy via EGLImage) and feeds GLMemory to the appsink, which the adapter unwraps via GstGlVideoBuffer.
316 capsStr = "video/x-raw(memory:GLMemory), format=";
317 capsStr += kFormats;
318#else
319# if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
320 // Legacy memory:DMABuf,format=NV12 only — covers v4l2h264dec / Mali / V3D LINEAR
321 // DMABuf paths. DMA_DRM caps deliberately omitted: gst-va on Intel iHD negotiates
322 // tiled+CCS layouts that crash both GPU and CPU paths. The system catch-all below
323 // routes va to GstVaMemory whose map() detiles via libva.
324 capsStr += "video/x-raw(memory:DMABuf), format=";
325 capsStr += kFormats;
326 capsStr += "; ";
327# endif
328# if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) && !defined(QGC_GST_BIN_USE_DMABUF)
329 // No glupload in USE_DMABUF bin — offering GLMemory lets upstream try (and fail) it.
330 capsStr += "video/x-raw(memory:GLMemory), format=";
331 capsStr += kFormats;
332 capsStr += "; ";
333# endif
334# if defined(QGC_HAS_GST_D3D11_GPU_PATH)
335 capsStr += "video/x-raw(memory:D3D11Memory), format=";
336 capsStr += kFormats;
337 capsStr += "; ";
338# endif
339# if defined(QGC_HAS_GST_D3D12_GPU_PATH)
340 capsStr += "video/x-raw(memory:D3D12Memory), format=";
341 capsStr += kFormats;
342 capsStr += "; ";
343# endif
344# if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
345 capsStr += "video/x-raw(memory:AHardwareBuffer), format=";
346 capsStr += kFormats;
347 capsStr += "; ";
348# endif
349 // System catch-all required: dropping it returns GST_PAD_LINK_NOFORMAT (-4) when
350 // upstream offers only system caps, and the receiver tears down.
351 capsStr += "video/x-raw, format=";
352 capsStr += kFormats;
353#endif
354 GstCaps *caps = gst_caps_from_string(capsStr.c_str());
355 if (!caps) {
356 GST_ERROR_OBJECT(self, "gst_caps_from_string() returned NULL for GPU caps");
357 gst_clear_object(&self->appsink);
358 return;
359 }
360 // emit-signals=FALSE: GstAppSinkAdapter installs callbacks via gst_app_sink_set_callbacks() — flipping this to TRUE silently breaks frame delivery (samples queue with no consumer).
361 g_object_set(self->appsink,
362 "caps", caps,
363 "emit-signals", FALSE,
364 "max-buffers", 1,
365 "drop", TRUE,
366 "sync", FALSE,
367 "wait-on-eos", FALSE,
368 NULL);
369 gst_caps_unref(caps);
370
371#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
372 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
373 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
374 gst_app_sink_set_max_time(GST_APP_SINK(self->appsink), 33 * GST_MSECOND);
375#endif
376
377#if defined(QGC_GST_BIN_USE_GLUPLOAD)
378 self->glupload = gst_element_factory_make("glupload", NULL);
379 if (!self->glupload) {
380 GST_ERROR_OBJECT(self, "Failed to create glupload element");
381 gst_clear_object(&self->appsink);
382 return;
383 }
384 gst_bin_add_many(GST_BIN(self), self->glupload, self->appsink, NULL);
385 if (!gst_element_link(self->glupload, self->appsink)
386 || !gst_qgc_video_sink_bin_ghost_pad(self, self->glupload)) {
387 GST_ERROR_OBJECT(self, "Failed to link/ghost glupload→appsink GPU path");
388 gst_bin_remove(GST_BIN(self), self->glupload);
389 gst_bin_remove(GST_BIN(self), self->appsink);
390 self->glupload = NULL;
391 self->appsink = NULL;
392 return;
393 }
394 GST_INFO_OBJECT(self, "Using glupload→appsink GPU path (DMABuf→GL EGLImage import)");
395#else
396 gst_bin_add(GST_BIN(self), self->appsink);
397 if (!gst_qgc_video_sink_bin_ghost_pad(self, self->appsink)) {
398 GST_ERROR_OBJECT(self, "Failed to ghost-pad appsink (GPU path)");
399 gst_bin_remove(GST_BIN(self), self->appsink);
400 self->appsink = NULL;
401 return;
402 }
403# if defined(QGC_GST_BIN_USE_DMABUF)
404 GST_INFO_OBJECT(self, "Using appsink GPU path (direct DMABuf import, no glupload)");
405# else
406 GST_INFO_OBJECT(self, "Using appsink GPU path (native memory passthrough)");
407# endif
408#endif
409 } else {
410 self->videoconvert = gst_qgc_video_sink_bin_make_conversion_element(self);
411 if (!self->videoconvert) {
412 GST_ERROR_OBJECT(self, "Failed to create video conversion element");
413 gst_clear_object(&self->appsink);
414 return;
415 }
416
417 // QVideoSink renders these natively; listing them avoids forcing videoconvert to BGRA.
418 GstCaps *caps = gst_caps_from_string(
419 "video/x-raw,format={ NV12, NV21, I420, YV12, Y42B, P010_10LE, "
420 "AYUV, YUY2, UYVY, GRAY8, GRAY16_LE, BGRA, RGBA }");
421 if (!caps) {
422 GST_ERROR_OBJECT(self, "gst_caps_from_string() returned NULL for CPU caps");
423 gst_clear_object(&self->videoconvert);
424 gst_clear_object(&self->appsink);
425 return;
426 }
427 // emit-signals=FALSE: GstAppSinkAdapter installs callbacks via gst_app_sink_set_callbacks() — flipping this to TRUE silently breaks frame delivery (samples queue with no consumer).
428 g_object_set(self->appsink,
429 "caps", caps,
430 "emit-signals", FALSE,
431 "max-buffers", 1,
432 "drop", TRUE,
433 "sync", FALSE,
434 "wait-on-eos", FALSE,
435 NULL);
436 gst_caps_unref(caps);
437
438#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
439 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
440 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
441 gst_app_sink_set_max_time(GST_APP_SINK(self->appsink), 33 * GST_MSECOND);
442#endif
443
444 // PAR=1/1 capsfilter normalizes non-square-pixel sources. disable-par for v4l2
445 // drivers without VIDIOC_CROPCAP that deadlock negotiation (GStreamer MR #6242).
446 if (!self->disable_par) {
447 self->par_capsfilter = gst_element_factory_make("capsfilter", NULL);
448 if (!self->par_capsfilter) {
449 GST_WARNING_OBJECT(self, "capsfilter factory missing — PAR normalization disabled");
450 } else {
451 GstCaps *par_caps = gst_caps_from_string(
452 "video/x-raw, pixel-aspect-ratio=(fraction)1/1");
453 g_object_set(self->par_capsfilter, "caps", par_caps, NULL);
454 gst_caps_unref(par_caps);
455 }
456 }
457
458 if (self->par_capsfilter) {
459 gst_bin_add_many(GST_BIN(self), self->videoconvert, self->par_capsfilter,
460 self->appsink, NULL);
461 if (!gst_element_link_many(self->videoconvert, self->par_capsfilter, self->appsink, NULL)
462 || !gst_qgc_video_sink_bin_ghost_pad(self, self->videoconvert)) {
463 GST_ERROR_OBJECT(self, "Failed to link/ghost appsink path (with PAR filter)");
464 gst_bin_remove(GST_BIN(self), self->videoconvert);
465 gst_bin_remove(GST_BIN(self), self->par_capsfilter);
466 gst_bin_remove(GST_BIN(self), self->appsink);
467 self->videoconvert = NULL;
468 self->par_capsfilter = NULL;
469 self->appsink = NULL;
470 return;
471 }
472 } else {
473 gst_bin_add_many(GST_BIN(self), self->videoconvert, self->appsink, NULL);
474 if (!gst_element_link(self->videoconvert, self->appsink)
475 || !gst_qgc_video_sink_bin_ghost_pad(self, self->videoconvert)) {
476 GST_ERROR_OBJECT(self, "Failed to link/ghost appsink path");
477 gst_bin_remove(GST_BIN(self), self->videoconvert);
478 gst_bin_remove(GST_BIN(self), self->appsink);
479 self->videoconvert = NULL;
480 self->appsink = NULL;
481 return;
482 }
483 }
484
485 GST_INFO_OBJECT(self, "Using appsink (videoconvert%s → appsink → QVideoSink)",
486 self->par_capsfilter ? " → PAR=1/1" : "");
487 }
488}
489
490static void
492{
493 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->constructed(object);
494 gst_qgc_video_sink_bin_setup(GST_QGC_VIDEO_SINK_BIN(object));
495}
496
497// GstBin's dispose unrefs all child elements; our cached self->appsink/videoconvert/glupload pointers
498// would then dangle. NULL them BEFORE chaining so any concurrent property accessor (which checks
499// G_LIKELY(self->appsink)) sees NULL instead of touching freed memory.
500static void
502{
503 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(object);
504 self->appsink = NULL;
505 self->videoconvert = NULL;
506 self->par_capsfilter = NULL;
507 self->glupload = NULL;
508 g_clear_pointer(&self->conversion_element, g_free);
509 G_OBJECT_CLASS(gst_qgc_video_sink_bin_parent_class)->dispose(object);
510}
511
512// Surfaces _setup() failures to the parent pipeline's bus on NULL→READY; without this the bin sits half-constructed (no ghost pad) and the parent reports a generic "no compatible pad" instead of the real cause logged at construction time.
513static GstStateChangeReturn
514gst_qgc_video_sink_bin_change_state(GstElement *element, GstStateChange transition)
515{
516 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(element);
517 if (transition == GST_STATE_CHANGE_NULL_TO_READY && !self->appsink) {
518 GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
519 ("qgcvideosinkbin construction failed; cannot transition to READY"),
520 ("see prior GST_ERROR messages from this element for the underlying cause"));
521 return GST_STATE_CHANGE_FAILURE;
522 }
523 return GST_ELEMENT_CLASS(gst_qgc_video_sink_bin_parent_class)->change_state(element, transition);
524}
525
526static void
527gst_qgc_video_sink_bin_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
528{
529 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(object);
530
531 switch (prop_id) {
533 if (G_LIKELY(self->appsink))
534 g_object_set(self->appsink,
536 g_value_get_boolean(value),
537 NULL);
538 break;
539 case PROP_SYNC:
540 if (G_LIKELY(self->appsink))
541 g_object_set(self->appsink,
543 g_value_get_boolean(value),
544 NULL);
545 break;
547 if (G_LIKELY(self->appsink))
548 g_object_set(self->appsink,
550 g_value_get_int64(value),
551 NULL);
552 break;
554 self->gpu_zerocopy = g_value_get_boolean(value);
555 break;
557 g_free(self->conversion_element);
558 self->conversion_element = g_value_dup_string(value);
559 break;
560 case PROP_DISABLE_PAR:
561 self->disable_par = g_value_get_boolean(value);
562 break;
563 default:
564 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
565 break;
566 }
567}
568
569static void
570gst_qgc_video_sink_bin_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
571{
572 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(object);
573
574 switch (prop_id) {
576 gboolean enable = FALSE;
577 if (G_LIKELY(self->appsink))
578 g_object_get(self->appsink,
580 &enable,
581 NULL);
582 g_value_set_boolean(value, enable);
583 break;
584 }
585 case PROP_LAST_SAMPLE: {
586 GstSample *sample = NULL;
587 if (G_LIKELY(self->appsink))
588 g_object_get(self->appsink,
590 &sample,
591 NULL);
592 if (sample) {
593 gst_value_set_sample(value, sample);
594 gst_sample_unref(sample);
595 }
596 break;
597 }
598 case PROP_SYNC: {
599 gboolean enable = FALSE;
600 if (G_LIKELY(self->appsink))
601 g_object_get(self->appsink,
603 &enable,
604 NULL);
605 g_value_set_boolean(value, enable);
606 break;
607 }
608 case PROP_MAX_LATENESS: {
609 gint64 lateness = DEFAULT_MAX_LATENESS;
610 if (G_LIKELY(self->appsink))
611 g_object_get(self->appsink,
613 &lateness,
614 NULL);
615 g_value_set_int64(value, lateness);
616 break;
617 }
619 g_value_set_boolean(value, self->gpu_zerocopy);
620 break;
622 g_value_set_string(value, self->conversion_element);
623 break;
624 case PROP_DISABLE_PAR:
625 g_value_set_boolean(value, self->disable_par);
626 break;
627 default:
628 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
629 break;
630 }
631}
632
634gst_qgc_video_sink_bin_get_appsink(GstQgcVideoSinkBin *self)
635{
636 g_return_val_if_fail(GST_IS_QGC_VIDEO_SINK_BIN(self), NULL);
637 return self->appsink ? GST_ELEMENT(gst_object_ref(self->appsink)) : NULL;
638}
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)
G_DEFINE_TYPE(GstQgcVideoSinkBin, gst_qgc_video_sink_bin, GST_TYPE_BIN)
static void gst_qgc_video_sink_bin_setup(GstQgcVideoSinkBin *self)
GstElement * gst_qgc_video_sink_bin_get_appsink(GstQgcVideoSinkBin *self)
static GParamSpec * properties[PROP_LAST]
#define DEFAULT_MAX_LATENESS
static GstPadProbeReturn gst_qgc_handle_allocation_query(GstQuery *query)
constexpr guint kProposedMinBuffers
#define gst_qgc_video_sink_bin_parent_class
static GstPadProbeReturn gst_qgc_appsink_query_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE(qgcvideosinkbin,"qgcvideosinkbin", GST_RANK_NONE, GST_TYPE_QGC_VIDEO_SINK_BIN, qgc_element_init(plugin))
static void gst_qgc_video_sink_bin_class_init(GstQgcVideoSinkBinClass *klass)
static GstStaticPadTemplate sink_factory
#define PROP_ENABLE_LAST_SAMPLE_NAME
#define DEFAULT_ENABLE_LAST_SAMPLE
#define DEFAULT_SYNC
#define PROP_MAX_LATENESS_NAME
static GstStateChangeReturn gst_qgc_video_sink_bin_change_state(GstElement *element, GstStateChange transition)
@ PROP_ENABLE_LAST_SAMPLE
@ PROP_DISABLE_PAR
@ PROP_GPU_ZEROCOPY
@ PROP_MAX_LATENESS
@ PROP_CONVERSION_ELEMENT
@ PROP_LAST
@ PROP_LAST_SAMPLE
@ PROP_SYNC
#define PROP_SYNC_NAME
static void gst_qgc_video_sink_bin_constructed(GObject *object)
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)
#define PROP_LAST_SAMPLE_NAME
static gboolean gst_qgc_video_sink_bin_ghost_pad(GstQgcVideoSinkBin *self, GstElement *inner)
static void gst_qgc_video_sink_bin_init(GstQgcVideoSinkBin *self)
#define GST_TYPE_QGC_VIDEO_SINK_BIN