summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoe Marcus Clarke <marcus@FreeBSD.org>2008-08-20 16:53:56 -0400
committerLennart Poettering <lennart@poettering.net>2008-08-21 04:23:38 +0200
commitd7d6d26e1f858841f333264a07edc512066269af (patch)
tree85659c6e24d69ad7fcc1ace2ffdfcf5f935f6e11
parentf1ca2213fa649a9e3635ec7638b6c5bced594787 (diff)
OSS driver take 3
This adds the necessary error checking, and fixes the s/alsa/oss/ problem in configure.ac. Signed-off-by: Lennart Poettering <lennart@poettering.net>
-rw-r--r--configure.ac85
-rw-r--r--src/Makefile.am28
-rw-r--r--src/alsa.c4
-rw-r--r--src/driver-order.c3
-rw-r--r--src/oss.c498
5 files changed, 617 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index 2a48177..f41cfe9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -202,6 +202,35 @@ fi
AC_SUBST(ALSA_CFLAGS)
AC_SUBST(ALSA_LIBS)
+### OSS support (optional) ###
+AC_ARG_ENABLE([oss],
+ AC_HELP_STRING([--disable-oss], [Disable optional OSS support]),
+ [
+ case "${enableval}" in
+ yes) oss=yes ;;
+ no) oss=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-oss) ;;
+ esac
+ ],
+ [oss=auto])
+
+if test "x${oss}" != xno ; then
+ AC_CHECK_HEADERS(soundcard.h sys/soundcard.h machine/soundcard.h)
+ if test "${ac_cv_header_sys_soundcard_h}" = "yes" || \
+ test "${ac_cv_header_soundcard_h}" = "yes" || \
+ test "${ac_cv_header_machine_soundcard_h}" = "yes"; then
+ HAVE_OSS=1
+ AC_DEFINE([HAVE_OSS], 1, [Have OSS?])
+ else
+ HAVE_OSS=0
+ if test "x$oss" = xyes ; then
+ AC_MSG_ERROR([*** OSS not found ***])
+ fi
+ fi
+else
+ HAVE_OSS=0
+fi
+
### PulseAudio (optional) ####
AC_ARG_ENABLE([pulse],
@@ -339,6 +368,7 @@ HAVE_DSO=0
BUILTIN_DSO=0
BUILTIN_PULSE=0
BUILTIN_ALSA=0
+BUILTIN_OSS=0
BUILTIN_NULL=0
case "x$with_builtin" in
@@ -349,6 +379,7 @@ case "x$with_builtin" in
BUILTIN_PULSE=1
HAVE_ALSA=0
+ HAVE_OSS=0
HAVE_NULL=0
;;
@@ -358,10 +389,22 @@ case "x$with_builtin" in
fi
BUILTIN_ALSA=1
+ HAVE_OSS=0
HAVE_PULSE=0
HAVE_NULL=0
;;
+ xoss)
+ if test "x$HAV_OSS" != x1 ; then
+ AC_MSG_ERROR([*** OSS selected for builtin driver, but not enabled. ***])
+ fi
+
+ BUILTIN_OSS=1
+ HAVE_ALSA=0
+ HAVE_PULSE=0
+ HAVE_NULL=0
+ ;;
+
xnull)
if test "x$HAVE_NULL" != x1 ; then
AC_MSG_ERROR([*** Null output selected for builtin driver, but not enabled. ***])
@@ -370,6 +413,7 @@ case "x$with_builtin" in
BUILTIN_NULL=1
HAVE_PULSE=0
HAVE_ALSA=0
+ HAVE_OSS=0
;;
xdso)
@@ -382,24 +426,28 @@ 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_NULL" != x1 ; then
+if test "x$HAVE_PULSE" != x1 -a "x$HAVE_ALSA" != x1 -a "x$HAVE_OSS" != x1 -a "x$HAVE_NULL" != x1 ; then
AC_MSG_ERROR([*** No backend enabled. ***])
fi
AC_SUBST(HAVE_DSO)
AC_SUBST(HAVE_PULSE)
AC_SUBST(HAVE_ALSA)
+AC_SUBST(HAVE_OSS)
AC_SUBST(HAVE_NULL)
AC_SUBST(BUILTIN_DSO)
AC_SUBST(BUILTIN_PULSE)
AC_SUBST(BUILTIN_ALSA)
+AC_SUBST(BUILTIN_OSS)
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_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_NULL], [test "x$BUILTIN_NULL" = x1])
GTK_DOC_CHECK(1.9)
@@ -443,6 +491,15 @@ if test "x$BUILTIN_ALSA" = "x1" ; then
ENABLE_BUILTIN_ALSA=yes
fi
+ENABLE_OSS=no
+if test "x$HAVE_OSS" = "x1" ; then
+ ENABLE_OSS=yes
+fi
+ENABLE_BUILTIN_OSS=no
+if test "x$BUILTIN_OSS" = "x1" ; then
+ ENABLE_BUILTIN_OSS=yes
+fi
+
ENABLE_NULL=no
if test "x$HAVE_NULL" = "x1" ; then
ENABLE_NULL=yes
@@ -472,8 +529,34 @@ echo "
Builtin PulseAudio: ${ENABLE_BUILTIN_PULSE}
Enable ALSA: ${ENABLE_ALSA}
Builtin ALSA: ${ENABLE_BUILTIN_ALSA}
+ Enable OSS: ${ENABLE_OSS}
+ Builtin OSS: ${ENABLE_BUILTIN_OSS}
Enable Null Output: ${ENABLE_NULL}
Builtin Null Output: ${ENABLE_BUILTIN_NULL}
Enable GTK+: ${ENABLE_GTK}
GTK Modules Directory: ${GTK_MODULES_DIR}
"
+
+
+if test "x$HAVE_OSS" = "x1" -a "x$HAVE_ALSA" = "x1" ; then
+
+ echo "
+WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
+
+You enabled the OSS driver although the ALSA driver is
+available. Please note that if ALSA is available the OSS driver is a
+very bad choice, since it currently doesn't support resampling or
+converting sample types to the necessities of the hardware if the
+hardware does not directly support the format/rate of the sound file
+to play.
+
+Packagers of Linux distributions! Please think twice if you package
+the OSS driver! It is probably best to not to pacakge it at all -- at
+least until the OSS driver learned sample type conversion and the most
+basic resampling. Otherwise you might end up getting bug reports from
+users misunderstanding the OSS vs. ALSA situation.
+
+WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
+"
+
+fi
diff --git a/src/Makefile.am b/src/Makefile.am
index 198202b..9f1549b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -167,6 +167,34 @@ libcanberra_alsa_la_LDFLAGS = \
endif
endif
+if HAVE_OSS
+if BUILTIN_OSS
+
+libcanberra_la_SOURCES += \
+ oss.c
+
+else
+
+plugin_LTLIBRARIES += \
+ libcanberra-oss.la
+
+libcanberra_oss_la_SOURCES = \
+ oss.c
+libcanberra_oss_la_CFLAGS = \
+ -Ddriver_open=oss_driver_open \
+ -Ddriver_destroy=oss_driver_destroy \
+ -Ddriver_change_device=oss_driver_change_device \
+ -Ddriver_change_props=oss_driver_change_props \
+ -Ddriver_play=oss_driver_play \
+ -Ddriver_cancel=oss_driver_cancel \
+ -Ddriver_cache=oss_driver_cache
+libcanberra_oss_la_LIBADD = \
+ libcanberra.la
+libcanberra_oss_la_LDFLAGS = \
+ -avoid-version -module -export-dynamic
+endif
+endif
+
if HAVE_NULL
if BUILTIN_NULL
diff --git a/src/alsa.c b/src/alsa.c
index 21da013..eff1598 100644
--- a/src/alsa.c
+++ b/src/alsa.c
@@ -199,6 +199,10 @@ static int translate_error(int error) {
return CA_ERROR_OOM;
case -EBUSY:
return CA_ERROR_NOTAVAILABLE;
+ case -EINVAL:
+ return CA_ERROR_INVALID;
+ case -ENOSYS:
+ return CA_ERROR_NOTSUPPORTED;
default:
if (ca_debug())
fprintf(stderr, "Got unhandled error from ALSA: %s\n", snd_strerror(error));
diff --git a/src/driver-order.c b/src/driver-order.c
index 252111b..36b4e8b 100644
--- a/src/driver-order.c
+++ b/src/driver-order.c
@@ -34,6 +34,9 @@ const char* const ca_driver_order[] = {
#ifdef HAVE_ALSA
"alsa",
#endif
+#ifdef HAVE_OSS
+ "oss",
+#endif
/* ... */
NULL
};
diff --git a/src/oss.c b/src/oss.c
new file mode 100644
index 0000000..351f436
--- /dev/null
+++ b/src/oss.c
@@ -0,0 +1,498 @@
+/***
+ This file is part of libcanberra.
+
+ Copyright 2008 Lennart Poettering
+ Joe Marcus Clarke
+
+ 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, see
+ <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <math.h>
+#include <unistd.h>
+
+#ifdef HAVE_MACHINE_SOUNDCARD_H
+# include <machine/soundcard.h>
+#else
+# ifdef HAVE_SOUNDCARD_H
+# include <soundcard.h>
+# else
+# include <sys/soundcard.h>
+# endif
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <pthread.h>
+#include <semaphore.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 private;
+
+struct outstanding {
+ CA_LLIST_FIELDS(struct outstanding);
+ ca_bool_t dead;
+ uint32_t id;
+ ca_finish_callback_t callback;
+ void *userdata;
+ ca_sound_file *file;
+ int pcm;
+ int pipe_fd[2];
+ 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) {
+ ca_assert(o);
+
+ if (o->pipe_fd[1] >= 0)
+ close(o->pipe_fd[1]);
+
+ if (o->pipe_fd[0] >= 0)
+ close(o->pipe_fd[0]);
+
+ if (o->file)
+ ca_sound_file_close(o->file);
+
+ if (o->pcm >= 0) {
+ close(o->pcm);
+ o->pcm = -1;
+ }
+
+ ca_free(o);
+}
+
+int driver_open(ca_context *c) {
+ struct private *p;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(!c->driver || ca_streq(c->driver, "oss"), CA_ERROR_NODRIVER);
+ ca_return_val_if_fail(!PRIVATE(c), CA_ERROR_STATE);
+
+ if (!(c->private = 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;
+
+ 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(c->private, CA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ if (p->outstanding_mutex) {
+ ca_mutex_lock(p->outstanding_mutex);
+
+ /* Tell all player threads to terminate */
+ for (out = p->outstanding; out; out = out->next) {
+
+ if (out->dead)
+ continue;
+
+ out->dead = TRUE;
+
+ if (out->callback)
+ out->callback(c, out->id, CA_ERROR_DESTROYED, out->userdata);
+
+ /* This will cause the thread to wakeup and terminate */
+ if (out->pipe_fd[1] >= 0) {
+ close(out->pipe_fd[1]);
+ out->pipe_fd[1] = -1;
+ }
+ }
+
+ 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);
+
+ c->private = NULL;
+
+ 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(c->private, 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);
+
+ 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);
+
+ return CA_ERROR_NOTSUPPORTED;
+}
+
+static int translate_error(int error) {
+
+ switch (error) {
+ case ENODEV:
+ case ENOENT:
+ return CA_ERROR_NOTFOUND;
+ case EACCES:
+ case EPERM:
+ return CA_ERROR_ACCESS;
+ case ENOMEM:
+ return CA_ERROR_OOM;
+ case EBUSY:
+ return CA_ERROR_NOTAVAILABLE;
+ case EINVAL:
+ return CA_ERROR_INVALID;
+ case ENOSYS:
+ return CA_ERROR_NOTSUPPORTED;
+ default:
+ if (ca_debug())
+ fprintf(stderr, "Got unhandled error from OSS: %s\n", strerror(error));
+ return CA_ERROR_IO;
+ }
+}
+
+static int open_oss(ca_context *c, struct outstanding *out) {
+ struct private *p;
+ int mode, val, test, ret;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, CA_ERROR_STATE);
+ ca_return_val_if_fail(out, CA_ERROR_INVALID);
+ ca_return_val_if_fail(ca_sound_file_get_nchannels(out->file) > 2, CA_ERROR_NOTSUPPORTED);
+
+ p = PRIVATE(c);
+
+ if ((out->pcm = open(c->device ? c->device : "/dev/dsp", O_WRONLY | O_NONBLOCK, 0)) < 0)
+ goto finish_errno;
+
+ if ((mode = fcntl(out->pcm, F_GETFL)) < 0)
+ goto finish_errno;
+
+ mode &= ~O_NONBLOCK;
+
+ if (fcntl(out->pcm, F_SETFL, mode) < 0)
+ goto finish_errno;
+
+ switch (ca_sound_file_get_sample_type(out->file)) {
+ case CA_SAMPLE_U8:
+ val = AFMT_U8;
+ break;
+ case CA_SAMPLE_S16NE:
+ val = AFMT_S16_NE;
+ break;
+ case CA_SAMPLE_S16RE:
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ val = AFMT_S16_BE;
+#else
+ val = AFMT_S16_LE;
+#endif
+ break;
+ }
+
+ test = val;
+ if (ioctl(out->pcm, SNDCTL_DSP_SETFMT, &val) < 0)
+ goto finish_errno;
+
+ if (val != test) {
+ ret = CA_ERROR_NOTSUPPORTED;
+ goto finish_ret;
+ }
+
+ test = val = (int) ca_sound_file_get_nchannels(out->file);
+ if (ioctl(out->pcm, SNDCTL_DSP_CHANNELS, &val) < 0)
+ goto finish_errno;
+
+ if (val != test) {
+ ret = CA_ERROR_NOTSUPPORTED;
+ goto finish_ret;
+ }
+
+ test = val = (int) ca_sound_file_get_rate(out->file);
+ if (ioctl(out->pcm, SNDCTL_DSP_SPEED, &val) < 0)
+ goto finish_errno;
+
+ /* Check to make sure the configured rate is close enough to the
+ * requested rate. */
+ if (fabs((double) (val - test)) > test * 0.05) {
+ ret = CA_ERROR_NOTSUPPORTED;
+ goto finish_ret;
+ }
+
+ return CA_SUCCESS;
+
+finish_errno:
+ return translate_error(errno);
+
+finish_ret:
+ return ret;
+}
+
+#define BUFSIZE (4*1024)
+
+static void* thread_func(void *userdata) {
+ struct outstanding *out = userdata;
+ int ret;
+ void *data, *d = NULL;
+ size_t fs, data_size;
+ size_t nbytes = 0;
+ struct pollfd pfd[2];
+ nfds_t n_pfd = 2;
+ struct private *p;
+
+ p = PRIVATE(out->context);
+
+ pthread_detach(pthread_self());
+
+ fs = ca_sound_file_frame_size(out->file);
+ data_size = (BUFSIZE/fs)*fs;
+
+ if (!(data = ca_malloc(data_size))) {
+ ret = CA_ERROR_OOM;
+ goto finish;
+ }
+
+ pfd[0].fd = out->pipe_fd[0];
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+ pfd[1].fd = out->pcm;
+ pfd[1].events = POLLOUT;
+ pfd[1].revents = 0;
+
+ for (;;) {
+ ssize_t bytes_written;
+
+ if (out->dead)
+ break;
+
+ if (poll(pfd, n_pfd, -1) < 0) {
+ ret = CA_ERROR_SYSTEM;
+ goto finish;
+ }
+
+ /* We have been asked to shut down */
+ if (pfd[0].revents)
+ break;
+
+ if (pfd[1].revents != POLLOUT) {
+ ret = CA_ERROR_IO;
+ goto finish;
+ }
+
+ if (nbytes <= 0) {
+ nbytes = data_size;
+
+ if ((ret = ca_sound_file_read_arbitrary(out->file, data, &nbytes)) < 0)
+ goto finish;
+
+ d = data;
+ }
+
+ if (nbytes <= 0)
+ break;
+
+ if ((bytes_written = write(out->pcm, d, nbytes)) <= 0) {
+ ret = translate_error(errno);
+ goto finish;
+ }
+
+ nbytes -= (size_t) bytes_written;
+ d = (uint8_t*) d + (size_t) bytes_written;
+ }
+
+ ret = CA_SUCCESS;
+
+finish:
+
+ ca_free(data);
+
+ if (!out->dead)
+ if (out->callback)
+ out->callback(out->context, out->id, ret, 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 NULL;
+}
+
+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;
+ int ret;
+ pthread_t thread;
+
+ 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);
+ ca_return_val_if_fail(c->private, CA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ if (!(out = ca_new0(struct outstanding, 1))) {
+ ret = CA_ERROR_OOM;
+ goto finish;
+ }
+
+ out->context = c;
+ out->id = id;
+ out->callback = cb;
+ out->userdata = userdata;
+ out->pipe_fd[0] = out->pipe_fd[1] = -1;
+
+ if (pipe(out->pipe_fd) < 0) {
+ ret = CA_ERROR_SYSTEM;
+ goto finish;
+ }
+
+ if ((ret = ca_lookup_sound(&out->file, &p->theme, c->props, proplist)) < 0)
+ goto finish;
+
+ if ((ret = open_oss(c, out)) < 0)
+ goto finish;
+
+ /* OK, we're ready to go, so let's add this to our list */
+ ca_mutex_lock(p->outstanding_mutex);
+ CA_LLIST_PREPEND(struct outstanding, p->outstanding, out);
+ ca_mutex_unlock(p->outstanding_mutex);
+
+ if (pthread_create(&thread, NULL, thread_func, out) < 0) {
+ ret = CA_ERROR_OOM;
+
+ ca_mutex_lock(p->outstanding_mutex);
+ CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
+ ca_mutex_unlock(p->outstanding_mutex);
+
+ goto finish;
+ }
+
+ ret = CA_SUCCESS;
+
+finish:
+
+ /* We keep the outstanding struct around if we need clean up later to */
+ if (ret != CA_SUCCESS)
+ outstanding_free(out);
+
+ return ret;
+}
+
+int driver_cancel(ca_context *c, uint32_t id) {
+ struct private *p;
+ struct outstanding *out;
+
+ ca_return_val_if_fail(c, CA_ERROR_INVALID);
+ ca_return_val_if_fail(c->private, CA_ERROR_STATE);
+
+ p = PRIVATE(c);
+
+ ca_mutex_lock(p->outstanding_mutex);
+
+ for (out = p->outstanding; out; out = out->next) {
+
+ if (out->id != id)
+ continue;
+
+ if (out->dead)
+ continue;
+
+ out->dead = TRUE;
+
+ if (out->callback)
+ out->callback(c, out->id, CA_ERROR_CANCELED, out->userdata);
+
+ /* This will cause the thread to wakeup and terminate */
+ if (out->pipe_fd[1] >= 0) {
+ close(out->pipe_fd[1]);
+ out->pipe_fd[1] = -1;
+ }
+ }
+
+ ca_mutex_unlock(p->outstanding_mutex);
+
+ return CA_SUCCESS;
+}