summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@gmail.com>2008-08-27 22:35:57 +0300
committerLennart Poettering <lennart@poettering.net>2008-08-27 23:47:45 +0200
commit8e82495a53a0f532b5725477b11b1837ed8c7b80 (patch)
treed642c09365a824d290b8012c7c942f262c19fc25
parent81143a4d2956bc6444fe6dee037155914ffc6a29 (diff)
driver: GStreamer using decodebin2
Signed-off-by: Marc-André Lureau <marcandre.lureau@gmail.com> Signed-off-by: Lennart Poettering <lennart@poettering.net>
-rw-r--r--configure.ac66
-rw-r--r--src/Makefile.am35
-rw-r--r--src/gstreamer.c436
3 files changed, 536 insertions, 1 deletions
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 <marc-andre.lureau@nokia.com>
+
+ 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
+ <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gst/gst.h>
+
+#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;
+}