[byzanz] Add a GStreamer-based Theora encoder



commit bca3dc205f5e4dd72f8faa9338c0ea4aefecf85d
Author: Benjamin Otte <otte gnome org>
Date:   Fri Aug 28 10:13:02 2009 +0200

    Add a GStreamer-based Theora encoder
    
    The encoder gets picked by file format in file chooser, or if that
    doesn't work (in the command-line app, or when having selected "All
    files") by file extension: use .ogv files to get a Theora stream.
    
    Also contains significant cleanup to error reporting from encoding
    threads, which used to happily crash all the time.

 configure.ac           |    7 +-
 src/Makefile.am        |    2 +
 src/byzanzapplet.c     |   18 +++--
 src/byzanzencoder.c    |    9 ++-
 src/byzanzencoderogv.c |  228 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/byzanzencoderogv.h |   56 ++++++++++++
 src/byzanzsession.c    |   60 +++++++------
 7 files changed, 342 insertions(+), 38 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 36365b9..46b7268 100644
--- a/configure.ac
+++ b/configure.ac
@@ -101,6 +101,7 @@ GTK_REQ="2.17.10"
 GTHREAD_REQ="2.6.0"
 APPLET_REQ="2.10.0"
 XDAMAGE_REQ="1.0"
+GST_REQ="0.10.22"
 
 PKG_CHECK_MODULES(GTK, cairo >= $CAIRO_REQ gtk+-2.0 >= $GTK_REQ x11, gio-2.0)
 
@@ -108,6 +109,8 @@ PKG_CHECK_MODULES(GTHREAD, xdamage >= $XDAMAGE_REQ gthread-2.0 >= $GTHREAD_REQ)
 
 PKG_CHECK_MODULES(APPLET, libpanelapplet-2.0 >= $APPLET_REQ)
 
+PKG_CHECK_MODULES(GST, gstreamer-app-0.10 >= $GST_REQ gstreamer-0.10 >= $GST_REQ)
+
 AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
 AC_PATH_PROG(GCONFTOOL, gconftool-2)
 
@@ -119,8 +122,8 @@ AS_SCRUB_INCLUDE(GIFENC_CFLAGS)
 AC_SUBST(GIFENC_CFLAGS)
 AC_SUBST(GIFENC_LIBS)
 
-BYZANZ_CFLAGS="$GTK_CFLAGS $GTHREAD_CFLAGS $ERROR_CFLAGS"
-BYZANZ_LIBS="$GTK_LIBS $GTHREAD_LIBS"
+BYZANZ_CFLAGS="$GTK_CFLAGS $GTHREAD_CFLAGS $GST_CFLAGS $ERROR_CFLAGS"
+BYZANZ_LIBS="$GTK_LIBS $GTHREAD_LIBS $GST_LIBS"
 AS_SCRUB_INCLUDE(BYZANZ_CFLAGS)
 AC_SUBST(BYZANZ_CFLAGS)
 AC_SUBST(BYZANZ_LIBS)
diff --git a/src/Makefile.am b/src/Makefile.am
index 9260196..97f9939 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -10,6 +10,7 @@ BUILT_SOURCES = \
 noinst_HEADERS = \
 	byzanzencoder.h \
 	byzanzencodergif.h \
+	byzanzencoderogv.h \
 	byzanzlayer.h \
 	byzanzlayercursor.h \
 	byzanzlayerwindow.h \
@@ -22,6 +23,7 @@ noinst_HEADERS = \
 libbyzanz_la_SOURCES = \
 	byzanzencoder.c \
 	byzanzencodergif.c \
+	byzanzencoderogv.c \
 	byzanzlayer.c \
 	byzanzlayercursor.c \
 	byzanzlayerwindow.c \
