gtk: Sync properties from the sink to the widget upon widget creation
[platform/upstream/gst-plugins-good.git] / ext / gtk / gstgtkglsink.c
1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * SECTION:gstgtkglsink
23  *
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "gstgtkglsink.h"
31
32 GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink);
33 #define GST_CAT_DEFAULT gst_debug_gtk_gl_sink
34
35 #define DEFAULT_FORCE_ASPECT_RATIO  TRUE
36 #define DEFAULT_PAR_N               0
37 #define DEFAULT_PAR_D               1
38
39 static void gst_gtk_gl_sink_finalize (GObject * object);
40 static void gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
41     const GValue * value, GParamSpec * param_spec);
42 static void gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
43     GValue * value, GParamSpec * param_spec);
44
45 static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
46
47 static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
48
49 static GstStateChangeReturn
50 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition);
51
52 static void gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
53     GstClockTime * start, GstClockTime * end);
54 static gboolean gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
55 static GstFlowReturn gst_gtk_gl_sink_show_frame (GstVideoSink * bsink,
56     GstBuffer * buf);
57 static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
58     GstQuery * query);
59
60 static GstStaticPadTemplate gst_gtk_gl_sink_template =
61 GST_STATIC_PAD_TEMPLATE ("sink",
62     GST_PAD_SINK,
63     GST_PAD_ALWAYS,
64     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
65         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")));
66
67 enum
68 {
69   PROP_0,
70   PROP_WIDGET,
71   PROP_FORCE_ASPECT_RATIO,
72   PROP_PIXEL_ASPECT_RATIO,
73 };
74
75 enum
76 {
77   SIGNAL_0,
78   LAST_SIGNAL
79 };
80
81 #define gst_gtk_gl_sink_parent_class parent_class
82 G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink,
83     GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
84         "gtkglsink", 0, "Gtk Video Sink"));
85
86 static void
87 gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
88 {
89   GObjectClass *gobject_class;
90   GstElementClass *gstelement_class;
91   GstBaseSinkClass *gstbasesink_class;
92   GstVideoSinkClass *gstvideosink_class;
93
94   gobject_class = (GObjectClass *) klass;
95   gstelement_class = (GstElementClass *) klass;
96   gstbasesink_class = (GstBaseSinkClass *) klass;
97   gstvideosink_class = (GstVideoSinkClass *) klass;
98
99   gobject_class->set_property = gst_gtk_gl_sink_set_property;
100   gobject_class->get_property = gst_gtk_gl_sink_get_property;
101
102   gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink",
103       "Sink/Video", "A video sink that renders to a GtkWidget",
104       "Matthew Waters <matthew@centricular.com>");
105
106   g_object_class_install_property (gobject_class, PROP_WIDGET,
107       g_param_spec_object ("widget", "Gtk Widget",
108           "The GtkWidget to place in the widget heirachy",
109           GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
110
111   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
112       g_param_spec_boolean ("force-aspect-ratio",
113           "Force aspect ratio",
114           "When enabled, scaling will respect original aspect ratio",
115           DEFAULT_FORCE_ASPECT_RATIO,
116           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
117
118   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
119       gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
120           "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
121           G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
122
123   gst_element_class_add_pad_template (gstelement_class,
124       gst_static_pad_template_get (&gst_gtk_gl_sink_template));
125
126   gobject_class->finalize = gst_gtk_gl_sink_finalize;
127
128   gstelement_class->change_state = gst_gtk_gl_sink_change_state;
129   gstbasesink_class->query = gst_gtk_gl_sink_query;
130   gstbasesink_class->set_caps = gst_gtk_gl_sink_set_caps;
131   gstbasesink_class->get_times = gst_gtk_gl_sink_get_times;
132   gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation;
133   gstbasesink_class->stop = gst_gtk_gl_sink_stop;
134
135   gstvideosink_class->show_frame = gst_gtk_gl_sink_show_frame;
136 }
137
138 static void
139 gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
140 {
141   gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
142   gtk_sink->par_n = DEFAULT_PAR_N;
143   gtk_sink->par_d = DEFAULT_PAR_D;
144 }
145
146 static void
147 gst_gtk_gl_sink_finalize (GObject * object)
148 {
149   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);;
150
151   g_object_unref (gtk_sink->widget);
152
153   G_OBJECT_CLASS (parent_class)->finalize (object);
154 }
155
156 static GtkGstGLWidget *
157 gst_gtk_gl_sink_get_widget (GstGtkGLSink * gtk_sink)
158 {
159   if (gtk_sink->widget != NULL)
160     return gtk_sink->widget;
161
162   /* Ensure GTK is initialized, this has no side effect if it was already
163    * initialized. Also, we do that lazily, so the application can be first */
164   if (!gtk_init_check (NULL, NULL)) {
165     GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
166     return NULL;
167   }
168
169   gtk_sink->widget = (GtkGstGLWidget *) gtk_gst_gl_widget_new ();
170   gtk_sink->bind_force_aspect_ratio =
171       g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget,
172       "force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
173   gtk_sink->bind_pixel_aspect_ratio =
174       g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget,
175       "pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
176
177   /* Take the floating ref, otherwise the destruction of the container will
178    * make this widget disapear possibly before we are done. */
179   gst_object_ref_sink (gtk_sink->widget);
180
181   return gtk_sink->widget;
182 }
183
184 static void
185 gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
186     GValue * value, GParamSpec * pspec)
187 {
188   GstGtkGLSink *gtk_sink;
189
190   gtk_sink = GST_GTK_GL_SINK (object);
191
192   switch (prop_id) {
193     case PROP_WIDGET:
194       g_value_set_object (value, gst_gtk_gl_sink_get_widget (gtk_sink));
195       break;
196     case PROP_FORCE_ASPECT_RATIO:
197       g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
198       break;
199     case PROP_PIXEL_ASPECT_RATIO:
200       gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
201       break;
202     default:
203       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
204       break;
205   }
206 }
207
208 static void
209 gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
210     const GValue * value, GParamSpec * pspec)
211 {
212   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
213
214   switch (prop_id) {
215     case PROP_FORCE_ASPECT_RATIO:
216       gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
217       break;
218     case PROP_PIXEL_ASPECT_RATIO:
219       gtk_sink->par_n = gst_value_get_fraction_numerator (value);
220       gtk_sink->par_d = gst_value_get_fraction_denominator (value);
221       break;
222     default:
223       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
224       break;
225   }
226 }
227
228 static gboolean
229 gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
230 {
231   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
232   gboolean res = FALSE;
233
234   switch (GST_QUERY_TYPE (query)) {
235     case GST_QUERY_CONTEXT:
236     {
237       const gchar *context_type;
238       GstContext *context, *old_context;
239       gboolean ret;
240
241       ret = gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
242           &gtk_sink->display, &gtk_sink->gtk_context);
243
244       if (gtk_sink->display)
245         gst_gl_display_filter_gl_api (gtk_sink->display, GST_GL_API_OPENGL3);
246
247       gst_query_parse_context_type (query, &context_type);
248
249       if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
250         GstStructure *s;
251
252         gst_query_parse_context (query, &old_context);
253
254         if (old_context)
255           context = gst_context_copy (old_context);
256         else
257           context = gst_context_new ("gst.gl.local_context", FALSE);
258
259         s = gst_context_writable_structure (context);
260         gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, gtk_sink->context,
261             NULL);
262         gst_query_set_context (query, context);
263         gst_context_unref (context);
264
265         ret = gtk_sink->context != NULL;
266       }
267       GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type,
268           ret);
269
270       if (ret)
271         return ret;
272     }
273     default:
274       res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
275       break;
276   }
277
278   return res;
279 }
280
281 static gboolean
282 gst_gtk_gl_sink_stop (GstBaseSink * bsink)
283 {
284   return TRUE;
285 }
286
287 static GstStateChangeReturn
288 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition)
289 {
290   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (element);
291   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
292
293   GST_DEBUG ("changing state: %s => %s",
294       gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
295       gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
296
297   switch (transition) {
298     case GST_STATE_CHANGE_NULL_TO_READY:
299       if (gst_gtk_gl_sink_get_widget (gtk_sink) == NULL)
300         return GST_STATE_CHANGE_FAILURE;
301
302       /* After this point, gtk_sink->widget will always be set */
303
304       if (!gtk_widget_get_parent (GTK_WIDGET (gtk_sink->widget))) {
305         GST_ERROR_OBJECT (gtk_sink,
306             "gtkglsink widget needs to be parented to work.");
307         return GST_STATE_CHANGE_FAILURE;
308       }
309
310       if (!gtk_gst_gl_widget_init_winsys (gtk_sink->widget))
311         return GST_STATE_CHANGE_FAILURE;
312
313       gtk_sink->display = gtk_gst_gl_widget_get_display (gtk_sink->widget);
314       gtk_sink->context = gtk_gst_gl_widget_get_context (gtk_sink->widget);
315       gtk_sink->gtk_context =
316           gtk_gst_gl_widget_get_gtk_context (gtk_sink->widget);
317
318       if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context)
319         return GST_STATE_CHANGE_FAILURE;
320       break;
321     case GST_STATE_CHANGE_READY_TO_PAUSED:
322       break;
323     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
324       break;
325     default:
326       break;
327   }
328
329   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
330   if (ret == GST_STATE_CHANGE_FAILURE)
331     return ret;
332
333   switch (transition) {
334     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
335       break;
336     case GST_STATE_CHANGE_PAUSED_TO_READY:
337       gtk_gst_gl_widget_set_buffer (gtk_sink->widget, NULL);
338       break;
339     case GST_STATE_CHANGE_READY_TO_NULL:
340       if (gtk_sink->display) {
341         gst_object_unref (gtk_sink->display);
342         gtk_sink->display = NULL;
343       }
344
345       if (gtk_sink->context) {
346         gst_object_unref (gtk_sink->context);
347         gtk_sink->context = NULL;
348       }
349
350       if (gtk_sink->gtk_context) {
351         gst_object_unref (gtk_sink->gtk_context);
352         gtk_sink->gtk_context = NULL;
353       }
354       break;
355     default:
356       break;
357   }
358
359   return ret;
360 }
361
362 static void
363 gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
364     GstClockTime * start, GstClockTime * end)
365 {
366   GstGtkGLSink *gtk_sink;
367
368   gtk_sink = GST_GTK_GL_SINK (bsink);
369
370   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
371     *start = GST_BUFFER_TIMESTAMP (buf);
372     if (GST_BUFFER_DURATION_IS_VALID (buf))
373       *end = *start + GST_BUFFER_DURATION (buf);
374     else {
375       if (GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info) > 0) {
376         *end = *start +
377             gst_util_uint64_scale_int (GST_SECOND,
378             GST_VIDEO_INFO_FPS_D (&gtk_sink->v_info),
379             GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info));
380       }
381     }
382   }
383 }
384
385 gboolean
386 gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
387 {
388   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
389
390   GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
391
392   if (!gst_video_info_from_caps (&gtk_sink->v_info, caps))
393     return FALSE;
394
395   if (!gtk_gst_gl_widget_set_caps (gtk_sink->widget, caps))
396     return FALSE;
397
398   return TRUE;
399 }
400
401 static GstFlowReturn
402 gst_gtk_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
403 {
404   GstGtkGLSink *gtk_sink;
405
406   GST_TRACE ("rendering buffer:%p", buf);
407
408   gtk_sink = GST_GTK_GL_SINK (vsink);
409
410   gtk_gst_gl_widget_set_buffer (gtk_sink->widget, buf);
411
412   return GST_FLOW_OK;
413 }
414
415 static gboolean
416 gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
417 {
418   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
419   GstBufferPool *pool;
420   GstStructure *config;
421   GstCaps *caps;
422   guint size;
423   gboolean need_pool;
424
425   if (!gtk_sink->display || !gtk_sink->context)
426     return FALSE;
427
428   gst_query_parse_allocation (query, &caps, &need_pool);
429
430   if (caps == NULL)
431     goto no_caps;
432
433   if ((pool = gtk_sink->pool))
434     gst_object_ref (pool);
435
436   if (pool != NULL) {
437     GstCaps *pcaps;
438
439     /* we had a pool, check caps */
440     GST_DEBUG_OBJECT (gtk_sink, "check existing pool caps");
441     config = gst_buffer_pool_get_config (pool);
442     gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
443
444     if (!gst_caps_is_equal (caps, pcaps)) {
445       GST_DEBUG_OBJECT (gtk_sink, "pool has different caps");
446       /* different caps, we can't use this pool */
447       gst_object_unref (pool);
448       pool = NULL;
449     }
450     gst_structure_free (config);
451   }
452
453   if (pool == NULL && need_pool) {
454     GstVideoInfo info;
455
456     if (!gst_video_info_from_caps (&info, caps))
457       goto invalid_caps;
458
459     GST_DEBUG_OBJECT (gtk_sink, "create new pool");
460     pool = gst_gl_buffer_pool_new (gtk_sink->context);
461
462     /* the normal size of a frame */
463     size = info.size;
464
465     config = gst_buffer_pool_get_config (pool);
466     gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
467     if (!gst_buffer_pool_set_config (pool, config))
468       goto config_failed;
469   }
470   /* we need at least 2 buffer because we hold on to the last one */
471   if (pool) {
472     gst_query_add_allocation_pool (query, pool, size, 2, 0);
473     gst_object_unref (pool);
474   }
475
476   /* we also support various metadata */
477   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
478
479   if (gtk_sink->context->gl_vtable->FenceSync)
480     gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
481
482   return TRUE;
483
484   /* ERRORS */
485 no_caps:
486   {
487     GST_DEBUG_OBJECT (bsink, "no caps specified");
488     return FALSE;
489   }
490 invalid_caps:
491   {
492     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
493     return FALSE;
494   }
495 config_failed:
496   {
497     GST_DEBUG_OBJECT (bsink, "failed setting config");
498     return FALSE;
499   }
500 }