/* * GStreamer * Copyright (C) 2015 Matthew Waters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:gstgtkglsink * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstgtkglsink.h" #include "gtkgstglwidget.h" GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink); #define GST_CAT_DEFAULT gst_debug_gtk_gl_sink static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink); static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink); static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query); static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query); static GstCaps *gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter); static GstStaticPadTemplate gst_gtk_gl_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; " GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", " GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA"))); #define gst_gtk_gl_sink_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink, GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink, "gtkglsink", 0, "Gtk Video Sink")); static void gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass) { GstElementClass *gstelement_class; GstBaseSinkClass *gstbasesink_class; GstGtkBaseSinkClass *gstgtkbasesink_class; gstelement_class = (GstElementClass *) klass; gstbasesink_class = (GstBaseSinkClass *) klass; gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass; gstbasesink_class->query = gst_gtk_gl_sink_query; gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation; gstbasesink_class->start = gst_gtk_gl_sink_start; gstbasesink_class->stop = gst_gtk_gl_sink_stop; gstbasesink_class->get_caps = gst_gtk_gl_sink_get_caps; gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new; gstgtkbasesink_class->window_title = "Gtk+ GL renderer"; gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink", "Sink/Video", "A video sink that renders to a GtkWidget", "Matthew Waters "); gst_element_class_add_static_pad_template (gstelement_class, &gst_gtk_gl_sink_template); } static void gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink) { } static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query) { GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); gboolean res = FALSE; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CONTEXT: { const gchar *context_type; GstContext *context, *old_context; res = gst_gl_handle_context_query ((GstElement *) gtk_sink, query, >k_sink->display, >k_sink->gtk_context); if (gtk_sink->display) gst_gl_display_filter_gl_api (gtk_sink->display, GST_GL_API_OPENGL3); gst_query_parse_context_type (query, &context_type); if (g_strcmp0 (context_type, "gst.gl.local_context") == 0) { GstStructure *s; gst_query_parse_context (query, &old_context); if (old_context) context = gst_context_copy (old_context); else context = gst_context_new ("gst.gl.local_context", FALSE); s = gst_context_writable_structure (context); gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, gtk_sink->context, NULL); gst_query_set_context (query, context); gst_context_unref (context); res = gtk_sink->context != NULL; } GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type, res); break; } default: res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); break; } return res; } static void _size_changed_cb (GtkWidget * widget, GdkRectangle * rectangle, GstGtkGLSink * gtk_sink) { gint scale_factor, width, height; gboolean reconfigure; scale_factor = gtk_widget_get_scale_factor (widget); width = scale_factor * gtk_widget_get_allocated_width (widget); height = scale_factor * gtk_widget_get_allocated_height (widget); GST_OBJECT_LOCK (gtk_sink); reconfigure = (width != gtk_sink->display_width || height != gtk_sink->display_height); gtk_sink->display_width = width; gtk_sink->display_height = height; GST_OBJECT_UNLOCK (gtk_sink); if (reconfigure) { GST_DEBUG_OBJECT (gtk_sink, "Sending reconfigure event on sinkpad."); gst_pad_push_event (GST_BASE_SINK (gtk_sink)->sinkpad, gst_event_new_reconfigure ()); } } static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink) { GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); GtkGstGLWidget *gst_widget; if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink)) return FALSE; /* After this point, gtk_sink->widget will always be set */ gst_widget = GTK_GST_GL_WIDGET (base_sink->widget); /* Track the allocation size */ g_signal_connect (gst_widget, "size-allocate", G_CALLBACK (_size_changed_cb), gtk_sink); _size_changed_cb (GTK_WIDGET (gst_widget), NULL, gtk_sink); if (!gtk_gst_gl_widget_init_winsys (gst_widget)) return FALSE; gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget); gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget); gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget); if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) return FALSE; return TRUE; } static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink) { GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); if (gtk_sink->display) { gst_object_unref (gtk_sink->display); gtk_sink->display = NULL; } if (gtk_sink->context) { gst_object_unref (gtk_sink->context); gtk_sink->context = NULL; } if (gtk_sink->gtk_context) { gst_object_unref (gtk_sink->gtk_context); gtk_sink->gtk_context = NULL; } return GST_BASE_SINK_CLASS (parent_class)->stop (bsink); } static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) { GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); GstBufferPool *pool = NULL; GstStructure *config; GstCaps *caps; guint size; gboolean need_pool; GstStructure *allocation_meta = NULL; gint display_width, display_height; if (!gtk_sink->display || !gtk_sink->context) return FALSE; gst_query_parse_allocation (query, &caps, &need_pool); if (caps == NULL) goto no_caps; if (need_pool) { GstVideoInfo info; if (!gst_video_info_from_caps (&info, caps)) goto invalid_caps; GST_DEBUG_OBJECT (gtk_sink, "create new pool"); pool = gst_gl_buffer_pool_new (gtk_sink->context); /* the normal size of a frame */ size = info.size; config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (config, caps, size, 0, 0); gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_GL_SYNC_META); if (!gst_buffer_pool_set_config (pool, config)) goto config_failed; /* we need at least 2 buffer because we hold on to the last one */ gst_query_add_allocation_pool (query, pool, size, 2, 0); gst_object_unref (pool); } GST_OBJECT_LOCK (gtk_sink); display_width = gtk_sink->display_width; display_height = gtk_sink->display_height; GST_OBJECT_UNLOCK (gtk_sink); if (display_width != 0 && display_height != 0) { GST_DEBUG_OBJECT (gtk_sink, "sending alloc query with size %dx%d", display_width, display_height); allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta", "width", G_TYPE_UINT, display_width, "height", G_TYPE_UINT, display_height, NULL); } gst_query_add_allocation_meta (query, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta); if (allocation_meta) gst_structure_free (allocation_meta); /* we also support various metadata */ gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0); if (gtk_sink->context->gl_vtable->FenceSync) gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0); return TRUE; /* ERRORS */ no_caps: { GST_DEBUG_OBJECT (bsink, "no caps specified"); return FALSE; } invalid_caps: { GST_DEBUG_OBJECT (bsink, "invalid caps specified"); return FALSE; } config_failed: { GST_DEBUG_OBJECT (bsink, "failed setting config"); return FALSE; } } static GstCaps * gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) { GstCaps *tmp = NULL; GstCaps *result = NULL; tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); if (filter) { GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT, filter); result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (tmp); } else { result = tmp; } result = gst_gl_overlay_compositor_add_caps (result); GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result); return result; }