diff --git a/src/byzanzapplet.c b/src/byzanzapplet.c
index 2de5c77..ced35a8 100644
--- a/src/byzanzapplet.c
+++ b/src/byzanzapplet.c
@@ -60,20 +60,24 @@ byzanz_applet_show_error (AppletPrivate *priv, const char *error, const char *de
   gchar *msg;
   va_list args;
 
-  g_return_if_fail (details != NULL);
+  g_return_if_fail (error != NULL);
   
-  va_start (args, details);
-  msg = g_strdup_vprintf (details, args);
-  va_end (args);
+  if (details) {
+    va_start (args, details);
+    msg = g_strdup_vprintf (details, args);
+    va_end (args);
+  } else {
+    msg = NULL;
+  }
   if (priv)
     parent = gtk_widget_get_toplevel (GTK_WIDGET (priv->applet));
   else
     parent = NULL;
   dialog = gtk_message_dialog_new (GTK_WINDOW (parent), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
-      GTK_BUTTONS_CLOSE, "%s", error ? error : msg);
+      GTK_BUTTONS_CLOSE, "%s", error);
   if (parent == NULL)
     gtk_window_set_icon_name (GTK_WINDOW (dialog), "byzanz-record-desktop");
-  if (error)
+  if (msg)
     gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", msg);
   g_free (msg);
   g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
@@ -137,9 +141,11 @@ byzanz_applet_session_notify (AppletPrivate *priv)
   error = byzanz_session_get_error (priv->rec);
   if (error) {
     byzanz_applet_show_error (priv, error->message, NULL);
+    g_signal_handlers_disconnect_by_func (priv->rec, byzanz_applet_session_notify, priv);
     g_object_unref (priv->rec);
     priv->rec = NULL;
   } else if (!byzanz_session_is_encoding (priv->rec)) {
+    g_signal_handlers_disconnect_by_func (priv->rec, byzanz_applet_session_notify, priv);
     g_object_unref (priv->rec);
     priv->rec = NULL;
   }
diff --git a/src/byzanzencoder.c b/src/byzanzencoder.c
index 91a7d5b..ef2749e 100644
--- a/src/byzanzencoder.c
+++ b/src/byzanzencoder.c
@@ -58,6 +58,7 @@ byzanz_encoder_finished (gpointer data)
   if (encoder->error)
     g_object_notify (G_OBJECT (encoder), "error");
   g_object_thaw_notify (G_OBJECT (encoder));
+  g_object_unref (encoder);
 
   return FALSE;
 }
@@ -94,7 +95,6 @@ byzanz_encoder_run (gpointer enc)
     byzanz_encoder_job_free (job);
   } while (error == NULL);
 
-
   g_idle_add_full (G_PRIORITY_DEFAULT, byzanz_encoder_finished, enc, NULL);
   return error;
 }
