From 8e82495a53a0f532b5725477b11b1837ed8c7b80 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Wed, 27 Aug 2008 22:35:57 +0300 Subject: driver: GStreamer using decodebin2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marc-André Lureau Signed-off-by: Lennart Poettering --- configure.ac | 66 ++++++++- src/Makefile.am | 35 +++++ src/gstreamer.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 src/gstreamer.c diff --git a/configure.ac b/configure.ac index f41cfe9..52cd26c 100644 --- a/configure.ac +++ b/configure.ac @@ -272,6 +272,38 @@ fi AC_SUBST(PULSE_CFLAGS) AC_SUBST(PULSE_LIBS) +#### GStreamer support (optional) #### + +AC_ARG_ENABLE([gstreamer], + AC_HELP_STRING([--disable-gstreamer], [Disable optional GStreamer support]), + [ + case "${enableval}" in + yes) gstreamer=yes ;; + no) gstreamer=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-gstreamer) ;; + esac + ], + [gstreamer=auto]) + +if test "x${gstreamer}" != xno ; then + PKG_CHECK_MODULES(GST, [ gstreamer-0.10 >= 0.10.15 ], + [ + HAVE_GSTREAMER=1 + AC_DEFINE([HAVE_GSTREAMER], 1, [Have GStreamer?]) + ], + [ + HAVE_GSTREAMER=0 + if test "x$gstreamer" = xyes ; then + AC_MSG_ERROR([*** GStreamer not found ***]) + fi + ]) +else + HAVE_GSTREAMER=0 +fi + +AC_SUBST(GSTREAMER_CFLAGS) +AC_SUBST(GSTREAMER_LIBS) + ### Null output (optional) #### AC_ARG_ENABLE([null], @@ -369,6 +401,7 @@ BUILTIN_DSO=0 BUILTIN_PULSE=0 BUILTIN_ALSA=0 BUILTIN_OSS=0 +BUILTIN_GSTREAMER=0 BUILTIN_NULL=0 case "x$with_builtin" in @@ -380,6 +413,7 @@ case "x$with_builtin" in BUILTIN_PULSE=1 HAVE_ALSA=0 HAVE_OSS=0 + HAVE_GSTREAMER=0 HAVE_NULL=0 ;; @@ -391,6 +425,19 @@ case "x$with_builtin" in BUILTIN_ALSA=1 HAVE_OSS=0 HAVE_PULSE=0 + HAVE_GSTREAMER=0 + HAVE_NULL=0 + ;; + + xgstreamer) + if test "x$HAVE_GSTREAMER" != x1 ; then + AC_MSG_ERROR([*** GStremaer selected for builtin driver, but not enabled. ***]) + fi + + BUILTIN_GSTREAMER=1 + HAVE_ALSA=0 + HAVE_OSS=0 + HAVE_PULSE=0 HAVE_NULL=0 ;; @@ -402,6 +449,7 @@ case "x$with_builtin" in BUILTIN_OSS=1 HAVE_ALSA=0 HAVE_PULSE=0 + HAVE_GSTREAMER=0 HAVE_NULL=0 ;; @@ -414,6 +462,7 @@ case "x$with_builtin" in HAVE_PULSE=0 HAVE_ALSA=0 HAVE_OSS=0 + HAVE_GSTREAMER=0 ;; xdso) @@ -426,7 +475,7 @@ case "x$with_builtin" in AC_MSG_ERROR([*** Unknown driver $with_builtin selected for builtin ***]) esac -if test "x$HAVE_PULSE" != x1 -a "x$HAVE_ALSA" != x1 -a "x$HAVE_OSS" != x1 -a "x$HAVE_NULL" != x1 ; then +if test "x$HAVE_PULSE" != x1 -a "x$HAVE_ALSA" != x1 -a "x$HAVE_OSS" != x1 -a "x$HAVE_GSTREAMER" != x1 -a "x$HAVE_NULL" != x1 ; then AC_MSG_ERROR([*** No backend enabled. ***]) fi @@ -434,20 +483,24 @@ AC_SUBST(HAVE_DSO) AC_SUBST(HAVE_PULSE) AC_SUBST(HAVE_ALSA) AC_SUBST(HAVE_OSS) +AC_SUBST(HAVE_GSTREAMER) AC_SUBST(HAVE_NULL) AC_SUBST(BUILTIN_DSO) AC_SUBST(BUILTIN_PULSE) AC_SUBST(BUILTIN_ALSA) AC_SUBST(BUILTIN_OSS) +AC_SUBST(BUILTIN_GSTREAMER) AC_SUBST(BUILTIN_NULL) AM_CONDITIONAL([HAVE_PULSE], [test "x$HAVE_PULSE" = x1]) AM_CONDITIONAL([HAVE_ALSA], [test "x$HAVE_ALSA" = x1]) AM_CONDITIONAL([HAVE_OSS], [test "x$HAVE_OSS" = x1]) +AM_CONDITIONAL([HAVE_GSTREAMER], [test "x$HAVE_GSTREAMER" = x1]) AM_CONDITIONAL([HAVE_NULL], [test "x$HAVE_NULL" = x1]) AM_CONDITIONAL([BUILTIN_DSO], [test "x$BUILTIN_DSO" = x1]) AM_CONDITIONAL([BUILTIN_PULSE], [test "x$BUILTIN_PULSE" = x1]) AM_CONDITIONAL([BUILTIN_ALSA], [test "x$BUILTIN_ALSA" = x1]) AM_CONDITIONAL([BUILTIN_OSS], [test "x$BUILTIN_OSS" = x1]) +AM_CONDITIONAL([BUILTIN_GSTREAMER], [test "x$BUILTIN_GSTREAMER" = x1]) AM_CONDITIONAL([BUILTIN_NULL], [test "x$BUILTIN_NULL" = x1]) GTK_DOC_CHECK(1.9) @@ -500,6 +553,15 @@ if test "x$BUILTIN_OSS" = "x1" ; then ENABLE_BUILTIN_OSS=yes fi +ENABLE_GSTREAMER=no +if test "x$HAVE_GSTREAMER" = "x1" ; then + ENABLE_GSTREAMER=yes +fi +ENABLE_BUILTIN_GSTREAMER=no +if test "x$BUILTIN_GSTREAMER" = "x1" ; then + ENABLE_BUILTIN_GSTREAMER=yes +fi + ENABLE_NULL=no if test "x$HAVE_NULL" = "x1" ; then ENABLE_NULL=yes @@ -531,6 +593,8 @@ echo " Builtin ALSA: ${ENABLE_BUILTIN_ALSA} Enable OSS: ${ENABLE_OSS} Builtin OSS: ${ENABLE_BUILTIN_OSS} + Enable GStreamer: ${ENABLE_GSTREAMER} + Builtin GStreamer: ${ENABLE_BUILTIN_GSTREAMER} Enable Null Output: ${ENABLE_NULL} Builtin Null Output: ${ENABLE_BUILTIN_NULL} Enable GTK+: ${ENABLE_GTK} diff --git a/src/Makefile.am b/src/Makefile.am index 9f1549b..dddfeba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,6 +195,41 @@ libcanberra_oss_la_LDFLAGS = \ endif endif +if HAVE_GSTREAMER +if BUILTIN_GSTREAMER + +libcanberra_la_SOURCES += \ + gstreamer.c +libcanberra_la_CFLAGS += \ + $(GST_CFLAGS) +libcanberra_la_LIBADD += \ + $(GST_LIBS) + +else + +plugin_LTLIBRARIES += \ + libcanberra-gstreamer.la + +libcanberra_gstreamer_la_SOURCES = \ + gstreamer.c +libcanberra_gstreamer_la_CFLAGS = \ + $(GST_CFLAGS) \ + -Ddriver_open=gstreamer_driver_open \ + -Ddriver_destroy=gstreamer_driver_destroy \ + -Ddriver_change_device=gstreamer_driver_change_device \ + -Ddriver_change_props=gstreamer_driver_change_props \ + -Ddriver_play=gstreamer_driver_play \ + -Ddriver_cancel=gstreamer_driver_cancel \ + -Ddriver_cache=gstreamer_driver_cache +libcanberra_gstreamer_la_LIBADD = \ + $(GST_LIBS) \ + libcanberra.la +libcanberra_gstreamer_la_LDFLAGS = \ + -avoid-version -module -export-dynamic + +endif +endif + if HAVE_NULL if BUILTIN_NULL diff --git a/src/gstreamer.c b/src/gstreamer.c new file mode 100644 index 0000000..172d410 --- /dev/null +++ b/src/gstreamer.c @@ -0,0 +1,436 @@ +/*** + This file is part of libcanberra. + + Copyright 2008 Nokia Corporation and/or its subsidiary(-ies). + + Author: Marc-Andre Lureau + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "canberra.h" +#include "common.h" +#include "driver.h" +#include "llist.h" +#include "read-sound-file.h" +#include "sound-theme-spec.h" +#include "malloc.h" + +struct outstanding { + CA_LLIST_FIELDS(struct outstanding); + ca_bool_t dead; + uint32_t id; + ca_finish_callback_t callback; + void *userdata; + GstElement *pipeline; + struct ca_context *context; +}; + +struct private { + ca_theme_data *theme; + ca_mutex *outstanding_mutex; + ca_bool_t signal_semaphore; + sem_t semaphore; + ca_bool_t semaphore_allocated; + CA_LLIST_HEAD(struct outstanding, outstanding); +}; + +#define PRIVATE(c) ((struct private *) ((c)->private)) + +static void outstanding_free(struct outstanding *o) { + GstBus *bus; + + ca_assert(o); + + bus = gst_pipeline_get_bus(GST_PIPELINE (o->pipeline)); + gst_bus_set_sync_handler(bus, NULL, NULL); + gst_object_unref(bus); + + if (o->pipeline) + gst_object_unref(GST_OBJECT(o->pipeline)); + + ca_free(o); +} + +int driver_open(ca_context *c) { + GError *error = NULL; + struct private *p; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(!PRIVATE(c), CA_ERROR_INVALID); + ca_return_val_if_fail(!c->driver || ca_streq(c->driver, "gstreamer"), CA_ERROR_NODRIVER); + + gst_init_check (NULL, NULL, &error); + if (error != NULL) { + g_warning("gst_init: %s ", error->message); + g_error_free(error); + return CA_ERROR_INVALID; + } + + if (!(p = ca_new0(struct private, 1))) + return CA_ERROR_OOM; + + if (!(p->outstanding_mutex = ca_mutex_new())) { + driver_destroy(c); + return CA_ERROR_OOM; + } + + if (sem_init(&p->semaphore, 0, 0) < 0) { + driver_destroy(c); + return CA_ERROR_OOM; + } + + p->semaphore_allocated = TRUE; + + c->private = p; + + return CA_SUCCESS; +} + +int driver_destroy(ca_context *c) { + struct private *p; + struct outstanding *out; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(PRIVATE(c), CA_ERROR_STATE); + + p = PRIVATE(c); + + if (p->outstanding_mutex) { + ca_mutex_lock(p->outstanding_mutex); + + /* Tell all player threads to terminate */ + out = p->outstanding; + while (out) { + GstElement *pipeline; + + if (out->dead) { + out = out->next; + continue; + } + + pipeline = out->pipeline; + out->dead = TRUE; + + if (out->callback) + out->callback(c, out->id, CA_ERROR_DESTROYED, out->userdata); + + out = out->next; + + ca_mutex_unlock(p->outstanding_mutex); + + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(pipeline)); + + ca_mutex_lock(p->outstanding_mutex); + } + + if (p->semaphore_allocated) { + /* Now wait until all players are destroyed */ + p->signal_semaphore = TRUE; + while (p->outstanding) { + ca_mutex_unlock(p->outstanding_mutex); + sem_wait(&p->semaphore); + ca_mutex_lock(p->outstanding_mutex); + } + } + + ca_mutex_unlock(p->outstanding_mutex); + ca_mutex_free(p->outstanding_mutex); + } + + if (p->theme) + ca_theme_data_free(p->theme); + + if (p->semaphore_allocated) + sem_destroy(&p->semaphore); + + ca_free(p); + + /* no gst_deinit (), see doc */ + + return CA_SUCCESS; +} + +int driver_change_device(ca_context *c, char *device) { + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(PRIVATE(c), CA_ERROR_STATE); + + return CA_SUCCESS; +} + +int driver_change_props(ca_context *c, ca_proplist *changed, ca_proplist *merged) { + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(changed, CA_ERROR_INVALID); + ca_return_val_if_fail(merged, CA_ERROR_INVALID); + ca_return_val_if_fail(PRIVATE(c), CA_ERROR_STATE); + + return CA_SUCCESS; +} + +static GstBusSyncReply bus_cb(GstBus *bus, GstMessage *message, gpointer data) { + int err; + struct outstanding *out; + struct private *p; + + ca_return_val_if_fail(bus, GST_BUS_DROP); + ca_return_val_if_fail(message, GST_BUS_DROP); + ca_return_val_if_fail(data, GST_BUS_DROP); + + out = data; + p = PRIVATE(out->context); + + switch (GST_MESSAGE_TYPE(message)) { + /* for all elements */ + case GST_MESSAGE_UNKNOWN: + return GST_BUS_DROP; + case GST_MESSAGE_ERROR: + err = CA_ERROR_SYSTEM; + break; + /* only from bin */ + case GST_MESSAGE_EOS: + if (GST_OBJECT(out->pipeline) != GST_MESSAGE_SRC(message)) + return GST_BUS_DROP; + + err = CA_SUCCESS; + break; + case GST_MESSAGE_STATE_CHANGED: { + GstState pending; + + if (GST_OBJECT(out->pipeline) != GST_MESSAGE_SRC(message)) + return GST_BUS_DROP; + + gst_message_parse_state_changed(message, NULL, NULL, &pending); + /* g_debug (gst_element_state_get_name (pending)); */ + + if (pending == GST_STATE_NULL || pending == GST_STATE_VOID_PENDING) + err = CA_SUCCESS; + else + return GST_BUS_DROP; + break; + } + default: + return GST_BUS_DROP; + } + + if (!out->dead && out->callback) + out->callback(out->context, out->id, err, out->userdata); + + ca_mutex_lock(p->outstanding_mutex); + + CA_LLIST_REMOVE(struct outstanding, p->outstanding, out); + + if (!p->outstanding && p->signal_semaphore) + sem_post(&p->semaphore); + + outstanding_free(out); + + ca_mutex_unlock(p->outstanding_mutex); + + return GST_BUS_DROP; +} + +struct ca_sound_file { + GstElement *fdsrc; +}; + +static int ca_gst_sound_file_open(ca_sound_file **_f, const char *fn) { + int fd; + ca_sound_file *f; + + ca_return_val_if_fail(_f, CA_ERROR_INVALID); + ca_return_val_if_fail(fn, CA_ERROR_INVALID); + + if ((fd = open(fn, O_RDONLY)) == -1) + return errno == ENOENT ? CA_ERROR_NOTFOUND : CA_ERROR_SYSTEM; + + if (!(f = ca_new0(ca_sound_file, 1))) + return CA_ERROR_OOM; + + if (!(f->fdsrc = gst_element_factory_make("fdsrc", NULL))) { + ca_free(f); + return CA_ERROR_OOM; + } + + g_object_set(GST_OBJECT(f->fdsrc), "fd", fd, NULL); + *_f = f; + + return CA_SUCCESS; +} + +static void on_pad_added(GstElement *element, GstPad *pad, gboolean arg1, gpointer data) +{ + GstPad *sinkpad; + GstElement *sink = GST_ELEMENT (data); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + + gst_pad_link (pad, sinkpad); + + gst_object_unref (sinkpad); +} + +int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_callback_t cb, void *userdata) { + struct private *p; + struct outstanding *out = NULL; + ca_sound_file *f; + GstElement *decodebin, *sink; + GstBus *bus; + int ret; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(proplist, CA_ERROR_INVALID); + ca_return_val_if_fail(!userdata || cb, CA_ERROR_INVALID); + + f = NULL; + sink = NULL; + decodebin = NULL; + + p = PRIVATE(c); + + if ((ret = ca_lookup_sound_with_callback(&f, ca_gst_sound_file_open, &p->theme, c->props, proplist)) < 0) + goto fail; + + if (!(out = ca_new0(struct outstanding, 1))) + return CA_ERROR_OOM; + + out->id = id; + out->callback = cb; + out->userdata = userdata; + out->context = c; + + if (!(out->pipeline = gst_pipeline_new(NULL)) + || !(decodebin = gst_element_factory_make("decodebin2", NULL)) + || !(sink = gst_element_factory_make("autoaudiosink", NULL))) { + ret = CA_ERROR_OOM; + goto fail; + } + + bus = gst_pipeline_get_bus(GST_PIPELINE (out->pipeline)); + gst_bus_set_sync_handler(bus, bus_cb, out); + gst_object_unref(bus); + + gst_bin_add_many(GST_BIN (out->pipeline), + f->fdsrc, decodebin, sink, NULL); + + if (!gst_element_link(f->fdsrc, decodebin)) { + f->fdsrc = NULL; + decodebin = NULL; + sink = NULL; + goto fail; + } + + g_signal_connect (decodebin, "new-decoded-pad", G_CALLBACK (on_pad_added), sink); + + decodebin = NULL; + sink = NULL; + ca_free(f); + f = NULL; + + ca_mutex_lock(p->outstanding_mutex); + CA_LLIST_PREPEND(struct outstanding, p->outstanding, out); + ca_mutex_unlock(p->outstanding_mutex); + + if (gst_element_set_state(out->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + ca_mutex_lock(p->outstanding_mutex); + CA_LLIST_REMOVE(struct outstanding, p->outstanding, out); + ca_mutex_unlock(p->outstanding_mutex); + + ret = CA_ERROR_NOTAVAILABLE; + goto fail; + } + + return CA_SUCCESS; + + fail: + if (f && f->fdsrc) + gst_object_unref(f->fdsrc); + + if (f) + ca_free(f); + + if (sink) + gst_object_unref(sink); + + if (decodebin) + gst_object_unref(decodebin); + + if (out->pipeline) + gst_object_unref(out->pipeline); + + ca_free(out); + + return ret; +} + +int driver_cancel(ca_context *c, uint32_t id) { + struct private *p; + struct outstanding *out = NULL; + + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(PRIVATE(c), CA_ERROR_STATE); + + p = PRIVATE(c); + + ca_mutex_lock(p->outstanding_mutex); + + for (out = p->outstanding; out; out = out->next) { + GstElement *pipeline; + + if (out->id != id) + continue; + + if (out->pipeline == NULL) + break; + + if (out->callback) + out->callback(c, out->id, CA_ERROR_CANCELED, out->userdata); + + pipeline = out->pipeline; + out->dead = TRUE; + + ca_mutex_unlock(p->outstanding_mutex); + gst_element_set_state(out->pipeline, GST_STATE_NULL); + ca_mutex_lock(p->outstanding_mutex); + + gst_object_unref(pipeline); + } + + ca_mutex_unlock(p->outstanding_mutex); + + return CA_SUCCESS; +} + +int driver_cache(ca_context *c, ca_proplist *proplist) { + ca_return_val_if_fail(c, CA_ERROR_INVALID); + ca_return_val_if_fail(proplist, CA_ERROR_INVALID); + ca_return_val_if_fail(PRIVATE(c), CA_ERROR_STATE); + + return CA_ERROR_NOTSUPPORTED; +} -- cgit