/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ /** * @file * @brief GStreamer plugin for QGC's Video Receiver * @author Andrew Voznyts * @author Tomaz Canabrava */ #include #include GST_DEBUG_CATEGORY_STATIC(gst_qgc_video_sink_bin_debug); #define GST_CAT_DEFAULT gst_qgc_video_sink_bin_debug typedef struct _GstQgcVideoSinkElement GstQgcVideoSinkElement; typedef struct _GstQgcVideoSinkBin { GstBin bin; GstElement* glupload; GstElement* qmlglsink; } GstQgcVideoSinkBin; typedef struct _GstQgcVideoSinkBinClass { GstBinClass parent_class; } GstQgcVideoSinkBinClass; #define GST_TYPE_VIDEO_SINK_BIN (_vsb_get_type()) #define GST_QGC_VIDEO_SINK_BIN_CAST(obj) ((GstQgcVideoSinkBin *)(obj)) #define GST_QGC_VIDEO_SINK_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_VIDEO_SINK_BIN, GstQgcVideoSinkBin)) #define GST_QGC_VIDEO_SINK_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_VIDEO_SINK_BIN, GstQgcVideoSinkBinClass)) #define GST_IS_VIDEO_SINK_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_VIDEO_SINK_BIN)) #define GST_IS_VIDEO_SINK_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_VIDEO_SINK_BIN)) enum { PROP_0, PROP_ENABLE_LAST_SAMPLE, PROP_LAST_SAMPLE, PROP_WIDGET, PROP_FORCE_ASPECT_RATIO, PROP_PIXEL_ASPECT_RATIO, PROP_SYNC, }; #define PROP_ENABLE_LAST_SAMPLE_NAME "enable-last-sample" #define PROP_LAST_SAMPLE_NAME "last-sample" #define PROP_WIDGET_NAME "widget" #define PROP_FORCE_ASPECT_RATIO_NAME "force-aspect-ratio" #define PROP_PIXEL_ASPECT_RATIO_NAME "pixel-aspect-ratio" #define PROP_SYNC_NAME "sync" #define DEFAULT_ENABLE_LAST_SAMPLE TRUE #define DEFAULT_FORCE_ASPECT_RATIO TRUE #define DEFAULT_PAR_N 0 #define DEFAULT_PAR_D 1 #define DEFAULT_SYNC TRUE static GstBinClass *parent_class; static void _vsb_init(GstQgcVideoSinkBin *vsb); static void _vsb_dispose(GObject *object); static void _vsb_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _vsb_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static GType _vsb_get_type(void); static void _vsb_class_init(GstQgcVideoSinkBinClass *klass); static gboolean _vsb_sink_pad_query(GstPad* pad, GstObject* parent, GstQuery* query) { GstQgcVideoSinkBin *vsb; GstElement* element; vsb = GST_QGC_VIDEO_SINK_BIN(parent); switch (GST_QUERY_TYPE(query)) { case GST_QUERY_CAPS: element = vsb->glupload; break; case GST_QUERY_CONTEXT: element = vsb->qmlglsink; break; default: return gst_pad_query_default (pad, parent, query); } if (element == NULL) { GST_ERROR_OBJECT(vsb, "No element found"); return FALSE; } GstPad* sinkpad = gst_element_get_static_pad(element, "sink"); if (sinkpad == NULL) { GST_ERROR_OBJECT(vsb, "No sink pad found"); return FALSE; } const gboolean ret = gst_pad_query(sinkpad, query); gst_object_unref(sinkpad); sinkpad = NULL; return ret; } static void _vsb_init(GstQgcVideoSinkBin *vsb) { gboolean initialized = FALSE; GstElement* glcolorconvert = NULL; GstPad* pad = NULL; do { if ((vsb->glupload = gst_element_factory_make("glupload", NULL)) == NULL) { GST_ERROR_OBJECT(vsb, "gst_element_factory_make('glupload') failed"); break; } if ((vsb->qmlglsink = gst_element_factory_make("qmlglsink", NULL)) == NULL) { GST_ERROR_OBJECT(vsb, "gst_element_factory_make('qmlglsink') failed"); break; } if ((glcolorconvert = gst_element_factory_make("glcolorconvert", NULL)) == NULL) { GST_ERROR_OBJECT(vsb, "gst_element_factory_make('glcolorconvert' failed)"); break; } if ((pad = gst_element_get_static_pad(vsb->glupload, "sink")) == NULL) { GST_ERROR_OBJECT(vsb, "gst_element_get_static_pad(glupload, 'sink') failed"); break; } gst_object_ref(vsb->glupload); gst_object_ref(vsb->qmlglsink); gst_bin_add_many(GST_BIN(vsb), vsb->glupload, glcolorconvert, vsb->qmlglsink, NULL); gboolean ret = gst_element_link_many(vsb->glupload, glcolorconvert, vsb->qmlglsink, NULL); glcolorconvert = NULL; if (!ret) { GST_ERROR_OBJECT(vsb, "gst_element_link_many() failed"); break; } GstPad* ghostpad; if ((ghostpad = gst_ghost_pad_new("sink", pad)) == NULL) { GST_ERROR_OBJECT(vsb, "gst_ghost_pad_new('sink') failed"); break; } gst_pad_set_query_function(ghostpad, _vsb_sink_pad_query); if (!gst_element_add_pad(GST_ELEMENT(vsb), ghostpad)) { GST_ERROR_OBJECT(vsb, "gst_element_add_pad() failed"); break; } initialized = TRUE; } while(0); if (pad != NULL) { gst_object_unref(pad); pad = NULL; } if (glcolorconvert != NULL) { gst_object_unref(glcolorconvert); glcolorconvert = NULL; } if (!initialized) { if (vsb->qmlglsink != NULL) { gst_object_unref(vsb->qmlglsink); vsb->qmlglsink = NULL; } if (vsb->glupload != NULL) { gst_object_unref(vsb->glupload); vsb->glupload = NULL; } } } static void _vsb_dispose(GObject *object) { GstQgcVideoSinkBin *vsb; vsb = GST_QGC_VIDEO_SINK_BIN(object); if (vsb->qmlglsink != NULL) { gst_object_unref(vsb->qmlglsink); vsb->qmlglsink = NULL; } if (vsb->glupload != NULL) { gst_object_unref(vsb->glupload); vsb->glupload = NULL; } G_OBJECT_CLASS(parent_class)->dispose(object); } static void _vsb_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstQgcVideoSinkBin *vsb; vsb = GST_QGC_VIDEO_SINK_BIN(object); switch (prop_id) { case PROP_ENABLE_LAST_SAMPLE: do { gboolean enable = FALSE; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_ENABLE_LAST_SAMPLE_NAME, &enable, NULL); g_value_set_boolean(value, enable); } while(0); break; case PROP_LAST_SAMPLE: do { GstSample *sample = NULL; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_LAST_SAMPLE_NAME, &sample, NULL); gst_value_set_sample(value, sample); if (sample != NULL) { gst_sample_unref(sample); sample = NULL; } } while(0); break; case PROP_WIDGET: do { gpointer widget = NULL; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_WIDGET_NAME, &widget, NULL); g_value_set_pointer(value, widget); } while(0); break; case PROP_FORCE_ASPECT_RATIO: do { gboolean enable = FALSE; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_FORCE_ASPECT_RATIO_NAME, &enable, NULL); g_value_set_boolean(value, enable); } while(0); break; case PROP_PIXEL_ASPECT_RATIO: do { gint num = 0, den = 1; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_PIXEL_ASPECT_RATIO_NAME, &num, &den, NULL); gst_value_set_fraction(value, num, den); } while(0); break; case PROP_SYNC: do { gboolean enable = FALSE; g_object_get(G_OBJECT(vsb->qmlglsink), PROP_SYNC_NAME, &enable, NULL); g_value_set_boolean(value, enable); } while(0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _vsb_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstQgcVideoSinkBin *vsb; vsb = GST_QGC_VIDEO_SINK_BIN(object); switch (prop_id) { case PROP_ENABLE_LAST_SAMPLE: g_object_set(G_OBJECT(vsb->qmlglsink), PROP_ENABLE_LAST_SAMPLE_NAME, g_value_get_boolean(value), NULL); break; case PROP_WIDGET: g_object_set(G_OBJECT(vsb->qmlglsink), PROP_WIDGET_NAME, g_value_get_pointer(value), NULL); break; case PROP_FORCE_ASPECT_RATIO: g_object_set(G_OBJECT(vsb->qmlglsink), PROP_FORCE_ASPECT_RATIO_NAME, g_value_get_boolean(value), NULL); break; case PROP_PIXEL_ASPECT_RATIO: g_object_set(G_OBJECT(vsb->qmlglsink), PROP_PIXEL_ASPECT_RATIO_NAME, gst_value_get_fraction_numerator(value), gst_value_get_fraction_denominator(value), NULL); break; case PROP_SYNC: g_object_set(G_OBJECT(vsb->qmlglsink), PROP_SYNC_NAME, g_value_get_boolean(value), NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static GType _vsb_get_type(void) { static GType _vsb_type = 0; if (!_vsb_type) { static const GTypeInfo _vsb_info = { sizeof(GstQgcVideoSinkBinClass), NULL, NULL, (GClassInitFunc)_vsb_class_init, NULL, NULL, sizeof(GstQgcVideoSinkBin), 0, (GInstanceInitFunc)_vsb_init, NULL}; _vsb_type = g_type_register_static(GST_TYPE_BIN, "GstQgcVideoSinkBin", &_vsb_info, (GTypeFlags)0); } return _vsb_type; } static void _vsb_class_init(GstQgcVideoSinkBinClass *klass) { GObjectClass *gobject_klass; GstElementClass *gstelement_klass; gobject_klass = (GObjectClass *)klass; gstelement_klass = (GstElementClass *)klass; parent_class = g_type_class_peek_parent(klass); gobject_klass->dispose = _vsb_dispose; gobject_klass->get_property = _vsb_get_property; gobject_klass->set_property = _vsb_set_property; g_object_class_install_property(gobject_klass, PROP_ENABLE_LAST_SAMPLE, g_param_spec_boolean(PROP_ENABLE_LAST_SAMPLE_NAME, "Enable Last Buffer", "Enable the last-sample property", DEFAULT_ENABLE_LAST_SAMPLE, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_klass, PROP_LAST_SAMPLE, g_param_spec_boxed(PROP_LAST_SAMPLE_NAME, "Last Sample", "The last sample received in the sink", GST_TYPE_SAMPLE, (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_klass, PROP_WIDGET, g_param_spec_pointer(PROP_WIDGET_NAME, "QQuickItem", "The QQuickItem to place in the object hierarchy", (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_klass, PROP_FORCE_ASPECT_RATIO, g_param_spec_boolean(PROP_FORCE_ASPECT_RATIO_NAME, "Force aspect ratio", "When enabled, scaling will respect original aspect ratio", DEFAULT_FORCE_ASPECT_RATIO, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_klass, PROP_PIXEL_ASPECT_RATIO, gst_param_spec_fraction(PROP_PIXEL_ASPECT_RATIO_NAME, "Pixel Aspect Ratio", "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, G_MAXINT, 1, 1, 1, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_klass, PROP_SYNC, g_param_spec_boolean(PROP_SYNC_NAME, "Sync", "Sync on the clock", DEFAULT_SYNC, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); gst_element_class_set_static_metadata(gstelement_klass, "QGC Video Sink Bin", "Sink/Video/Bin", "Video rendering for QGC", "Andrew Voznytsa , Tomaz Canabrava "); } gboolean gst_qgc_video_sink_bin_plugin_init(GstPlugin *plugin) { GST_DEBUG_CATEGORY_INIT(gst_qgc_video_sink_bin_debug, "qgcvideosinkbin", 0, "QGC Video Sink Bin"); return gst_element_register(plugin, "qgcvideosinkbin", GST_RANK_NONE, GST_TYPE_VIDEO_SINK_BIN); }