@@ -228,7 +228,6 @@ byzanz_encoder_finalize (GObject *object)
 {
   ByzanzEncoder *encoder = BYZANZ_ENCODER (object);
 
-  /* FIXME: handle the case where the thread is still alive */
   g_assert (encoder->thread == NULL);
 
   g_object_unref (encoder->output_stream);
@@ -249,6 +248,8 @@ byzanz_encoder_constructed (GObject *object)
 
   encoder->thread = g_thread_create (byzanz_encoder_run, encoder, 
       TRUE, &encoder->error);
+  if (encoder->thread)
+    g_object_ref (encoder);
 
   if (G_OBJECT_CLASS (byzanz_encoder_parent_class)->constructed)
     G_OBJECT_CLASS (byzanz_encoder_parent_class)->constructed (object);
@@ -396,10 +397,12 @@ byzanz_encoder_type_get_filter (GType encoder_type)
 
 /* all the encoders */
 #include "byzanzencodergif.h"
+#include "byzanzencoderogv.h"
 
 typedef GType (* TypeFunc) (void);
 static const TypeFunc functions[] = {
-  byzanz_encoder_gif_get_type
+  byzanz_encoder_gif_get_type,
+  byzanz_encoder_ogv_get_type
 };
 #define BYZANZ_ENCODER_DEFAULT_TYPE (functions[0] ())
 
diff --git a/src/byzanzencoderogv.c b/src/byzanzencoderogv.c
new file mode 100644
index 0000000..bc8cfc4
--- /dev/null
+++ b/src/byzanzencoderogv.c
@@ -0,0 +1,228 @@
+/* desktop session recorder
+ * 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 3 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "byzanzencoderogv.h"
+
+#include <glib/gi18n.h>
+#include <gst/app/gstappbuffer.h>
+#include <gst/video/video.h>
+
+G_DEFINE_TYPE (ByzanzEncoderOgv, byzanz_encoder_ogv, BYZANZ_TYPE_ENCODER)
+
+#define PIPELINE_STRING "appsrc name=src ! ffmpegcolorspace ! videorate ! video/x-raw-yuv,framerate=25/1 ! theoraenc ! oggmux ! giostreamsink name=sink"
+
+static gboolean
+byzanz_encoder_ogv_setup (ByzanzEncoder * encoder,
+                          GOutputStream * stream,
+                          guint           width,
+                          guint           height,
+                          GCancellable *  cancellable,
+                          GError **	  error)
+{
+  ByzanzEncoderOgv *ogv = BYZANZ_ENCODER_OGG (encoder);
+  GstElement *sink;
+
+  ogv->pipeline = gst_parse_launch (PIPELINE_STRING, error);
+  if (ogv->pipeline == NULL)
+    return FALSE;
+
+  g_assert (GST_IS_PIPELINE (ogv->pipeline));
+  ogv->src = GST_APP_SRC (gst_bin_get_by_name (GST_BIN (ogv->pipeline), "src"));
+  g_assert (GST_IS_APP_SRC (ogv->src));
+  sink = gst_bin_get_by_name (GST_BIN (ogv->pipeline), "sink");
+  g_assert (sink);
+  g_object_set (sink, "stream", stream, NULL);
+  g_object_unref (sink);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+  ogv->caps = gst_caps_from_string (GST_VIDEO_CAPS_BGRx);
+#elif G_BYTE_ORDER == G_BIG_ENDIAN
+  ogv->caps = gst_caps_new_from_string (GST_VIDEO_CAPS_xRGB);
+#else
+#error "Please add the Cairo caps format here"
+#endif
+  gst_caps_set_simple (ogv->caps,
+      "width", G_TYPE_INT, width, 
+      "height", G_TYPE_INT, height,
+      "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
+  g_assert (gst_caps_is_fixed (ogv->caps));
+
+  gst_app_src_set_caps (ogv->src, ogv->caps);
+  gst_app_src_set_stream_type (ogv->src, GST_APP_STREAM_TYPE_STREAM);
+
+  if (!gst_element_set_state (ogv->pipeline, GST_STATE_PLAYING)) {
+    g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to start GStreamer pipeline"));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+byzanz_encoder_ogv_got_error (ByzanzEncoderOgv *ogv, GError **error)
+{
+  GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (ogv->pipeline));
+  GstMessage *message = gst_bus_pop_filtered (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
+
+  g_object_unref (bus);
+  if (message != NULL) {
+    if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+      gst_message_parse_error (message, error, NULL);
+    } else {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Premature end of GStreamer pipeline"));
+    }
+    gst_message_unref (message);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+byzanz_encoder_ogv_process (ByzanzEncoder *   encoder,
+                            GOutputStream *   stream,
+                            cairo_surface_t * surface,
+                            const GdkRegion * region,
+                            const GTimeVal *  total_elapsed,
+                            GCancellable *    cancellable,
+                            GError **	      error)
+{
+  ByzanzEncoderOgv *ogv = BYZANZ_ENCODER_OGG (encoder);
+  GstBuffer *buffer;
+
+  if (!byzanz_encoder_ogv_got_error (ogv, error))
+    return FALSE;
+
+  /* update the surface */
+  if (ogv->surface == NULL) {
+    /* just assume that the size is right and pray */
+    ogv->surface = cairo_surface_reference (surface);
+    ogv->start_time = *total_elapsed;
+  } else {
+    cairo_t *cr;
+
+    if (cairo_surface_get_reference_count (ogv->surface) > 1) {
+      cairo_surface_t *copy = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+          cairo_image_surface_get_width (ogv->surface), cairo_image_surface_get_height (ogv->surface));
+      
+      cr = cairo_create (copy);
+      cairo_set_source_surface (cr, ogv->surface, 0, 0);
+      cairo_paint (cr);
+      cairo_destroy (cr);
+
+      cairo_surface_destroy (ogv->surface);
+      ogv->surface = copy;
+    }
+    cr = cairo_create (ogv->surface);
+    cairo_set_source_surface (cr, surface, 0, 0);
+    gdk_cairo_region (cr, region);
+    cairo_fill (cr);
+    cairo_destroy (cr);
+  }
+
+  /* create a buffer and send it */
+  /* FIXME: stride just works? */
+  cairo_surface_reference (ogv->surface);
+  buffer = gst_app_buffer_new (cairo_image_surface_get_data (ogv->surface),
+      cairo_image_surface_get_stride (ogv->surface) * cairo_image_surface_get_height (ogv->surface),
+      (GstAppBufferFinalizeFunc) cairo_surface_destroy, ogv->surface);
+  GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_READONLY);
+  GST_BUFFER_TIMESTAMP (buffer) = GST_TIMEVAL_TO_TIME (*total_elapsed) - GST_TIMEVAL_TO_TIME (ogv->start_time);
+  gst_buffer_set_caps (buffer, ogv->caps);
+  gst_app_src_push_buffer (ogv->src, buffer);
+
+  return TRUE;
+}
+
+static gboolean
+byzanz_encoder_ogv_close (ByzanzEncoder *  encoder,
+                          GOutputStream *  stream,
+                          const GTimeVal * total_elapsed,
+                          GCancellable *   cancellable,
+                          GError **	   error)
+{
+  ByzanzEncoderOgv *ogv = BYZANZ_ENCODER_OGG (encoder);
+  GstBus *bus;
+  GstMessage *message;
+
+  gst_app_src_end_of_stream (ogv->src);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (ogv->pipeline));
+  message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
+
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+    gst_message_parse_error (message, error, NULL);
+    gst_message_unref (message);
+    return FALSE;
+  }
+  gst_message_unref (message);
+  g_object_unref (bus);
+  gst_element_set_state (ogv->pipeline, GST_STATE_NULL);
+
+  return TRUE;
+}
+
+static void
+byzanz_encoder_ogv_finalize (GObject *object)
+{
+  ByzanzEncoderOgv *ogv = BYZANZ_ENCODER_OGG (object);
+
+  if (ogv->pipeline) {
+    gst_element_set_state (ogv->pipeline, GST_STATE_NULL);
+    g_object_unref (ogv->pipeline);
+  }
+  if (ogv->src)
+    g_object_unref (ogv->src);
+  if (ogv->caps)
+    gst_caps_unref (ogv->caps);
+
+  G_OBJECT_CLASS (byzanz_encoder_ogv_parent_class)->finalize (object);
+}
+
+static void
+byzanz_encoder_ogv_class_init (ByzanzEncoderOgvClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ByzanzEncoderClass *encoder_class = BYZANZ_ENCODER_CLASS (klass);
+
+  gst_init (NULL, NULL);
+
+  object_class->finalize = byzanz_encoder_ogv_finalize;
+
+  encoder_class->setup = byzanz_encoder_ogv_setup;
+  encoder_class->process = byzanz_encoder_ogv_process;
+  encoder_class->close = byzanz_encoder_ogv_close;
+
+  encoder_class->filter = gtk_file_filter_new ();
+  g_object_ref_sink (encoder_class->filter);
+  gtk_file_filter_set_name (encoder_class->filter, _("Theora videos"));
+  gtk_file_filter_add_mime_type (encoder_class->filter, "video/ogg");
+  gtk_file_filter_add_pattern (encoder_class->filter, "*.ogv");
+  gtk_file_filter_add_pattern (encoder_class->filter, "*.ogg");
+}
+
+static void
+byzanz_encoder_ogv_init (ByzanzEncoderOgv *encoder_ogv)
+{
+}
+
diff --git a/src/byzanzencoderogv.h b/src/byzanzencoderogv.h
new file mode 100644
index 0000000..6337d7f
--- /dev/null
+++ b/src/byzanzencoderogv.h
@@ -0,0 +1,56 @@
+/* desktop session recorder
+ * 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 3 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.
+ */
+
+#include "byzanzencoder.h"
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+
+#ifndef __HAVE_BYZANZ_ENCODER_OGG_H__
+#define __HAVE_BYZANZ_ENCODER_OGG_H__
+
+typedef struct _ByzanzEncoderOgv ByzanzEncoderOgv;
+typedef struct _ByzanzEncoderOgvClass ByzanzEncoderOgvClass;
+
+#define BYZANZ_TYPE_ENCODER_OGG                    (byzanz_encoder_ogv_get_type())
+#define BYZANZ_IS_ENCODER_OGG(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BYZANZ_TYPE_ENCODER_OGG))
+#define BYZANZ_IS_ENCODER_OGG_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), BYZANZ_TYPE_ENCODER_OGG))
+#define BYZANZ_ENCODER_OGG(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), BYZANZ_TYPE_ENCODER_OGG, ByzanzEncoderOgv))
+#define BYZANZ_ENCODER_OGG_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), BYZANZ_TYPE_ENCODER_OGG, ByzanzEncoderOgvClass))
+#define BYZANZ_ENCODER_OGG_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), BYZANZ_TYPE_ENCODER_OGG, ByzanzEncoderOgvClass))
+
+struct _ByzanzEncoderOgv {
+  ByzanzEncoder         encoder;
+
+  cairo_surface_t *     surface;        /* last surface pushed down the pipeline */
+  GTimeVal              start_time;     /* timestamp of first image */
+
+  GstElement *          pipeline;       /* The pipeline */
+  GstAppSrc *           src;            /* the source we feed with images */
+  GstCaps *             caps;           /* caps of video stream */
+};
+
+struct _ByzanzEncoderOgvClass {
+  ByzanzEncoderClass    encoder_class;
+};
+
+GType		byzanz_encoder_ogv_get_type		(void) G_GNUC_CONST;
+
+
+#endif /* __HAVE_BYZANZ_ENCODER_OGG_H__ */
diff --git a/src/byzanzsession.c b/src/byzanzsession.c
index 31314c9..95226c5 100644
--- a/src/byzanzsession.c
+++ b/src/byzanzsession.c
@@ -115,33 +115,6 @@ byzanz_session_set_property (GObject *object, guint param_id, const GValue *valu
 }
 
 static void
