summaryrefslogtreecommitdiff
path: root/ext/xlib/gstcairoglxsink.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/xlib/gstcairoglxsink.c')
-rw-r--r--ext/xlib/gstcairoglxsink.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/ext/xlib/gstcairoglxsink.c b/ext/xlib/gstcairoglxsink.c
new file mode 100644
index 0000000..9d8638e
--- /dev/null
+++ b/ext/xlib/gstcairoglxsink.c
@@ -0,0 +1,426 @@
+/* GStreamer
+ * Copyright (C) 2009 Benjamin Otte <otte@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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-cairo_glx_sink
+ *
+ * Convert video frames between a great variety of glx_sink formats.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v videotestsrc ! video/x-raw-yuv,format=\(fourcc\)YUY2 ! cairo_glx_sink ! ximagesink
+ * ]|
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "gstcairoglxsink.h"
+
+#include <cairo-gl.h>
+#include <gst/interfaces/navigation.h>
+
+#include "gstcairoxsource.h"
+
+static void
+gst_cairo_glx_sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstCairoGLXSink *xsink = GST_CAIRO_GLX_SINK (navigation);
+ GstEvent *event;
+ GstPad *pad;
+ double d;
+
+ pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (xsink));
+ if (!pad) {
+ gst_structure_free (structure);
+ return;
+ }
+
+ if (gst_structure_get_double (structure, "pointer_x", &d))
+ gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, d *
+ gst_cairo_format_get_width (xsink->format) /
+ cairo_gl_surface_get_width (xsink->surface), NULL);
+ if (gst_structure_get_double (structure, "pointer_y", &d))
+ gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, d *
+ gst_cairo_format_get_height (xsink->format) /
+ cairo_gl_surface_get_height (xsink->surface), NULL);
+
+ event = gst_event_new_navigation (structure);
+
+ gst_pad_send_event (pad, event);
+ gst_object_unref (pad);
+}
+
+static void
+gst_cairo_glx_sink_navigation_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_cairo_glx_sink_navigation_send_event;
+}
+
+static void
+_do_init (GType g_define_type_id)
+{
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_cairo_glx_sink_navigation_init);
+}
+
+GST_BOILERPLATE_FULL (GstCairoGLXSink, gst_cairo_glx_sink, GstElement,
+ GST_TYPE_VIDEO_SINK, _do_init)
+
+/* THREAD */
+ static void
+ gst_cairo_glx_sink_paint (GstCairoGLXSink * xsink, int x, int y,
+ int width, int height)
+{
+ GstBaseSink *bsink = GST_BASE_SINK (xsink);
+ cairo_surface_t *source;
+ GstCairoFormat *format;
+ GstBuffer *buffer;
+ cairo_t *cr;
+
+ buffer = gst_base_sink_get_last_buffer (bsink);
+ if (buffer == NULL)
+ return;
+
+ format = gst_cairo_format_new (GST_BUFFER_CAPS (buffer));
+ source = gst_cairo_create_surface (buffer, format);
+ cr = cairo_create (xsink->surface);
+
+ cairo_scale (cr,
+ (double) cairo_gl_surface_get_width (xsink->surface) /
+ gst_cairo_format_get_width (format),
+ (double) cairo_gl_surface_get_height (xsink->surface) /
+ gst_cairo_format_get_height (format));
+ cairo_set_source_surface (cr, source, 0, 0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (source);
+
+ cairo_gl_surface_swapbuffers (xsink->surface);
+ gst_buffer_unref (buffer);
+ gst_cairo_format_free (format);
+}
+
+static gboolean
+gst_cairo_glx_sink_handle_event (XEvent * event, gpointer sink)
+{
+ GstCairoGLXSink *xsink = sink;
+
+ switch (event->type) {
+ case Expose:
+ {
+ XExposeEvent *expose = &event->xexpose;
+ gst_cairo_glx_sink_paint (xsink, expose->x, expose->y, expose->width,
+ expose->height);
+ break;
+ }
+ case ConfigureNotify:
+ {
+ XConfigureEvent *configure = &event->xconfigure;
+ cairo_gl_surface_set_size (xsink->surface, configure->width,
+ configure->height);
+ break;
+ }
+ case ButtonPress:
+ gst_navigation_send_mouse_event (GST_NAVIGATION (xsink),
+ "mouse-button-press", event->xbutton.button, event->xbutton.x,
+ event->xbutton.y);
+ break;
+ case ButtonRelease:
+ gst_navigation_send_mouse_event (GST_NAVIGATION (xsink),
+ "mouse-button-release", event->xbutton.button, event->xbutton.x,
+ event->xbutton.y);
+ break;
+ case MotionNotify:
+ gst_navigation_send_mouse_event (GST_NAVIGATION (xsink),
+ "mouse-move", 0, event->xmotion.x, event->xmotion.y);
+ break;
+ case KeyPress:
+ case KeyRelease:
+ {
+ KeySym keysym = XKeycodeToKeysym (xsink->display,
+ event->xkey.keycode, 0);
+ if (keysym != NoSymbol) {
+ char *key = NULL;
+
+ key = XKeysymToString (keysym);
+ if (key == NULL)
+ key = "unknown";
+ gst_navigation_send_key_event (GST_NAVIGATION (xsink),
+ event->type == KeyPress ? "key-press" : "key-release", key);
+ }
+ break;
+ }
+ case UnmapNotify:
+ case MapNotify:
+ case ReparentNotify:
+ /* can safely be ignored */
+ break;
+ default:
+ g_warning ("handle event %u\n", event->type);
+ break;
+ }
+
+ return TRUE;
+}
+
+static gpointer
+gst_cairo_glx_sink_run (gpointer sink)
+{
+ GstCairoGLXSink *xsink = sink;
+ GSource *source;
+
+ XSelectInput (xsink->display, xsink->window,
+ ExposureMask | StructureNotifyMask | PointerMotionMask | ButtonPressMask
+ | ButtonReleaseMask | KeyPressMask | KeyReleaseMask);
+
+ source =
+ gst_cairo_x_source_new (xsink->display,
+ gst_cairo_glx_sink_handle_event, xsink);
+ g_source_attach (source, xsink->context);
+ g_source_unref (source);
+ g_main_loop_run (xsink->loop);
+
+ return NULL;
+}
+
+static gboolean
+gst_cairo_glx_sink_repaint (gpointer sink)
+{
+ GstCairoGLXSink *xsink = sink;
+
+ gst_cairo_glx_sink_paint (xsink, 0, 0,
+ cairo_gl_surface_get_width (xsink->surface),
+ cairo_gl_surface_get_height (xsink->surface));
+
+ return FALSE;
+}
+
+static gboolean
+gst_cairo_glx_sink_stop_thread (gpointer sink)
+{
+ GstCairoGLXSink *xsink = sink;
+
+ g_main_loop_quit (xsink->loop);
+
+ return FALSE;
+}
+
+/*** MAIN THREAD ***/
+
+static gboolean
+gst_cairo_glx_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstCairoGLXSink *xsink = GST_CAIRO_GLX_SINK (bsink);
+
+ gst_cairo_format_free (xsink->format);
+ xsink->format = gst_cairo_format_new (caps);
+
+ XResizeWindow (xsink->display, xsink->window,
+ gst_cairo_format_get_width (xsink->format),
+ gst_cairo_format_get_height (xsink->format));
+ XMapWindow (xsink->display, xsink->window);
+
+ return TRUE;
+}
+
+static gboolean
+gst_cairo_glx_sink_start (GstBaseSink * bsink)
+{
+ GstCairoGLXSink *glxsink = GST_CAIRO_GLX_SINK (bsink);
+ int rgba_attribs[] = { GLX_RGBA,
+ GLX_RED_SIZE, 1,
+ GLX_GREEN_SIZE, 1,
+ GLX_BLUE_SIZE, 1,
+ GLX_ALPHA_SIZE, 1,
+ GLX_DOUBLEBUFFER,
+ None
+ };
+ XVisualInfo *visual;
+ GLXContext ctx;
+ XSetWindowAttributes attr;
+
+ glxsink->display = XOpenDisplay (NULL);
+ if (glxsink->display == NULL) {
+ GST_WARNING_OBJECT (glxsink, "Failed to open connection to X server");
+ return FALSE;
+ }
+
+ visual =
+ glXChooseVisual (glxsink->display, DefaultScreen (glxsink->display),
+ rgba_attribs);
+ if (visual == NULL) {
+ GST_WARNING_OBJECT (glxsink,
+ "Failed to create RGBA, double-buffered visual");
+ XCloseDisplay (glxsink->display);
+ return FALSE;
+ }
+
+ attr.colormap = XCreateColormap (glxsink->display,
+ RootWindow (glxsink->display, visual->screen), visual->visual, AllocNone);
+ attr.border_pixel = 0;
+ glxsink->window = XCreateWindow (glxsink->display,
+ DefaultRootWindow (glxsink->display), 0, 0, 400, 300,
+ 0, visual->depth, InputOutput, visual->visual,
+ CWBorderPixel | CWColormap, &attr);
+ XMapWindow (glxsink->display, glxsink->window);
+
+ ctx = glXCreateContext (glxsink->display, visual, NULL, True);
+ XFree (visual);
+
+ glxsink->device = cairo_glx_device_create (glxsink->display, ctx);
+ glxsink->surface = cairo_gl_surface_create_for_window (glxsink->device,
+ glxsink->window, 400, 300);
+
+ if (cairo_surface_status (glxsink->surface)) {
+ GST_WARNING_OBJECT (glxsink, "Failed to create GLX surface: %s",
+ cairo_status_to_string (cairo_surface_status (glxsink->surface)));
+ goto cleanup;
+ }
+
+ glxsink->context = g_main_context_new ();
+ glxsink->loop = g_main_loop_new (glxsink->context, TRUE);
+ glxsink->thread =
+ g_thread_create (gst_cairo_glx_sink_run, glxsink, TRUE, NULL);
+ if (!glxsink->thread) {
+ GST_WARNING_OBJECT (glxsink, "Failed to create event thread");
+ g_main_loop_unref (glxsink->loop);
+ glxsink->loop = NULL;
+ g_main_context_unref (glxsink->context);
+ glxsink->context = NULL;
+ goto cleanup;
+ }
+
+ return TRUE;
+
+cleanup:
+ cairo_surface_destroy (glxsink->surface);
+ glxsink->surface = NULL;
+ cairo_device_destroy (glxsink->device);
+ glxsink->device = NULL;
+ XDestroyWindow (glxsink->display, glxsink->window);
+ XCloseDisplay (glxsink->display);
+ glxsink->display = NULL;
+ return FALSE;
+}
+
+static gboolean
+gst_cairo_glx_sink_stop (GstBaseSink * bsink)
+{
+ GstCairoGLXSink *xsink = GST_CAIRO_GLX_SINK (bsink);
+ GSource *source;
+
+ source = g_idle_source_new ();
+ g_source_set_callback (source, gst_cairo_glx_sink_stop_thread, xsink, NULL);
+ g_source_attach (source, xsink->context);
+ g_source_unref (source);
+
+ g_thread_join (xsink->thread);
+
+ g_main_loop_unref (xsink->loop);
+ xsink->loop = NULL;
+ g_main_context_unref (xsink->context);
+ xsink->context = NULL;
+
+ cairo_surface_destroy (xsink->surface);
+ xsink->surface = NULL;
+ cairo_device_destroy (xsink->device);
+ xsink->device = NULL;
+ XDestroyWindow (xsink->display, xsink->window);
+
+ XCloseDisplay (xsink->display);
+ xsink->display = NULL;
+ gst_cairo_format_free (xsink->format);
+ xsink->format = NULL;
+
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_cairo_glx_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
+{
+ GstCairoGLXSink *xsink = GST_CAIRO_GLX_SINK (vsink);
+ GSource *source;
+
+ source = g_idle_source_new ();
+ g_source_set_callback (source, gst_cairo_glx_sink_repaint, xsink, NULL);
+ g_source_attach (source, xsink->context);
+ g_source_unref (source);
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_cairo_glx_sink_buffer_alloc (GstBaseSink * bsink, guint64 offset,
+ guint size, GstCaps * caps, GstBuffer ** buf)
+{
+ GstCairoGLXSink *xsink = GST_CAIRO_GLX_SINK (bsink);
+ GstCairoFormat *format;
+
+ format = gst_cairo_format_new (caps);
+ if (gst_cairo_format_is_native (format)) {
+ g_assert (xsink->surface);
+ *buf = gst_cairo_buffer_new_similar (xsink->surface, format);
+ } else {
+ *buf = NULL;
+ }
+ gst_cairo_format_free (format);
+
+ return GST_FLOW_OK;
+}
+
+static void
+gst_cairo_glx_sink_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_cairo_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS));
+ gst_element_class_set_details_simple (element_class,
+ "Cairo GLX Video Sink",
+ "Sink/Video",
+ "Outputs video to X11 using Cairo", "Benjamin Otte <otte@gnome.org>");
+}
+
+static void
+gst_cairo_glx_sink_class_init (GstCairoGLXSinkClass * klass)
+{
+ GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
+ GstVideoSinkClass *videosink_class = GST_VIDEO_SINK_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ basesink_class->start = gst_cairo_glx_sink_start;
+ basesink_class->stop = gst_cairo_glx_sink_stop;
+ basesink_class->set_caps = gst_cairo_glx_sink_set_caps;
+ basesink_class->buffer_alloc = gst_cairo_glx_sink_buffer_alloc;
+
+ videosink_class->show_frame = gst_cairo_glx_sink_show_frame;
+}
+
+static void
+gst_cairo_glx_sink_init (GstCairoGLXSink * xsink, GstCairoGLXSinkClass * klass)
+{
+}