gtkglsink: Port to GtkGstBaseWidget
[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 #define DEFAULT_IGNORE_ALPHA        TRUE
39
40 static void gst_gtk_gl_sink_finalize (GObject * object);
41 static void gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
42     const GValue * value, GParamSpec * param_spec);
43 static void gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
44     GValue * value, GParamSpec * param_spec);
45
46 static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
47
48 static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
49
50 static GstStateChangeReturn
51 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition);
52
53 static void gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
54     GstClockTime * start, GstClockTime * end);
55 static gboolean gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
56 static GstFlowReturn gst_gtk_gl_sink_show_frame (GstVideoSink * bsink,
57     GstBuffer * buf);
58 static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
59     GstQuery * query);
60
61 static GstStaticPadTemplate gst_gtk_gl_sink_template =
62 GST_STATIC_PAD_TEMPLATE ("sink",
63     GST_PAD_SINK,
64     GST_PAD_ALWAYS,
65     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
66         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")));
67
68 enum
69 {
70   PROP_0,
71   PROP_WIDGET,
72   PROP_FORCE_ASPECT_RATIO,
73   PROP_PIXEL_ASPECT_RATIO,
74   PROP_IGNORE_ALPHA,
75 };
76
77 enum
78 {
79   SIGNAL_0,
80   LAST_SIGNAL
81 };
82
83 #define gst_gtk_gl_sink_parent_class parent_class
84 G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink,
85     GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
86         "gtkglsink", 0, "Gtk Video Sink"));
87
88 static void
89 gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
90 {
91   GObjectClass *gobject_class;
92   GstElementClass *gstelement_class;
93   GstBaseSinkClass *gstbasesink_class;
94   GstVideoSinkClass *gstvideosink_class;
95
96   gobject_class = (GObjectClass *) klass;
97   gstelement_class = (GstElementClass *) klass;
98   gstbasesink_class = (GstBaseSinkClass *) klass;
99   gstvideosink_class = (GstVideoSinkClass *) klass;
100
101   gobject_class->set_property = gst_gtk_gl_sink_set_property;
102   gobject_class->get_property = gst_gtk_gl_sink_get_property;
103
104   gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink",
105       "Sink/Video", "A video sink that renders to a GtkWidget",
106       "Matthew Waters <matthew@centricular.com>");
107
108   g_object_class_install_property (gobject_class, PROP_WIDGET,
109       g_param_spec_object ("widget", "Gtk Widget",
110           "The GtkWidget to place in the widget heirachy",
111           GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
112
113   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
114       g_param_spec_boolean ("force-aspect-ratio",
115           "Force aspect ratio",
116           "When enabled, scaling will respect original aspect ratio",
117           DEFAULT_FORCE_ASPECT_RATIO,
118           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
119
120   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
121       gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
122           "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
123           G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
124
125   g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA,
126       g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
127           "When enabled, alpha will be ignored and converted to black",
128           DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
129
130   gst_element_class_add_pad_template (gstelement_class,
131       gst_static_pad_template_get (&gst_gtk_gl_sink_template));
132
133   gobject_class->finalize = gst_gtk_gl_sink_finalize;
134
135   gstelement_class->change_state = gst_gtk_gl_sink_change_state;
136   gstbasesink_class->query = gst_gtk_gl_sink_query;
137   gstbasesink_class->set_caps = gst_gtk_gl_sink_set_caps;
138   gstbasesink_class->get_times = gst_gtk_gl_sink_get_times;
139   gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation;
140   gstbasesink_class->stop = gst_gtk_gl_sink_stop;
141
142   gstvideosink_class->show_frame = gst_gtk_gl_sink_show_frame;
143 }
144
145 static void
146 gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
147 {
148   gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
149   gtk_sink->par_n = DEFAULT_PAR_N;
150   gtk_sink->par_d = DEFAULT_PAR_D;
151   gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA;
152 }
153
154 static void
155 gst_gtk_gl_sink_finalize (GObject * object)
156 {
157   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);;
158
159   g_clear_object (&gtk_sink->widget);
160
161   G_OBJECT_CLASS (parent_class)->finalize (object);
162 }
163
164 static void
165 widget_destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink)
166 {
167   GST_OBJECT_LOCK (gtk_sink);
168   g_clear_object (&gtk_sink->widget);
169   GST_OBJECT_UNLOCK (gtk_sink);
170 }
171
172 static GtkGstBaseWidget *
173 gst_gtk_gl_sink_get_widget (GstGtkGLSink * gtk_sink)
174 {
175   if (gtk_sink->widget != NULL)
176     return gtk_sink->widget;
177
178   /* Ensure GTK is initialized, this has no side effect if it was already
179    * initialized. Also, we do that lazily, so the application can be first */
180   if (!gtk_init_check (NULL, NULL)) {
181     GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
182     return NULL;
183   }
184
185   gtk_sink->widget = (GtkGstBaseWidget *) gtk_gst_gl_widget_new ();
186   gtk_sink->bind_force_aspect_ratio =
187       g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget,
188       "force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
189   gtk_sink->bind_pixel_aspect_ratio =
190       g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget,
191       "pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
192   gtk_sink->bind_ignore_alpha =
193       g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget,
194       "ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
195
196   /* Take the floating ref, otherwise the destruction of the container will
197    * make this widget disapear possibly before we are done. */
198   gst_object_ref_sink (gtk_sink->widget);
199   g_signal_connect (gtk_sink->widget, "destroy",
200       G_CALLBACK (widget_destroy_cb), gtk_sink);
201
202   return gtk_sink->widget;
203 }
204
205 static void
206 gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
207     GValue * value, GParamSpec * pspec)
208 {
209   GstGtkGLSink *gtk_sink;
210
211   gtk_sink = GST_GTK_GL_SINK (object);
212
213   switch (prop_id) {
214     case PROP_WIDGET:
215       g_value_set_object (value, gst_gtk_gl_sink_get_widget (gtk_sink));
216       break;
217     case PROP_FORCE_ASPECT_RATIO:
218       g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
219       break;
220     case PROP_PIXEL_ASPECT_RATIO:
221       gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
222       break;
223     case PROP_IGNORE_ALPHA:
224       g_value_set_boolean (value, gtk_sink->ignore_alpha);
225       break;
226     default:
227       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
228       break;
229   }
230 }
231
232 static void
233 gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
234     const GValue * value, GParamSpec * pspec)
235 {
236   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
237
238   switch (prop_id) {
239     case PROP_FORCE_ASPECT_RATIO:
240       gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
241       break;
242     case PROP_PIXEL_ASPECT_RATIO:
243       gtk_sink->par_n = gst_value_get_fraction_numerator (value);
244       gtk_sink->par_d = gst_value_get_fraction_denominator (value);
245       break;
246     case PROP_IGNORE_ALPHA:
247       gtk_sink->ignore_alpha = g_value_get_boolean (value);
248       break;
249     default:
250       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
251       break;
252   }
253 }
254
255 static gboolean
256 gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
257 {
258   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
259   gboolean res = FALSE;
260
261   switch (GST_QUERY_TYPE (query)) {
262     case GST_QUERY_CONTEXT:
263     {
264       const gchar *context_type;
265       GstContext *context, *old_context;
266       gboolean ret;
267
268       ret = gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
269           &gtk_sink->display, &gtk_sink->gtk_context);
270
271       if (gtk_sink->display)
272         gst_gl_display_filter_gl_api (gtk_sink->display, GST_GL_API_OPENGL3);
273
274       gst_query_parse_context_type (query, &context_type);
275
276       if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
277         GstStructure *s;
278
279         gst_query_parse_context (query, &old_context);
280
281         if (old_context)
282           context = gst_context_copy (old_context);
283         else
284           context = gst_context_new ("gst.gl.local_context", FALSE);
285
286         s = gst_context_writable_structure (context);
287         gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, gtk_sink->context,
288             NULL);
289         gst_query_set_context (query, context);
290         gst_context_unref (context);
291
292         ret = gtk_sink->context != NULL;
293       }
294       GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type,
295           ret);
296
297       if (ret)
298         return ret;
299     }
300     default:
301       res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
302       break;
303   }
304
305   return res;
306 }
307
308 static gboolean
309 gst_gtk_gl_sink_stop (GstBaseSink * bsink)
310 {
311   return TRUE;
312 }
313
314 static GstStateChangeReturn
315 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition)
316 {
317   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (element);
318   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
319   GtkWidget *toplevel;
320
321   GST_DEBUG ("changing state: %s => %s",
322       gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
323       gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
324
325   switch (transition) {
326     case GST_STATE_CHANGE_NULL_TO_READY:{
327       GtkGstGLWidget *gst_widget;
328
329       if (gst_gtk_gl_sink_get_widget (gtk_sink) == NULL)
330         return GST_STATE_CHANGE_FAILURE;
331
332       /* After this point, gtk_sink->widget will always be set */
333       gst_widget = GTK_GST_GL_WIDGET (gtk_sink->widget);
334
335       toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_widget));
336       if (!gtk_widget_is_toplevel (toplevel)) {
337         GtkWidget *window;
338
339         /* User did not add widget its own UI, let's popup a new GtkWindow to
340          * make gst-launch-1.0 work. */
341         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
342         gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
343         gtk_window_set_title (GTK_WINDOW (window), "Gtk+ OpenGL renderer");
344         gtk_container_add (GTK_CONTAINER (window), toplevel);
345         gtk_widget_show_all (window);
346       }
347
348       if (!gtk_gst_gl_widget_init_winsys (gst_widget))
349         return GST_STATE_CHANGE_FAILURE;
350
351       gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget);
352       gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget);
353       gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget);
354
355       if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context)
356         return GST_STATE_CHANGE_FAILURE;
357       break;
358     }
359     case GST_STATE_CHANGE_READY_TO_PAUSED:
360       break;
361     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
362       break;
363     default:
364       break;
365   }
366
367   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
368   if (ret == GST_STATE_CHANGE_FAILURE)
369     return ret;
370
371   switch (transition) {
372     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
373       break;
374     case GST_STATE_CHANGE_PAUSED_TO_READY:
375       GST_OBJECT_LOCK (gtk_sink);
376       if (gtk_sink->widget)
377         gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL);
378       GST_OBJECT_UNLOCK (gtk_sink);
379       break;
380     case GST_STATE_CHANGE_READY_TO_NULL:
381       if (gtk_sink->display) {
382         gst_object_unref (gtk_sink->display);
383         gtk_sink->display = NULL;
384       }
385
386       if (gtk_sink->context) {
387         gst_object_unref (gtk_sink->context);
388         gtk_sink->context = NULL;
389       }
390
391       if (gtk_sink->gtk_context) {
392         gst_object_unref (gtk_sink->gtk_context);
393         gtk_sink->gtk_context = NULL;
394       }
395       break;
396     default:
397       break;
398   }
399
400   return ret;
401 }
402
403 static void
404 gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
405     GstClockTime * start, GstClockTime * end)
406 {
407   GstGtkGLSink *gtk_sink;
408
409   gtk_sink = GST_GTK_GL_SINK (bsink);
410
411   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
412     *start = GST_BUFFER_TIMESTAMP (buf);
413     if (GST_BUFFER_DURATION_IS_VALID (buf))
414       *end = *start + GST_BUFFER_DURATION (buf);
415     else {
416       if (GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info) > 0) {
417         *end = *start +
418             gst_util_uint64_scale_int (GST_SECOND,
419             GST_VIDEO_INFO_FPS_D (&gtk_sink->v_info),
420             GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info));
421       }
422     }
423   }
424 }
425
426 gboolean
427 gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
428 {
429   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
430
431   GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
432
433   if (!gst_video_info_from_caps (&gtk_sink->v_info, caps))
434     return FALSE;
435
436   GST_OBJECT_LOCK (gtk_sink);
437
438   if (gtk_sink->widget == NULL) {
439     GST_OBJECT_UNLOCK (gtk_sink);
440     GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
441         ("%s", "Output widget was destroyed"), (NULL));
442     return FALSE;
443   }
444
445   if (!gtk_gst_base_widget_set_caps (gtk_sink->widget, caps))
446     return FALSE;
447
448   GST_OBJECT_UNLOCK (gtk_sink);
449
450   return TRUE;
451 }
452
453 static GstFlowReturn
454 gst_gtk_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
455 {
456   GstGtkGLSink *gtk_sink;
457
458   GST_TRACE ("rendering buffer:%p", buf);
459
460   gtk_sink = GST_GTK_GL_SINK (vsink);
461
462   GST_OBJECT_LOCK (vsink);
463
464   if (gtk_sink->widget == NULL) {
465     GST_OBJECT_UNLOCK (vsink);
466     GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
467         ("%s", "Output widget was destroyed"), (NULL));
468     return GST_FLOW_ERROR;
469   }
470
471   gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf);
472
473   GST_OBJECT_UNLOCK (vsink);
474
475   return GST_FLOW_OK;
476 }
477
478 static gboolean
479 gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
480 {
481   GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
482   GstBufferPool *pool;
483   GstStructure *config;
484   GstCaps *caps;
485   guint size;
486   gboolean need_pool;
487
488   if (!gtk_sink->display || !gtk_sink->context)
489     return FALSE;
490
491   gst_query_parse_allocation (query, &caps, &need_pool);
492
493   if (caps == NULL)
494     goto no_caps;
495
496   if ((pool = gtk_sink->pool))
497     gst_object_ref (pool);
498
499   if (pool != NULL) {
500     GstCaps *pcaps;
501
502     /* we had a pool, check caps */
503     GST_DEBUG_OBJECT (gtk_sink, "check existing pool caps");
504     config = gst_buffer_pool_get_config (pool);
505     gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
506
507     if (!gst_caps_is_equal (caps, pcaps)) {
508       GST_DEBUG_OBJECT (gtk_sink, "pool has different caps");
509       /* different caps, we can't use this pool */
510       gst_object_unref (pool);
511       pool = NULL;
512     }
513     gst_structure_free (config);
514   }
515
516   if (pool == NULL && need_pool) {
517     GstVideoInfo info;
518
519     if (!gst_video_info_from_caps (&info, caps))
520       goto invalid_caps;
521
522     GST_DEBUG_OBJECT (gtk_sink, "create new pool");
523     pool = gst_gl_buffer_pool_new (gtk_sink->context);
524
525     /* the normal size of a frame */
526     size = info.size;
527
528     config = gst_buffer_pool_get_config (pool);
529     gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
530     if (!gst_buffer_pool_set_config (pool, config))
531       goto config_failed;
532   }
533   /* we need at least 2 buffer because we hold on to the last one */
534   if (pool) {
535     gst_query_add_allocation_pool (query, pool, size, 2, 0);
536     gst_object_unref (pool);
537   }
538
539   /* we also support various metadata */
540   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
541
542   if (gtk_sink->context->gl_vtable->FenceSync)
543     gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
544
545   return TRUE;
546
547   /* ERRORS */
548 no_caps:
549   {
550     GST_DEBUG_OBJECT (bsink, "no caps specified");
551     return FALSE;
552   }
553 invalid_caps:
554   {
555     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
556     return FALSE;
557   }
558 config_failed:
559   {
560     GST_DEBUG_OBJECT (bsink, "failed setting config");
561     return FALSE;
562   }
563 }