summaryrefslogtreecommitdiff
path: root/ext/gl
diff options
context:
space:
mode:
authorJan Schmidt <jan@centricular.com>2015-05-30 02:29:04 +1000
committerJan Schmidt <jan@centricular.com>2015-06-19 01:49:33 +1000
commiteefdb2ed8675b738202b074470e097c6215bea9b (patch)
tree60a90817a36e6fcd94161bb4508f9e5ac8d21ee0 /ext/gl
parent00d6fe9068da64c4991dc071bc8ca779c9cc79ac (diff)
downloadgstreamer-plugins-bad-eefdb2ed8675b738202b074470e097c6215bea9b.tar.gz
gl: Add glviewconvert, glstereomix and glstereosplit elements
Conversion elements for transforming multiview/stereoscopic video https://bugzilla.gnome.org/show_bug.cgi?id=611157
Diffstat (limited to 'ext/gl')
-rw-r--r--ext/gl/Makefile.am6
-rw-r--r--ext/gl/gstglstereomix.c702
-rw-r--r--ext/gl/gstglstereomix.h83
-rw-r--r--ext/gl/gstglstereosplit.c928
-rw-r--r--ext/gl/gstglstereosplit.h66
-rw-r--r--ext/gl/gstglviewconvert.c353
-rw-r--r--ext/gl/gstglviewconvert.h53
-rw-r--r--ext/gl/gstopengl.c17
8 files changed, 2208 insertions, 0 deletions
diff --git a/ext/gl/Makefile.am b/ext/gl/Makefile.am
index 5d02afcc9..1a634ef2e 100644
--- a/ext/gl/Makefile.am
+++ b/ext/gl/Makefile.am
@@ -61,6 +61,9 @@ if USE_OPENGL
libgstopengl_la_SOURCES += \
gstglfilterglass.c \
gstgldeinterlace.c \
+ gstglviewconvert.c \
+ gstglstereosplit.c \
+ gstglstereomix.c \
gltestsrc.c \
gstgltestsrc.c \
gstglmosaic.c
@@ -68,6 +71,9 @@ libgstopengl_la_SOURCES += \
noinst_HEADERS += \
gstglfilterglass.h \
gstgldeinterlace.h \
+ gstglstereosplit.h \
+ gstglstereomix.h \
+ gstglviewconvert.h \
gltestsrc.h \
gstgltestsrc.h \
gstglmosaic.h \
diff --git a/ext/gl/gstglstereomix.c b/ext/gl/gstglstereomix.c
new file mode 100644
index 000000000..1422823f7
--- /dev/null
+++ b/ext/gl/gstglstereomix.c
@@ -0,0 +1,702 @@
+/*
+ * Combine video streams to 3D stereo
+ *
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@noraisin.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglstereomix.h"
+
+#define GST_CAT_DEFAULT gst_gl_stereo_mix_debug
+GST_DEBUG_CATEGORY (gst_gl_stereo_mix_debug);
+
+#define gst_gl_stereo_mix_parent_class parent_class
+G_DEFINE_TYPE (GstGLStereoMix, gst_gl_stereo_mix, GST_TYPE_GL_MIXER);
+
+static GstCaps *_update_caps (GstVideoAggregator * vagg, GstCaps * caps);
+static gboolean _negotiated_caps (GstVideoAggregator * videoaggregator,
+ GstCaps * caps);
+gboolean gst_gl_stereo_mix_make_output (GstGLStereoMix * mix);
+static gboolean gst_gl_stereo_mix_process_frames (GstGLStereoMix * mixer,
+ GPtrArray * in_frames);
+
+#define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
+
+/* GLStereoMix signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_DOWNMIX_MODE
+};
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA") "; "
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META,
+ "RGBA")
+ "; " GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS))
+ );
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA") "; "
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META,
+ "RGBA")
+ "; " GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS))
+ );
+
+static GstFlowReturn gst_gl_stereo_mix_get_output_buffer (GstVideoAggregator *
+ videoaggregator, GstBuffer ** outbuf);
+static gboolean gst_gl_stereo_mix_stop (GstAggregator * agg);
+static gboolean gst_gl_stereo_mix_start (GstAggregator * agg);
+static gboolean gst_gl_stereo_mix_src_query (GstAggregator * agg,
+ GstQuery * query);
+
+static void
+gst_gl_stereo_mix_find_best_format (GstVideoAggregator * vagg,
+ GstCaps * downstream_caps, GstVideoInfo * best_info,
+ gboolean * at_least_one_alpha);
+
+static void gst_gl_stereo_mix_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_gl_stereo_mix_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void gst_gl_stereo_mix_finalize (GObject * object);
+
+static GstFlowReturn
+gst_gl_stereo_mix_aggregate_frames (GstVideoAggregator * vagg,
+ GstBuffer * outbuffer);
+
+static void
+gst_gl_stereo_mix_class_init (GstGLStereoMixClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstVideoAggregatorClass *videoaggregator_class =
+ (GstVideoAggregatorClass *) klass;
+ GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
+ GstGLBaseMixerClass *base_mix_class = (GstGLBaseMixerClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glstereomixer", 0,
+ "opengl stereoscopic mixer");
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gl_stereo_mix_finalize);
+
+ gobject_class->get_property = gst_gl_stereo_mix_get_property;
+ gobject_class->set_property = gst_gl_stereo_mix_set_property;
+
+ gst_element_class_set_metadata (element_class, "OpenGL stereo video combiner",
+ "Filter/Effect/Video", "OpenGL stereo video combiner",
+ "Jan Schmidt <jan@centricular.com>");
+
+ g_object_class_install_property (gobject_class, PROP_DOWNMIX_MODE,
+ g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sink_factory));
+
+ agg_class->stop = gst_gl_stereo_mix_stop;
+ agg_class->start = gst_gl_stereo_mix_start;
+ agg_class->src_query = gst_gl_stereo_mix_src_query;
+
+ videoaggregator_class->aggregate_frames = gst_gl_stereo_mix_aggregate_frames;
+ videoaggregator_class->update_caps = _update_caps;
+ videoaggregator_class->negotiated_caps = _negotiated_caps;
+ videoaggregator_class->get_output_buffer =
+ gst_gl_stereo_mix_get_output_buffer;
+ videoaggregator_class->find_best_format = gst_gl_stereo_mix_find_best_format;
+ videoaggregator_class->preserve_update_caps_result = TRUE;
+
+ base_mix_class->supported_gl_api = GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
+}
+
+static void
+gst_gl_stereo_mix_init (GstGLStereoMix * mix)
+{
+}
+
+static void
+gst_gl_stereo_mix_finalize (GObject * object)
+{
+ //GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_gl_stereo_mix_query_caps (GstPad * pad, GstAggregator * agg,
+ GstQuery * query)
+{
+ GstCaps *filter, *caps;
+
+ gst_query_parse_caps (query, &filter);
+
+ caps = gst_pad_get_current_caps (agg->srcpad);
+ if (caps == NULL) {
+ caps = gst_pad_get_pad_template_caps (agg->srcpad);
+ }
+
+ if (filter)
+ caps = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+
+ gst_query_set_caps_result (query, caps);
+ gst_caps_unref (caps);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_stereo_mix_src_query (GstAggregator * agg, GstQuery * query)
+{
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ return gst_gl_stereo_mix_query_caps (agg->srcpad, agg, query);
+ break;
+ default:
+ break;
+ }
+
+ return GST_AGGREGATOR_CLASS (parent_class)->src_query (agg, query);
+}
+
+
+static GstFlowReturn
+gst_gl_stereo_mix_get_output_buffer (GstVideoAggregator * videoaggregator,
+ GstBuffer ** outbuf)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (videoaggregator);
+ GstFlowReturn ret = GST_FLOW_OK;
+
+#if 0
+
+ if (!mix->priv->pool_active) {
+ if (!gst_buffer_pool_set_active (mix->priv->pool, TRUE)) {
+ GST_ELEMENT_ERROR (mix, RESOURCE, SETTINGS,
+ ("failed to activate bufferpool"), ("failed to activate bufferpool"));
+ return GST_FLOW_ERROR;
+ }
+ mix->priv->pool_active = TRUE;
+ }
+
+ return gst_buffer_pool_acquire_buffer (mix->priv->pool, outbuf, NULL);
+#endif
+
+ if (!gst_gl_stereo_mix_make_output (mix)) {
+ gst_buffer_replace (&mix->primary_out, NULL);
+ gst_buffer_replace (&mix->auxilliary_out, NULL);
+ GST_ELEMENT_ERROR (mix, RESOURCE, SETTINGS,
+ ("Failed to generate output"), ("failed to generate output"));
+ ret = GST_FLOW_ERROR;
+ }
+
+ if (mix->auxilliary_out) {
+ *outbuf = mix->auxilliary_out;
+ mix->auxilliary_out = NULL;
+ } else {
+ *outbuf = mix->primary_out;
+ mix->primary_out = NULL;
+ }
+ return ret;
+}
+
+gboolean
+gst_gl_stereo_mix_make_output (GstGLStereoMix * mix)
+{
+ guint i;
+ GList *walk;
+ gboolean res = FALSE;
+ guint array_index = 0;
+ GstElement *element = GST_ELEMENT (mix);
+ gboolean missing_buffer = FALSE;
+
+ GST_LOG_OBJECT (mix, "Processing buffers");
+
+ GST_OBJECT_LOCK (mix);
+ walk = element->sinkpads;
+
+ i = mix->frames->len;
+ g_ptr_array_set_size (mix->frames, element->numsinkpads);
+ for (; i < element->numsinkpads; i++)
+ mix->frames->pdata[i] = g_slice_new0 (GstGLStereoMixFrameData);
+ while (walk) {
+ GstGLMixerPad *pad = GST_GL_MIXER_PAD (walk->data);
+ GstVideoAggregatorPad *vaggpad = walk->data;
+ GstGLStereoMixFrameData *frame;
+
+ GST_LOG_OBJECT (mix, "Checking pad %" GST_PTR_FORMAT, vaggpad);
+
+ frame = g_ptr_array_index (mix->frames, array_index);
+ frame->base.pad = pad;
+ frame->buf = NULL;
+
+ walk = g_list_next (walk);
+
+ if (vaggpad->buffer != NULL) {
+ frame->buf = vaggpad->buffer;
+
+ GST_DEBUG_OBJECT (pad, "Got buffer %" GST_PTR_FORMAT, frame->buf);
+ } else {
+ GST_LOG_OBJECT (mix, "No buffer on pad %" GST_PTR_FORMAT, vaggpad);
+ missing_buffer = TRUE;
+ }
+ ++array_index;
+ }
+ if (missing_buffer) {
+ /* We're still waiting for a buffer to turn up on at least one input */
+ GST_WARNING_OBJECT (mix, "Not generating output - need more input buffers");
+ res = TRUE;
+ goto out;
+ }
+
+ /* Copy GL memory from each input frame to the output */
+ if (!gst_gl_stereo_mix_process_frames (mix, mix->frames)) {
+ GST_LOG_OBJECT (mix, "Failed to process frames to output");
+ goto out;
+ }
+
+ if (mix->primary_out == NULL)
+ goto out;
+
+ res = TRUE;
+
+out:
+ GST_OBJECT_UNLOCK (mix);
+
+ return res;
+}
+
+static GstFlowReturn
+gst_gl_stereo_mix_aggregate_frames (GstVideoAggregator * vagg,
+ GstBuffer * outbuf)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+ /* If we're operating in frame-by-frame mode, push
+ * the primary view now, and let the parent class
+ * push the remaining auxilliary view */
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (&vagg->info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ /* Transfer the timestamps video-agg put on the aux buffer */
+ gst_buffer_copy_into (mix->primary_out, outbuf,
+ GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ gst_aggregator_finish_buffer (GST_AGGREGATOR (vagg), mix->primary_out);
+ mix->primary_out = NULL;
+
+ /* And actually, we don't want timestamps on the aux buffer */
+ GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
+ }
+ return GST_FLOW_OK;
+}
+
+static void
+gst_gl_stereo_mix_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ switch (prop_id) {
+ case PROP_DOWNMIX_MODE:
+ g_value_set_enum (value, mix->downmix_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_stereo_mix_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (object);
+
+ switch (prop_id) {
+ case PROP_DOWNMIX_MODE:
+ mix->downmix_mode = g_value_get_enum (value);
+ if (mix->viewconvert)
+ g_object_set_property (G_OBJECT (mix->viewconvert), "downmix-mode",
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_free_glmixer_frame_data (GstGLStereoMixFrameData * frame)
+{
+ if (frame == NULL)
+ return;
+ if (frame->buf)
+ gst_buffer_unref (frame->buf);
+ g_slice_free1 (sizeof (GstGLStereoMixFrameData), frame);
+}
+
+static gboolean
+gst_gl_stereo_mix_start (GstAggregator * agg)
+{
+ guint i;
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (agg);
+ GstElement *element = GST_ELEMENT (agg);
+
+ if (!GST_AGGREGATOR_CLASS (parent_class)->start (agg))
+ return FALSE;
+
+ GST_OBJECT_LOCK (mix);
+ mix->array_buffers = g_ptr_array_new_full (element->numsinkpads,
+ (GDestroyNotify) _free_glmixer_frame_data);
+ mix->frames = g_ptr_array_new_full (element->numsinkpads, NULL);
+
+ g_ptr_array_set_size (mix->array_buffers, element->numsinkpads);
+ g_ptr_array_set_size (mix->frames, element->numsinkpads);
+
+ for (i = 0; i < element->numsinkpads; i++)
+ mix->frames->pdata[i] = g_slice_new0 (GstGLStereoMixFrameData);
+
+ mix->viewconvert = gst_gl_view_convert_new ();
+ g_object_set (G_OBJECT (mix->viewconvert), "downmix-mode",
+ mix->downmix_mode, NULL);
+
+ GST_OBJECT_UNLOCK (mix);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gl_stereo_mix_stop (GstAggregator * agg)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (agg);
+
+ if (!GST_AGGREGATOR_CLASS (parent_class)->stop (agg))
+ return FALSE;
+
+ GST_OBJECT_LOCK (agg);
+ g_ptr_array_free (mix->frames, TRUE);
+ mix->frames = NULL;
+ g_ptr_array_free (mix->array_buffers, TRUE);
+ mix->array_buffers = NULL;
+ GST_OBJECT_UNLOCK (agg);
+
+ if (mix->viewconvert) {
+ gst_object_unref (mix->viewconvert);
+ mix->viewconvert = NULL;
+ }
+
+ return TRUE;
+}
+
+/* Convert to caps that can be accepted by this element... */
+static GstCaps *
+get_converted_caps (GstGLStereoMix * mix, GstCaps * caps)
+{
+#if 0
+ GstGLContext *context = GST_GL_BASE_MIXER (mix)->context;
+ GstCaps *result, *tmp;
+
+ GST_LOG_OBJECT (mix, "Converting caps %" GST_PTR_FORMAT, caps);
+ result = gst_gl_upload_transform_caps (context, GST_PAD_SINK, caps, NULL);
+ tmp = result;
+ GST_TRACE_OBJECT (mix, "transfer returned caps %" GST_PTR_FORMAT, tmp);
+
+ result =
+ gst_gl_color_convert_transform_caps (context, GST_PAD_SINK, tmp, NULL);
+ gst_caps_unref (tmp);
+ GST_TRACE_OBJECT (mix, "convert returned caps %" GST_PTR_FORMAT, tmp);
+
+ tmp = result;
+ result = gst_gl_view_convert_transform_caps (mix->viewconvert,
+ GST_PAD_SINK, tmp, NULL);
+ gst_caps_unref (tmp);
+#else
+ GstCaps *result;
+
+ GST_LOG_OBJECT (mix, "Converting caps %" GST_PTR_FORMAT, caps);
+ result = gst_gl_view_convert_transform_caps (mix->viewconvert,
+ GST_PAD_SINK, caps, NULL);
+#endif
+
+ GST_LOG_OBJECT (mix, "returning caps %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+/* Return the possible output caps we decided in find_best_format() */
+static GstCaps *
+_update_caps (GstVideoAggregator * vagg, GstCaps * caps)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+
+ return gst_caps_ref (mix->out_caps);
+}
+
+/* Called after videoaggregator fixates our caps */
+static gboolean
+_negotiated_caps (GstVideoAggregator * vagg, GstCaps * caps)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+
+ GST_LOG_OBJECT (mix, "Configured output caps %" GST_PTR_FORMAT, caps);
+
+ if (GST_VIDEO_AGGREGATOR_CLASS (parent_class)->negotiated_caps)
+ if (!GST_VIDEO_AGGREGATOR_CLASS (parent_class)->negotiated_caps (vagg,
+ caps))
+ return FALSE;
+
+ /* Update the glview_convert output */
+ if (!gst_video_info_from_caps (&mix->out_info, caps))
+ return FALSE;
+
+ /* We can configure the view_converter now */
+ gst_gl_view_convert_set_context (mix->viewconvert,
+ GST_GL_BASE_MIXER (mix)->context);
+ gst_gl_view_convert_set_format (mix->viewconvert, &mix->mix_info,
+ &mix->out_info);
+
+ return TRUE;
+
+}
+
+static gboolean
+gst_gl_stereo_mix_process_frames (GstGLStereoMix * mixer, GPtrArray * frames)
+{
+ GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (mixer);
+ GstBuffer *converted_buffer, *inbuf;
+ GstVideoInfo *out_info = &vagg->info;
+ gint count = 0, n;
+ gint v, views;
+ gint valid_views = 0;
+
+ inbuf = gst_buffer_new ();
+ while (count < frames->len) {
+ GstGLStereoMixFrameData *frame;
+ GstMemory *in_mem;
+
+ frame = g_ptr_array_index (frames, count);
+ GST_LOG_OBJECT (mixer, "Handling frame %d", count);
+
+ if (!frame) {
+ GST_DEBUG ("skipping texture, null frame");
+ count++;
+ continue;
+ }
+
+ in_mem = gst_buffer_get_memory (frame->buf, 0);
+
+ GST_LOG_OBJECT (mixer,
+ "Appending memory %" GST_PTR_FORMAT " to intermediate buffer", in_mem);
+ /* Appending the memory to a 2nd buffer locks it
+ * exclusive a 2nd time, which will mark it for
+ * copy-on-write. The ref will keep the memory
+ * alive but we add a parent_buffer_meta to also
+ * prevent the input buffer from returning to any buffer
+ * pool it might belong to
+ */
+ gst_buffer_append_memory (inbuf, in_mem);
+ /* Use parent buffer meta to keep input buffer alive */
+ gst_buffer_add_parent_buffer_meta (inbuf, frame->buf);
+
+ count++;
+ valid_views++;
+ }
+
+ if (mixer->mix_info.views != valid_views) {
+ GST_WARNING_OBJECT (mixer, "Not enough input views to process");
+ return FALSE;
+ }
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (out_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_SEPARATED)
+ views = out_info->views;
+ else
+ views = 1;
+
+ if (gst_gl_view_convert_submit_input_buffer (mixer->viewconvert,
+ FALSE, inbuf) != GST_FLOW_OK)
+ return FALSE;
+
+ /* Clear any existing buffers, just in case */
+ gst_buffer_replace (&mixer->primary_out, NULL);
+ gst_buffer_replace (&mixer->auxilliary_out, NULL);
+
+ if (gst_gl_view_convert_get_output (mixer->viewconvert,
+ &mixer->primary_out) != GST_FLOW_OK)
+ return FALSE;
+
+ if (GST_VIDEO_INFO_MULTIVIEW_MODE (out_info) ==
+ GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
+ if (gst_gl_view_convert_get_output (mixer->viewconvert,
+ &mixer->auxilliary_out) != GST_FLOW_OK)
+ return FALSE;
+ }
+
+ if (mixer->primary_out == NULL)
+ return FALSE;
+
+ converted_buffer = mixer->primary_out;
+ v = 0;
+ n = gst_buffer_n_memory (converted_buffer);
+ g_assert (n == GST_VIDEO_INFO_N_PLANES (out_info) * views);
+ for (v = 0; v < views; v++) {
+ gst_buffer_add_video_meta_full (converted_buffer, v,
+ GST_VIDEO_INFO_FORMAT (out_info),
+ GST_VIDEO_INFO_WIDTH (out_info),
+ GST_VIDEO_INFO_HEIGHT (out_info),
+ GST_VIDEO_INFO_N_PLANES (out_info), out_info->offset, out_info->stride);
+ if (mixer->auxilliary_out) {
+ gst_buffer_add_video_meta_full (mixer->auxilliary_out, v,
+ GST_VIDEO_INFO_FORMAT (out_info),
+ GST_VIDEO_INFO_WIDTH (out_info),
+ GST_VIDEO_INFO_HEIGHT (out_info),
+ GST_VIDEO_INFO_N_PLANES (out_info), out_info->offset,
+ out_info->stride);
+ }
+ }
+
+ return TRUE;
+}
+
+/* Iterate the input sink pads, and choose the blend format
+ * we will generate before output conversion, which is RGBA
+ * at some suitable size */
+static void
+gst_gl_stereo_mix_find_best_format (GstVideoAggregator * vagg,
+ GstCaps * downstream_caps, GstVideoInfo * best_info,
+ gboolean * at_least_one_alpha)
+{
+ GstGLStereoMix *mix = GST_GL_STEREO_MIX (vagg);
+ GList *l;
+ gint best_width = -1, best_height = -1;
+ gdouble best_fps = -1, cur_fps;
+ gint best_fps_n = 0, best_fps_d = 1;
+ GstVideoInfo *mix_info;
+ GstCaps *blend_caps, *tmp_caps;
+
+ /* We'll deal with alpha internally, so just tell aggregator to
+ * be quiet */
+ *at_least_one_alpha = FALSE;
+
+ GST_OBJECT_LOCK (vagg);
+
+ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
+ GstVideoAggregatorPad *pad = l->data;
+ GstVideoInfo tmp = pad->info;
+ gint this_width, this_height;
+ gint fps_n, fps_d;
+
+ if (!pad->info.finfo)
+ continue;
+
+ /* This can happen if we release a pad and another pad hasn't been negotiated_caps yet */
+ if (GST_VIDEO_INFO_FORMAT (&pad->info) == GST_VIDEO_FORMAT_UNKNOWN)
+ continue;
+
+ /* Convert to per-view width/height for unpacked forms */
+ gst_video_multiview_video_info_change_mode (&tmp,
+ GST_VIDEO_MULTIVIEW_MODE_SEPARATED, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+
+ this_width = GST_VIDEO_INFO_WIDTH (&tmp);
+ this_height = GST_VIDEO_INFO_HEIGHT (&tmp);
+ fps_n = GST_VIDEO_INFO_FPS_N (&tmp);
+ fps_d = GST_VIDEO_INFO_FPS_D (&tmp);
+
+ GST_INFO_OBJECT (vagg, "Input pad %" GST_PTR_FORMAT
+ " w %u h %u", pad, this_width, this_height);
+
+ if (this_width == 0 || this_height == 0)
+ continue;
+
+ if (best_width < this_width)
+ best_width = this_width;
+ if (best_height < this_height)
+ best_height = this_height;
+
+ if (fps_d == 0)
+ cur_fps = 0.0;
+ else
+ gst_util_fraction_to_double (fps_n, fps_d, &cur_fps);
+
+ if (best_fps < cur_fps) {
+ best_fps = cur_fps;
+ best_fps_n = fps_n;
+ best_fps_d = fps_d;
+ }
+
+ /* FIXME: Preserve PAR for at least one input when different sized inputs */
+ }
+ GST_OBJECT_UNLOCK (vagg);
+
+ mix_info = &mix->mix_info;
+ gst_video_info_set_format (mix_info, GST_VIDEO_FORMAT_RGBA, best_width,
+ best_height);
+
+ GST_VIDEO_INFO_FPS_N (mix_info) = best_fps_n;
+ GST_VIDEO_INFO_FPS_D (mix_info) = best_fps_d;
+
+ GST_VIDEO_INFO_MULTIVIEW_MODE (mix_info) = GST_VIDEO_MULTIVIEW_MODE_SEPARATED;
+ GST_VIDEO_INFO_VIEWS (mix_info) = 2;
+
+ /* FIXME: If input is marked as flipped or flopped, preserve those flags */
+ GST_VIDEO_INFO_MULTIVIEW_FLAGS (mix_info) = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
+
+ /* Choose our output format based on downstream preferences */
+ blend_caps = gst_video_info_to_caps (mix_info);
+
+ gst_caps_set_features (blend_caps, 0,
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+
+ tmp_caps = get_converted_caps (GST_GL_STEREO_MIX (vagg), blend_caps);
+ gst_caps_unref (blend_caps);
+
+ if (mix->out_caps)
+ gst_caps_unref (mix->out_caps);
+
+ mix->out_caps = gst_caps_intersect (downstream_caps, tmp_caps);
+ gst_caps_unref (tmp_caps);
+
+ GST_DEBUG_OBJECT (vagg, "Possible output caps %" GST_PTR_FORMAT,
+ mix->out_caps);
+ /* Tell videoaggregator our preferred size. Actual info gets
+ * overridden during caps nego */
+ *best_info = *mix_info;
+}
diff --git a/ext/gl/gstglstereomix.h b/ext/gl/gstglstereomix.h
new file mode 100644
index 000000000..debe347c4
--- /dev/null
+++ b/ext/gl/gstglstereomix.h
@@ -0,0 +1,83 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@noraisin.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_GL_STEREO_MIX_H__
+#define __GST_GL_STEREO_MIX_H__
+
+#include "gstglmixer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_STEREO_MIX (gst_gl_stereo_mix_get_type())
+#define GST_GL_STEREO_MIX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_STEREO_MIX, GstGLStereoMix))
+#define GST_GL_STEREO_MIX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_STEREO_MIX, GstGLStereoMixClass))
+#define GST_IS_GL_STEREO_MIX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_STEREO_MIX))
+#define GST_IS_GL_STEREO_MIX_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_STEREO_MIX))
+#define GST_GL_STEREO_MIX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_GL_STEREO_MIX,GstGLStereoMixClass))
+
+typedef struct _GstGLStereoMix GstGLStereoMix;
+typedef struct _GstGLStereoMixClass GstGLStereoMixClass;
+typedef struct _GstGLStereoMixFrameData GstGLStereoMixFrameData;
+
+struct _GstGLStereoMix
+{
+ GstGLMixer mixer;
+
+ GPtrArray *array_buffers;
+ GPtrArray *frames;
+
+ GLuint out_tex_id;
+ GstGLDownload *download;
+
+ GstGLViewConvert *viewconvert;
+ GstGLStereoDownmix downmix_mode;
+
+ GstCaps *out_caps;
+ GstVideoInfo out_info;
+
+ GstVideoInfo mix_info;
+
+ GPtrArray *input_frames;
+ GstBuffer *primary_out;
+ GstBuffer *auxilliary_out;
+};
+
+struct _GstGLStereoMixClass
+{
+ GstGLMixerClass mixer_class;
+};
+
+struct _GstGLStereoMixFrameData
+{
+ GstGLMixerFrameData base;
+ gboolean mapped;
+ GstBuffer *buf;
+};
+
+GType gst_gl_stereo_mix_get_type(void);
+
+G_END_DECLS
+#endif /* __GST_GL_STEREO_MIX_H__ */
diff --git a/ext/gl/gstglstereosplit.c b/ext/gl/gstglstereosplit.c
new file mode 100644
index 000000000..a9e500c0b
--- /dev/null
+++ b/ext/gl/gstglstereosplit.c
@@ -0,0 +1,928 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-glstereosplit
+ *
+ * Receive a stereoscopic video stream and split into left/right
+ *
+ * <refsect2>
+ * <title>Examples</title>
+ * |[
+ * gst-launch videotestsrc ! glstereosplit name=s ! queue ! glimagesink s. ! queue ! glimagesink
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstglstereosplit.h"
+
+#if GST_GL_HAVE_PLATFORM_EGL
+#include <gst/gl/egl/gsteglimagememory.h>
+#endif
+
+#define GST_CAT_DEFAULT gst_gl_stereosplit_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define SUPPORTED_GL_APIS GST_GL_API_OPENGL | GST_GL_API_OPENGL3
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_stereosplit_debug, "glstereosplit", 0, "glstereosplit element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLStereoSplit, gst_gl_stereosplit,
+ GST_TYPE_ELEMENT, DEBUG_INIT);
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA") "; "
+#if GST_GL_HAVE_PLATFORM_EGL
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_EGL_IMAGE,
+ "RGBA") "; "
+#endif
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META, "RGBA") "; "
+ GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS)
+ )
+ );
+
+static GstStaticPadTemplate src_left_template = GST_STATIC_PAD_TEMPLATE ("left",
+ GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA")
+#if 0
+ "; "
+#if GST_GL_HAVE_PLATFORM_EGL
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_EGL_IMAGE,
+ "RGBA") "; "
+#endif
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META, "RGBA") "; "
+ GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS)
+#endif
+ )
+ );
+
+static GstStaticPadTemplate src_right_template =
+ GST_STATIC_PAD_TEMPLATE ("right",
+ GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
+ "RGBA")
+#if 0
+ "; "
+#if GST_GL_HAVE_PLATFORM_EGL
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_EGL_IMAGE,
+ "RGBA") "; "
+#endif
+ GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+ (GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META, "RGBA") "; "
+ GST_VIDEO_CAPS_MAKE (GST_GL_COLOR_CONVERT_FORMATS)
+#endif
+ )
+ );
+
+static void stereosplit_reset (GstGLStereoSplit * self);
+static void stereosplit_finalize (GstGLStereoSplit * self);
+static void stereosplit_set_context (GstElement * element,
+ GstContext * context);
+static GstFlowReturn stereosplit_chain (GstPad * pad, GstGLStereoSplit * split,
+ GstBuffer * buf);
+static GstStateChangeReturn stereosplit_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean stereosplit_sink_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+static gboolean stereosplit_sink_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean stereosplit_src_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+static gboolean stereosplit_src_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean ensure_context (GstGLStereoSplit * self);
+
+static void
+gst_gl_stereosplit_class_init (GstGLStereoSplitClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_set_static_metadata (element_class,
+ "GLStereoSplit", "Codec/Converter",
+ "Splits a stereoscopic stream into separate left/right streams",
+ "Jan Schmidt <jan@centricular.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ gobject_class->finalize = (GObjectFinalizeFunc) (stereosplit_finalize);
+
+ element_class->change_state = stereosplit_change_state;
+ element_class->set_context = stereosplit_set_context;
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sink_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_left_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_right_template));
+}
+
+static void
+gst_gl_stereosplit_init (GstGLStereoSplit * self)
+{
+ GstPad *pad;
+
+ pad = self->sink_pad =
+ gst_pad_new_from_static_template (&sink_template, "sink");
+
+ gst_pad_set_chain_function (pad, (GstPadChainFunction) (stereosplit_chain));
+ gst_pad_set_query_function (pad, stereosplit_sink_query);
+ gst_pad_set_event_function (pad, stereosplit_sink_event);
+
+ gst_element_add_pad (GST_ELEMENT (self), self->sink_pad);
+
+ pad = self->left_pad =
+ gst_pad_new_from_static_template (&src_left_template, "left");
+ gst_pad_set_query_function (pad, stereosplit_src_query);
+ gst_pad_set_event_function (pad, stereosplit_src_event);
+ gst_element_add_pad (GST_ELEMENT (self), self->left_pad);
+
+ pad = self->right_pad =
+ gst_pad_new_from_static_template (&src_right_template, "right");
+ gst_pad_set_query_function (pad, stereosplit_src_query);
+ gst_pad_set_event_function (pad, stereosplit_src_event);
+ gst_element_add_pad (GST_ELEMENT (self), self->right_pad);
+
+ self->viewconvert = gst_gl_view_convert_new ();
+}
+
+static void
+stereosplit_reset (GstGLStereoSplit * self)
+{
+ if (self->upload)
+ gst_object_replace ((GstObject **) & self->upload, NULL);
+ if (self->convert)
+ gst_object_replace ((GstObject **) & self->convert, NULL);
+ if (self->context)
+ gst_object_replace ((GstObject **) & self->context, NULL);
+ if (self->display)
+ gst_object_replace ((GstObject **) & self->display, NULL);
+}
+
+static void
+stereosplit_finalize (GstGLStereoSplit * self)
+{
+ GObjectClass *klass = G_OBJECT_CLASS (gst_gl_stereosplit_parent_class);
+
+ if (self->viewconvert)
+ gst_object_replace ((GstObject **) & self->viewconvert, NULL);
+
+ klass->finalize ((GObject *) (self));
+}
+
+static void
+stereosplit_set_context (GstElement * element, GstContext * context)
+{
+ GstGLStereoSplit *stereosplit = GST_GL_STEREOSPLIT (element);
+
+ gst_gl_handle_set_context (element, context, &stereosplit->display,
+ &stereosplit->other_context);
+
+ if (stereosplit->display)
+ gst_gl_display_filter_gl_api (stereosplit->display, SUPPORTED_GL_APIS);
+}
+
+static GstStateChangeReturn
+stereosplit_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGLStereoSplit *stereosplit = GST_GL_STEREOSPLIT (element);
+ GstStateChangeReturn result;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_gl_ensure_element_data (element, &stereosplit->display,
+ &stereosplit->other_context))
+ return GST_STATE_CHANGE_FAILURE;
+
+ gst_gl_display_filter_gl_api (stereosplit->display, SUPPORTED_GL_APIS);
+ break;
+ default:
+ break;
+ }
+
+ result =
+ GST_ELEMENT_CLASS (gst_gl_stereosplit_parent_class)->change_state
+ (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (stereosplit->other_context) {
+ gst_object_unref (stereosplit->other_context);
+ stereosplit->other_context = NULL;
+ }
+
+ if (stereosplit->display) {
+ gst_object_unref (stereosplit->display);
+ stereosplit->display = NULL;
+ }
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ stereosplit_reset (stereosplit);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static GstCaps *
+stereosplit_transform_caps (GstGLStereoSplit * self, GstPadDirection direction,
+ GstCaps * caps, GstCaps * filter)
+{
+ GstCaps *next_caps;
+
+ /* FIXME: Is this the right way to ensure a context here ? */
+ if (!ensure_context (self))
+ return NULL;
+
+ if (direction == GST_PAD_SINK) {
+ next_caps =
+ gst_gl_upload_transform_caps (self->context, direction, caps, filter);
+ caps = next_caps;
+
+ next_caps =
+ gst_gl_color_convert_transform_caps (self->context, direction, caps,
+ NULL);
+ gst_caps_unref (caps);
+ caps = next_caps;
+
+ next_caps =
+ gst_gl_view_convert_transform_caps (self->viewconvert, direction, caps,
+ NULL);
+ gst_caps_unref (caps);
+ } else {
+ next_caps =
+ gst_gl_view_convert_transform_caps (self->viewconvert, direction, caps,
+ filter);
+ caps = next_caps;
+
+ next_caps =
+ gst_gl_color_convert_transform_caps (self->context, direction, caps,
+ NULL);
+ gst_caps_unref (caps);
+ caps = next_caps;
+
+ next_caps =
+ gst_gl_upload_transform_caps (self->context, direction, caps, NULL);
+ gst_caps_unref (caps);
+ }
+
+ return next_caps;
+}
+
+static GstCaps *
+strip_mview_fields (GstCaps * incaps, GstVideoMultiviewFlags keep_flags)
+{
+ GstCaps *outcaps = gst_caps_make_writable (incaps);
+
+ gint i, n;
+
+ n = gst_caps_get_size (outcaps);
+ for (i = 0; i < n; i++) {
+ GstStructure *st = gst_caps_get_structure (outcaps, i);
+ GstVideoMultiviewFlags flags, mask;
+
+ gst_structure_remove_field (st, "multiview-mode");
+ if (gst_structure_get_flagset (st, "multiview-flags", &flags, &mask)) {
+ flags &= keep_flags;
+ mask = keep_flags;
+ gst_structure_set (st, "multiview-flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags, mask, NULL);
+ }
+ }
+
+ return outcaps;
+}
+
+static gboolean stereosplit_do_bufferpool (GstGLStereoSplit * self,
+ GstCaps * caps);
+
+static GstCaps *
+stereosplit_get_src_caps (GstGLStereoSplit * split,
+ GstPad * pad, GstVideoMultiviewMode preferred_mode)
+{
+ GstCaps *outcaps, *tmp, *templ_caps;
+ GValue item = G_VALUE_INIT, list = G_VALUE_INIT;
+
+ /* Get the template format */
+ templ_caps = gst_pad_get_pad_template_caps (pad);
+
+ /* And limit down to the preferred mode or mono */
+ templ_caps = gst_caps_make_writable (templ_caps);
+
+ g_value_init (&item, G_TYPE_STRING);
+ g_value_init (&list, GST_TYPE_LIST);
+ g_value_set_static_string (&item,
+ gst_video_multiview_mode_to_caps_string (preferred_mode));
+ gst_value_list_append_value (&list, &item);
+ g_value_set_static_string (&item,
+ gst_video_multiview_mode_to_caps_string (GST_VIDEO_MULTIVIEW_MODE_MONO));
+ gst_value_list_append_value (&list, &item);
+
+ gst_caps_set_value (templ_caps, "multiview-mode", &list);
+
+ g_value_unset (&list);
+ g_value_unset (&item);
+
+ /* And intersect with the peer */
+ if ((tmp = gst_pad_peer_query_caps (pad, NULL)) == NULL) {
+ gst_caps_unref (templ_caps);
+ return NULL;
+ }
+
+ outcaps = gst_caps_intersect_full (tmp, templ_caps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (tmp);
+ gst_caps_unref (templ_caps);
+
+ GST_DEBUG_OBJECT (split, "Src pad %" GST_PTR_FORMAT " caps %" GST_PTR_FORMAT,
+ pad, outcaps);
+ return outcaps;
+}
+
+static gboolean
+stereosplit_set_output_caps (GstGLStereoSplit * split, GstCaps * sinkcaps)
+{
+ GstCaps *left = NULL, *right = NULL, *tridcaps = NULL;
+ GstCaps *tmp, *combined;
+ gboolean res = FALSE;
+
+ /* Choose some preferred output caps.
+ * Keep input width/height and PAR, preserve preferred output
+ * multiview flags for flipping/flopping if any, and set each
+ * left right pad to either left/mono and right/mono, as they prefer
+ */
+
+ /* Calculate what downstream can collectively support */
+ left =
+ stereosplit_get_src_caps (split, split->left_pad,
+ GST_VIDEO_MULTIVIEW_MODE_LEFT);
+ if (left == NULL)
+ goto fail;
+ right =
+ stereosplit_get_src_caps (split, split->right_pad,
+ GST_VIDEO_MULTIVIEW_MODE_RIGHT);
+ if (right == NULL)
+ goto fail;
+
+ tridcaps = stereosplit_transform_caps (split, GST_PAD_SINK, sinkcaps, NULL);
+
+ if (!tridcaps || gst_caps_is_empty (tridcaps)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to transform input caps %" GST_PTR_FORMAT, sinkcaps);
+ goto fail;
+ }
+
+ /* Preserve downstream preferred flipping/flopping */
+ tmp =
+ strip_mview_fields (gst_caps_ref (left),
+ GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED |
+ GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED);
+ combined = gst_caps_intersect (tridcaps, tmp);
+ gst_caps_unref (tridcaps);
+ gst_caps_unref (tmp);
+ tridcaps = combined;
+
+ tmp =
+ strip_mview_fields (gst_caps_ref (right),
+ GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED |
+ GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED);
+ combined = gst_caps_intersect (tridcaps, tmp);
+ gst_caps_unref (tridcaps);
+ gst_caps_unref (tmp);
+ tridcaps = combined;
+
+ if (G_UNLIKELY (gst_caps_is_empty (tridcaps))) {
+ gst_caps_unref (tridcaps);
+ goto fail;
+ }
+
+ /* Now generate the version for each output pad */
+ GST_DEBUG_OBJECT (split, "Attempting to set output caps %" GST_PTR_FORMAT,
+ tridcaps);
+ tmp = gst_caps_intersect (tridcaps, left);
+ gst_caps_unref (left);
+ left = tmp;
+ left = gst_caps_fixate (left);
+ if (!gst_pad_set_caps (split->left_pad, left)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to set left output caps %" GST_PTR_FORMAT, left);
+ goto fail;
+ }
+
+ tmp = gst_caps_intersect (tridcaps, right);
+ gst_caps_unref (right);
+ right = tmp;
+ right = gst_caps_fixate (right);
+ if (!gst_pad_set_caps (split->right_pad, right)) {
+ GST_ERROR_OBJECT (split,
+ "Failed to set right output caps %" GST_PTR_FORMAT, right);
+ goto fail;
+ }
+
+ /* FIXME: Provide left and right caps to do_bufferpool */
+ stereosplit_do_bufferpool (split, left);
+
+ res = TRUE;
+
+fail:
+ if (left)
+ gst_caps_unref (left);
+ if (right)
+ gst_caps_unref (right);
+ if (tridcaps)
+ gst_caps_unref (tridcaps);
+ return res;
+}
+
+static gboolean
+_find_local_gl_context (GstGLStereoSplit * split)
+{
+ GstQuery *query;
+ GstContext *context;
+ const GstStructure *s;
+
+ if (split->context)
+ return TRUE;
+
+ query = gst_query_new_context ("gst.gl.local_context");
+ if (!split->context
+ && gst_gl_run_query (GST_ELEMENT (split), query, GST_PAD_SRC)) {
+ gst_query_parse_context (query, &context);
+ if (context) {
+ s = gst_context_get_structure (context);
+ gst_structure_get (s, "context", GST_GL_TYPE_CONTEXT, &split->context,
+ NULL);
+ }
+ }
+ if (!split->context
+ && gst_gl_run_query (GST_ELEMENT (split), query, GST_PAD_SINK)) {
+ gst_query_parse_context (query, &context);
+ if (context) {
+ s = gst_context_get_structure (context);
+ gst_structure_get (s, "context", GST_GL_TYPE_CONTEXT, &split->context,
+ NULL);
+ }
+ }
+
+ GST_DEBUG_OBJECT (split, "found local context %p", split->context);
+
+ gst_query_unref (query);
+
+ if (split->context)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+_init_upload (GstGLStereoSplit * split)
+{
+ GstGLContext *context = split->context;
+
+ if (!split->upload) {
+ GstCaps *in_caps = gst_pad_get_current_caps (GST_PAD (split->sink_pad));
+ GstCaps *split_caps = gst_pad_get_current_caps (split->left_pad);
+ GstCaps *upload_caps = gst_caps_copy (in_caps);
+ GstCapsFeatures *gl_features =
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ GstCaps *gl_caps;
+
+ split->upload = gst_gl_upload_new (context);
+
+ gst_caps_set_features (upload_caps, 0,
+ gst_caps_features_copy (gl_features));
+ gst_gl_upload_set_caps (split->upload, in_caps, upload_caps);
+ gst_caps_unref (in_caps);
+
+ gl_caps = gst_caps_copy (upload_caps);
+ gst_caps_set_simple (gl_caps, "format", G_TYPE_STRING, "RGBA", NULL);
+ gst_caps_set_features (gl_caps, 0, gst_caps_features_copy (gl_features));
+
+ if (!split->convert) {
+ split->convert = gst_gl_color_convert_new (context);
+ gst_gl_color_convert_set_caps (split->convert, upload_caps, gl_caps);
+ }
+
+ gst_caps_unref (upload_caps);
+ gst_caps_features_free (gl_features);
+
+ gst_gl_view_convert_set_context (split->viewconvert, split->context);
+
+ split_caps = gst_caps_make_writable (split_caps);
+ gst_caps_set_simple (split_caps, "multiview-mode", G_TYPE_STRING,
+ "separated", "views", G_TYPE_INT, 2, NULL);
+
+ gst_gl_view_convert_set_caps (split->viewconvert, gl_caps, split_caps);
+
+ gst_caps_unref (split_caps);
+ gst_caps_unref (gl_caps);
+ }
+}
+
+static gboolean
+ensure_context (GstGLStereoSplit * self)
+{
+ GError *error = NULL;
+
+ if (!gst_gl_ensure_element_data (self, &self->display, &self->other_context))
+ return FALSE;
+
+ gst_gl_display_filter_gl_api (self->display, SUPPORTED_GL_APIS);
+
+ _find_local_gl_context (self);
+
+ if (!self->context) {
+ GST_OBJECT_LOCK (self->display);
+ do {
+ if (self->context)
+ gst_object_unref (self->context);
+ /* just get a GL context. we don't care */
+ self->context =
+ gst_gl_display_get_gl_context_for_thread (self->display, NULL);
+ if (!self->context) {
+ self->context = gst_gl_context_new (self->display);
+ if (!gst_gl_context_create (self->context, self->other_context, &error))
+ goto context_error;
+ }
+ } while (!gst_gl_display_add_context (self->display, self->context));
+ GST_OBJECT_UNLOCK (self->display);
+ }
+
+ return TRUE;
+
+context_error:
+ {
+ GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("%s", error->message),
+ (NULL));
+ return FALSE;
+ }
+}
+
+static gboolean
+stereosplit_decide_allocation (GstGLStereoSplit * self, GstQuery * query)
+{
+ if (!ensure_context (self))
+ return FALSE;
+ if (self->upload)
+ gst_object_replace ((GstObject **) & self->upload, NULL);
+ if (self->convert)
+ gst_object_replace ((GstObject **) & self->convert, NULL);
+
+ return TRUE;
+
+}
+
+static gboolean
+stereosplit_propose_allocation (GstGLStereoSplit * self, GstQuery * query)
+{
+
+ if (!gst_gl_ensure_element_data (self, &self->display, &self->other_context))
+ return FALSE;
+
+ _init_upload (self);
+
+ gst_gl_upload_propose_allocation (self->upload, NULL, query);
+
+ return TRUE;
+}
+
+static gboolean
+stereosplit_do_bufferpool (GstGLStereoSplit * self, GstCaps * caps)
+{
+ GstQuery *query;
+
+ query = gst_query_new_allocation (caps, TRUE);
+ if (!gst_pad_peer_query (self->left_pad, query)) {
+ if (!gst_pad_peer_query (self->right_pad, query)) {
+ GST_DEBUG_OBJECT (self, "peer ALLOCATION query failed on both src pads");
+ }
+ }
+
+ if (!stereosplit_decide_allocation (self, query)) {
+ gst_query_unref (query);
+ return FALSE;
+ }
+
+ gst_query_unref (query);
+ return TRUE;
+}
+
+static GstFlowReturn
+stereosplit_chain (GstPad * pad, GstGLStereoSplit * split, GstBuffer * buf)
+{
+ GstBuffer *uploaded_buffer, *converted_buffer, *left, *right;
+ GstBuffer *split_buffer = NULL;
+ GstFlowReturn ret;
+ gint i, n_planes;
+
+ if (!split->upload)
+ _init_upload (split);
+
+ n_planes = GST_VIDEO_INFO_N_PLANES (&split->viewconvert->out_info);
+
+ GST_LOG_OBJECT (split, "chaining buffer %" GST_PTR_FORMAT, buf);
+
+ if (GST_GL_UPLOAD_DONE != gst_gl_upload_perform_with_buffer (split->upload,
+ buf, &uploaded_buffer)) {
+ gst_buffer_unref (buf);
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to upload buffer"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+ gst_buffer_unref (buf);
+
+ if (!(converted_buffer =
+ gst_gl_color_convert_perform (split->convert, uploaded_buffer))) {
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to convert buffer"), (NULL));
+ gst_buffer_unref (uploaded_buffer);
+ return GST_FLOW_ERROR;
+ }
+ gst_buffer_unref (uploaded_buffer);
+
+ if (gst_gl_view_convert_submit_input_buffer (split->viewconvert,
+ GST_BUFFER_IS_DISCONT (converted_buffer),
+ converted_buffer) != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to 3d convert buffer"),
+ ("Could not get submit input buffer"));
+ return GST_FLOW_ERROR;
+ }
+
+ ret = gst_gl_view_convert_get_output (split->viewconvert, &split_buffer);
+ if (ret != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (split, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to 3d convert buffer"), ("Could not get output buffer"));
+ return GST_FLOW_ERROR;
+ }
+ if (split_buffer == NULL)
+ return GST_FLOW_OK; /* Need another input buffer */
+
+ left = gst_buffer_new ();
+ gst_buffer_copy_into (left, buf,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ GST_BUFFER_FLAG_UNSET (left, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE);
+
+ gst_buffer_add_parent_buffer_meta (left, split_buffer);
+
+ for (i = 0; i < n_planes; i++) {
+ GstMemory *mem = gst_buffer_get_memory (split_buffer, i);
+ gst_buffer_append_memory (left, mem);
+ }
+
+ ret = gst_pad_push (split->left_pad, gst_buffer_ref (left));
+ /* Allow unlinked on the first pad - as long as the 2nd isn't unlinked */
+ gst_buffer_unref (left);
+ if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)) {
+ gst_buffer_unref (split_buffer);
+ return ret;
+ }
+
+ right = gst_buffer_new ();
+ gst_buffer_copy_into (right, buf,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+ GST_BUFFER_FLAG_UNSET (left, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE);
+ gst_buffer_add_parent_buffer_meta (right, split_buffer);
+ for (i = n_planes; i < n_planes * 2; i++) {
+ GstMemory *mem = gst_buffer_get_memory (split_buffer, i);
+ gst_buffer_append_memory (right, mem);
+ }
+
+ ret = gst_pad_push (split->right_pad, gst_buffer_ref (right));
+ gst_buffer_unref (right);
+ gst_buffer_unref (split_buffer);
+ return ret;
+}
+
+static gboolean
+stereosplit_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ const gchar *context_type;
+ GstContext *context, *old_context;
+ gboolean ret;
+
+ ret = gst_gl_handle_context_query ((GstElement *) split, query,
+ &split->display, &split->other_context);
+ if (split->display)
+ gst_gl_display_filter_gl_api (split->display, SUPPORTED_GL_APIS);
+ gst_query_parse_context_type (query, &context_type);
+
+ if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
+ GstStructure *s;
+
+ gst_query_parse_context (query, &old_context);
+
+ if (old_context)
+ context = gst_context_copy (old_context);
+ else
+ context = gst_context_new ("gst.gl.local_context", FALSE);
+
+ s = gst_context_writable_structure (context);
+ gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, split->context,
+ NULL);
+ gst_query_set_context (query, context);
+ gst_context_unref (context);
+
+ ret = split->context != NULL;
+ }
+ GST_LOG_OBJECT (split, "context query of type %s %i", context_type, ret);
+
+ if (ret)
+ return ret;
+
+ return gst_pad_query_default (pad, parent, query);
+ }
+ /* FIXME: Handle caps query */
+ default:
+ return gst_pad_query_default (pad, parent, query);
+ }
+}
+
+static gboolean
+stereosplit_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ return gst_pad_event_default (pad, parent, event);
+}
+
+static gboolean
+stereosplit_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ GST_DEBUG_OBJECT (split, "sink query %s",
+ gst_query_type_get_name (GST_QUERY_TYPE (query)));
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CONTEXT:
+ {
+ const gchar *context_type;
+ GstContext *context, *old_context;
+ gboolean ret;
+
+ ret = gst_gl_handle_context_query ((GstElement *) split, query,
+ &split->display, &split->other_context);
+ if (split->display)
+ gst_gl_display_filter_gl_api (split->display, SUPPORTED_GL_APIS);
+ gst_query_parse_context_type (query, &context_type);
+
+ if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
+ GstStructure *s;
+
+ gst_query_parse_context (query, &old_context);
+
+ if (old_context)
+ context = gst_context_copy (old_context);
+ else
+ context = gst_context_new ("gst.gl.local_context", FALSE);
+
+ s = gst_context_writable_structure (context);
+ gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, split->context,
+ NULL);
+ gst_query_set_context (query, context);
+ gst_context_unref (context);
+
+ ret = split->context != NULL;
+ }
+ GST_LOG_OBJECT (split, "context query of type %s %i", context_type, ret);
+
+ if (ret)
+ return ret;
+
+ return gst_pad_query_default (pad, parent, query);
+ }
+ case GST_QUERY_ALLOCATION:
+ {
+ return stereosplit_propose_allocation (split, query);
+ }
+ case GST_QUERY_ACCEPT_CAPS:
+ {
+ GstCaps *possible, *caps;
+ gboolean allowed;
+
+ gst_query_parse_accept_caps (query, &caps);
+
+ if (!(possible = gst_pad_query_caps (split->sink_pad, caps)))
+ return FALSE;
+
+ allowed = gst_caps_is_subset (caps, possible);
+ gst_caps_unref (possible);
+
+ gst_query_set_accept_caps_result (query, allowed);
+ return allowed;
+ }
+ case GST_QUERY_CAPS:
+ {
+ GstCaps *filter, *left, *right, *combined, *ret, *templ_caps;
+
+ gst_query_parse_caps (query, &filter);
+
+ /* Calculate what downstream can collectively support */
+ if (!(left = gst_pad_peer_query_caps (split->left_pad, NULL)))
+ return FALSE;
+ if (!(right = gst_pad_peer_query_caps (split->right_pad, NULL)))
+ return FALSE;
+
+ /* Strip out multiview mode and flags that might break the
+ * intersection, since we can convert.
+ * We could keep downstream preferred flip/flopping and list
+ * separated as preferred in the future which might
+ * theoretically allow us an easier conversion, but it's not essential
+ */
+ left = strip_mview_fields (left, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+ right = strip_mview_fields (right, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
+
+ combined = gst_caps_intersect (left, right);
+ gst_caps_unref (left);
+ gst_caps_unref (right);
+
+ /* Intersect peer caps with our template formats */
+ templ_caps = gst_pad_get_pad_template_caps (split->left_pad);
+ ret =
+ gst_caps_intersect_full (combined, templ_caps,
+ GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (templ_caps);
+
+ gst_caps_unref (combined);
+ combined = ret;
+
+ if (!combined || gst_caps_is_empty (combined)) {
+ gst_caps_unref (combined);
+ return FALSE;
+ }
+
+ /* Convert from the src pad caps to input formats we support */
+ ret = stereosplit_transform_caps (split, GST_PAD_SRC, combined, filter);
+ gst_caps_unref (combined);
+ combined = ret;
+
+ /* Intersect with the sink pad template then */
+ templ_caps = gst_pad_get_pad_template_caps (split->sink_pad);
+ ret =
+ gst_caps_intersect_full (combined, templ_caps,
+ GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (templ_caps);
+
+ GST_LOG_OBJECT (split, "Returning sink pad caps %" GST_PTR_FORMAT, ret);
+
+ gst_query_set_caps_result (query, ret);
+ return !gst_caps_is_empty (ret);
+ }
+ default:
+ return gst_pad_query_default (pad, parent, query);
+ }
+}
+
+static gboolean
+stereosplit_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ GstGLStereoSplit *split = GST_GL_STEREOSPLIT (parent);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
+
+ return stereosplit_set_output_caps (split, caps);
+ }
+ default:
+ return gst_pad_event_default (pad, parent, event);
+ }
+}
diff --git a/ext/gl/gstglstereosplit.h b/ext/gl/gstglstereosplit.h
new file mode 100644
index 000000000..5a1e3c6b0
--- /dev/null
+++ b/ext/gl/gstglstereosplit.h
@@ -0,0 +1,66 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_GL_STEREOSPLIT_H__
+#define __GST_GL_STEREOSPLIT_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GL_STEREOSPLIT (gst_gl_stereosplit_get_type())
+#define GST_GL_STEREOSPLIT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplit))
+#define GST_IS_GL_STEREOSPLIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_STEREOSPLIT))
+#define GST_GL_STEREOSPLIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplitClass))
+#define GST_IS_GL_STEREOSPLIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_STEREOSPLIT))
+#define GST_GL_STEREOSPLIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_STEREOSPLIT,GstGLStereoSplitClass))
+
+typedef struct _GstGLStereoSplit GstGLStereoSplit;
+typedef struct _GstGLStereoSplitClass GstGLStereoSplitClass;
+
+struct _GstGLStereoSplit
+{
+ GstElement parent;
+
+ GstPad *sink_pad;
+ GstPad *left_pad;
+ GstPad *right_pad;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+ GstGLContext *other_context;
+
+ GstGLUpload *upload;
+ GstGLColorConvert *convert;
+ GstGLViewConvert *viewconvert;
+};
+
+struct _GstGLStereoSplitClass
+{
+ GstElementClass parent_class;
+};
+
+GType gst_gl_stereosplit_get_type (void);
+
+G_END_DECLS
+
+#endif
diff --git a/ext/gl/gstglviewconvert.c b/ext/gl/gstglviewconvert.c
new file mode 100644
index 000000000..6dcad7db2
--- /dev/null
+++ b/ext/gl/gstglviewconvert.c
@@ -0,0 +1,353 @@
+/*
+ * GStreamer
+ * Copyright (C) 2009 Julien Isorce <julien.isorce@mail.com>
+ * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-glviewconvert
+ *
+ * Convert stereoscopic video between different representations using fragment shaders.
+ *
+ * The element can use either property settings or caps negotiation to choose the
+ * input and output formats to process.
+ *
+ * <refsect2>
+ * <title>Examples</title>
+ * |[
+ * gst-launch-1.0 videotestsrc ! glviewconvert ! glimagesink
+ * ]|
+ * |[
+ * gst-launch-1.0 videotestsrc pattern=checkers-1 ! glviewconvert input-mode-override=side-by-side ! glimagesink -v
+ * ]|
+ * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/base/gstbasetransform.h>
+#include "gstglviewconvert.h"
+
+#define GST_CAT_DEFAULT gst_gl_view_convert_element_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+enum
+{
+ PROP_0,
+ PROP_INPUT_LAYOUT,
+ PROP_INPUT_FLAGS,
+ PROP_OUTPUT_LAYOUT,
+ PROP_OUTPUT_FLAGS,
+ PROP_OUTPUT_DOWNMIX_MODE
+};
+
+#define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
+
+#define DEBUG_INIT \
+ GST_DEBUG_CATEGORY_INIT (gst_gl_view_convert_element_debug, "glview_convertelement", 0, "glview_convert element");
+
+G_DEFINE_TYPE_WITH_CODE (GstGLViewConvertElement, gst_gl_view_convert_element,
+ GST_TYPE_GL_FILTER, DEBUG_INIT);
+#define parent_class gst_gl_view_convert_element_parent_class
+
+static void gst_gl_view_convert_dispose (GObject * object);
+static void gst_gl_view_convert_element_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gl_view_convert_element_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_gl_view_convert_element_stop (GstBaseTransform * bt);
+static gboolean
+gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps);
+static GstCaps *gst_gl_view_convert_element_transform_internal_caps (GstGLFilter
+ * filter, GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps);
+static GstCaps *gst_gl_view_convert_element_fixate_caps (GstBaseTransform *
+ trans, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
+static GstFlowReturn
+gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
+ gboolean is_discont, GstBuffer * input);
+static gboolean
+gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
+ GstBuffer ** outbuf);
+
+static void
+gst_gl_view_convert_element_class_init (GstGLViewConvertElementClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ element_class = GST_ELEMENT_CLASS (klass);
+
+ gobject_class->set_property = gst_gl_view_convert_element_set_property;
+ gobject_class->get_property = gst_gl_view_convert_element_get_property;
+ gobject_class->dispose = gst_gl_view_convert_dispose;
+
+ gst_element_class_set_metadata (element_class,
+ "OpenGL Multiview/3D conversion filter", "Filter",
+ "Convert stereoscopic/multiview video formats",
+ "Jan Schmidt <jan@centricular.com>\n"
+ "Matthew Waters <matthew@centricular.com>");
+
+ GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_view_convert_element_set_caps;
+
+ GST_GL_FILTER_CLASS (klass)->transform_internal_caps =
+ gst_gl_view_convert_element_transform_internal_caps;
+ GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_view_convert_element_stop;
+ GST_BASE_TRANSFORM_CLASS (klass)->fixate_caps =
+ gst_gl_view_convert_element_fixate_caps;
+ GST_BASE_TRANSFORM_CLASS (klass)->submit_input_buffer =
+ gst_gl_view_convert_element_submit_input_buffer;
+ GST_BASE_TRANSFORM_CLASS (klass)->generate_output =
+ gst_gl_view_convert_element_generate_output_buffer;
+
+ g_object_class_install_property (gobject_class, PROP_INPUT_LAYOUT,
+ g_param_spec_enum ("input-mode-override",
+ "Input Multiview Mode Override",
+ "Override any input information about multiview layout",
+ GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
+ GST_VIDEO_MULTIVIEW_MODE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INPUT_FLAGS,
+ g_param_spec_flags ("input-flags-override",
+ "Input Multiview Flags Override",
+ "Override any input information about multiview layout flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_LAYOUT,
+ g_param_spec_enum ("output-mode-override",
+ "Output Multiview Mode Override",
+ "Override automatic output mode selection for multiview layout",
+ GST_TYPE_VIDEO_MULTIVIEW_MODE, GST_VIDEO_MULTIVIEW_MODE_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_FLAGS,
+ g_param_spec_flags ("output-flags-override",
+ "Output Multiview Flags Override",
+ "Override automatic negotiation for output multiview layout flags",
+ GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_OUTPUT_DOWNMIX_MODE,
+ g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
+ "Output anaglyph type to generate when downmixing to mono",
+ GST_TYPE_GL_STEREO_DOWNMIX_MODE_TYPE, DEFAULT_DOWNMIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_gl_view_convert_element_init (GstGLViewConvertElement * convert)
+{
+ convert->viewconvert = gst_gl_view_convert_new ();
+}
+
+static void
+gst_gl_view_convert_dispose (GObject * object)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ if (convert->viewconvert) {
+ gst_object_unref (convert->viewconvert);
+ convert->viewconvert = NULL;
+ }
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (filter);
+ GstCapsFeatures *gl_features;
+ gboolean ret;
+
+ GST_DEBUG_OBJECT (filter, "incaps %" GST_PTR_FORMAT
+ " outcaps %" GST_PTR_FORMAT, incaps, outcaps);
+ /* The view_convert component needs RGBA caps */
+ incaps = gst_caps_copy (incaps);
+ outcaps = gst_caps_copy (outcaps);
+
+ gst_caps_set_simple (incaps, "format", G_TYPE_STRING, "RGBA", NULL);
+ gl_features =
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ gst_caps_set_features (incaps, 0, gl_features);
+
+ gst_caps_set_simple (outcaps, "format", G_TYPE_STRING, "RGBA", NULL);
+ gl_features =
+ gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
+ gst_caps_set_features (outcaps, 0, gl_features);
+
+ ret = gst_gl_view_convert_set_caps (viewconvert_filter->viewconvert,
+ incaps, outcaps);
+
+ gst_caps_unref (incaps);
+ gst_caps_unref (outcaps);
+
+ return ret;
+}
+
+static GstCaps *
+gst_gl_view_convert_element_transform_internal_caps (GstGLFilter * filter,
+ GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (filter);
+ GstCaps *result;
+
+ GST_DEBUG_OBJECT (filter, "dir %s transforming caps: %" GST_PTR_FORMAT,
+ direction == GST_PAD_SINK ? "sink" : "src", caps);
+
+ result =
+ gst_gl_view_convert_transform_caps (viewconvert_filter->viewconvert,
+ direction, caps, NULL);
+
+ GST_DEBUG_OBJECT (filter, "returning caps: %" GST_PTR_FORMAT, result);
+
+ return result;
+}
+
+static GstCaps *
+gst_gl_view_convert_element_fixate_caps (GstBaseTransform * trans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (trans);
+
+ othercaps = gst_gl_view_convert_fixate_caps (viewconvert_filter->viewconvert,
+ direction, caps, othercaps);
+
+ if (gst_caps_is_empty (othercaps))
+ return othercaps;
+
+ /* Let GLfilter do the rest */
+ return
+ GST_BASE_TRANSFORM_CLASS
+ (gst_gl_view_convert_element_parent_class)->fixate_caps (trans, direction,
+ caps, othercaps);
+}
+
+static gboolean
+gst_gl_view_convert_element_stop (GstBaseTransform * bt)
+{
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (bt);
+
+ gst_gl_view_convert_reset (viewconvert_filter->viewconvert);
+
+ return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt);
+}
+
+static void
+gst_gl_view_convert_element_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ switch (prop_id) {
+ case PROP_INPUT_LAYOUT:
+ case PROP_INPUT_FLAGS:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
+ break;
+ case PROP_OUTPUT_LAYOUT:
+ case PROP_OUTPUT_FLAGS:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
+ break;
+ case PROP_OUTPUT_DOWNMIX_MODE:
+ g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gl_view_convert_element_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
+
+ switch (prop_id) {
+ case PROP_INPUT_LAYOUT:
+ case PROP_INPUT_FLAGS:
+ case PROP_OUTPUT_LAYOUT:
+ case PROP_OUTPUT_FLAGS:
+ case PROP_OUTPUT_DOWNMIX_MODE:
+ g_object_get_property (G_OBJECT (convert->viewconvert), pspec->name,
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstFlowReturn
+gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
+ gboolean is_discont, GstBuffer * input)
+{
+ GstGLContext *context = GST_GL_BASE_FILTER (trans)->context;
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (trans);
+ GstFlowReturn ret;
+
+ ret =
+ GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (trans,
+ is_discont, input);
+ if (ret != GST_FLOW_OK || trans->queued_buf == NULL)
+ return ret;
+
+ gst_gl_view_convert_set_context (viewconvert_filter->viewconvert, context);
+
+ /* Takes the ref to the input buffer */
+ ret =
+ gst_gl_view_convert_submit_input_buffer (viewconvert_filter->viewconvert,
+ is_discont, input);
+ trans->queued_buf = NULL;
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
+ GstBuffer ** outbuf_ptr)
+{
+ GstGLFilter *filter = GST_GL_FILTER (bt);
+ GstGLViewConvertElement *viewconvert_filter =
+ GST_GL_VIEW_CONVERT_ELEMENT (bt);
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ ret = gst_gl_view_convert_get_output (viewconvert_filter->viewconvert,
+ outbuf_ptr);
+
+ if (ret != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (filter, RESOURCE, SETTINGS,
+ ("failed to perform view conversion on input buffer"), (NULL));
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/ext/gl/gstglviewconvert.h b/ext/gl/gstglviewconvert.h
new file mode 100644
index 000000000..1403d27a1
--- /dev/null
+++ b/ext/gl/gstglviewconvert.h
@@ -0,0 +1,53 @@
+/*
+ * GStreamer
+ * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_GL_VIEW_CONVERT_ELEMENT_H_
+#define _GST_GL_VIEW_CONVERT_ELEMENT_H_
+
+#include <gst/gl/gstglviewconvert.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_GL_VIEW_CONVERT_ELEMENT (gst_gl_view_convert_element_get_type())
+#define GST_GL_VIEW_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElement))
+#define GST_IS_GL_VIEW_CONVERT_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_VIEW_CONVERT_ELEMENT))
+#define GST_GL_VIEW_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElementClass))
+#define GST_IS_GL_VIEW_CONVERT_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT))
+#define GST_GL_VIEW_CONVERT_ELEMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_VIEW_CONVERT_ELEMENT,GstGLViewConvertElementClass))
+
+typedef struct _GstGLViewConvertElement GstGLViewConvertElement;
+typedef struct _GstGLViewConvertElementClass GstGLViewConvertElementClass;
+
+struct _GstGLViewConvertElement
+{
+ GstGLFilter filter;
+
+ GstGLViewConvert *viewconvert;
+};
+
+struct _GstGLViewConvertElementClass
+{
+ GstGLFilterClass filter_class;
+};
+
+GType gst_gl_view_convert_element_get_type (void);
+
+G_END_DECLS
+#endif /* _GST_GL_VIEW_CONVERT_H_ */
diff --git a/ext/gl/gstopengl.c b/ext/gl/gstopengl.c
index 308c65c08..7b43e49e1 100644
--- a/ext/gl/gstopengl.c
+++ b/ext/gl/gstopengl.c
@@ -58,6 +58,9 @@
#include "gstglvideomixer.h"
#include "gstglfiltershader.h"
#include "gstglfilterapp.h"
+#include "gstglstereosplit.h"
+#include "gstglstereomix.h"
+
#if HAVE_GRAPHENE
#include "gstgltransformation.h"
#endif
@@ -72,6 +75,7 @@
#include "gstglfilterglass.h"
/* #include "gstglfilterreflectedscreen.h" */
#include "gstgldeinterlace.h"
+#include "gstglviewconvert.h"
#include "gstglmosaic.h"
#if HAVE_PNG
#include "gstgldifferencematte.h"
@@ -203,6 +207,14 @@ plugin_init (GstPlugin * plugin)
}
#endif /* HAVE_PNG */
#endif /* HAVE_JPEG */
+ if (!gst_element_register (plugin, "glstereosplit",
+ GST_RANK_NONE, GST_TYPE_GL_STEREOSPLIT)) {
+ return FALSE;
+ }
+ if (!gst_element_register (plugin, "glstereomix",
+ GST_RANK_NONE, GST_TYPE_GL_STEREO_MIX)) {
+ return FALSE;
+ }
#if GST_GL_HAVE_OPENGL
if (!gst_element_register (plugin, "gltestsrc",
GST_RANK_NONE, GST_TYPE_GL_TEST_SRC)) {
@@ -224,6 +236,11 @@ plugin_init (GstPlugin * plugin)
return FALSE;
}
+ if (!gst_element_register (plugin, "glviewconvert",
+ GST_RANK_NONE, GST_TYPE_GL_VIEW_CONVERT_ELEMENT)) {
+ return FALSE;
+ }
+
if (!gst_element_register (plugin, "glmosaic",
GST_RANK_NONE, GST_TYPE_GL_MOSAIC)) {
return FALSE;