| /* |
| * 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; |
| } |