diff options
author | Tim-Philipp Müller <tim@centricular.com> | 2018-02-10 12:49:36 +0000 |
---|---|---|
committer | Tim-Philipp Müller <tim@centricular.com> | 2018-02-10 12:49:36 +0000 |
commit | a12f8df0c6933af629d7d75585a42371e59e9021 (patch) | |
tree | 0439d617d09cdcdaca044268d1418cd581e39024 | |
parent | 192b447e0b0db632054f9de9f0bbc5820178c9ab (diff) | |
parent | 3bdb3a89c25ff009064ab75bde91a985147f3404 (diff) |
Move gtk plugin from -bad
https://bugzilla.gnome.org/show_bug.cgi?id=754094
-rw-r--r-- | ext/gtk/Makefile.am | 46 | ||||
-rw-r--r-- | ext/gtk/gstgtkbasesink.c | 495 | ||||
-rw-r--r-- | ext/gtk/gstgtkbasesink.h | 94 | ||||
-rw-r--r-- | ext/gtk/gstgtkglsink.c | 311 | ||||
-rw-r--r-- | ext/gtk/gstgtkglsink.h | 82 | ||||
-rw-r--r-- | ext/gtk/gstgtksink.c | 78 | ||||
-rw-r--r-- | ext/gtk/gstgtksink.h | 69 | ||||
-rw-r--r-- | ext/gtk/gstgtkutils.c | 71 | ||||
-rw-r--r-- | ext/gtk/gstgtkutils.h | 29 | ||||
-rw-r--r-- | ext/gtk/gstplugin.c | 52 | ||||
-rw-r--r-- | ext/gtk/gtkgstbasewidget.c | 497 | ||||
-rw-r--r-- | ext/gtk/gtkgstbasewidget.h | 97 | ||||
-rw-r--r-- | ext/gtk/gtkgstglwidget.c | 558 | ||||
-rw-r--r-- | ext/gtk/gtkgstglwidget.h | 77 | ||||
-rw-r--r-- | ext/gtk/gtkgstwidget.c | 191 | ||||
-rw-r--r-- | ext/gtk/gtkgstwidget.h | 68 | ||||
-rw-r--r-- | ext/gtk/meson.build | 54 | ||||
-rw-r--r-- | tests/examples/gtk/Makefile.am | 41 | ||||
-rw-r--r-- | tests/examples/gtk/glliveshader.c | 350 | ||||
-rw-r--r-- | tests/examples/gtk/gtkglsink.c | 200 | ||||
-rw-r--r-- | tests/examples/gtk/gtksink.c | 185 |
21 files changed, 3645 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 (>k_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 (>k_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 (>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)); + } + } + } +} + +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 (>k_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, >k_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 + diff --git a/tests/examples/gtk/Makefile.am b/tests/examples/gtk/Makefile.am new file mode 100644 index 000000000..cd7962d52 --- /dev/null +++ b/tests/examples/gtk/Makefile.am @@ -0,0 +1,41 @@ + +noinst_PROGRAMS = gtksink + +gtksink_SOURCES = gtksink.c +gtksink_CFLAGS = $(GTK3_CFLAGS) \ + $(GST_CFLAGS) +gtksink_LDADD = $(GTK3_LIBS) \ + $(GST_LIBS) + +if USE_GTK3_GL +if USE_GL +noinst_PROGRAMS += gtkglsink glliveshader + +gtkglsink_SOURCES = gtkglsink.c +gtkglsink_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_GL_CFLAGS) \ + $(GST_CFLAGS) \ + $(GL_CFLAGS) \ + $(GTK3_CFLAGS) +gtkglsink_LDADD = \ + $(GST_GL_LIBS) \ + $(GST_LIBS) \ + $(GTK3_LIBS) \ + $(X11_LIBS) + +glliveshader_SOURCES = glliveshader.c +glliveshader_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_GL_CFLAGS) \ + $(GST_CFLAGS) \ + $(GTK3_CFLAGS) +glliveshader_LDADD = \ + $(GST_GL_LIBS) \ + $(GST_LIBS) \ + $(GTK3_LIBS) \ + $(X11_LIBS) +endif +endif diff --git a/tests/examples/gtk/glliveshader.c b/tests/examples/gtk/glliveshader.c new file mode 100644 index 000000000..03e745ee3 --- /dev/null +++ b/tests/examples/gtk/glliveshader.c @@ -0,0 +1,350 @@ +/* + * 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. + */ + +#include <gst/gst.h> +#include <gst/gl/gl.h> +#include <gst/gl/gstglfuncs.h> +#include <gtk/gtk.h> +#if GST_GL_HAVE_WINDOW_X11 +#include <X11/Xlib.h> +#endif + +#ifndef GL_GEOMETRY_SHADER +#define GL_GEOMETRY_SHADER 0x8DD9 +#endif + +static GMainLoop *loop; + +static const gchar *vert = "#version 330\n\ +in vec4 a_position;\n\ +in vec2 a_texcoord;\n\ +out vec2 v_texcoord;\n\ +uniform float time;\n\ +uniform float width;\n\ +uniform float height;\n\ +void main()\n\ +{\n\ + gl_Position = a_position;\n\ + v_texcoord = a_texcoord;\n\ +}\n"; + +static const gchar *geom = "#version 330\n\ +\n\ +layout(triangles) in;\n\ +layout(triangle_strip, max_vertices = 3) out;\n\ +in vec2 v_texcoord[];\n\ +out vec2 g_texcoord;\n\ +\n\ +void main() {\n\ + for(int i = 0; i < 3; i++) {\n\ + gl_Position = gl_in[i].gl_Position;\n\ + g_texcoord = v_texcoord[i];\n\ + EmitVertex();\n\ + }\n\ + EndPrimitive();\n\ +}\n"; + +static const gchar *frag = "#version 330\n\ +in vec2 g_texcoord;\n\ +uniform sampler2D tex;\n\ +uniform float time;\n\ +uniform float width;\n\ +uniform float height;\n\ +void main()\n\ +{\n\ + gl_FragColor = texture2D(tex, g_texcoord);\n\ +}\n"; + +#define MAX_SHADER_STAGES 8 +struct shader_state; + +struct text_view_state +{ + struct shader_state *state; + + GLenum type; + gchar *str; +}; + +struct shader_state +{ + GstGLContext *context; + GstElement *shader; + gboolean shader_linked; + GtkWidget *label; + struct text_view_state text_states[MAX_SHADER_STAGES]; + gint n_stages; +}; + +static gboolean +bus_call (GstBus * bus, GstMessage * msg, gpointer data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS: + g_print ("End of stream\n"); + g_main_loop_quit (loop); + break; + case GST_MESSAGE_ERROR:{ + gchar *debug; + GError *error; + + gst_message_parse_error (msg, &error, &debug); + g_free (debug); + + g_printerr ("Error: %s\n", error->message); + g_error_free (error); + + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static gchar * +_find_source_for_shader_type (struct shader_state *state, GLenum type) +{ + int i = 0; + + for (i = 0; i < state->n_stages; i++) { + if (state->text_states[i].type == type) + return state->text_states[i].str; + } + + return NULL; +} + +static gboolean +_add_stage_to_shader (GstGLShader * shader, struct shader_state *state, + GLenum type, const gchar * default_src) +{ + GError *error = NULL; + GstGLSLVersion version; + GstGLSLProfile profile; + GstGLSLStage *stage; + const gchar *src; + + src = _find_source_for_shader_type (state, type); + if (!src) + src = default_src; + if (!src) + /* FIXME: assume this stage is not needed */ + return TRUE; + + if (!gst_glsl_string_get_version_profile (src, &version, &profile)) { + g_print ("Warning: failed to retreive GLSL version and profile for " + "shader type 0x%x\nsrc:\n%s\n", type, src); + } + + if (!(stage = gst_glsl_stage_new_with_string (shader->context, type, + version, profile, src))) { + g_print ("Error: Failed to create GLSL Stage from src:\n%s\n", src); + return FALSE; + } + + if (!gst_gl_shader_compile_attach_stage (shader, stage, &error)) { + /* ignore failed shader compilations */ + g_print ("%s", error->message); + return FALSE; + } + + return TRUE; +} + +static GstGLShader * +_new_shader (GstGLContext * context, struct shader_state *state) +{ + GstGLShader *shader = gst_gl_shader_new (context); + GError *error = NULL; + + if (!_add_stage_to_shader (shader, state, GL_VERTEX_SHADER, vert)) { + gst_object_unref (shader); + return NULL; + } + if (!_add_stage_to_shader (shader, state, GL_GEOMETRY_SHADER, geom)) { + gst_object_unref (shader); + return NULL; + } + if (!_add_stage_to_shader (shader, state, GL_FRAGMENT_SHADER, frag)) { + gst_object_unref (shader); + return NULL; + } + + if (!gst_gl_shader_link (shader, &error)) { + /* ignore failed shader compilations */ + g_print ("%s", error->message); + gst_object_unref (shader); + return NULL; + } + + return shader; +} + +static gboolean +_set_compilation_state (struct shader_state *state) +{ + gtk_label_set_text (GTK_LABEL (state->label), + state->shader_linked ? "Success" : "Failure"); + + return G_SOURCE_REMOVE; +} + +static GstGLShader * +_create_shader (GstElement * element, struct shader_state *state) +{ + GstGLContext *context; + GstGLShader *shader, *new_shader; + + g_object_get (G_OBJECT (element), "context", &context, "shader", &shader, + NULL); + + new_shader = _new_shader (context, state); + if (!shader && !new_shader) + g_warning ("Failed to create a shader!"); + state->shader_linked = new_shader != NULL; + + if (shader) + gst_object_unref (shader); + gst_object_unref (context); + + g_main_context_invoke (NULL, (GSourceFunc) _set_compilation_state, state); + + return new_shader; +} + +static void +_on_text_changed (GtkTextBuffer * text, struct text_view_state *state) +{ + GtkTextIter start, end; + + gtk_text_buffer_get_bounds (text, &start, &end); + g_free (state->str); + state->str = gtk_text_buffer_get_text (text, &start, &end, FALSE); + g_object_set (state->state->shader, "update-shader", TRUE, NULL); +} + +static GtkWidget * +_new_source_view (struct shader_state *state, GLenum type, const gchar * templ) +{ + static int i = 0; + GtkWidget *scroll, *text_view; + GtkTextBuffer *text; + + g_return_val_if_fail (i < MAX_SHADER_STAGES, NULL); + + state->text_states[i].state = state; + state->text_states[i].type = type; + state->text_states[i].str = g_strdup (templ); + + scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (scroll, 20, 20); + text_view = gtk_text_view_new (); + gtk_container_add (GTK_CONTAINER (scroll), text_view); + text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + if (state->text_states[i].str) + gtk_text_buffer_set_text (text, state->text_states[i].str, -1); + g_signal_connect (text, "changed", G_CALLBACK (_on_text_changed), + &state->text_states[i]); + state->n_stages++; + i++; + + return scroll; +} + +int +main (int argc, char *argv[]) +{ + GstElement *pipeline, *src, *upload, *shader, *sink; + GtkWidget *window, *paned, *video, *right_box, *book; + struct shader_state state = { 0, }; + GstBus *bus; + +#if GST_GL_HAVE_WINDOW_X11 + XInitThreads (); +#endif + + gst_init (&argc, &argv); + gtk_init (&argc, &argv); + + loop = g_main_loop_new (NULL, FALSE); + + pipeline = gst_pipeline_new (NULL); + src = gst_element_factory_make ("videotestsrc", NULL); + upload = gst_element_factory_make ("glupload", NULL); + shader = gst_element_factory_make ("glshader", NULL); + sink = gst_element_factory_make ("gtkglsink", NULL); + g_object_get (sink, "widget", &video, NULL); + + g_assert (src && shader && sink); + gst_bin_add_many (GST_BIN (pipeline), src, upload, shader, sink, NULL); + g_assert (gst_element_link_many (src, upload, shader, sink, NULL)); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_watch (bus, bus_call, loop); + gst_object_unref (bus); + + state.shader = gst_object_ref (shader); + g_signal_connect (shader, "create-shader", G_CALLBACK (_create_shader), + &state); + + book = gtk_notebook_new (); + /* text view inside a scroll view */ + gtk_notebook_append_page (GTK_NOTEBOOK (book), _new_source_view (&state, + GL_VERTEX_SHADER, vert), gtk_label_new ("Vertex")); + gtk_notebook_append_page (GTK_NOTEBOOK (book), _new_source_view (&state, + GL_GEOMETRY_SHADER, geom), gtk_label_new ("Geometry")); + gtk_notebook_append_page (GTK_NOTEBOOK (book), _new_source_view (&state, + GL_FRAGMENT_SHADER, frag), gtk_label_new ("Fragment")); + /* status label */ + state.label = gtk_label_new ("Success"); + + /* right side source code editor */ + right_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (right_box), book, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (right_box), state.label, FALSE, TRUE, 0); + + paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_paned_pack1 (GTK_PANED (paned), video, TRUE, FALSE); + gtk_widget_set_size_request (video, 20, 20); + gtk_paned_pack2 (GTK_PANED (paned), right_box, TRUE, FALSE); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + gtk_container_add (GTK_CONTAINER (window), paned); + + gtk_widget_show_all (window); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + g_main_loop_run (loop); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + /*shader strings leaked here */ + /*g_free (state.str); */ + gst_object_unref (state.shader); + + gst_object_unref (pipeline); + + return 0; +} diff --git a/tests/examples/gtk/gtkglsink.c b/tests/examples/gtk/gtkglsink.c new file mode 100644 index 000000000..4b71985b0 --- /dev/null +++ b/tests/examples/gtk/gtkglsink.c @@ -0,0 +1,200 @@ +/* + * GStreamer + * Copyright (C) 2014 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. + */ + +#include <gtk/gtk.h> +#include <gst/gst.h> + +#include <gst/gl/gl.h> + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) +#include <gst/gl/x11/gstgldisplay_x11.h> +#endif + +static void +button_state_null_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_NULL); + g_print ("GST_STATE_NULL\n"); +} + +static void +button_state_ready_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_READY); + g_print ("GST_STATE_READY\n"); +} + +static void +button_state_paused_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_PAUSED); + g_print ("GST_STATE_PAUSED\n"); +} + +static void +button_state_playing_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_PLAYING); + g_print ("GST_STATE_PLAYING\n"); +} + +static void +end_stream_cb (GstBus * bus, GstMessage * message, GstElement * pipeline) +{ + g_print ("End of stream\n"); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + gtk_main_quit (); +} + +static void +destroy_cb (GtkWidget * widget, GdkEvent * event, GstElement * pipeline) +{ + g_print ("Close\n"); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + gtk_main_quit (); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window, *window_control; + GtkWidget *button_state_null, *button_state_ready; + GtkWidget *button_state_paused, *button_state_playing; + GtkWidget *grid, *area; + GstElement *pipeline; + GstElement *videosrc, *upload, *effect, *videosink; + GstStateChangeReturn ret; + GstCaps *caps; + GstBus *bus; + +#if GST_GL_HAVE_WINDOW_X11 && defined(GDK_WINDOWING_X11) + XInitThreads (); +#endif + + gst_init (&argc, &argv); + gtk_init (&argc, &argv); + + pipeline = gst_pipeline_new ("pipeline"); + + //window that contains an area where the video is drawn + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + gtk_window_move (GTK_WINDOW (window), 300, 10); + gtk_window_set_title (GTK_WINDOW (window), "gtkgstwidget"); + + //window to control the states + window_control = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_resizable (GTK_WINDOW (window_control), FALSE); + gtk_window_move (GTK_WINDOW (window_control), 10, 10); + grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (window_control), grid); + + //control state null + button_state_null = gtk_button_new_with_label ("GST_STATE_NULL"); + g_signal_connect (G_OBJECT (button_state_null), "clicked", + G_CALLBACK (button_state_null_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_null, 0, 1, 1, 1); + gtk_widget_show (button_state_null); + + //control state ready + button_state_ready = gtk_button_new_with_label ("GST_STATE_READY"); + g_signal_connect (G_OBJECT (button_state_ready), "clicked", + G_CALLBACK (button_state_ready_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_ready, 0, 2, 1, 1); + gtk_widget_show (button_state_ready); + + //control state paused + button_state_paused = gtk_button_new_with_label ("GST_STATE_PAUSED"); + g_signal_connect (G_OBJECT (button_state_paused), "clicked", + G_CALLBACK (button_state_paused_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_paused, 0, 3, 1, 1); + gtk_widget_show (button_state_paused); + + //control state playing + button_state_playing = gtk_button_new_with_label ("GST_STATE_PLAYING"); + g_signal_connect (G_OBJECT (button_state_playing), "clicked", + G_CALLBACK (button_state_playing_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_playing, 0, 4, 1, 1); + gtk_widget_show (button_state_playing); + + gtk_widget_show (grid); + gtk_widget_show (window_control); + + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (destroy_cb), + pipeline); + + //configure the pipeline + videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc"); + upload = gst_element_factory_make ("glupload", "glupload"); + effect = gst_element_factory_make ("glfiltercube", "glfiltercube"); + videosink = gst_element_factory_make ("gtkglsink", "gtksink"); + + g_object_get (videosink, "widget", &area, NULL); + gtk_container_add (GTK_CONTAINER (window), area); + g_object_unref (area); + + caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, 640, + "height", G_TYPE_INT, 480, "format", G_TYPE_STRING, "RGBA", + "framerate", GST_TYPE_FRACTION, 30, 1, NULL); + + gst_bin_add_many (GST_BIN (pipeline), videosrc, upload, effect, videosink, + NULL); + + if (!gst_element_link_filtered (videosrc, upload, caps)) { + gst_caps_unref (caps); + g_warning ("Failed to link videosrc to glfiltercube!\n"); + return -1; + } + gst_caps_unref (caps); + + if (!gst_element_link_many (upload, effect, videosink, NULL)) { + g_warning ("Failed to link videosrc to glfiltercube!\n"); + return -1; + } + //set window id on this event + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb), + pipeline); + g_signal_connect (bus, "message::warning", G_CALLBACK (end_stream_cb), + pipeline); + g_signal_connect (bus, "message::eos", G_CALLBACK (end_stream_cb), pipeline); + gst_object_unref (bus); + + //start + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + g_print ("Failed to start up pipeline!\n"); + return -1; + } + + gtk_widget_show_all (window); + + gtk_main (); + + gst_deinit (); + + return 0; +} diff --git a/tests/examples/gtk/gtksink.c b/tests/examples/gtk/gtksink.c new file mode 100644 index 000000000..d86fd090e --- /dev/null +++ b/tests/examples/gtk/gtksink.c @@ -0,0 +1,185 @@ +/* + * GStreamer + * Copyright (C) 2014 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. + */ + +#include <gtk/gtk.h> +#include <gst/gst.h> + +static void +button_state_null_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_NULL); + g_print ("GST_STATE_NULL\n"); +} + +static void +button_state_ready_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_READY); + g_print ("GST_STATE_READY\n"); +} + +static void +button_state_paused_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_PAUSED); + g_print ("GST_STATE_PAUSED\n"); +} + +static void +button_state_playing_cb (GtkWidget * widget, GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_PLAYING); + g_print ("GST_STATE_PLAYING\n"); +} + +static void +end_stream_cb (GstBus * bus, GstMessage * message, GstElement * pipeline) +{ + g_print ("End of stream\n"); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + gtk_main_quit (); +} + +static void +destroy_cb (GtkWidget * widget, GdkEvent * event, GstElement * pipeline) +{ + g_print ("Close\n"); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + gtk_main_quit (); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window, *window_control; + GtkWidget *button_state_null, *button_state_ready; + GtkWidget *button_state_paused, *button_state_playing; + GtkWidget *grid, *area; + GstElement *pipeline; + GstElement *videosrc, *videosink; + GstStateChangeReturn ret; + GstCaps *caps; + GstBus *bus; + + gst_init (&argc, &argv); + gtk_init (&argc, &argv); + + pipeline = gst_pipeline_new ("pipeline"); + + //window that contains an area where the video is drawn + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + gtk_window_move (GTK_WINDOW (window), 300, 10); + gtk_window_set_title (GTK_WINDOW (window), "gtkgstwidget"); + + //window to control the states + window_control = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_resizable (GTK_WINDOW (window_control), FALSE); + gtk_window_move (GTK_WINDOW (window_control), 10, 10); + grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (window_control), grid); + + //control state null + button_state_null = gtk_button_new_with_label ("GST_STATE_NULL"); + g_signal_connect (G_OBJECT (button_state_null), "clicked", + G_CALLBACK (button_state_null_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_null, 0, 1, 1, 1); + gtk_widget_show (button_state_null); + + //control state ready + button_state_ready = gtk_button_new_with_label ("GST_STATE_READY"); + g_signal_connect (G_OBJECT (button_state_ready), "clicked", + G_CALLBACK (button_state_ready_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_ready, 0, 2, 1, 1); + gtk_widget_show (button_state_ready); + + //control state paused + button_state_paused = gtk_button_new_with_label ("GST_STATE_PAUSED"); + g_signal_connect (G_OBJECT (button_state_paused), "clicked", + G_CALLBACK (button_state_paused_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_paused, 0, 3, 1, 1); + gtk_widget_show (button_state_paused); + + //control state playing + button_state_playing = gtk_button_new_with_label ("GST_STATE_PLAYING"); + g_signal_connect (G_OBJECT (button_state_playing), "clicked", + G_CALLBACK (button_state_playing_cb), pipeline); + gtk_grid_attach (GTK_GRID (grid), button_state_playing, 0, 4, 1, 1); + gtk_widget_show (button_state_playing); + + gtk_widget_show (grid); + gtk_widget_show (window_control); + + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (destroy_cb), + pipeline); + + //configure the pipeline + videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc"); + videosink = gst_element_factory_make ("gtksink", "gtksink"); + + g_object_get (videosink, "widget", &area, NULL); + gtk_container_add (GTK_CONTAINER (window), area); + g_object_unref (area); + + gtk_widget_realize (area); + + caps = gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, 640, + "height", G_TYPE_INT, 480, "format", G_TYPE_STRING, "BGRA", NULL); + + gst_bin_add_many (GST_BIN (pipeline), videosrc, videosink, NULL); + + if (!gst_element_link_filtered (videosrc, videosink, caps)) { + gst_caps_unref (caps); + g_warning ("Failed to link videosrc to glfiltercube!\n"); + return -1; + } + gst_caps_unref (caps); + + //set window id on this event + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb), + pipeline); + g_signal_connect (bus, "message::warning", G_CALLBACK (end_stream_cb), + pipeline); + g_signal_connect (bus, "message::eos", G_CALLBACK (end_stream_cb), pipeline); + gst_object_unref (bus); + + //start + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + g_print ("Failed to start up pipeline!\n"); + return -1; + } + + gtk_widget_show_all (window); + + gtk_main (); + + gst_deinit (); + + return 0; +} |