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
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);
45 static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
47 static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
49 static GstStateChangeReturn
50 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition);
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,
57 static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
60 static GstStaticPadTemplate gst_gtk_gl_sink_template =
61 GST_STATIC_PAD_TEMPLATE ("sink",
64 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
65 (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")));
71 PROP_FORCE_ASPECT_RATIO,
72 PROP_PIXEL_ASPECT_RATIO,
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"));
87 gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
89 GObjectClass *gobject_class;
90 GstElementClass *gstelement_class;
91 GstBaseSinkClass *gstbasesink_class;
92 GstVideoSinkClass *gstvideosink_class;
94 gobject_class = (GObjectClass *) klass;
95 gstelement_class = (GstElementClass *) klass;
96 gstbasesink_class = (GstBaseSinkClass *) klass;
97 gstvideosink_class = (GstVideoSinkClass *) klass;
99 gobject_class->set_property = gst_gtk_gl_sink_set_property;
100 gobject_class->get_property = gst_gtk_gl_sink_get_property;
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>");
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));
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));
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));
123 gst_element_class_add_pad_template (gstelement_class,
124 gst_static_pad_template_get (&gst_gtk_gl_sink_template));
126 gobject_class->finalize = gst_gtk_gl_sink_finalize;
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;
135 gstvideosink_class->show_frame = gst_gtk_gl_sink_show_frame;
139 gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
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;
147 gst_gtk_gl_sink_finalize (GObject * object)
149 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);;
151 g_object_unref (gtk_sink->widget);
153 G_OBJECT_CLASS (parent_class)->finalize (object);
156 static GtkGstGLWidget *
157 gst_gtk_gl_sink_get_widget (GstGtkGLSink * gtk_sink)
159 if (gtk_sink->widget != NULL)
160 return gtk_sink->widget;
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.");
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);
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);
181 return gtk_sink->widget;
185 gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
186 GValue * value, GParamSpec * pspec)
188 GstGtkGLSink *gtk_sink;
190 gtk_sink = GST_GTK_GL_SINK (object);
194 g_value_set_object (value, gst_gtk_gl_sink_get_widget (gtk_sink));
196 case PROP_FORCE_ASPECT_RATIO:
197 g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
199 case PROP_PIXEL_ASPECT_RATIO:
200 gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
203 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
209 gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
210 const GValue * value, GParamSpec * pspec)
212 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
215 case PROP_FORCE_ASPECT_RATIO:
216 gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
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);
223 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
229 gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
231 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
232 gboolean res = FALSE;
234 switch (GST_QUERY_TYPE (query)) {
235 case GST_QUERY_CONTEXT:
237 const gchar *context_type;
238 GstContext *context, *old_context;
241 ret = gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
242 >k_sink->display, >k_sink->gtk_context);
244 if (gtk_sink->display)
245 gst_gl_display_filter_gl_api (gtk_sink->display, GST_GL_API_OPENGL3);
247 gst_query_parse_context_type (query, &context_type);
249 if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
252 gst_query_parse_context (query, &old_context);
255 context = gst_context_copy (old_context);
257 context = gst_context_new ("gst.gl.local_context", FALSE);
259 s = gst_context_writable_structure (context);
260 gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, gtk_sink->context,
262 gst_query_set_context (query, context);
263 gst_context_unref (context);
265 ret = gtk_sink->context != NULL;
267 GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type,
274 res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
282 gst_gtk_gl_sink_stop (GstBaseSink * bsink)
287 static GstStateChangeReturn
288 gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition)
290 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (element);
291 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
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)));
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;
302 /* After this point, gtk_sink->widget will always be set */
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;
310 if (!gtk_gst_gl_widget_init_winsys (gtk_sink->widget))
311 return GST_STATE_CHANGE_FAILURE;
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);
318 if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context)
319 return GST_STATE_CHANGE_FAILURE;
321 case GST_STATE_CHANGE_READY_TO_PAUSED:
323 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
329 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
330 if (ret == GST_STATE_CHANGE_FAILURE)
333 switch (transition) {
334 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
336 case GST_STATE_CHANGE_PAUSED_TO_READY:
337 gtk_gst_gl_widget_set_buffer (gtk_sink->widget, NULL);
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;
345 if (gtk_sink->context) {
346 gst_object_unref (gtk_sink->context);
347 gtk_sink->context = NULL;
350 if (gtk_sink->gtk_context) {
351 gst_object_unref (gtk_sink->gtk_context);
352 gtk_sink->gtk_context = NULL;
363 gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
364 GstClockTime * start, GstClockTime * end)
366 GstGtkGLSink *gtk_sink;
368 gtk_sink = GST_GTK_GL_SINK (bsink);
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);
375 if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) {
377 gst_util_uint64_scale_int (GST_SECOND,
378 GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
379 GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
386 gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
388 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
390 GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
392 if (!gst_video_info_from_caps (>k_sink->v_info, caps))
395 if (!gtk_gst_gl_widget_set_caps (gtk_sink->widget, caps))
402 gst_gtk_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
404 GstGtkGLSink *gtk_sink;
406 GST_TRACE ("rendering buffer:%p", buf);
408 gtk_sink = GST_GTK_GL_SINK (vsink);
410 gtk_gst_gl_widget_set_buffer (gtk_sink->widget, buf);
416 gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
418 GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
420 GstStructure *config;
425 if (!gtk_sink->display || !gtk_sink->context)
428 gst_query_parse_allocation (query, &caps, &need_pool);
433 if ((pool = gtk_sink->pool))
434 gst_object_ref (pool);
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);
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);
450 gst_structure_free (config);
453 if (pool == NULL && need_pool) {
456 if (!gst_video_info_from_caps (&info, caps))
459 GST_DEBUG_OBJECT (gtk_sink, "create new pool");
460 pool = gst_gl_buffer_pool_new (gtk_sink->context);
462 /* the normal size of a frame */
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))
470 /* we need at least 2 buffer because we hold on to the last one */
472 gst_query_add_allocation_pool (query, pool, size, 2, 0);
473 gst_object_unref (pool);
476 /* we also support various metadata */
477 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
479 if (gtk_sink->context->gl_vtable->FenceSync)
480 gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
487 GST_DEBUG_OBJECT (bsink, "no caps specified");
492 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
497 GST_DEBUG_OBJECT (bsink, "failed setting config");