*/
/**
- * SECTION:gstgtkglsink
- *
+ * SECTION:element-gtkglsink
+ * @title: gtkglsink
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#include <gst/gl/gstglfuncs.h>
+
#include "gstgtkglsink.h"
+#include "gtkgstglwidget.h"
GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink);
#define GST_CAT_DEFAULT gst_debug_gtk_gl_sink
-static void gst_gtk_gl_sink_finalize (GObject * object);
-static void gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * param_spec);
-static void gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
- GValue * value, GParamSpec * param_spec);
-
+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 GstStateChangeReturn
-gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition);
-
-static void gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
- GstClockTime * start, GstClockTime * end);
-static gboolean gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
-static GstFlowReturn gst_gtk_gl_sink_show_frame (GstVideoSink * bsink,
- GstBuffer * buf);
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 void gst_gtk_gl_sink_finalize (GObject * object);
static GstStaticPadTemplate gst_gtk_gl_sink_template =
-GST_STATIC_PAD_TEMPLATE ("sink",
+ 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")));
-
-enum
-{
- ARG_0,
- PROP_WIDGET
-};
-
-enum
-{
- SIGNAL_0,
- LAST_SIGNAL
-};
+ (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_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
- "gtkglsink", 0, "Gtk Video Sink"));
+ GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
+ "gtkglsink", 0, "Gtk GL Video Sink"));
static void
gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseSinkClass *gstbasesink_class;
- GstVideoSinkClass *gstvideosink_class;
+ GstGtkBaseSinkClass *gstgtkbasesink_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstbasesink_class = (GstBaseSinkClass *) klass;
- gstvideosink_class = (GstVideoSinkClass *) klass;
-
- gobject_class->set_property = gst_gtk_gl_sink_set_property;
- gobject_class->get_property = gst_gtk_gl_sink_get_property;
-
- gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink",
- "Sink/Video", "A video sink the renders to a GtkWidget",
- "Matthew Waters <matthew@centricular.com>");
-
- g_object_class_install_property (gobject_class, PROP_WIDGET,
- g_param_spec_object ("widget", "Gtk Widget",
- "The GtkWidget to place in the widget heirachy",
- GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&gst_gtk_gl_sink_template));
+ gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass;
gobject_class->finalize = gst_gtk_gl_sink_finalize;
- gstelement_class->change_state = gst_gtk_gl_sink_change_state;
gstbasesink_class->query = gst_gtk_gl_sink_query;
- gstbasesink_class->set_caps = gst_gtk_gl_sink_set_caps;
- gstbasesink_class->get_times = gst_gtk_gl_sink_get_times;
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;
- gstvideosink_class->show_frame = gst_gtk_gl_sink_show_frame;
-}
-
-static void
-gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
-{
-}
-
-static void
-gst_gtk_gl_sink_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec)
-{
- switch (prop_id) {
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-gst_gtk_gl_sink_finalize (GObject * object)
-{
- GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);;
-
- g_object_unref (gtk_sink->widget);
+ gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new;
+ gstgtkbasesink_class->window_title = "Gtk+ GL renderer";
- G_OBJECT_CLASS (parent_class)->finalize (object);
-}
-
-static GtkGstGLWidget *
-gst_gtk_gl_sink_get_widget (GstGtkGLSink * gtk_sink)
-{
- if (gtk_sink->widget != NULL)
- return gtk_sink->widget;
-
- /* Ensure GTK is initialized, this has no side effect if it was already
- * initialized. Also, we do that lazylli, so the application can be first */
- if (!gtk_init_check (NULL, NULL)) {
- GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
- return NULL;
- }
-
- gtk_sink->widget = (GtkGstGLWidget *) gtk_gst_gl_widget_new ();
-
- /* Take the floating ref, otherwise the destruction of the container will
- * make this widget disapear possibly before we are done. */
- gst_object_ref_sink (gtk_sink->widget);
+ gst_element_class_set_metadata (gstelement_class, "Gtk GL Video Sink",
+ "Sink/Video", "A video sink that renders to a GtkWidget using OpenGL",
+ "Matthew Waters <matthew@centricular.com>");
- return gtk_sink->widget;
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &gst_gtk_gl_sink_template);
}
static void
-gst_gtk_gl_sink_get_property (GObject * object, guint prop_id,
- GValue * value, GParamSpec * pspec)
+gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
{
- GstGtkGLSink *gtk_sink;
-
- gtk_sink = GST_GTK_GL_SINK (object);
-
- switch (prop_id) {
- case PROP_WIDGET:
- g_value_set_object (value, gst_gtk_gl_sink_get_widget (gtk_sink));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
}
static gboolean
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
{
- const gchar *context_type;
- GstContext *context, *old_context;
- gboolean ret;
-
- ret = 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);
-
- ret = gtk_sink->context != NULL;
- }
- GST_LOG_OBJECT (gtk_sink, "context query of type %s %i", context_type,
- ret);
-
- if (ret)
- return ret;
+ if (gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
+ gtk_sink->display, gtk_sink->context, gtk_sink->gtk_context))
+ return TRUE;
+ break;
}
default:
res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
return res;
}
-static gboolean
-gst_gtk_gl_sink_stop (GstBaseSink * bsink)
+static void
+_size_changed_cb (GtkWidget * widget, GdkRectangle * rectangle,
+ GstGtkGLSink * gtk_sink)
{
- return TRUE;
+ 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 GstStateChangeReturn
-gst_gtk_gl_sink_change_state (GstElement * element, GstStateChange transition)
+static void
+destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink)
{
- GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (element);
- GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
-
- GST_DEBUG ("changing state: %s => %s",
- gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
- gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
-
- switch (transition) {
- case GST_STATE_CHANGE_NULL_TO_READY:
- if (gst_gtk_gl_sink_get_widget (gtk_sink) == NULL)
- return GST_STATE_CHANGE_FAILURE;
-
- /* After this point, gtk_sink->widget will always be set */
+ if (gtk_sink->size_allocate_sig_handler) {
+ g_signal_handler_disconnect (widget, gtk_sink->size_allocate_sig_handler);
+ gtk_sink->size_allocate_sig_handler = 0;
+ }
- if (!gtk_widget_get_parent (GTK_WIDGET (gtk_sink->widget))) {
- GST_ERROR_OBJECT (gtk_sink,
- "gtkglsink widget need to be parented to work.");
- return GST_STATE_CHANGE_FAILURE;
- }
+ if (gtk_sink->widget_destroy_sig_handler) {
+ g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler);
+ gtk_sink->widget_destroy_sig_handler = 0;
+ }
+}
- if (!gtk_gst_gl_widget_init_winsys (gtk_sink->widget))
- return GST_STATE_CHANGE_FAILURE;
+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;
- gtk_sink->display = gtk_gst_gl_widget_get_display (gtk_sink->widget);
- gtk_sink->context = gtk_gst_gl_widget_get_context (gtk_sink->widget);
- gtk_sink->gtk_context =
- gtk_gst_gl_widget_get_gtk_context (gtk_sink->widget);
+ if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink))
+ return FALSE;
- if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context)
- return GST_STATE_CHANGE_FAILURE;
- break;
- case GST_STATE_CHANGE_READY_TO_PAUSED:
- break;
- case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
- break;
- default:
- break;
- }
+ /* After this point, gtk_sink->widget will always be set */
+ gst_widget = GTK_GST_GL_WIDGET (base_sink->widget);
- ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
- if (ret == GST_STATE_CHANGE_FAILURE)
- return ret;
+ /* Track the allocation size */
+ gtk_sink->size_allocate_sig_handler =
+ g_signal_connect (gst_widget, "size-allocate",
+ G_CALLBACK (_size_changed_cb), gtk_sink);
- switch (transition) {
- case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
- break;
- case GST_STATE_CHANGE_PAUSED_TO_READY:
- gtk_gst_gl_widget_set_buffer (gtk_sink->widget, NULL);
- break;
- case GST_STATE_CHANGE_READY_TO_NULL:
- 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;
- }
- break;
- default:
- break;
- }
+ gtk_sink->widget_destroy_sig_handler =
+ g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb),
+ gtk_sink);
- return ret;
-}
+ _size_changed_cb (GTK_WIDGET (gst_widget), NULL, gtk_sink);
-static void
-gst_gtk_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
- GstClockTime * start, GstClockTime * end)
-{
- GstGtkGLSink *gtk_sink;
-
- gtk_sink = GST_GTK_GL_SINK (bsink);
-
- if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
- *start = GST_BUFFER_TIMESTAMP (buf);
- if (GST_BUFFER_DURATION_IS_VALID (buf))
- *end = *start + GST_BUFFER_DURATION (buf);
- else {
- if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) {
- *end = *start +
- gst_util_uint64_scale_int (GST_SECOND,
- GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
- GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
- }
- }
+ if (!gtk_gst_gl_widget_init_winsys (gst_widget)) {
+ GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to initialize OpenGL with Gtk"), (NULL));
+ return FALSE;
}
-}
-gboolean
-gst_gtk_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
-{
- GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
+ 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);
- GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
-
- if (!gst_video_info_from_caps (>k_sink->v_info, caps))
+ if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) {
+ GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
+ "Failed to retrieve OpenGL context from Gtk"), (NULL));
return FALSE;
+ }
- if (!gtk_gst_gl_widget_set_caps (gtk_sink->widget, caps))
- return FALSE;
+ gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
+ gtk_sink->display);
return TRUE;
}
-static GstFlowReturn
-gst_gtk_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+static gboolean
+gst_gtk_gl_sink_stop (GstBaseSink * bsink)
{
- GstGtkGLSink *gtk_sink;
+ GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
- GST_TRACE ("rendering buffer:%p", buf);
+ if (gtk_sink->display) {
+ gst_object_unref (gtk_sink->display);
+ gtk_sink->display = NULL;
+ }
- gtk_sink = GST_GTK_GL_SINK (vsink);
+ if (gtk_sink->context) {
+ gst_object_unref (gtk_sink->context);
+ gtk_sink->context = NULL;
+ }
- gtk_gst_gl_widget_set_buffer (gtk_sink->widget, buf);
+ if (gtk_sink->gtk_context) {
+ gst_object_unref (gtk_sink->gtk_context);
+ gtk_sink->gtk_context = NULL;
+ }
- return GST_FLOW_OK;
+ 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;
+ GstBufferPool *pool = NULL;
GstStructure *config;
GstCaps *caps;
+ GstVideoInfo info;
guint size;
gboolean need_pool;
+ GstStructure *allocation_meta = NULL;
+ gint display_width, display_height;
if (!gtk_sink->display || !gtk_sink->context)
return FALSE;
if (caps == NULL)
goto no_caps;
- if ((pool = gtk_sink->pool))
- gst_object_ref (pool);
-
- if (pool != NULL) {
- GstCaps *pcaps;
+ if (!gst_video_info_from_caps (&info, caps))
+ goto invalid_caps;
- /* we had a pool, check caps */
- GST_DEBUG_OBJECT (gtk_sink, "check existing pool caps");
- config = gst_buffer_pool_get_config (pool);
- gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
-
- if (!gst_caps_is_equal (caps, pcaps)) {
- GST_DEBUG_OBJECT (gtk_sink, "pool has different caps");
- /* different caps, we can't use this pool */
- gst_object_unref (pool);
- pool = NULL;
- }
- gst_structure_free (config);
- }
-
- if (pool == NULL && need_pool) {
- GstVideoInfo info;
-
- if (!gst_video_info_from_caps (&info, caps))
- goto invalid_caps;
+ /* the normal size of a frame */
+ size = info.size;
+ if (need_pool) {
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 */
- if (pool) {
- gst_query_add_allocation_pool (query, pool, size, 2, 0);
+ gst_query_add_allocation_pool (query, pool, size, 2, 0);
+ if (pool)
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);
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;
+}
+
+static void
+gst_gtk_gl_sink_finalize (GObject * object)
+{
+ GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
+ GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object);
+
+ if (gtk_sink->size_allocate_sig_handler) {
+ g_signal_handler_disconnect (base_sink->widget,
+ gtk_sink->size_allocate_sig_handler);
+ gtk_sink->size_allocate_sig_handler = 0;
+ }
+
+ if (gtk_sink->widget_destroy_sig_handler) {
+ g_signal_handler_disconnect (base_sink->widget,
+ gtk_sink->widget_destroy_sig_handler);
+ gtk_sink->widget_destroy_sig_handler = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}