summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.com>2018-02-10 12:49:36 +0000
committerTim-Philipp Müller <tim@centricular.com>2018-02-10 12:49:36 +0000
commita12f8df0c6933af629d7d75585a42371e59e9021 (patch)
tree0439d617d09cdcdaca044268d1418cd581e39024 /ext
parent192b447e0b0db632054f9de9f0bbc5820178c9ab (diff)
parent3bdb3a89c25ff009064ab75bde91a985147f3404 (diff)
Move gtk plugin from -bad
https://bugzilla.gnome.org/show_bug.cgi?id=754094
Diffstat (limited to 'ext')
-rw-r--r--ext/gtk/Makefile.am46
-rw-r--r--ext/gtk/gstgtkbasesink.c495
-rw-r--r--ext/gtk/gstgtkbasesink.h94
-rw-r--r--ext/gtk/gstgtkglsink.c311
-rw-r--r--ext/gtk/gstgtkglsink.h82
-rw-r--r--ext/gtk/gstgtksink.c78
-rw-r--r--ext/gtk/gstgtksink.h69
-rw-r--r--ext/gtk/gstgtkutils.c71
-rw-r--r--ext/gtk/gstgtkutils.h29
-rw-r--r--ext/gtk/gstplugin.c52
-rw-r--r--ext/gtk/gtkgstbasewidget.c497
-rw-r--r--ext/gtk/gtkgstbasewidget.h97
-rw-r--r--ext/gtk/gtkgstglwidget.c558
-rw-r--r--ext/gtk/gtkgstglwidget.h77
-rw-r--r--ext/gtk/gtkgstwidget.c191
-rw-r--r--ext/gtk/gtkgstwidget.h68
-rw-r--r--ext/gtk/meson.build54
17 files changed, 2869 insertions, 0 deletions
diff --git a/ext/gtk/Makefile.am b/ext/gtk/Makefile.am
new file mode 100644
index 000000000..057ff3f18
--- /dev/null
+++ b/ext/gtk/Makefile.am
@@ -0,0 +1,46 @@
+# preamble
+NULL =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST =
+DISTCLEANFILES =
+lib_LTLIBRARIES =
+
+# source
+sources = \
+ gtkgstbasewidget.c \
+ gtkgstbasewidget.h \
+ gtkgstwidget.c \
+ gtkgstwidget.h \
+ gstgtkbasesink.c \
+ gstgtkbasesink.h \
+ gstgtksink.c \
+ gstgtksink.h \
+ gstgtkutils.c \
+ gstgtkutils.h \
+ gstplugin.c \
+ $(NULL)
+
+libgstgtk_la_CFLAGS = \
+ $(GST_PLUGINS_BAD_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_GL_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(GTK3_CFLAGS)
+libgstgtk_la_LIBADD = \
+ $(GST_PLUGINS_BASE_LIBS) \
+ -lgstvideo-$(GST_API_VERSION) \
+ $(GST_BASE_LIBS) \
+ $(GTK3_LIBS)
+
+libgstgtk_la_SOURCES = $(sources)
+libgstgtk_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+
+if USE_GTK3_GL
+if USE_GL
+libgstgtk_la_SOURCES += gstgtkglsink.c gstgtkglsink.h gtkgstglwidget.c gtkgstglwidget.h
+libgstgtk_la_LIBADD += $(GST_GL_LIBS)
+endif
+endif
+
+plugin_LTLIBRARIES = libgstgtk.la
diff --git a/ext/gtk/gstgtkbasesink.c b/ext/gtk/gstgtkbasesink.c
new file mode 100644
index 000000000..843c97f1b
--- /dev/null
+++ b/ext/gtk/gstgtkbasesink.c
@@ -0,0 +1,495 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:gtkgstsink
+ * @title: GstGtkBaseSink
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgtkbasesink.h"
+#include "gstgtkutils.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink);
+#define GST_CAT_DEFAULT gst_debug_gtk_base_sink
+
+#define DEFAULT_FORCE_ASPECT_RATIO TRUE
+#define DEFAULT_PAR_N 0
+#define DEFAULT_PAR_D 1
+#define DEFAULT_IGNORE_ALPHA TRUE
+
+static void gst_gtk_base_sink_finalize (GObject * object);
+static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * param_spec);
+static void gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * param_spec);
+
+static gboolean gst_gtk_base_sink_start (GstBaseSink * bsink);
+static gboolean gst_gtk_base_sink_stop (GstBaseSink * bsink);
+
+static GstStateChangeReturn
+gst_gtk_base_sink_change_state (GstElement * element,
+ GstStateChange transition);
+
+static void gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end);
+static gboolean gst_gtk_base_sink_set_caps (GstBaseSink * bsink,
+ GstCaps * caps);
+static GstFlowReturn gst_gtk_base_sink_show_frame (GstVideoSink * bsink,
+ GstBuffer * buf);
+
+static void
+gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface);
+
+enum
+{
+ PROP_0,
+ PROP_WIDGET,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+ PROP_IGNORE_ALPHA,
+};
+
+#define gst_gtk_base_sink_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink,
+ GST_TYPE_VIDEO_SINK,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_gtk_base_sink_navigation_interface_init);
+ GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink,
+ "gtkbasesink", 0, "Gtk Video Sink base class"));
+
+
+static void
+gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+ GstVideoSinkClass *gstvideosink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+ gstvideosink_class = (GstVideoSinkClass *) klass;
+
+ gobject_class->set_property = gst_gtk_base_sink_set_property;
+ gobject_class->get_property = gst_gtk_base_sink_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_WIDGET,
+ g_param_spec_object ("widget", "Gtk Widget",
+ "The GtkWidget to place in the widget hierarchy "
+ "(must only be get from the GTK main thread)",
+ GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio",
+ DEFAULT_FORCE_ASPECT_RATIO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
+ gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+ "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
+ G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA,
+ g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
+ "When enabled, alpha will be ignored and converted to black",
+ DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gobject_class->finalize = gst_gtk_base_sink_finalize;
+
+ gstelement_class->change_state = gst_gtk_base_sink_change_state;
+ gstbasesink_class->set_caps = gst_gtk_base_sink_set_caps;
+ gstbasesink_class->get_times = gst_gtk_base_sink_get_times;
+ gstbasesink_class->start = gst_gtk_base_sink_start;
+ gstbasesink_class->stop = gst_gtk_base_sink_stop;
+
+ gstvideosink_class->show_frame = gst_gtk_base_sink_show_frame;
+}
+
+static void
+gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink)
+{
+ gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
+ gtk_sink->par_n = DEFAULT_PAR_N;
+ gtk_sink->par_d = DEFAULT_PAR_D;
+ gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA;
+}
+
+static void
+gst_gtk_base_sink_finalize (GObject * object)
+{
+ GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
+
+ GST_OBJECT_LOCK (gtk_sink);
+ if (gtk_sink->window && gtk_sink->window_destroy_id)
+ g_signal_handler_disconnect (gtk_sink->window, gtk_sink->window_destroy_id);
+ if (gtk_sink->widget && gtk_sink->widget_destroy_id)
+ g_signal_handler_disconnect (gtk_sink->widget, gtk_sink->widget_destroy_id);
+
+ g_clear_object (&gtk_sink->widget);
+ GST_OBJECT_UNLOCK (gtk_sink);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+widget_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
+{
+ GST_OBJECT_LOCK (gtk_sink);
+ g_clear_object (&gtk_sink->widget);
+ GST_OBJECT_UNLOCK (gtk_sink);
+}
+
+static void
+window_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
+{
+ GST_OBJECT_LOCK (gtk_sink);
+ gtk_sink->window = NULL;
+ GST_OBJECT_UNLOCK (gtk_sink);
+}
+
+static GtkGstBaseWidget *
+gst_gtk_base_sink_get_widget (GstGtkBaseSink * 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 lazily, so the application can be first */
+ if (!gtk_init_check (NULL, NULL)) {
+ GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
+ return NULL;
+ }
+
+ g_assert (GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget);
+ gtk_sink->widget = (GtkGstBaseWidget *)
+ GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget ();
+
+ gtk_sink->bind_aspect_ratio =
+ g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget,
+ "force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ gtk_sink->bind_pixel_aspect_ratio =
+ g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget,
+ "pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ gtk_sink->bind_ignore_alpha =
+ g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget,
+ "ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ /* Take the floating ref, other wise the destruction of the container will
+ * make this widget disapear possibly before we are done. */
+ gst_object_ref_sink (gtk_sink->widget);
+ gtk_sink->widget_destroy_id = g_signal_connect (gtk_sink->widget, "destroy",
+ G_CALLBACK (widget_destroy_cb), gtk_sink);
+
+ /* back pointer */
+ gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (gtk_sink->widget),
+ GST_ELEMENT (gtk_sink));
+
+ return gtk_sink->widget;
+}
+
+static void
+gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_WIDGET:
+ {
+ GObject *widget = NULL;
+
+ GST_OBJECT_LOCK (gtk_sink);
+ if (gtk_sink->widget != NULL)
+ widget = G_OBJECT (gtk_sink->widget);
+ GST_OBJECT_UNLOCK (gtk_sink);
+
+ if (!widget)
+ widget =
+ gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget,
+ gtk_sink);
+
+ g_value_set_object (value, widget);
+ break;
+ }
+ case PROP_FORCE_ASPECT_RATIO:
+ g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
+ break;
+ case PROP_IGNORE_ALPHA:
+ g_value_set_boolean (value, gtk_sink->ignore_alpha);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_FORCE_ASPECT_RATIO:
+ gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ gtk_sink->par_n = gst_value_get_fraction_numerator (value);
+ gtk_sink->par_d = gst_value_get_fraction_denominator (value);
+ break;
+ case PROP_IGNORE_ALPHA:
+ gtk_sink->ignore_alpha = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_gtk_base_sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstGtkBaseSink *sink = GST_GTK_BASE_SINK (navigation);
+ GstEvent *event;
+ GstPad *pad;
+
+ event = gst_event_new_navigation (structure);
+ pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
+
+ GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure);
+
+ if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+ if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+ /* If upstream didn't handle the event we'll post a message with it
+ * for the application in case it wants to do something with it */
+ gst_element_post_message (GST_ELEMENT_CAST (sink),
+ gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+}
+
+static void
+gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_gtk_base_sink_navigation_send_event;
+}
+
+static gboolean
+gst_gtk_base_sink_start_on_main (GstBaseSink * bsink)
+{
+ GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
+ GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink);
+ GtkWidget *toplevel;
+
+ if (gst_gtk_base_sink_get_widget (gst_sink) == NULL)
+ return FALSE;
+
+ /* After this point, gtk_sink->widget will always be set */
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_sink->widget));
+ if (!gtk_widget_is_toplevel (toplevel)) {
+ /* sanity check */
+ g_assert (klass->window_title);
+
+ /* User did not add widget its own UI, let's popup a new GtkWindow to
+ * make gst-launch-1.0 work. */
+ gst_sink->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480);
+ gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title);
+ gtk_container_add (GTK_CONTAINER (gst_sink->window), toplevel);
+ gst_sink->window_destroy_id = g_signal_connect (gst_sink->window, "destroy",
+ G_CALLBACK (window_destroy_cb), gst_sink);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gst_gtk_base_sink_start (GstBaseSink * bsink)
+{
+ return ! !gst_gtk_invoke_on_main ((GThreadFunc)
+ gst_gtk_base_sink_start_on_main, bsink);
+}
+
+static gboolean
+gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink)
+{
+ GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
+
+ if (gst_sink->window) {
+ gtk_widget_destroy (gst_sink->window);
+ gst_sink->window = NULL;
+ gst_sink->widget = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gst_gtk_base_sink_stop (GstBaseSink * bsink)
+{
+ GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
+
+ if (gst_sink->window)
+ return ! !gst_gtk_invoke_on_main ((GThreadFunc)
+ gst_gtk_base_sink_stop_on_main, bsink);
+
+ return TRUE;
+}
+
+static void
+gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
+{
+ gtk_widget_show_all (widget);
+ g_object_unref (widget);
+}
+
+static GstStateChangeReturn
+gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (element);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG_OBJECT (element, "changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ {
+ GtkWindow *window = NULL;
+
+ GST_OBJECT_LOCK (gtk_sink);
+ if (gtk_sink->window)
+ window = g_object_ref (gtk_sink->window);
+ GST_OBJECT_UNLOCK (gtk_sink);
+
+ if (window)
+ gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
+ window);
+
+ break;
+ }
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_OBJECT_LOCK (gtk_sink);
+ if (gtk_sink->widget)
+ gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL);
+ GST_OBJECT_UNLOCK (gtk_sink);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
+ GstClockTime * start, GstClockTime * end)
+{
+ GstGtkBaseSink *gtk_sink;
+
+ gtk_sink = GST_GTK_BASE_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 (&gtk_sink->v_info) > 0) {
+ *end = *start +
+ gst_util_uint64_scale_int (GST_SECOND,
+ GST_VIDEO_INFO_FPS_D (&gtk_sink->v_info),
+ GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info));
+ }
+ }
+ }
+}
+
+gboolean
+gst_gtk_base_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (bsink);
+
+ GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
+
+ if (!gst_video_info_from_caps (&gtk_sink->v_info, caps))
+ return FALSE;
+
+ GST_OBJECT_LOCK (gtk_sink);
+
+ if (gtk_sink->widget == NULL) {
+ GST_OBJECT_UNLOCK (gtk_sink);
+ GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Output widget was destroyed"), (NULL));
+ return FALSE;
+ }
+
+ if (!gtk_gst_base_widget_set_format (gtk_sink->widget, &gtk_sink->v_info)) {
+ GST_OBJECT_UNLOCK (gtk_sink);
+ return FALSE;
+ }
+ GST_OBJECT_UNLOCK (gtk_sink);
+
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+ GstGtkBaseSink *gtk_sink;
+
+ GST_TRACE ("rendering buffer:%p", buf);
+
+ gtk_sink = GST_GTK_BASE_SINK (vsink);
+
+ GST_OBJECT_LOCK (vsink);
+
+ if (gtk_sink->widget == NULL) {
+ GST_OBJECT_UNLOCK (gtk_sink);
+ GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
+ ("%s", "Output widget was destroyed"), (NULL));
+ return GST_FLOW_ERROR;
+ }
+
+ gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf);
+
+ GST_OBJECT_UNLOCK (gtk_sink);
+
+ return GST_FLOW_OK;
+}
diff --git a/ext/gtk/gstgtkbasesink.h b/ext/gtk/gstgtkbasesink.h
new file mode 100644
index 000000000..ef8c28495
--- /dev/null
+++ b/ext/gtk/gstgtkbasesink.h
@@ -0,0 +1,94 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GTK_BASE_SINK_H__
+#define __GST_GTK_BASE_SINK_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include "gtkgstbasewidget.h"
+
+#define GST_TYPE_GTK_BASE_SINK (gst_gtk_base_sink_get_type())
+#define GST_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSink))
+#define GST_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSinkClass))
+#define GST_GTK_BASE_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GTK_BASE_SINK, GstGtkBaseSinkClass))
+#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_BASE_SINK))
+#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_BASE_SINK))
+#define GST_GTK_BASE_SINK_CAST(obj) ((GstGtkBaseSink*)(obj))
+
+G_BEGIN_DECLS
+
+typedef struct _GstGtkBaseSink GstGtkBaseSink;
+typedef struct _GstGtkBaseSinkClass GstGtkBaseSinkClass;
+
+GType gst_gtk_base_sink_get_type (void);
+
+/**
+ * GstGtkBaseSink:
+ *
+ * Opaque #GstGtkBaseSink object
+ */
+struct _GstGtkBaseSink
+{
+ /* <private> */
+ GstVideoSink parent;
+
+ GstVideoInfo v_info;
+
+ GtkGstBaseWidget *widget;
+
+ /* properties */
+ gboolean force_aspect_ratio;
+ GBinding *bind_aspect_ratio;
+
+ gint par_n;
+ gint par_d;
+ GBinding *bind_pixel_aspect_ratio;
+
+ gboolean ignore_alpha;
+ GBinding *bind_ignore_alpha;
+
+ GtkWidget *window;
+ gulong widget_destroy_id;
+ gulong window_destroy_id;
+};
+
+/**
+ * GstGtkBaseSinkClass:
+ *
+ * The #GstGtkBaseSinkClass struct only contains private data
+ */
+struct _GstGtkBaseSinkClass
+{
+ GstVideoSinkClass object_class;
+
+ /* metadata */
+ const gchar *window_title;
+
+ /* virtuals */
+ GtkWidget* (*create_widget) (void);
+};
+
+G_END_DECLS
+
+#endif /* __GST_GTK_BASE_SINK_H__ */
diff --git a/ext/gtk/gstgtkglsink.c b/ext/gtk/gstgtkglsink.c
new file mode 100644
index 000000000..4439e85fb
--- /dev/null
+++ b/ext/gtk/gstgtkglsink.c
@@ -0,0 +1,311 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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: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 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 GL 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 GL Video Sink",
+ "Sink/Video", "A video sink that renders to a GtkWidget using OpenGL",
+ "Matthew Waters <matthew@centricular.com>");
+
+ 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:
+ {
+ 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);
+ 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;
+
+ gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
+ gtk_sink->display);
+
+ 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;
+ 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;
+
+ gst_query_parse_allocation (query, &caps, &need_pool);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ 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);
+
+ 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);
+ 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);
+
+ 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;
+}
diff --git a/ext/gtk/gstgtkglsink.h b/ext/gtk/gstgtkglsink.h
new file mode 100644
index 000000000..789cf33f6
--- /dev/null
+++ b/ext/gtk/gstgtkglsink.h
@@ -0,0 +1,82 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GTK_GL_SINK_H__
+#define __GST_GTK_GL_SINK_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+#include <gst/gl/gl.h>
+
+#include "gstgtkbasesink.h"
+
+
+#define GST_TYPE_GTK_GL_SINK (gst_gtk_gl_sink_get_type())
+#define GST_GTK_GL_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_GL_SINK,GstGtkGLSink))
+#define GST_GTK_GL_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_GL_SINK,GstGtkGLSinkClass))
+#define GST_IS_GTK_GL_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_GL_SINK))
+#define GST_IS_GTK_GL_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_GL_SINK))
+#define GST_GTK_GL_SINK_CAST(obj) ((GstGtkGLSink*)(obj))
+
+G_BEGIN_DECLS
+
+typedef struct _GstGtkGLSink GstGtkGLSink;
+typedef struct _GstGtkGLSinkClass GstGtkGLSinkClass;
+
+GType gst_gtk_gl_sink_get_type (void);
+
+/**
+ * GstGtkGLSink:
+ *
+ * Opaque #GstGtkGLSink object
+ */
+struct _GstGtkGLSink
+{
+ /* <private> */
+ GstGtkBaseSink parent;
+
+ GstGLDisplay *display;
+ GstGLContext *context;
+ GstGLContext *gtk_context;
+
+ GstGLUpload *upload;
+ GstBuffer *uploaded_buffer;
+
+ /* read/write with object lock */
+ gint display_width;
+ gint display_height;
+};
+
+/**
+ * GstGtkGLSinkClass:
+ *
+ * The #GstGtkGLSinkClass struct only contains private data
+ */
+struct _GstGtkGLSinkClass
+{
+ /* <private> */
+ GstGtkBaseSinkClass object_class;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GTK_GL_SINK_H__ */
diff --git a/ext/gtk/gstgtksink.c b/ext/gtk/gstgtksink.c
new file mode 100644
index 000000000..ba8ea33ca
--- /dev/null
+++ b/ext/gtk/gstgtksink.c
@@ -0,0 +1,78 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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:element-gtkgstsink
+ * @title: gtkgstsink
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gtkgstwidget.h"
+#include "gstgtksink.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gtk_sink);
+#define GST_CAT_DEFAULT gst_debug_gtk_sink
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define FORMATS "{ BGRx, BGRA }"
+#else
+#define FORMATS "{ xRGB, ARGB }"
+#endif
+
+static GstStaticPadTemplate gst_gtk_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (FORMATS))
+ );
+
+#define gst_gtk_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGtkSink, gst_gtk_sink, GST_TYPE_GTK_BASE_SINK,
+ GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_sink, "gtksink", 0,
+ "Gtk Video Sink"));
+
+static void
+gst_gtk_sink_class_init (GstGtkSinkClass * klass)
+{
+ GstElementClass *gstelement_class;
+ GstGtkBaseSinkClass *base_class;
+
+ gstelement_class = (GstElementClass *) klass;
+ base_class = (GstGtkBaseSinkClass *) klass;
+
+ base_class->create_widget = gtk_gst_widget_new;
+ base_class->window_title = "Gtk+ Cairo renderer";
+
+ gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink",
+ "Sink/Video", "A video sink that renders to a GtkWidget",
+ "Matthew Waters <matthew@centricular.com>");
+
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &gst_gtk_sink_template);
+}
+
+static void
+gst_gtk_sink_init (GstGtkSink * gtk_sink)
+{
+}
diff --git a/ext/gtk/gstgtksink.h b/ext/gtk/gstgtksink.h
new file mode 100644
index 000000000..7dad3b053
--- /dev/null
+++ b/ext/gtk/gstgtksink.h
@@ -0,0 +1,69 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GTK_SINK_H__
+#define __GST_GTK_SINK_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+
+#include "gstgtkbasesink.h"
+
+#define GST_TYPE_GTK_SINK (gst_gtk_sink_get_type())
+#define GST_GTK_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_SINK,GstGtkSink))
+#define GST_GTK_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_SINK,GstGtkSinkClass))
+#define GST_IS_GTK_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_SINK))
+#define GST_IS_GTK_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_SINK))
+#define GST_GTK_SINK_CAST(obj) ((GstGtkSink*)(obj))
+
+G_BEGIN_DECLS
+
+typedef struct _GstGtkSink GstGtkSink;
+typedef struct _GstGtkSinkClass GstGtkSinkClass;
+
+GType gst_gtk_sink_get_type (void);
+
+/**
+ * GstGtkSink:
+ *
+ * Opaque #GstGtkSink object
+ */
+struct _GstGtkSink
+{
+ /* <private> */
+ GstGtkBaseSink parent;
+};
+
+/**
+ * GstGtkSinkClass:
+ *
+ * The #GstGtkSinkClass struct only contains private data
+ */
+struct _GstGtkSinkClass
+{
+ /* <private> */
+ GstGtkBaseSinkClass object_class;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GTK_SINK_H__ */
diff --git a/ext/gtk/gstgtkutils.c b/ext/gtk/gstgtkutils.c
new file mode 100644
index 000000000..c730f0188
--- /dev/null
+++ b/ext/gtk/gstgtkutils.c
@@ -0,0 +1,71 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * 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.
+ */
+
+#include "gstgtkutils.h"
+
+struct invoke_context
+{
+ GThreadFunc func;
+ gpointer data;
+ GMutex lock;
+ GCond cond;
+ gboolean fired;
+
+ gpointer res;
+};
+
+static gboolean
+gst_gtk_invoke_func (struct invoke_context *info)
+{
+ g_mutex_lock (&info->lock);
+ info->res = info->func (info->data);
+ info->fired = TRUE;
+ g_cond_signal (&info->cond);
+ g_mutex_unlock (&info->lock);
+
+ return G_SOURCE_REMOVE;
+}
+
+gpointer
+gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
+{
+ GMainContext *main_context = g_main_context_default ();
+ struct invoke_context info;
+
+ g_mutex_init (&info.lock);
+ g_cond_init (&info.cond);
+ info.fired = FALSE;
+ info.func = func;
+ info.data = data;
+
+ g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
+ &info);
+
+ g_mutex_lock (&info.lock);
+ while (!info.fired)
+ g_cond_wait (&info.cond, &info.lock);
+ g_mutex_unlock (&info.lock);
+
+ g_mutex_clear (&info.lock);
+ g_cond_clear (&info.cond);
+
+ return info.res;
+}
diff --git a/ext/gtk/gstgtkutils.h b/ext/gtk/gstgtkutils.h
new file mode 100644
index 000000000..7584ae2c6
--- /dev/null
+++ b/ext/gtk/gstgtkutils.h
@@ -0,0 +1,29 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * 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.
+ */
+
+#ifndef __GST_GTK_UTILS_H__
+#define __GST_GTK_UTILS_H__
+
+#include <glib.h>
+
+gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
+
+#endif /* __GST_GTK_UTILS_H__ */
diff --git a/ext/gtk/gstplugin.c b/ext/gtk/gstplugin.c
new file mode 100644
index 000000000..ed275785b
--- /dev/null
+++ b/ext/gtk/gstplugin.c
@@ -0,0 +1,52 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgtksink.h"
+#if defined(HAVE_GTK3_GL)
+#include "gstgtkglsink.h"
+#endif
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_element_register (plugin, "gtksink",
+ GST_RANK_NONE, GST_TYPE_GTK_SINK)) {
+ return FALSE;
+ }
+#if defined(HAVE_GTK3_GL)
+ if (!gst_element_register (plugin, "gtkglsink",
+ GST_RANK_NONE, GST_TYPE_GTK_GL_SINK)) {
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ gtk,
+ "Gtk+ sink",
+ plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN)
diff --git a/ext/gtk/gtkgstbasewidget.c b/ext/gtk/gtkgstbasewidget.c
new file mode 100644
index 000000000..4858f2764
--- /dev/null
+++ b/ext/gtk/gtkgstbasewidget.c
@@ -0,0 +1,497 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include "gtkgstbasewidget.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
+#define GST_CAT_DEFAULT gst_debug_gtk_base_widget
+
+#define DEFAULT_FORCE_ASPECT_RATIO TRUE
+#define DEFAULT_PAR_N 0
+#define DEFAULT_PAR_D 1
+#define DEFAULT_IGNORE_ALPHA TRUE
+
+enum
+{
+ PROP_0,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+ PROP_IGNORE_ALPHA,
+};
+
+static void
+gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
+ gint * natural)
+{
+ GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
+ gint video_width = gst_widget->display_width;
+
+ if (!gst_widget->negotiated)
+ video_width = 10;
+
+ if (min)
+ *min = 1;
+ if (natural)
+ *natural = video_width;
+}
+
+static void
+gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
+ gint * natural)
+{
+ GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
+ gint video_height = gst_widget->display_height;
+
+ if (!gst_widget->negotiated)
+ video_height = 10;
+
+ if (min)
+ *min = 1;
+ if (natural)
+ *natural = video_height;
+}
+
+static void
+gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
+
+ switch (prop_id) {
+ case PROP_FORCE_ASPECT_RATIO:
+ gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ gtk_widget->par_n = gst_value_get_fraction_numerator (value);
+ gtk_widget->par_d = gst_value_get_fraction_denominator (value);
+ break;
+ case PROP_IGNORE_ALPHA:
+ gtk_widget->ignore_alpha = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
+
+ switch (prop_id) {
+ case PROP_FORCE_ASPECT_RATIO:
+ g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
+ break;
+ case PROP_PIXEL_ASPECT_RATIO:
+ gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
+ break;
+ case PROP_IGNORE_ALPHA:
+ g_value_set_boolean (value, gtk_widget->ignore_alpha);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
+{
+ gboolean ok;
+ gint width, height;
+ gint par_n, par_d;
+ gint display_par_n, display_par_d;
+
+ width = GST_VIDEO_INFO_WIDTH (info);
+ height = GST_VIDEO_INFO_HEIGHT (info);
+
+ par_n = GST_VIDEO_INFO_PAR_N (info);
+ par_d = GST_VIDEO_INFO_PAR_D (info);
+
+ if (!par_n)
+ par_n = 1;
+
+ /* get display's PAR */
+ if (widget->par_n != 0 && widget->par_d != 0) {
+ display_par_n = widget->par_n;
+ display_par_d = widget->par_d;
+ } else {
+ display_par_n = 1;
+ display_par_d = 1;
+ }
+
+
+ ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
+ &widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
+ display_par_d);
+
+ if (ok) {
+ GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
+ display_par_d);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+_apply_par (GtkGstBaseWidget * widget)
+{
+ guint display_ratio_num, display_ratio_den;
+ gint width, height;
+
+ width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
+ height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
+
+ display_ratio_num = widget->display_ratio_num;
+ display_ratio_den = widget->display_ratio_den;
+
+ if (height % display_ratio_den == 0) {
+ GST_DEBUG ("keeping video height");
+ widget->display_width = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ widget->display_height = height;
+ } else if (width % display_ratio_num == 0) {
+ GST_DEBUG ("keeping video width");
+ widget->display_width = width;
+ widget->display_height = (guint)
+ gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+ } else {
+ GST_DEBUG ("approximating while keeping video height");
+ widget->display_width = (guint)
+ gst_util_uint64_scale_int (height, display_ratio_num,
+ display_ratio_den);
+ widget->display_height = height;
+ }
+
+ GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
+}
+
+static gboolean
+_queue_draw (GtkGstBaseWidget * widget)
+{
+ GTK_GST_BASE_WIDGET_LOCK (widget);
+ widget->draw_id = 0;
+
+ if (widget->pending_resize) {
+ widget->pending_resize = FALSE;
+
+ widget->v_info = widget->pending_v_info;
+ widget->negotiated = TRUE;
+
+ _apply_par (widget);
+
+ gtk_widget_queue_resize (GTK_WIDGET (widget));
+ } else {
+ gtk_widget_queue_draw (GTK_WIDGET (widget));
+ }
+
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+
+ return G_SOURCE_REMOVE;
+}
+
+static const gchar *
+_gdk_key_to_navigation_string (guint keyval)
+{
+ /* TODO: expand */
+ switch (keyval) {
+#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
+ KEY (Up);
+ KEY (Down);
+ KEY (Left);
+ KEY (Right);
+ KEY (Home);
+ KEY (End);
+#undef KEY
+ default:
+ return NULL;
+ }
+}
+
+static gboolean
+gtk_gst_base_widget_key_event (GtkWidget * widget, GdkEventKey * event)
+{
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+ GstElement *element;
+
+ if ((element = g_weak_ref_get (&base_widget->element))) {
+ if (GST_IS_NAVIGATION (element)) {
+ const gchar *str = _gdk_key_to_navigation_string (event->keyval);
+ const gchar *key_type =
+ event->type == GDK_KEY_PRESS ? "key-press" : "key-release";
+
+ if (!str)
+ str = event->string;
+
+ gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
+ }
+ g_object_unref (element);
+ }
+
+ return FALSE;
+}
+
+static void
+_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
+ GtkAllocation * allocation, GstVideoRectangle * result)
+{
+ if (base_widget->force_aspect_ratio) {
+ GstVideoRectangle src, dst;
+
+ src.x = 0;
+ src.y = 0;
+ src.w = base_widget->display_width;
+ src.h = base_widget->display_height;
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = allocation->width;
+ dst.h = allocation->height;
+
+ gst_video_sink_center_rect (src, dst, result, TRUE);
+ } else {
+ result->x = 0;
+ result->y = 0;
+ result->w = allocation->width;
+ result->h = allocation->height;
+ }
+}
+
+static void
+_display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x,
+ gdouble y, gdouble * stream_x, gdouble * stream_y)
+{
+ gdouble stream_width, stream_height;
+ GtkAllocation allocation;
+ GstVideoRectangle result;
+
+ gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
+ _fit_stream_to_allocated_size (base_widget, &allocation, &result);
+
+ stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
+ stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
+
+ /* from display coordinates to stream coordinates */
+ if (result.w > 0)
+ *stream_x = (x - result.x) / result.w * stream_width;
+ else
+ *stream_x = 0.;
+
+ /* clip to stream size */
+ if (*stream_x < 0.)
+ *stream_x = 0.;
+ if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
+ *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
+
+ /* same for y-axis */
+ if (result.h > 0)
+ *stream_y = (y - result.y) / result.h * stream_height;
+ else
+ *stream_y = 0.;
+
+ if (*stream_y < 0.)
+ *stream_y = 0.;
+ if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
+ *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
+
+ GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
+}
+
+static gboolean
+gtk_gst_base_widget_button_event (GtkWidget * widget, GdkEventButton * event)
+{
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+ GstElement *element;
+
+ if ((element = g_weak_ref_get (&base_widget->element))) {
+ if (GST_IS_NAVIGATION (element)) {
+ const gchar *key_type =
+ event->type ==
+ GDK_BUTTON_PRESS ? "mouse-button-press" : "mouse-button-release";
+ gdouble x, y;
+
+ _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
+
+ gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
+ event->button, x, y);
+ }
+ g_object_unref (element);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_gst_base_widget_motion_event (GtkWidget * widget, GdkEventMotion * event)
+{
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+ GstElement *element;
+
+ if ((element = g_weak_ref_get (&base_widget->element))) {
+ if (GST_IS_NAVIGATION (element)) {
+ gdouble x, y;
+
+ _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
+
+ gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
+ 0, x, y);
+ }
+ g_object_unref (element);
+ }
+
+ return FALSE;
+}
+
+void
+gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
+{
+ GObjectClass *gobject_klass = (GObjectClass *) klass;
+ GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
+
+ gobject_klass->set_property = gtk_gst_base_widget_set_property;
+ gobject_klass->get_property = gtk_gst_base_widget_get_property;
+
+ g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
+ g_param_spec_boolean ("force-aspect-ratio",
+ "Force aspect ratio",
+ "When enabled, scaling will respect original aspect ratio",
+ DEFAULT_FORCE_ASPECT_RATIO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
+ gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+ "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
+ G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
+ g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
+ "When enabled, alpha will be ignored and converted to black",
+ DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
+ widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
+ widget_klass->key_press_event = gtk_gst_base_widget_key_event;
+ widget_klass->key_release_event = gtk_gst_base_widget_key_event;
+ widget_klass->button_press_event = gtk_gst_base_widget_button_event;
+ widget_klass->button_release_event = gtk_gst_base_widget_button_event;
+ widget_klass->motion_notify_event = gtk_gst_base_widget_motion_event;
+
+ GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
+ "Gtk Video Base Widget");
+}
+
+void
+gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
+{
+ int event_mask;
+
+ widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
+ widget->par_n = DEFAULT_PAR_N;
+ widget->par_d = DEFAULT_PAR_D;
+ widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
+
+ gst_video_info_init (&widget->v_info);
+ gst_video_info_init (&widget->pending_v_info);
+
+ g_weak_ref_init (&widget->element, NULL);
+ g_mutex_init (&widget->lock);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
+ event_mask = gtk_widget_get_events (GTK_WIDGET (widget));
+ event_mask |= GDK_KEY_PRESS_MASK
+ | GDK_KEY_RELEASE_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK;
+ gtk_widget_set_events (GTK_WIDGET (widget), event_mask);
+}
+
+void
+gtk_gst_base_widget_finalize (GObject * object)
+{
+ GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
+
+ gst_buffer_replace (&widget->pending_buffer, NULL);
+ gst_buffer_replace (&widget->buffer, NULL);
+ g_mutex_clear (&widget->lock);
+ g_weak_ref_clear (&widget->element);
+
+ if (widget->draw_id)
+ g_source_remove (widget->draw_id);
+}
+
+void
+gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
+ GstElement * element)
+{
+ g_weak_ref_set (&widget->element, element);
+}
+
+gboolean
+gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
+ GstVideoInfo * v_info)
+{
+ GTK_GST_BASE_WIDGET_LOCK (widget);
+
+ if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+ return TRUE;
+ }
+
+ if (!_calculate_par (widget, v_info)) {
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+ return FALSE;
+ }
+
+ widget->pending_resize = TRUE;
+ widget->pending_v_info = *v_info;
+
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+
+ return TRUE;
+}
+
+void
+gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
+{
+ /* As we have no type, this is better then no check */
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ GTK_GST_BASE_WIDGET_LOCK (widget);
+
+ gst_buffer_replace (&widget->pending_buffer, buffer);
+
+ if (!widget->draw_id) {
+ widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+ (GSourceFunc) _queue_draw, widget, NULL);
+ }
+
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+}
diff --git a/ext/gtk/gtkgstbasewidget.h b/ext/gtk/gtkgstbasewidget.h
new file mode 100644
index 000000000..13737c632
--- /dev/null
+++ b/ext/gtk/gtkgstbasewidget.h
@@ -0,0 +1,97 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GTK_GST_BASE_WIDGET_H__
+#define __GTK_GST_BASE_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#define GTK_GST_BASE_WIDGET(w) ((GtkGstBaseWidget *)(w))
+#define GTK_GST_BASE_WIDGET_CLASS(k) ((GtkGstBaseWidgetClass *)(k))
+#define GTK_GST_BASE_WIDGET_LOCK(w) g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock)
+#define GTK_GST_BASE_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock)
+
+G_BEGIN_DECLS
+
+typedef struct _GtkGstBaseWidget GtkGstBaseWidget;
+typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass;
+
+struct _GtkGstBaseWidget
+{
+ union {
+ GtkDrawingArea drawing_area;
+#if GTK_CHECK_VERSION(3, 15, 0)
+ GtkGLArea gl_area;
+#endif
+ } parent;
+
+ /* properties */
+ gboolean force_aspect_ratio;
+ gint par_n, par_d;
+ gboolean ignore_alpha;
+
+ gint display_width;
+ gint display_height;
+
+ gboolean negotiated;
+ GstBuffer *pending_buffer;
+ GstBuffer *buffer;
+ GstVideoInfo v_info;
+
+ /* resize */
+ gboolean pending_resize;
+ GstVideoInfo pending_v_info;
+ guint display_ratio_num;
+ guint display_ratio_den;
+
+ /*< private >*/
+ GMutex lock;
+ GWeakRef element;
+
+ /* Pending draw idles callback */
+ guint draw_id;
+};
+
+struct _GtkGstBaseWidgetClass
+{
+ union {
+ GtkDrawingAreaClass drawing_area_class;
+#if GTK_CHECK_VERSION(3, 15, 0)
+ GtkGLAreaClass gl_area_class;
+#endif
+ } parent_class;
+};
+
+/* For implementer */
+void gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass);
+void gtk_gst_base_widget_init (GtkGstBaseWidget * widget);
+
+void gtk_gst_base_widget_finalize (GObject * object);
+
+/* API */
+gboolean gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, GstVideoInfo * v_info);
+void gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer);
+void gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, GstElement * element);
+
+G_END_DECLS
+
+#endif /* __GTK_GST_BASE_WIDGET_H__ */
diff --git a/ext/gtk/gtkgstglwidget.c b/ext/gtk/gtkgstglwidget.c
new file mode 100644
index 000000000..76c46c9c8
--- /dev/null
+++ b/ext/gtk/gtkgstglwidget.c
@@ -0,0 +1,558 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include "gtkgstglwidget.h"
+#include "gstgtkutils.h"
+#include <gst/gl/gstglfuncs.h>
+#include <gst/video/video.h>
+
+#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <gst/gl/x11/gstgldisplay_x11.h>
+#endif
+
+#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
+#include <gdk/gdkwayland.h>
+#include <gst/gl/wayland/gstgldisplay_wayland.h>
+#endif
+
+/**
+ * SECTION:gtkgstglwidget
+ * @title: GtkGstGlWidget
+ * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers
+ * @see_also: #GtkGLArea, #GstBuffer
+ *
+ * #GtkGstGLWidget is an #GtkWidget that renders GStreamer video buffers.
+ */
+
+#define GST_CAT_DEFAULT gtk_gst_gl_widget_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA,
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0,
+ "Gtk Gst GL Widget"););
+
+#define GTK_GST_GL_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ GTK_TYPE_GST_GL_WIDGET, GtkGstGLWidgetPrivate))
+
+struct _GtkGstGLWidgetPrivate
+{
+ gboolean initted;
+ GstGLDisplay *display;
+ GdkGLContext *gdk_context;
+ GstGLContext *other_context;
+ GstGLContext *context;
+ GstGLUpload *upload;
+ GstGLShader *shader;
+ GLuint vao;
+ GLuint vertex_buffer;
+ GLint attr_position;
+ GLint attr_texture;
+ GLuint current_tex;
+ GstGLOverlayCompositor *overlay_compositor;
+};
+
+static const GLfloat vertices[] = {
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 1.0f
+};
+
+static void
+gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ const GstGLFuncs *gl = priv->context->gl_vtable;
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
+
+ /* Load the vertex position */
+ gl->VertexAttribPointer (priv->attr_position, 3, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) 0);
+
+ /* Load the texture coordinate */
+ gl->VertexAttribPointer (priv->attr_texture, 2, GL_FLOAT, GL_FALSE,
+ 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
+
+ gl->EnableVertexAttribArray (priv->attr_position);
+ gl->EnableVertexAttribArray (priv->attr_texture);
+}
+
+static void
+gtk_gst_gl_widget_unbind_buffer (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ const GstGLFuncs *gl = priv->context->gl_vtable;
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gl->DisableVertexAttribArray (priv->attr_position);
+ gl->DisableVertexAttribArray (priv->attr_texture);
+}
+
+static void
+gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ const GstGLFuncs *gl = priv->context->gl_vtable;
+ GError *error = NULL;
+
+ gst_gl_insert_debug_marker (priv->other_context, "initializing redisplay");
+ if (!(priv->shader = gst_gl_shader_new_default (priv->context, &error))) {
+ GST_ERROR ("Failed to initialize shader: %s", error->message);
+ return;
+ }
+
+ priv->attr_position =
+ gst_gl_shader_get_attribute_location (priv->shader, "a_position");
+ priv->attr_texture =
+ gst_gl_shader_get_attribute_location (priv->shader, "a_texcoord");
+
+ if (gl->GenVertexArrays) {
+ gl->GenVertexArrays (1, &priv->vao);
+ gl->BindVertexArray (priv->vao);
+ }
+
+ gl->GenBuffers (1, &priv->vertex_buffer);
+ gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
+ gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
+ GL_STATIC_DRAW);
+
+ if (gl->GenVertexArrays) {
+ gtk_gst_gl_widget_bind_buffer (gst_widget);
+ gl->BindVertexArray (0);
+ }
+
+ gl->BindBuffer (GL_ARRAY_BUFFER, 0);
+
+ priv->overlay_compositor =
+ gst_gl_overlay_compositor_new (priv->other_context);
+
+ priv->initted = TRUE;
+}
+
+static void
+_redraw_texture (GtkGstGLWidget * gst_widget, guint tex)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ const GstGLFuncs *gl = priv->context->gl_vtable;
+ const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
+
+ if (gst_widget->base.force_aspect_ratio) {
+ GstVideoRectangle src, dst, result;
+ gint widget_width, widget_height, widget_scale;
+
+ gl->ClearColor (0.0, 0.0, 0.0, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+
+ widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget);
+ widget_width = gtk_widget_get_allocated_width ((GtkWidget *) gst_widget);
+ widget_height = gtk_widget_get_allocated_height ((GtkWidget *) gst_widget);
+
+ src.x = 0;
+ src.y = 0;
+ src.w = gst_widget->base.display_width;
+ src.h = gst_widget->base.display_height;
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = widget_width * widget_scale;
+ dst.h = widget_height * widget_scale;
+
+ gst_video_sink_center_rect (src, dst, &result, TRUE);
+
+ gl->Viewport (result.x, result.y, result.w, result.h);
+ }
+
+ gst_gl_shader_use (priv->shader);
+
+ if (gl->BindVertexArray)
+ gl->BindVertexArray (priv->vao);
+ gtk_gst_gl_widget_bind_buffer (gst_widget);
+
+ gl->ActiveTexture (GL_TEXTURE0);
+ gl->BindTexture (GL_TEXTURE_2D, tex);
+ gst_gl_shader_set_uniform_1i (priv->shader, "tex", 0);
+
+ gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
+
+ if (gl->BindVertexArray)
+ gl->BindVertexArray (0);
+ gtk_gst_gl_widget_unbind_buffer (gst_widget);
+
+ gl->BindTexture (GL_TEXTURE_2D, 0);
+}
+
+static inline void
+_draw_black (GstGLContext * context)
+{
+ const GstGLFuncs *gl = context->gl_vtable;
+
+ gst_gl_insert_debug_marker (context, "no buffer. rendering black");
+ gl->ClearColor (0.0, 0.0, 0.0, 0.0);
+ gl->Clear (GL_COLOR_BUFFER_BIT);
+}
+
+static gboolean
+gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context)
+{
+ GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (widget)->priv;
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+
+ GTK_GST_BASE_WIDGET_LOCK (widget);
+
+ if (!priv->context || !priv->other_context)
+ goto done;
+
+ gst_gl_context_activate (priv->other_context, TRUE);
+
+ if (!priv->initted)
+ gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget));
+
+ if (!priv->initted || !base_widget->negotiated) {
+ _draw_black (priv->other_context);
+ goto done;
+ }
+
+ /* Upload latest buffer */
+ if (base_widget->pending_buffer) {
+ GstBuffer *buffer = base_widget->pending_buffer;
+ GstVideoFrame gl_frame;
+ GstGLSyncMeta *sync_meta;
+
+ if (!gst_video_frame_map (&gl_frame, &base_widget->v_info, buffer,
+ GST_MAP_READ | GST_MAP_GL)) {
+ _draw_black (priv->other_context);
+ goto done;
+ }
+
+ priv->current_tex = *(guint *) gl_frame.data[0];
+ gst_gl_insert_debug_marker (priv->other_context, "redrawing texture %u",
+ priv->current_tex);
+
+ gst_gl_overlay_compositor_upload_overlays (priv->overlay_compositor,
+ buffer);
+
+ sync_meta = gst_buffer_get_gl_sync_meta (buffer);
+ if (sync_meta) {
+ /* XXX: the set_sync() seems to be needed for resizing */
+ gst_gl_sync_meta_set_sync_point (sync_meta, priv->context);
+ gst_gl_sync_meta_wait (sync_meta, priv->other_context);
+ }
+
+ gst_video_frame_unmap (&gl_frame);
+
+ if (base_widget->buffer)
+ gst_buffer_unref (base_widget->buffer);
+
+ /* Keep the buffer to ensure current_tex stay valid */
+ base_widget->buffer = buffer;
+ base_widget->pending_buffer = NULL;
+ }
+
+ GST_DEBUG ("rendering buffer %p with gdk context %p",
+ base_widget->buffer, context);
+
+ _redraw_texture (GTK_GST_GL_WIDGET (widget), priv->current_tex);
+ gst_gl_overlay_compositor_draw_overlays (priv->overlay_compositor);
+
+ gst_gl_insert_debug_marker (priv->other_context, "texture %u redrawn",
+ priv->current_tex);
+
+done:
+ if (priv->other_context)
+ gst_gl_context_activate (priv->other_context, FALSE);
+
+ GTK_GST_BASE_WIDGET_UNLOCK (widget);
+ return FALSE;
+}
+
+static void
+_reset_gl (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ const GstGLFuncs *gl = priv->other_context->gl_vtable;
+
+ if (!priv->gdk_context)
+ priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
+
+ if (priv->gdk_context == NULL)
+ return;
+
+ gdk_gl_context_make_current (priv->gdk_context);
+ gst_gl_context_activate (priv->other_context, TRUE);
+
+ if (priv->vao) {
+ gl->DeleteVertexArrays (1, &priv->vao);
+ priv->vao = 0;
+ }
+
+ if (priv->vertex_buffer) {
+ gl->DeleteBuffers (1, &priv->vertex_buffer);
+ priv->vertex_buffer = 0;
+ }
+
+ if (priv->upload) {
+ gst_object_unref (priv->upload);
+ priv->upload = NULL;
+ }
+
+ if (priv->shader) {
+ gst_object_unref (priv->shader);
+ priv->shader = NULL;
+ }
+
+ if (priv->overlay_compositor)
+ gst_object_unref (priv->overlay_compositor);
+
+ gst_gl_context_activate (priv->other_context, FALSE);
+
+ gst_object_unref (priv->other_context);
+ priv->other_context = NULL;
+
+ gdk_gl_context_clear_current ();
+
+ g_object_unref (priv->gdk_context);
+ priv->gdk_context = NULL;
+}
+
+static void
+gtk_gst_gl_widget_finalize (GObject * object)
+{
+ GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (object)->priv;
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (object);
+
+ if (priv->other_context)
+ gst_gtk_invoke_on_main ((GThreadFunc) _reset_gl, base_widget);
+
+ if (priv->context)
+ gst_object_unref (priv->context);
+
+ if (priv->display)
+ gst_object_unref (priv->display);
+
+ gtk_gst_base_widget_finalize (object);
+ G_OBJECT_CLASS (gtk_gst_gl_widget_parent_class)->finalize (object);
+}
+
+static void
+gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass)
+{
+ GObjectClass *gobject_klass = (GObjectClass *) klass;
+ GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (GtkGstGLWidgetPrivate));
+ gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
+
+ gobject_klass->finalize = gtk_gst_gl_widget_finalize;
+ gl_widget_klass->render = gtk_gst_gl_widget_render;
+}
+
+static void
+gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget)
+{
+ GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (gst_widget);
+ GdkDisplay *display;
+ GtkGstGLWidgetPrivate *priv;
+
+ gtk_gst_base_widget_init (base_widget);
+
+ gst_widget->priv = priv = GTK_GST_GL_WIDGET_GET_PRIVATE (gst_widget);
+
+ display = gdk_display_get_default ();
+
+#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
+ if (GDK_IS_X11_DISPLAY (display))
+ priv->display = (GstGLDisplay *)
+ gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay
+ (display));
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (display)) {
+ struct wl_display *wayland_display =
+ gdk_wayland_display_get_wl_display (display);
+ priv->display = (GstGLDisplay *)
+ gst_gl_display_wayland_new_with_display (wayland_display);
+ }
+#endif
+
+ (void) display;
+
+ if (!priv->display)
+ priv->display = gst_gl_display_new ();
+
+ gtk_gl_area_set_has_alpha (GTK_GL_AREA (gst_widget),
+ !base_widget->ignore_alpha);
+}
+
+static void
+_get_gl_context (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ GstGLPlatform platform;
+ GstGLAPI gl_api;
+ guintptr gl_handle;
+
+ gtk_widget_realize (GTK_WIDGET (gst_widget));
+
+ if (priv->other_context)
+ gst_object_unref (priv->other_context);
+ priv->other_context = NULL;
+
+ if (priv->gdk_context)
+ g_object_unref (priv->gdk_context);
+
+ priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
+ if (priv->gdk_context == NULL) {
+ GError *error = gtk_gl_area_get_error (GTK_GL_AREA (gst_widget));
+
+ GST_ERROR_OBJECT (gst_widget, "Error creating GdkGLContext : %s",
+ error ? error->message : "No error set by Gdk");
+ g_clear_error (&error);
+ return;
+ }
+
+ g_object_ref (priv->gdk_context);
+
+ gdk_gl_context_make_current (priv->gdk_context);
+
+#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
+ if (GST_IS_GL_DISPLAY_X11 (priv->display)) {
+ platform = GST_GL_PLATFORM_GLX;
+ gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
+ gl_handle = gst_gl_context_get_current_gl_context (platform);
+ if (gl_handle)
+ priv->other_context =
+ gst_gl_context_new_wrapped (priv->display, gl_handle,
+ platform, gl_api);
+ }
+#endif
+#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
+ if (GST_IS_GL_DISPLAY_WAYLAND (priv->display)) {
+ platform = GST_GL_PLATFORM_EGL;
+ gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
+ gl_handle = gst_gl_context_get_current_gl_context (platform);
+ if (gl_handle)
+ priv->other_context =
+ gst_gl_context_new_wrapped (priv->display, gl_handle,
+ platform, gl_api);
+ }
+#endif
+
+ (void) platform;
+ (void) gl_api;
+ (void) gl_handle;
+
+ if (priv->other_context) {
+ GError *error = NULL;
+
+ gst_gl_context_activate (priv->other_context, TRUE);
+ if (!gst_gl_context_fill_info (priv->other_context, &error)) {
+ GST_ERROR ("failed to retrieve gdk context info: %s", error->message);
+ g_clear_error (&error);
+ g_object_unref (priv->other_context);
+ priv->other_context = NULL;
+ } else {
+ gst_gl_context_activate (priv->other_context, FALSE);
+ }
+ }
+}
+
+GtkWidget *
+gtk_gst_gl_widget_new (void)
+{
+ return (GtkWidget *) g_object_new (GTK_TYPE_GST_GL_WIDGET, NULL);
+}
+
+gboolean
+gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * gst_widget)
+{
+ GtkGstGLWidgetPrivate *priv = gst_widget->priv;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GTK_IS_GST_GL_WIDGET (gst_widget), FALSE);
+ g_return_val_if_fail (priv->display != NULL, FALSE);
+
+ GTK_GST_BASE_WIDGET_LOCK (gst_widget);
+
+ if (priv->display && priv->gdk_context && priv->other_context) {
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ return TRUE;
+ }
+
+ if (!priv->other_context) {
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ gst_gtk_invoke_on_main ((GThreadFunc) _get_gl_context, gst_widget);
+ GTK_GST_BASE_WIDGET_LOCK (gst_widget);
+ }
+
+ if (!GST_IS_GL_CONTEXT (priv->other_context)) {
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ return FALSE;
+ }
+
+ GST_OBJECT_LOCK (priv->display);
+ if (!gst_gl_display_create_context (priv->display, priv->other_context,
+ &priv->context, &error)) {
+ g_clear_error (&error);
+ GST_OBJECT_UNLOCK (priv->display);
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ return FALSE;
+ }
+ gst_gl_display_add_context (priv->display, priv->context);
+ GST_OBJECT_UNLOCK (priv->display);
+
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ return TRUE;
+}
+
+GstGLContext *
+gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * gst_widget)
+{
+ if (!gst_widget->priv->other_context)
+ return NULL;
+
+ return gst_object_ref (gst_widget->priv->other_context);
+}
+
+GstGLContext *
+gtk_gst_gl_widget_get_context (GtkGstGLWidget * gst_widget)
+{
+ if (!gst_widget->priv->context)
+ return NULL;
+
+ return gst_object_ref (gst_widget->priv->context);
+}
+
+GstGLDisplay *
+gtk_gst_gl_widget_get_display (GtkGstGLWidget * gst_widget)
+{
+ if (!gst_widget->priv->display)
+ return NULL;
+
+ return gst_object_ref (gst_widget->priv->display);
+}
diff --git a/ext/gtk/gtkgstglwidget.h b/ext/gtk/gtkgstglwidget.h
new file mode 100644
index 000000000..7f055c481
--- /dev/null
+++ b/ext/gtk/gtkgstglwidget.h
@@ -0,0 +1,77 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GTK_GST_GL_WIDGET_H__
+#define __GTK_GST_GL_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/gl/gl.h>
+
+#include "gtkgstbasewidget.h"
+
+G_BEGIN_DECLS
+
+GType gtk_gst_gl_widget_get_type (void);
+#define GTK_TYPE_GST_GL_WIDGET (gtk_gst_gl_widget_get_type())
+#define GTK_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidget))
+#define GTK_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidgetClass))
+#define GTK_IS_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_GL_WIDGET))
+#define GTK_IS_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_GL_WIDGET))
+#define GTK_GST_GL_WIDGET_CAST(obj) ((GtkGstGLWidget*)(obj))
+
+typedef struct _GtkGstGLWidget GtkGstGLWidget;
+typedef struct _GtkGstGLWidgetClass GtkGstGLWidgetClass;
+typedef struct _GtkGstGLWidgetPrivate GtkGstGLWidgetPrivate;
+
+/**
+ * GtkGstGLWidget:
+ *
+ * Opaque #GtkGstGLWidget object
+ */
+struct _GtkGstGLWidget
+{
+ /* <private> */
+ GtkGstBaseWidget base;
+
+ GtkGstGLWidgetPrivate *priv;
+};
+
+/**
+ * GtkGstGLWidgetClass:
+ *
+ * The #GtkGstGLWidgetClass struct only contains private data
+ */
+struct _GtkGstGLWidgetClass
+{
+ /* <private> */
+ GtkGstBaseWidgetClass base_class;
+};
+
+GtkWidget * gtk_gst_gl_widget_new (void);
+
+gboolean gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * widget);
+GstGLDisplay * gtk_gst_gl_widget_get_display (GtkGstGLWidget * widget);
+GstGLContext * gtk_gst_gl_widget_get_context (GtkGstGLWidget * widget);
+GstGLContext * gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * widget);
+
+G_END_DECLS
+
+#endif /* __GTK_GST_GL_WIDGET_H__ */
diff --git a/ext/gtk/gtkgstwidget.c b/ext/gtk/gtkgstwidget.c
new file mode 100644
index 000000000..a936210ba
--- /dev/null
+++ b/ext/gtk/gtkgstwidget.c
@@ -0,0 +1,191 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include "gtkgstwidget.h"
+#include <gst/video/video.h>
+
+/**
+ * SECTION:gtkgstwidget
+ * @title: GtkGstWidget
+ * @short_description: a #GtkWidget that renders GStreamer video #GstBuffers
+ * @see_also: #GtkDrawingArea, #GstBuffer
+ *
+ * #GtkGstWidget is an #GtkWidget that renders GStreamer video buffers.
+ */
+
+G_DEFINE_TYPE (GtkGstWidget, gtk_gst_widget, GTK_TYPE_DRAWING_AREA);
+
+static gboolean
+gtk_gst_widget_draw (GtkWidget * widget, cairo_t * cr)
+{
+ GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
+ guint widget_width, widget_height;
+ cairo_surface_t *surface;
+ GstVideoFrame frame;
+
+ widget_width = gtk_widget_get_allocated_width (widget);
+ widget_height = gtk_widget_get_allocated_height (widget);
+
+ GTK_GST_BASE_WIDGET_LOCK (gst_widget);
+
+ /* There is not much to optimize in term of redisplay, so simply swap the
+ * pending_buffer with the active buffer */
+ if (gst_widget->pending_buffer) {
+ if (gst_widget->buffer)
+ gst_buffer_unref (gst_widget->buffer);
+ gst_widget->buffer = gst_widget->pending_buffer;
+ gst_widget->pending_buffer = NULL;
+ }
+
+ /* failed to map the video frame */
+ if (gst_widget->negotiated && gst_widget->buffer
+ && gst_video_frame_map (&frame, &gst_widget->v_info,
+ gst_widget->buffer, GST_MAP_READ)) {
+ gdouble scale_x = (gdouble) widget_width / gst_widget->display_width;
+ gdouble scale_y = (gdouble) widget_height / gst_widget->display_height;
+ GstVideoRectangle result;
+ cairo_format_t format;
+
+ gst_widget->v_info = frame.info;
+ if (frame.info.finfo->format == GST_VIDEO_FORMAT_ARGB ||
+ frame.info.finfo->format == GST_VIDEO_FORMAT_BGRA) {
+ format = CAIRO_FORMAT_ARGB32;
+ } else {
+ format = CAIRO_FORMAT_RGB24;
+ }
+
+ surface = cairo_image_surface_create_for_data (frame.data[0],
+ format, frame.info.width, frame.info.height, frame.info.stride[0]);
+
+ if (gst_widget->force_aspect_ratio) {
+ GstVideoRectangle src, dst;
+
+ src.x = 0;
+ src.y = 0;
+ src.w = gst_widget->display_width;
+ src.h = gst_widget->display_height;
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = widget_width;
+ dst.h = widget_height;
+
+ gst_video_sink_center_rect (src, dst, &result, TRUE);
+
+ scale_x = scale_y = MIN (scale_x, scale_y);
+ } else {
+ result.x = 0;
+ result.y = 0;
+ result.w = widget_width;
+ result.h = widget_height;
+ }
+
+ if (gst_widget->ignore_alpha) {
+ GdkRGBA color = { 0.0, 0.0, 0.0, 1.0 };
+
+ gdk_cairo_set_source_rgba (cr, &color);
+ if (result.x > 0) {
+ cairo_rectangle (cr, 0, 0, result.x, widget_height);
+ cairo_fill (cr);
+ }
+ if (result.y > 0) {
+ cairo_rectangle (cr, 0, 0, widget_width, result.y);
+ cairo_fill (cr);
+ }
+ if (result.w < widget_width) {
+ cairo_rectangle (cr, result.x + result.w, 0, widget_width - result.w,
+ widget_height);
+ cairo_fill (cr);
+ }
+ if (result.h < widget_height) {
+ cairo_rectangle (cr, 0, result.y + result.h, widget_width,
+ widget_height - result.h);
+ cairo_fill (cr);
+ }
+ }
+
+ scale_x *= (gdouble) gst_widget->display_width / (gdouble) frame.info.width;
+ scale_y *=
+ (gdouble) gst_widget->display_height / (gdouble) frame.info.height;
+
+ cairo_translate (cr, result.x, result.y);
+ cairo_scale (cr, scale_x, scale_y);
+ cairo_rectangle (cr, 0, 0, result.w, result.h);
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+
+ cairo_surface_destroy (surface);
+
+ gst_video_frame_unmap (&frame);
+ } else {
+ GdkRGBA color;
+
+ if (gst_widget->ignore_alpha) {
+ color.red = color.blue = color.green = 0.0;
+ color.alpha = 1.0;
+ } else {
+ gtk_style_context_get_color (gtk_widget_get_style_context (widget),
+ GTK_STATE_FLAG_NORMAL, &color);
+ }
+ gdk_cairo_set_source_rgba (cr, &color);
+ cairo_rectangle (cr, 0, 0, widget_width, widget_height);
+ cairo_fill (cr);
+ }
+
+ GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
+ return FALSE;
+}
+
+static void
+gtk_gst_widget_finalize (GObject * object)
+{
+ gtk_gst_base_widget_finalize (object);
+
+ G_OBJECT_CLASS (gtk_gst_widget_parent_class)->finalize (object);
+}
+
+static void
+gtk_gst_widget_class_init (GtkGstWidgetClass * klass)
+{
+ GObjectClass *gobject_klass = (GObjectClass *) klass;
+ GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
+
+ gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
+ gobject_klass->finalize = gtk_gst_widget_finalize;
+ widget_klass->draw = gtk_gst_widget_draw;
+}
+
+static void
+gtk_gst_widget_init (GtkGstWidget * widget)
+{
+ gtk_gst_base_widget_init (GTK_GST_BASE_WIDGET (widget));
+}
+
+GtkWidget *
+gtk_gst_widget_new (void)
+{
+ return (GtkWidget *) g_object_new (GTK_TYPE_GST_WIDGET, NULL);
+}
diff --git a/ext/gtk/gtkgstwidget.h b/ext/gtk/gtkgstwidget.h
new file mode 100644
index 000000000..427eebac4
--- /dev/null
+++ b/ext/gtk/gtkgstwidget.h
@@ -0,0 +1,68 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * 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.
+ */
+
+#ifndef __GTK_GST_WIDGET_H__
+#define __GTK_GST_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+
+#include "gtkgstbasewidget.h"
+
+G_BEGIN_DECLS
+
+GType gtk_gst_widget_get_type (void);
+#define GTK_TYPE_GST_WIDGET (gtk_gst_widget_get_type())
+#define GTK_GST_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_WIDGET,GtkGstWidget))
+#define GTK_GST_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_WIDGET,GtkGstWidgetClass))
+#define GTK_IS_GST_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_WIDGET))
+#define GST_IS_GST_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_WIDGET))
+#define GTK_GST_WIDGET_CAST(obj) ((GtkGstWidget*)(obj))
+
+typedef struct _GtkGstWidget GtkGstWidget;
+typedef struct _GtkGstWidgetClass GtkGstWidgetClass;
+
+/**
+ * GtkGstWidget:
+ *
+ * Opaque #GtkGstWidget object
+ */
+struct _GtkGstWidget
+{
+ /* <private> */
+ GtkGstBaseWidget base;
+};
+
+/**
+ * GtkGstWidgetClass:
+ *
+ * The #GtkGstWidgetClass struct only contains private data
+ */
+struct _GtkGstWidgetClass
+{
+ /* <private> */
+ GtkGstBaseWidgetClass base_class;
+};
+
+GtkWidget * gtk_gst_widget_new (void);
+
+G_END_DECLS
+
+#endif /* __GTK_GST_WIDGET_H__ */
diff --git a/ext/gtk/meson.build b/ext/gtk/meson.build
new file mode 100644
index 000000000..324bfba89
--- /dev/null
+++ b/ext/gtk/meson.build
@@ -0,0 +1,54 @@
+gtk_sources = [
+ 'gstgtkbasesink.c',
+ 'gstgtksink.c',
+ 'gstgtkutils.c',
+ 'gstplugin.c',
+ 'gtkgstbasewidget.c',
+ 'gtkgstwidget.c',
+]
+
+gtk_defines = []
+optional_deps = []
+
+gtk_dep = dependency('gtk+-3.0', required : false)
+if gtk_dep.found()
+ if build_gstgl and gstgl_dep.found() and gtk_dep.version().version_compare('>=3.15.0')
+ have_gtk3_gl_windowing = false
+
+ if gst_gl_have_window_x11 and gst_gl_have_platform_glx
+ gtk_x11_dep = dependency('gtk+-x11-3.0', required : false)
+ if gtk_x11_dep.found()
+ optional_deps += gtk_x11_dep
+ have_gtk3_gl_windowing = true
+ endif
+ endif
+
+ if gst_gl_have_window_wayland and gst_gl_have_platform_egl
+ gtk_wayland_dep = dependency('gtk+-wayland-3.0', required : false)
+ if gtk_wayland_dep.found()
+ optional_deps += gtk_wayland_dep
+ have_gtk3_gl_windowing = true
+ endif
+ endif
+
+ if have_gtk3_gl_windowing
+ gtk_sources += [
+ 'gstgtkglsink.c',
+ 'gtkgstglwidget.c',
+ ]
+ optional_deps += gstgl_dep
+ gtk_defines += ['-DGST_USE_UNSTABLE_API', '-DHAVE_GTK3_GL']
+ endif
+ endif
+
+ gstgtk = library('gstgtk',
+ gtk_sources,
+ c_args : gst_plugins_bad_args + gtk_defines,
+ link_args : noseh_link_args,
+ include_directories : [configinc],
+ dependencies : [gtk_dep, gstvideo_dep, gstbase_dep, libm] + optional_deps,
+ install : true,
+ install_dir : plugins_install_dir,
+ )
+endif
+