From 22465514d7ad174e6d4fe734c0d9b591d915cf7b Mon Sep 17 00:00:00 2001 From: Andrew Voznytsa Date: Mon, 17 Feb 2020 20:54:56 +0200 Subject: [PATCH] Repackage video sink into GstQgcVideoSinkBin --- src/FlightDisplay/VideoManager.cc | 118 +------- src/VideoStreaming/VideoStreaming.cc | 3 + src/VideoStreaming/VideoStreaming.pri | 4 + src/VideoStreaming/gstqgc.c | 40 +++ src/VideoStreaming/gstqgcvideosinkbin.c | 368 ++++++++++++++++++++++++ 5 files changed, 420 insertions(+), 113 deletions(-) create mode 100644 src/VideoStreaming/gstqgc.c create mode 100644 src/VideoStreaming/gstqgcvideosinkbin.c diff --git a/src/FlightDisplay/VideoManager.cc b/src/FlightDisplay/VideoManager.cc index 0c10f5259..7fd2ef136 100644 --- a/src/FlightDisplay/VideoManager.cc +++ b/src/FlightDisplay/VideoManager.cc @@ -282,123 +282,15 @@ VideoManager::setfullScreen(bool f) //----------------------------------------------------------------------------- #if defined(QGC_GST_STREAMING) -gboolean -VideoManager::_videoSinkQuery(GstPad* pad, GstObject* parent, GstQuery* query) -{ - GstElement* element; - - switch (GST_QUERY_TYPE(query)) { - case GST_QUERY_CAPS: - element = gst_bin_get_by_name(GST_BIN(parent), "glupload"); - break; - case GST_QUERY_CONTEXT: - element = gst_bin_get_by_name(GST_BIN(parent), "qmlglsink"); - break; - default: - return gst_pad_query_default (pad, parent, query); - } - - if (element == nullptr) { - qWarning() << "VideoManager::_videoSinkQuery(): No element found"; - return FALSE; - } - - GstPad* sinkpad = gst_element_get_static_pad(element, "sink"); - - if (sinkpad == nullptr) { - qWarning() << "VideoManager::_videoSinkQuery(): No sink pad found"; - return FALSE; - } - - const gboolean ret = gst_pad_query(sinkpad, query); - - gst_object_unref(sinkpad); - sinkpad = nullptr; - - return ret; -} - GstElement* VideoManager::_makeVideoSink(gpointer widget) { - GstElement* glupload = nullptr; - GstElement* glcolorconvert = nullptr; - GstElement* qmlglsink = nullptr; - GstElement* bin = nullptr; - GstElement* sink = nullptr; - - do { - if ((glupload = gst_element_factory_make("glupload", "glupload")) == nullptr) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_factory_make('glupload')"; - break; - } - - if ((glcolorconvert = gst_element_factory_make("glcolorconvert", "glcolorconvert")) == nullptr) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_factory_make('glcolorconvert')"; - break; - } - - if ((qmlglsink = gst_element_factory_make("qmlglsink", "qmlglsink")) == nullptr) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_factory_make('qmlglsink')"; - break; - } - - g_object_set(qmlglsink, "widget", widget, NULL); - - if ((bin = gst_bin_new("videosink")) == nullptr) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_bin_new('videosink')"; - break; - } - - GstPad* pad; - - if ((pad = gst_element_get_static_pad(glupload, "sink")) == nullptr) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_get_static_pad(glupload, 'sink')"; - break; - } - - gst_bin_add_many(GST_BIN(bin), glupload, glcolorconvert, qmlglsink, nullptr); - - gboolean ret = gst_element_link_many(glupload, glcolorconvert, qmlglsink, nullptr); - - qmlglsink = glcolorconvert = glupload = nullptr; - - if (!ret) { - qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_link_many()"; - break; - } - - GstPad* ghostpad = gst_ghost_pad_new("sink", pad); + GstElement* sink; - gst_pad_set_query_function(ghostpad, _videoSinkQuery); - - gst_element_add_pad(bin, ghostpad); - - gst_object_unref(pad); - pad = nullptr; - - sink = bin; - bin = nullptr; - } while(0); - - if (bin != nullptr) { - gst_object_unref(bin); - bin = nullptr; - } - - if (qmlglsink != nullptr) { - gst_object_unref(qmlglsink); - qmlglsink = nullptr; - } - - if (glcolorconvert != nullptr) { - gst_object_unref(glcolorconvert); - glcolorconvert = nullptr; - } - - if (glupload != nullptr) { - gst_object_unref(glupload); - glupload = nullptr; + if ((sink = gst_element_factory_make("qgcvideosinkbin", nullptr)) != nullptr) { + g_object_set(sink, "widget", widget, NULL); + } else { + qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_factory_make('qgcvideosinkbin')"; } return sink; diff --git a/src/VideoStreaming/VideoStreaming.cc b/src/VideoStreaming/VideoStreaming.cc index 00983bd59..30d6c0721 100644 --- a/src/VideoStreaming/VideoStreaming.cc +++ b/src/VideoStreaming/VideoStreaming.cc @@ -67,6 +67,7 @@ static void gst_android_log(GstDebugCategory * category, #endif #endif GST_PLUGIN_STATIC_DECLARE(qmlgl); + GST_PLUGIN_STATIC_DECLARE(qgc); G_END_DECLS #endif @@ -168,6 +169,8 @@ void initializeVideoStreaming(int &argc, char* argv[], char* logpath, char* debu } else { qCritical() << "unable to find qmlglsink - you need to build it yourself and add to GST_PLUGIN_PATH"; } + + GST_PLUGIN_STATIC_REGISTER(qgc); #else qmlRegisterType("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem"); Q_UNUSED(argc) diff --git a/src/VideoStreaming/VideoStreaming.pri b/src/VideoStreaming/VideoStreaming.pri index adf584364..c25eaf5da 100644 --- a/src/VideoStreaming/VideoStreaming.pri +++ b/src/VideoStreaming/VideoStreaming.pri @@ -127,6 +127,10 @@ VideoEnabled { $$PWD/iOS } + SOURCES += \ + $$PWD/gstqgcvideosinkbin.c \ + $$PWD/gstqgc.c + include($$PWD/../../qmlglsink.pri) } else { LinuxBuild|MacBuild|iOSBuild|WindowsBuild|AndroidBuild { diff --git a/src/VideoStreaming/gstqgc.c b/src/VideoStreaming/gstqgc.c new file mode 100644 index 000000000..f8b8cf5d8 --- /dev/null +++ b/src/VideoStreaming/gstqgc.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * (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 + +gboolean gst_qgc_video_sink_bin_plugin_init(GstPlugin *plugin); + +static gboolean +plugin_init(GstPlugin* plugin) +{ + if (!gst_qgc_video_sink_bin_plugin_init(plugin)) { + return FALSE; + } + + return TRUE; +} + +#define PACKAGE "QGC Video Receiver" +#define PACKAGE_VERSION "current" +#define GST_LICENSE "LGPL" +#define GST_PACKAGE_NAME "GStreamer plugin for QGC's Video Receiver" +#define GST_PACKAGE_ORIGIN "http://qgroundcontrol.com/" + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, + qgc, "QGC Video Receiver plugin", + plugin_init, PACKAGE_VERSION, + GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/src/VideoStreaming/gstqgcvideosinkbin.c b/src/VideoStreaming/gstqgcvideosinkbin.c new file mode 100644 index 000000000..28e6dc207 --- /dev/null +++ b/src/VideoStreaming/gstqgcvideosinkbin.c @@ -0,0 +1,368 @@ +/**************************************************************************** + * + * (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, +}; + +#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 DEFAULT_ENABLE_LAST_SAMPLE TRUE +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 + +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; + 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; + 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))); + + 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); +} -- 2.22.0