-byzanz_session_dispose (GObject *object)
-{
-  ByzanzSession *session = BYZANZ_SESSION (object);
-
-  byzanz_session_abort (session);
-
-  G_OBJECT_CLASS (byzanz_session_parent_class)->dispose (object);
-}
-
-static void
-byzanz_session_finalize (GObject *object)
-{
-  ByzanzSession *session = BYZANZ_SESSION (object);
-
-  g_object_unref (session->recorder);
-  if (session->encoder)
-    g_object_unref (session->encoder);
-  g_object_unref (session->window);
-  g_object_unref (session->file);
-
-  if (session->error)
-    g_error_free (session->error);
-
-  G_OBJECT_CLASS (byzanz_session_parent_class)->finalize (object);
-}
-
-static void
 byzanz_session_set_error (ByzanzSession *session, const GError *error)
 {
   GObject *object = G_OBJECT (session);
@@ -150,11 +123,13 @@ byzanz_session_set_error (ByzanzSession *session, const GError *error)
     return;
 
   session->error = g_error_copy (error);
+  g_object_ref (session);
   g_object_freeze_notify (object);
   g_object_notify (object, "error");
   if (byzanz_recorder_get_recording (session->recorder))
     byzanz_session_stop (session);
   g_object_thaw_notify (object);
+  g_object_unref (session);
 }
 
 static void
@@ -193,6 +168,37 @@ byzanz_session_recorder_image_cb (ByzanzRecorder *  recorder,
 }
 
 static void
+byzanz_session_dispose (GObject *object)
+{
+  ByzanzSession *session = BYZANZ_SESSION (object);
+
+  byzanz_session_abort (session);
+
+  G_OBJECT_CLASS (byzanz_session_parent_class)->dispose (object);
+}
+
+static void
+byzanz_session_finalize (GObject *object)
+{
+  ByzanzSession *session = BYZANZ_SESSION (object);
+
+  g_assert (session != NULL);
+
+  g_object_unref (session->recorder);
+  if (session->encoder) {
+    g_signal_handlers_disconnect_by_func (session->encoder, byzanz_session_encoder_notify_cb, session);
+    g_object_unref (session->encoder);
+  }
+  g_object_unref (session->window);
+  g_object_unref (session->file);
+
+  if (session->error)
+    g_error_free (session->error);
+
+  G_OBJECT_CLASS (byzanz_session_parent_class)->finalize (object);
+}
+
+static void
 byzanz_session_constructed (GObject *object)
 {
   ByzanzSession *session = BYZANZ_SESSION (object);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]