5 changed files with 420 additions and 113 deletions
@ -0,0 +1,40 @@ |
|||||||
|
/****************************************************************************
|
||||||
|
* |
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
* |
||||||
|
* 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 <andrew.voznytsa@gmail.com> |
||||||
|
* @author Tomaz Canabrava <tcanabrava@kde.org> |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <gst/gst.h> |
||||||
|
|
||||||
|
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) |
@ -0,0 +1,368 @@ |
|||||||
|
/****************************************************************************
|
||||||
|
* |
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
* |
||||||
|
* 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 <andrew.voznytsa@gmail.com> |
||||||
|
* @author Tomaz Canabrava <tcanabrava@kde.org> |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <glib-object.h> |
||||||
|
#include <gst/gst.h> |
||||||
|
|
||||||
|
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 <andrew.voznytsa@gmail.com>, Tomaz Canabrava <tcanabrava@kde.org>"); |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
} |
Loading…
Reference in new issue