3 * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
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.
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.
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.
22 * SECTION:gstgtkglsink
30 #include "gstgtkglsink.h"
32 GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink);
33 #define GST_CAT_DEFAULT gst_debug_gtk_gl_sink
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
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);
46 static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
48 static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
50 static GstStateChangeReturn
51 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition);
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,
58 static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
61 static GstStaticPadTemplate gst_gtk_gl_sink_template =
62 GST_STATIC_PAD_TEMPLATE ("sink",
65 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
66 (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")));
72 PROP_FORCE_ASPECT_RATIO,
73 PROP_PIXEL_ASPECT_RATIO,
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"));
89 gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
91 GObjectClass *gobject_class;
92 GstElementClass *gstelement_class;
93 GstBaseSinkClass *gstbasesink_class;
94 GstVideoSinkClass *gstvideosink_class;
96 gobject_class = (GObjectClass *) klass;
97 gstelement_class = (GstElementClass *) klass;
98 gstbasesink_class = (GstBaseSinkClass *) klass;
99 gstvideosink_class = (GstVideoSinkClass *) klass;
101 gobject_class->set_property = gst_gtk_gl_sink_set_property;
102 gobject_class->get_property = gst_gtk_gl_sink_get_property;
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>");
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));
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));
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));
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));
130 gst_element_class_add_pad_template (gstelement_class,
131 gst_static_pad_template_get (&gst_gtk_gl_sink_template));
133 gobject_class->finalize = gst_gtk_gl_sink_finalize;
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;
142 gstvideosink_class->show_frame = gst_gtk_gl_sink_show_frame;
146 gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
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;
155 gst_gtk_gl_sink_finalize (GObject * object)
157 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);;
159 g_clear_object (>k_sink->widget);
161 G_OBJECT_CLASS (parent_class)->finalize (object);
165 widget_destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink)
167 GST_OBJECT_LOCK (gtk_sink);
168 g_clear_object (>k_sink->widget);
169 GST_OBJECT_UNLOCK (gtk_sink);
172 static GtkGstBaseWidget *
173 gst_gtk_gl_sink_get_widget (GstGtkGLSink * gtk_sink)
175 if (gtk_sink->widget != NULL)
176 return gtk_sink->widget;
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.");
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);
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);
202 return gtk_sink->widget;
206 gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
207 GValue * value, GParamSpec * pspec)
209 GstGtkGLSink *gtk_sink;
211 gtk_sink = GST_GTK_GL_SINK (object);
215 g_value_set_object (value, gst_gtk_gl_sink_get_widget (gtk_sink));
217 case PROP_FORCE_ASPECT_RATIO:
218 g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
220 case PROP_PIXEL_ASPECT_RATIO:
221 gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
223 case PROP_IGNORE_ALPHA:
224 g_value_set_boolean (value, gtk_sink->ignore_alpha);
227 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
233 gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
234 const GValue * value, GParamSpec * pspec)
236 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
239 case PROP_FORCE_ASPECT_RATIO:
240 gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
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);
246 case PROP_IGNORE_ALPHA:
247 gtk_sink->ignore_alpha = g_value_get_boolean (value);
250 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
256 gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
258 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
259 gboolean res = FALSE;
261 switch (GST_QUERY_TYPE (query)) {
262 case GST_QUERY_CONTEXT:
264 const gchar *context_type;
265 GstContext *context, *old_context;
268 ret = gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
269 >k_sink->display, >k_sink->gtk_context);
271 if (gtk_sink->display)
272 gst_gl_display_filter_gl_api (gtk_sink->display, GST_GL_API_OPENGL3);
274 gst_query_parse_context_type (query, &context_type);
276 if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
279 gst_query_parse_context (query, &old_context);
282 context = gst_context_copy (old_context);
284 context = gst_context_new ("gst.gl.local_context", FALSE);
286 s = gst_context_writable_structure (context);
287 gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, gtk_sink->context,
289 gst_query_set_context (query, context);
290 gst_context_unref (context);
292 ret = gtk_sink->context != NULL;
294 GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type,
301 res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
309 gst_gtk_gl_sink_stop (GstBaseSink * bsink)
314 static GstStateChangeReturn
315 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition)
317 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (element);
318 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
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)));
325 switch (transition) {
326 case GST_STATE_CHANGE_NULL_TO_READY:{
327 GtkGstGLWidget *gst_widget;
329 if (gst_gtk_gl_sink_get_widget (gtk_sink) == NULL)
330 return GST_STATE_CHANGE_FAILURE;
332 /* After this point, gtk_sink->widget will always be set */
333 gst_widget = GTK_GST_GL_WIDGET (gtk_sink->widget);
335 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_widget));
336 if (!gtk_widget_is_toplevel (toplevel)) {
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);
348 if (!gtk_gst_gl_widget_init_winsys (gst_widget))
349 return GST_STATE_CHANGE_FAILURE;
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);
355 if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context)
356 return GST_STATE_CHANGE_FAILURE;
359 case GST_STATE_CHANGE_READY_TO_PAUSED:
361 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
367 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
368 if (ret == GST_STATE_CHANGE_FAILURE)
371 switch (transition) {
372 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
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);
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;
386 if (gtk_sink->context) {
387 gst_object_unref (gtk_sink->context);
388 gtk_sink->context = NULL;
391 if (gtk_sink->gtk_context) {
392 gst_object_unref (gtk_sink->gtk_context);
393 gtk_sink->gtk_context = NULL;
404 gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
405 GstClockTime * start, GstClockTime * end)
407 GstGtkGLSink *gtk_sink;
409 gtk_sink = GST_GTK_GL_SINK (bsink);
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);
416 if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) {
418 gst_util_uint64_scale_int (GST_SECOND,
419 GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
420 GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
427 gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
429 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
431 GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
433 if (!gst_video_info_from_caps (>k_sink->v_info, caps))
436 GST_OBJECT_LOCK (gtk_sink);
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));
445 if (!gtk_gst_base_widget_set_caps (gtk_sink->widget, caps))
448 GST_OBJECT_UNLOCK (gtk_sink);
454 gst_gtk_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
456 GstGtkGLSink *gtk_sink;
458 GST_TRACE ("rendering buffer:%p", buf);
460 gtk_sink = GST_GTK_GL_SINK (vsink);
462 GST_OBJECT_LOCK (vsink);
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;
471 gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf);
473 GST_OBJECT_UNLOCK (vsink);
479 gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
481 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
483 GstStructure *config;
488 if (!gtk_sink->display || !gtk_sink->context)
491 gst_query_parse_allocation (query, &caps, &need_pool);
496 if ((pool = gtk_sink->pool))
497 gst_object_ref (pool);
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);
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);
513 gst_structure_free (config);
516 if (pool == NULL && need_pool) {
519 if (!gst_video_info_from_caps (&info, caps))
522 GST_DEBUG_OBJECT (gtk_sink, "create new pool");
523 pool = gst_gl_buffer_pool_new (gtk_sink->context);
525 /* the normal size of a frame */
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))
533 /* we need at least 2 buffer because we hold on to the last one */
535 gst_query_add_allocation_pool (query, pool, size, 2, 0);
536 gst_object_unref (pool);
539 /* we also support various metadata */
540 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
542 if (gtk_sink->context->gl_vtable->FenceSync)
543 gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
550 GST_DEBUG_OBJECT (bsink, "no caps specified");
555 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
560 GST_DEBUG_OBJECT (bsink, "failed setting config");