66 GObjectClass *object_class = G_OBJECT_CLASS(klass);
67 GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
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)
83 "last-sample",
"Last sample",
84 "Last preroll/played sample held by the sink",
86 (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)
91 "Synchronise frame presentation to the pipeline clock",
93 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
97 "max-lateness",
"Max lateness",
98 "Maximum number of nanoseconds a buffer can be late before it is dropped (-1 unlimited)",
101 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
107 "Enable DMABuf zero-copy path (Linux only). Construct-only.",
109 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
112 "conversion-element",
113 "Conversion element factory",
114 "Factory name to use for the CPU branch's color conversion. Empty/NULL = auto-probe.",
116 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
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.",
124 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
128 gst_element_class_add_static_pad_template(GST_ELEMENT_CLASS(klass), &
sink_factory);
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"
143 GstCaps *caps =
nullptr;
144 gboolean need_pool = FALSE;
145 gst_query_parse_allocation(query, &caps, &need_pool);
147 return GST_PAD_PROBE_OK;
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;
163 if (!gst_video_info_from_caps(&vinfo, caps)) {
164 return GST_PAD_PROBE_OK;
166 const gsize size = GST_VIDEO_INFO_SIZE(&vinfo);
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);
173 if (!is_system_memory) {
176 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);
177 return GST_PAD_PROBE_OK;
181 return GST_PAD_PROBE_OK;
184 GstBufferPool *pool = gst_buffer_pool_new();
186 return GST_PAD_PROBE_OK;
188 GstStructure *config = gst_buffer_pool_get_config(pool);
190 gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META);
191 if (gst_buffer_pool_set_config(pool, config)) {
193 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);
195 gst_object_unref(pool);
196 return GST_PAD_PROBE_OK;
266 if (self->conversion_element && self->conversion_element[0] !=
'\0') {
267 GstElement *e = gst_element_factory_make(self->conversion_element, NULL);
269 GST_INFO_OBJECT(self,
"Using conversion-element override '%s'", self->conversion_element);
272 GST_WARNING_OBJECT(self,
273 "conversion-element='%s' factory missing — falling through to defaults",
274 self->conversion_element);
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]);
283 return gst_element_factory_make(
"videoconvert", NULL);
289 self->appsink = gst_element_factory_make(
"appsink",
"qgcappsink");
290 if (!self->appsink) {
291 GST_ERROR_OBJECT(self,
"Failed to create appsink element");
296 if (GstPad *appsinkPad = gst_element_get_static_pad(self->appsink,
"sink")) {
298 const gulong probe_id = gst_pad_add_probe(appsinkPad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
301 GST_WARNING_OBJECT(self,
"gst_pad_add_probe(QUERY_DOWNSTREAM) returned 0 — appsink query interception disabled");
303 gst_object_unref(appsinkPad);
306 if (self->gpu_zerocopy) {
310 static constexpr const char kFormats[] =
311 "{ NV12, NV21, I420, YV12, Y42B, P010_10LE, AYUV, YUY2, UYVY, "
312 "GRAY8, GRAY16_LE, BGRA, RGBA }";
314#if defined(QGC_GST_BIN_USE_GLUPLOAD)
316 capsStr =
"video/x-raw(memory:GLMemory), format=";
319# if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
324 capsStr +=
"video/x-raw(memory:DMABuf), format=";
328# if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) && !defined(QGC_GST_BIN_USE_DMABUF)
330 capsStr +=
"video/x-raw(memory:GLMemory), format=";
334# if defined(QGC_HAS_GST_D3D11_GPU_PATH)
335 capsStr +=
"video/x-raw(memory:D3D11Memory), format=";
339# if defined(QGC_HAS_GST_D3D12_GPU_PATH)
340 capsStr +=
"video/x-raw(memory:D3D12Memory), format=";
344# if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
345 capsStr +=
"video/x-raw(memory:AHardwareBuffer), format=";
351 capsStr +=
"video/x-raw, format=";
354 GstCaps *caps = gst_caps_from_string(capsStr.c_str());
356 GST_ERROR_OBJECT(self,
"gst_caps_from_string() returned NULL for GPU caps");
357 gst_clear_object(&self->appsink);
361 g_object_set(self->appsink,
363 "emit-signals", FALSE,
367 "wait-on-eos", FALSE,
369 gst_caps_unref(caps);
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);
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);
384 gst_bin_add_many(GST_BIN(self), self->glupload, self->appsink, NULL);
385 if (!gst_element_link(self->glupload, self->appsink)
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;
394 GST_INFO_OBJECT(self,
"Using glupload→appsink GPU path (DMABuf→GL EGLImage import)");
396 gst_bin_add(GST_BIN(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;
403# if defined(QGC_GST_BIN_USE_DMABUF)
404 GST_INFO_OBJECT(self,
"Using appsink GPU path (direct DMABuf import, no glupload)");
406 GST_INFO_OBJECT(self,
"Using appsink GPU path (native memory passthrough)");
411 if (!self->videoconvert) {
412 GST_ERROR_OBJECT(self,
"Failed to create video conversion element");
413 gst_clear_object(&self->appsink);
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 }");
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);
428 g_object_set(self->appsink,
430 "emit-signals", FALSE,
434 "wait-on-eos", FALSE,
436 gst_caps_unref(caps);
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);
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");
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);
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)
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;
473 gst_bin_add_many(GST_BIN(self), self->videoconvert, self->appsink, NULL);
474 if (!gst_element_link(self->videoconvert, self->appsink)
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;
485 GST_INFO_OBJECT(self,
"Using appsink (videoconvert%s → appsink → QVideoSink)",
486 self->par_capsfilter ?
" → PAR=1/1" :
"");
529 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(
object);
533 if (G_LIKELY(self->appsink))
534 g_object_set(self->appsink,
536 g_value_get_boolean(value),
540 if (G_LIKELY(self->appsink))
541 g_object_set(self->appsink,
543 g_value_get_boolean(value),
547 if (G_LIKELY(self->appsink))
548 g_object_set(self->appsink,
550 g_value_get_int64(value),
554 self->gpu_zerocopy = g_value_get_boolean(value);
557 g_free(self->conversion_element);
558 self->conversion_element = g_value_dup_string(value);
561 self->disable_par = g_value_get_boolean(value);
564 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);
572 GstQgcVideoSinkBin *self = GST_QGC_VIDEO_SINK_BIN(
object);
576 gboolean enable = FALSE;
577 if (G_LIKELY(self->appsink))
578 g_object_get(self->appsink,
582 g_value_set_boolean(value, enable);
586 GstSample *sample = NULL;
587 if (G_LIKELY(self->appsink))
588 g_object_get(self->appsink,
593 gst_value_set_sample(value, sample);
594 gst_sample_unref(sample);
599 gboolean enable = FALSE;
600 if (G_LIKELY(self->appsink))
601 g_object_get(self->appsink,
605 g_value_set_boolean(value, enable);
610 if (G_LIKELY(self->appsink))
611 g_object_get(self->appsink,
615 g_value_set_int64(value, lateness);
619 g_value_set_boolean(value, self->gpu_zerocopy);
622 g_value_set_string(value, self->conversion_element);
625 g_value_set_boolean(value, self->disable_par);
628 G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);