1 /*
2  * GStreamer
3  * Copyright (C) 2009 Julien Isorce <julien.isorce@mail.com>
4  * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
5  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:element-glviewconvert
25  * @title: glviewconvert
26  *
27  * Convert stereoscopic video between different representations using fragment shaders.
28  *
29  * The element can use either property settings or caps negotiation to choose the
30  * input and output formats to process.
31  *
32  * ## Examples
33  * |[
34  * gst-launch-1.0 videotestsrc ! glupload ! glviewconvert ! glimagesink
35  * ]| Simple placebo example demonstrating identity passthrough of mono video
36  * |[
37  * gst-launch-1.0 videotestsrc pattern=checkers-1 ! glupload ! \
38  *     glviewconvert input-mode-override=side-by-side ! glimagesink -v
39  * ]| Force re-interpretation of the input checkers pattern as a side-by-side stereoscopic
40  *    image and display in glimagesink.
41  * FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
42  *
43  */
44 
45 #ifdef HAVE_CONFIG_H
46 #include "config.h"
47 #endif
48 
49 #include <gst/base/gstbasetransform.h>
50 
51 #include "gstglviewconvert.h"
52 
53 #define GST_CAT_DEFAULT gst_gl_view_convert_element_debug
54 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
55 
56 enum
57 {
58   PROP_0,
59   PROP_INPUT_LAYOUT,
60   PROP_INPUT_FLAGS,
61   PROP_OUTPUT_LAYOUT,
62   PROP_OUTPUT_FLAGS,
63   PROP_OUTPUT_DOWNMIX_MODE
64 };
65 
66 #define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
67 
68 #define DEBUG_INIT \
69   GST_DEBUG_CATEGORY_INIT (gst_gl_view_convert_element_debug, "glview_convertelement", 0, "glview_convert element");
70 
71 G_DEFINE_TYPE_WITH_CODE (GstGLViewConvertElement, gst_gl_view_convert_element,
72     GST_TYPE_GL_FILTER, DEBUG_INIT);
73 #define parent_class gst_gl_view_convert_element_parent_class
74 
75 static void gst_gl_view_convert_dispose (GObject * object);
76 static void gst_gl_view_convert_element_set_property (GObject * object,
77     guint prop_id, const GValue * value, GParamSpec * pspec);
78 static void gst_gl_view_convert_element_get_property (GObject * object,
79     guint prop_id, GValue * value, GParamSpec * pspec);
80 
81 static gboolean gst_gl_view_convert_element_stop (GstBaseTransform * bt);
82 static gboolean
83 gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
84     GstCaps * outcaps);
85 static GstCaps *gst_gl_view_convert_element_transform_internal_caps (GstGLFilter
86     * filter, GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps);
87 static GstCaps *gst_gl_view_convert_element_fixate_caps (GstBaseTransform *
88     trans, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
89 static GstFlowReturn
90 gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
91     gboolean is_discont, GstBuffer * input);
92 static GstFlowReturn
93 gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
94     GstBuffer ** outbuf);
95 
96 static void
gst_gl_view_convert_element_class_init(GstGLViewConvertElementClass * klass)97 gst_gl_view_convert_element_class_init (GstGLViewConvertElementClass * klass)
98 {
99   GObjectClass *gobject_class;
100   GstElementClass *element_class;
101 
102   gobject_class = (GObjectClass *) klass;
103   element_class = GST_ELEMENT_CLASS (klass);
104 
105   gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
106 
107   gobject_class->set_property = gst_gl_view_convert_element_set_property;
108   gobject_class->get_property = gst_gl_view_convert_element_get_property;
109   gobject_class->dispose = gst_gl_view_convert_dispose;
110 
111   gst_element_class_set_metadata (element_class,
112       "OpenGL Multiview/3D conversion filter", "Filter",
113       "Convert stereoscopic/multiview video formats",
114       "Jan Schmidt <jan@centricular.com>\n"
115       "Matthew Waters <matthew@centricular.com>");
116 
117   GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_view_convert_element_set_caps;
118 
119   GST_GL_FILTER_CLASS (klass)->transform_internal_caps =
120       gst_gl_view_convert_element_transform_internal_caps;
121   GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_view_convert_element_stop;
122   GST_BASE_TRANSFORM_CLASS (klass)->fixate_caps =
123       gst_gl_view_convert_element_fixate_caps;
124   GST_BASE_TRANSFORM_CLASS (klass)->submit_input_buffer =
125       gst_gl_view_convert_element_submit_input_buffer;
126   GST_BASE_TRANSFORM_CLASS (klass)->generate_output =
127       gst_gl_view_convert_element_generate_output_buffer;
128 
129   g_object_class_install_property (gobject_class, PROP_INPUT_LAYOUT,
130       g_param_spec_enum ("input-mode-override",
131           "Input Multiview Mode Override",
132           "Override any input information about multiview layout",
133           GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
134           GST_VIDEO_MULTIVIEW_MODE_NONE,
135           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
136   g_object_class_install_property (gobject_class, PROP_INPUT_FLAGS,
137       g_param_spec_flags ("input-flags-override",
138           "Input Multiview Flags Override",
139           "Override any input information about multiview layout flags",
140           GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
141           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
142   g_object_class_install_property (gobject_class, PROP_OUTPUT_LAYOUT,
143       g_param_spec_enum ("output-mode-override",
144           "Output Multiview Mode Override",
145           "Override automatic output mode selection for multiview layout",
146           GST_TYPE_VIDEO_MULTIVIEW_MODE, GST_VIDEO_MULTIVIEW_MODE_NONE,
147           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
148   g_object_class_install_property (gobject_class, PROP_OUTPUT_FLAGS,
149       g_param_spec_flags ("output-flags-override",
150           "Output Multiview Flags Override",
151           "Override automatic negotiation for output multiview layout flags",
152           GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
153           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154   g_object_class_install_property (gobject_class, PROP_OUTPUT_DOWNMIX_MODE,
155       g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
156           "Output anaglyph type to generate when downmixing to mono",
157           GST_TYPE_GL_STEREO_DOWNMIX, DEFAULT_DOWNMIX,
158           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159 }
160 
161 static void
gst_gl_view_convert_element_init(GstGLViewConvertElement * convert)162 gst_gl_view_convert_element_init (GstGLViewConvertElement * convert)
163 {
164   convert->viewconvert = gst_gl_view_convert_new ();
165 }
166 
167 static void
gst_gl_view_convert_dispose(GObject * object)168 gst_gl_view_convert_dispose (GObject * object)
169 {
170   GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
171 
172   if (convert->viewconvert) {
173     gst_object_unref (convert->viewconvert);
174     convert->viewconvert = NULL;
175   }
176   G_OBJECT_CLASS (parent_class)->dispose (object);
177 }
178 
179 static gboolean
gst_gl_view_convert_element_set_caps(GstGLFilter * filter,GstCaps * incaps,GstCaps * outcaps)180 gst_gl_view_convert_element_set_caps (GstGLFilter * filter, GstCaps * incaps,
181     GstCaps * outcaps)
182 {
183   GstGLViewConvertElement *viewconvert_filter =
184       GST_GL_VIEW_CONVERT_ELEMENT (filter);
185   GstCapsFeatures *gl_features;
186   gboolean ret;
187 
188   GST_DEBUG_OBJECT (filter, "incaps %" GST_PTR_FORMAT
189       " outcaps %" GST_PTR_FORMAT, incaps, outcaps);
190   /* The view_convert component needs RGBA caps */
191   incaps = gst_caps_copy (incaps);
192   outcaps = gst_caps_copy (outcaps);
193 
194   gst_caps_set_simple (incaps, "format", G_TYPE_STRING, "RGBA", NULL);
195   gl_features =
196       gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
197   gst_caps_set_features (incaps, 0, gl_features);
198 
199   gst_caps_set_simple (outcaps, "format", G_TYPE_STRING, "RGBA", NULL);
200   gl_features =
201       gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
202   gst_caps_set_features (outcaps, 0, gl_features);
203 
204   ret = gst_gl_view_convert_set_caps (viewconvert_filter->viewconvert,
205       incaps, outcaps);
206 
207   gst_caps_unref (incaps);
208   gst_caps_unref (outcaps);
209 
210   return ret;
211 }
212 
213 static GstCaps *
gst_gl_view_convert_element_transform_internal_caps(GstGLFilter * filter,GstPadDirection direction,GstCaps * caps,GstCaps * filter_caps)214 gst_gl_view_convert_element_transform_internal_caps (GstGLFilter * filter,
215     GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
216 {
217   GstGLViewConvertElement *viewconvert_filter =
218       GST_GL_VIEW_CONVERT_ELEMENT (filter);
219   GstCaps *result;
220 
221   GST_DEBUG_OBJECT (filter, "dir %s transforming caps: %" GST_PTR_FORMAT,
222       direction == GST_PAD_SINK ? "sink" : "src", caps);
223 
224   result =
225       gst_gl_view_convert_transform_caps (viewconvert_filter->viewconvert,
226       direction, caps, NULL);
227 
228   GST_DEBUG_OBJECT (filter, "returning caps: %" GST_PTR_FORMAT, result);
229 
230   return result;
231 }
232 
233 static GstCaps *
gst_gl_view_convert_element_fixate_caps(GstBaseTransform * trans,GstPadDirection direction,GstCaps * caps,GstCaps * othercaps)234 gst_gl_view_convert_element_fixate_caps (GstBaseTransform * trans,
235     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
236 {
237   GstGLViewConvertElement *viewconvert_filter =
238       GST_GL_VIEW_CONVERT_ELEMENT (trans);
239 
240   othercaps = gst_gl_view_convert_fixate_caps (viewconvert_filter->viewconvert,
241       direction, caps, othercaps);
242 
243   if (gst_caps_is_empty (othercaps))
244     return othercaps;
245 
246   /* Let GLfilter do the rest */
247   return
248       GST_BASE_TRANSFORM_CLASS
249       (gst_gl_view_convert_element_parent_class)->fixate_caps (trans, direction,
250       caps, othercaps);
251 }
252 
253 static gboolean
gst_gl_view_convert_element_stop(GstBaseTransform * bt)254 gst_gl_view_convert_element_stop (GstBaseTransform * bt)
255 {
256   GstGLViewConvertElement *viewconvert_filter =
257       GST_GL_VIEW_CONVERT_ELEMENT (bt);
258 
259   gst_gl_view_convert_reset (viewconvert_filter->viewconvert);
260 
261   return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt);
262 }
263 
264 static void
gst_gl_view_convert_element_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)265 gst_gl_view_convert_element_set_property (GObject * object, guint prop_id,
266     const GValue * value, GParamSpec * pspec)
267 {
268   GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
269 
270   switch (prop_id) {
271     case PROP_INPUT_LAYOUT:
272     case PROP_INPUT_FLAGS:
273       g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
274           value);
275       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
276       break;
277     case PROP_OUTPUT_LAYOUT:
278     case PROP_OUTPUT_FLAGS:
279       g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
280           value);
281       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (convert));
282       break;
283     case PROP_OUTPUT_DOWNMIX_MODE:
284       g_object_set_property (G_OBJECT (convert->viewconvert), pspec->name,
285           value);
286       break;
287     default:
288       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
289       break;
290   }
291 }
292 
293 static void
gst_gl_view_convert_element_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)294 gst_gl_view_convert_element_get_property (GObject * object, guint prop_id,
295     GValue * value, GParamSpec * pspec)
296 {
297   GstGLViewConvertElement *convert = GST_GL_VIEW_CONVERT_ELEMENT (object);
298 
299   switch (prop_id) {
300     case PROP_INPUT_LAYOUT:
301     case PROP_INPUT_FLAGS:
302     case PROP_OUTPUT_LAYOUT:
303     case PROP_OUTPUT_FLAGS:
304     case PROP_OUTPUT_DOWNMIX_MODE:
305       g_object_get_property (G_OBJECT (convert->viewconvert), pspec->name,
306           value);
307       break;
308     default:
309       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
310       break;
311   }
312 }
313 
314 static GstFlowReturn
gst_gl_view_convert_element_submit_input_buffer(GstBaseTransform * trans,gboolean is_discont,GstBuffer * input)315 gst_gl_view_convert_element_submit_input_buffer (GstBaseTransform * trans,
316     gboolean is_discont, GstBuffer * input)
317 {
318   GstGLContext *context = GST_GL_BASE_FILTER (trans)->context;
319   GstGLViewConvertElement *viewconvert_filter =
320       GST_GL_VIEW_CONVERT_ELEMENT (trans);
321   GstFlowReturn ret;
322 
323   ret =
324       GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (trans,
325       is_discont, input);
326   if (ret != GST_FLOW_OK || trans->queued_buf == NULL)
327     return ret;
328 
329   gst_gl_view_convert_set_context (viewconvert_filter->viewconvert, context);
330 
331   /* Takes the ref to the input buffer */
332   ret =
333       gst_gl_view_convert_submit_input_buffer (viewconvert_filter->viewconvert,
334       is_discont, input);
335   trans->queued_buf = NULL;
336 
337   return ret;
338 }
339 
340 static GstFlowReturn
gst_gl_view_convert_element_generate_output_buffer(GstBaseTransform * bt,GstBuffer ** outbuf_ptr)341 gst_gl_view_convert_element_generate_output_buffer (GstBaseTransform * bt,
342     GstBuffer ** outbuf_ptr)
343 {
344   GstGLFilter *filter = GST_GL_FILTER (bt);
345   GstGLViewConvertElement *viewconvert_filter =
346       GST_GL_VIEW_CONVERT_ELEMENT (bt);
347   GstFlowReturn ret = GST_FLOW_OK;
348 
349   ret = gst_gl_view_convert_get_output (viewconvert_filter->viewconvert,
350       outbuf_ptr);
351 
352   if (ret != GST_FLOW_OK) {
353     GST_ELEMENT_ERROR (filter, RESOURCE, SETTINGS,
354         ("failed to perform view conversion on input buffer"), (NULL));
355     return ret;
356   }
357 
358   return ret;
359 }
360