[byzanz] Add a GStreamer-based Theora encoder
- From: Benjamin Otte <otte src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [byzanz] Add a GStreamer-based Theora encoder
- Date: Fri, 28 Aug 2009 14:03:04 +0000 (UTC)
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]