| /* |
| * 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-1.0 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_GLES2 | 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); |
| |
| GST_ELEMENT_CLASS (gst_gl_stereosplit_parent_class)->set_context (element, |
| context); |
| } |
| |
| 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) { |
| if (!gst_gl_display_create_context (self->display, self->other_context, |
| &self->context, &error)) { |
| GST_OBJECT_UNLOCK (self->display); |
| goto context_error; |
| } |
| } |
| } while (!gst_gl_display_add_context (self->display, self->context)); |
| GST_OBJECT_UNLOCK (self->display); |
| } |
| |
| { |
| GstGLAPI current_gl_api = gst_gl_context_get_gl_api (self->context); |
| if ((current_gl_api & (SUPPORTED_GL_APIS)) == 0) |
| goto unsupported_gl_api; |
| } |
| |
| return TRUE; |
| |
| unsupported_gl_api: |
| { |
| GstGLAPI gl_api = gst_gl_context_get_gl_api (self->context); |
| gchar *gl_api_str = gst_gl_api_to_string (gl_api); |
| gchar *supported_gl_api_str = gst_gl_api_to_string (SUPPORTED_GL_APIS); |
| GST_ELEMENT_ERROR (self, RESOURCE, BUSY, |
| ("GL API's not compatible context: %s supported: %s", gl_api_str, |
| supported_gl_api_str), (NULL)); |
| |
| g_free (supported_gl_api_str); |
| g_free (gl_api_str); |
| return FALSE; |
| } |
| context_error: |
| { |
| GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("%s", error->message), |
| (NULL)); |
| g_clear_error (&error); |
| 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); |
| } |
| } |