X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/12dc4c21ac4e31becf687513ac9852a3ae6893e5..54afcf25980662d3033328705ec481c62b223e56:/src/utils/padsp.c diff --git a/src/utils/padsp.c b/src/utils/padsp.c index 8bad126b..2e6e5575 100644 --- a/src/utils/padsp.c +++ b/src/utils/padsp.c @@ -1,20 +1,21 @@ -/* $Id$ */ - /*** - This file is part of polypaudio. - - polypaudio is free software; you can redistribute it and/or modify + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman for Cendio AB + + PulseAudio 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 of the License, or (at your option) any later version. - - polypaudio is distributed in the hope that it will be useful, but + + PulseAudio 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 General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License - along with polypaudio; if not, write to the Free Software + along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,15 +46,26 @@ #include #include +#ifdef __linux__ #include +#endif + +#include +#include +#include + +/* On some systems SIOCINQ isn't defined, but FIONREAD is just an alias */ +#if !defined(SIOCINQ) && defined(FIONREAD) +# define SIOCINQ FIONREAD +#endif -#include -#include -#include +/* make sure gcc doesn't redefine open and friends as macros */ +#undef open +#undef open64 typedef enum { FD_INFO_MIXER, - FD_INFO_PLAYBACK + FD_INFO_STREAM, } fd_info_type_t; typedef struct fd_info fd_info; @@ -61,7 +74,7 @@ struct fd_info { pthread_mutex_t mutex; int ref; int unusable; - + fd_info_type_t type; int app_fd, thread_fd; @@ -71,18 +84,25 @@ struct fd_info { pa_threaded_mainloop *mainloop; pa_context *context; - pa_stream *stream; + pa_stream *play_stream; + pa_stream *rec_stream; + int play_precork; + int rec_precork; pa_io_event *io_event; + pa_io_event_flags_t io_flags; void *buf; + size_t rec_offset; int operation_success; - pa_cvolume volume; - uint32_t sink_index; + pa_cvolume sink_volume, source_volume; + uint32_t sink_index, source_index; int volume_modify_count; - + + int optr_n_blocks; + PA_LLIST_FIELDS(fd_info); }; @@ -98,9 +118,20 @@ static int (*_ioctl)(int, int, void*) = NULL; static int (*_close)(int) = NULL; static int (*_open)(const char *, int, mode_t) = NULL; static FILE* (*_fopen)(const char *path, const char *mode) = NULL; +static int (*_stat)(const char *, struct stat *) = NULL; +#ifdef _STAT_VER +static int (*___xstat)(int, const char *, struct stat *) = NULL; +#endif +#ifdef HAVE_OPEN64 static int (*_open64)(const char *, int, mode_t) = NULL; static FILE* (*_fopen64)(const char *path, const char *mode) = NULL; +static int (*_stat64)(const char *, struct stat64 *) = NULL; +#ifdef _STAT_VER +static int (*___xstat64)(int, const char *, struct stat64 *) = NULL; +#endif +#endif static int (*_fclose)(FILE *f) = NULL; +static int (*_access)(const char *, int) = NULL; /* dlsym() violates ISO C, so confide the breakage into this function to * avoid warnings. */ @@ -141,6 +172,46 @@ do { \ pthread_mutex_unlock(&func_mutex); \ } while(0) +#define LOAD_ACCESS_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_access) \ + _access = (int (*)(const char*, int)) dlsym_fn(RTLD_NEXT, "access"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_STAT_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_stat) \ + _stat = (int (*)(const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "stat"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_STAT64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_stat64) \ + _stat64 = (int (*)(const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "stat64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_XSTAT_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!___xstat) \ + ___xstat = (int (*)(int, const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "__xstat"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_XSTAT64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!___xstat64) \ + ___xstat64 = (int (*)(int, const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "__xstat64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + #define LOAD_FOPEN_FUNC() \ do { \ pthread_mutex_lock(&func_mutex); \ @@ -167,28 +238,50 @@ do { \ #define CONTEXT_CHECK_DEAD_GOTO(i, label) do { \ if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY) { \ - debug(__FILE__": Not connected: %s", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ goto label; \ } \ -} while(0); +} while(0) + +#define PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, label) do { \ +if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \ + !(i)->play_stream || pa_stream_get_state((i)->play_stream) != PA_STREAM_READY) { \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + goto label; \ +} \ +} while(0) -#define STREAM_CHECK_DEAD_GOTO(i, label) do { \ +#define RECORD_STREAM_CHECK_DEAD_GOTO(i, label) do { \ if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \ - !(i)->stream || pa_stream_get_state((i)->stream) != PA_STREAM_READY) { \ - debug(__FILE__": Not connected: %s", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + !(i)->rec_stream || pa_stream_get_state((i)->rec_stream) != PA_STREAM_READY) { \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ goto label; \ } \ -} while(0); +} while(0) + +static void debug(int level, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); -static void debug(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2); +#define DEBUG_LEVEL_ALWAYS 0 +#define DEBUG_LEVEL_NORMAL 1 +#define DEBUG_LEVEL_VERBOSE 2 -static void debug(const char *format, ...) { +static void debug(int level, const char *format, ...) { va_list ap; - if (getenv("PADSP_DEBUG")) { - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - } + const char *dlevel_s; + int dlevel; + + dlevel_s = getenv("PADSP_DEBUG"); + if (!dlevel_s) + return; + + dlevel = atoi(dlevel_s); + + if (dlevel < level) + return; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); } static int padsp_disabled(void) { @@ -202,26 +295,25 @@ static int padsp_disabled(void) { * -> disable /dev/dsp emulation, bit 2 -> disable /dev/sndstat * emulation, bit 3 -> disable /dev/mixer emulation. Hence a value * of 7 disables padsp entirely. */ - + pthread_mutex_lock(&func_mutex); if (!sym_resolved) { sym = (int*) dlsym(RTLD_DEFAULT, "__padsp_disabled__"); sym_resolved = 1; - } pthread_mutex_unlock(&func_mutex); if (!sym) return 0; - + return *sym; } static int dsp_cloak_enable(void) { if (padsp_disabled() & 1) return 0; - - if (getenv("PADSP_NO_DSP")) + + if (getenv("PADSP_NO_DSP") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -231,7 +323,7 @@ static int sndstat_cloak_enable(void) { if (padsp_disabled() & 2) return 0; - if (getenv("PADSP_NO_SNDSTAT")) + if (getenv("PADSP_NO_SNDSTAT") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -241,7 +333,7 @@ static int mixer_cloak_enable(void) { if (padsp_disabled() & 4) return 0; - if (getenv("PADSP_NO_MIXER")) + if (getenv("PADSP_NO_MIXER") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -256,7 +348,7 @@ static int function_enter(void) { /* Avoid recursive calls */ static pthread_once_t recursion_key_once = PTHREAD_ONCE_INIT; pthread_once(&recursion_key_once, recursion_key_alloc); - + if (pthread_getspecific(recursion_key)) return 0; @@ -271,23 +363,28 @@ static void function_exit(void) { static void fd_info_free(fd_info *i) { assert(i); - debug(__FILE__": freeing fd info (fd=%i)\n", i->app_fd); + debug(DEBUG_LEVEL_NORMAL, __FILE__": freeing fd info (fd=%i)\n", i->app_fd); dsp_drain(i); - + if (i->mainloop) pa_threaded_mainloop_stop(i->mainloop); - - if (i->stream) { - pa_stream_disconnect(i->stream); - pa_stream_unref(i->stream); + + if (i->play_stream) { + pa_stream_disconnect(i->play_stream); + pa_stream_unref(i->play_stream); + } + + if (i->rec_stream) { + pa_stream_disconnect(i->rec_stream); + pa_stream_unref(i->rec_stream); } if (i->context) { pa_context_disconnect(i->context); pa_context_unref(i->context); } - + if (i->mainloop) pa_threaded_mainloop_free(i->mainloop); @@ -309,12 +406,12 @@ static void fd_info_free(fd_info *i) { static fd_info *fd_info_ref(fd_info *i) { assert(i); - + pthread_mutex_lock(&i->mutex); assert(i->ref >= 1); i->ref++; -/* debug(__FILE__": ref++, now %i\n", i->ref); */ + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref++, now %i\n", i->ref); pthread_mutex_unlock(&i->mutex); return i; @@ -325,7 +422,7 @@ static void fd_info_unref(fd_info *i) { pthread_mutex_lock(&i->mutex); assert(i->ref >= 1); r = --i->ref; -/* debug(__FILE__": ref--, now %i\n", i->ref); */ + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref--, now %i\n", i->ref); pthread_mutex_unlock(&i->mutex); if (r <= 0) @@ -353,8 +450,8 @@ static void context_state_cb(pa_context *c, void *userdata) { static void reset_params(fd_info *i) { assert(i); - - i->sample_spec.format = PA_SAMPLE_ULAW; + + i->sample_spec.format = PA_SAMPLE_U8; i->sample_spec.channels = 1; i->sample_spec.rate = 8000; i->fragment_size = 0; @@ -367,9 +464,9 @@ static const char *client_name(char *buf, size_t n) { if ((e = getenv("PADSP_CLIENT_NAME"))) return e; - + if (pa_get_binary_name(p, sizeof(p))) - snprintf(buf, n, "OSS Emulation[%s]", pa_path_get_filename(p)); + snprintf(buf, n, "OSS Emulation[%s]", p); else snprintf(buf, n, "OSS"); @@ -388,8 +485,8 @@ static const char *stream_name(void) { static void atfork_prepare(void) { fd_info *i; - debug(__FILE__": atfork_prepare() enter\n"); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() enter\n"); + function_enter(); pthread_mutex_lock(&fd_infos_mutex); @@ -401,14 +498,13 @@ static void atfork_prepare(void) { pthread_mutex_lock(&func_mutex); - - debug(__FILE__": atfork_prepare() exit\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() exit\n"); } static void atfork_parent(void) { fd_info *i; - - debug(__FILE__": atfork_parent() enter\n"); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() enter\n"); pthread_mutex_unlock(&func_mutex); @@ -420,19 +516,19 @@ static void atfork_parent(void) { pthread_mutex_unlock(&fd_infos_mutex); function_exit(); - - debug(__FILE__": atfork_parent() exit\n"); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() exit\n"); } static void atfork_child(void) { fd_info *i; - - debug(__FILE__": atfork_child() enter\n"); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() enter\n"); /* We do only the bare minimum to get all fds closed */ pthread_mutex_init(&func_mutex, NULL); pthread_mutex_init(&fd_infos_mutex, NULL); - + for (i = fd_infos; i; i = i->next) { pthread_mutex_init(&i->mutex, NULL); @@ -442,18 +538,25 @@ static void atfork_child(void) { i->context = NULL; } - if (i->stream) { - pa_stream_unref(i->stream); - i->stream = NULL; + if (i->play_stream) { + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + } + + if (i->rec_stream) { + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; } if (i->app_fd >= 0) { - close(i->app_fd); + LOAD_CLOSE_FUNC(); + _close(i->app_fd); i->app_fd = -1; } if (i->thread_fd >= 0) { - close(i->thread_fd); + LOAD_CLOSE_FUNC(); + _close(i->thread_fd); i->thread_fd = -1; } @@ -462,7 +565,7 @@ static void atfork_child(void) { function_exit(); - debug(__FILE__": atfork_child() exit\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() exit\n"); } static void install_atfork(void) { @@ -495,12 +598,12 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { char name[64]; static pthread_once_t install_atfork_once = PTHREAD_ONCE_INIT; - debug(__FILE__": fd_info_new()\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": fd_info_new()\n"); signal(SIGPIPE, SIG_IGN); /* Yes, ugly as hell */ pthread_once(&install_atfork_once, install_atfork); - + if (!(i = malloc(sizeof(fd_info)))) { *_errno = ENOMEM; goto fail; @@ -511,22 +614,30 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { i->mainloop = NULL; i->context = NULL; - i->stream = NULL; + i->play_stream = NULL; + i->rec_stream = NULL; + i->play_precork = 0; + i->rec_precork = 0; i->io_event = NULL; + i->io_flags = 0; pthread_mutex_init(&i->mutex, NULL); i->ref = 1; i->buf = NULL; + i->rec_offset = 0; i->unusable = 0; - pa_cvolume_reset(&i->volume, 2); + pa_cvolume_reset(&i->sink_volume, 2); + pa_cvolume_reset(&i->source_volume, 2); i->volume_modify_count = 0; i->sink_index = (uint32_t) -1; + i->source_index = (uint32_t) -1; + i->optr_n_blocks = 0; PA_LLIST_INIT(fd_info, i); reset_params(i); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfds) < 0) { *_errno = errno; - debug(__FILE__": socket() failed: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": socket() failed: %s\n", strerror(errno)); goto fail; } @@ -535,13 +646,13 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { if (!(i->mainloop = pa_threaded_mainloop_new())) { *_errno = EIO; - debug(__FILE__": pa_threaded_mainloop_new() failed\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_new() failed\n"); goto fail; } if (!(i->context = pa_context_new(pa_threaded_mainloop_get_api(i->mainloop), client_name(name, sizeof(name))))) { *_errno = EIO; - debug(__FILE__": pa_context_new() failed\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_new() failed\n"); goto fail; } @@ -549,7 +660,7 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { if (pa_context_connect(i->context, NULL, 0, NULL) < 0) { *_errno = ECONNREFUSED; - debug(__FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } @@ -557,7 +668,7 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { if (pa_threaded_mainloop_start(i->mainloop) < 0) { *_errno = EIO; - debug(__FILE__": pa_threaded_mainloop_start() failed\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_start() failed\n"); goto unlock_and_fail; } @@ -566,7 +677,7 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { if (pa_context_get_state(i->context) != PA_CONTEXT_READY) { *_errno = ECONNREFUSED; - debug(__FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); goto unlock_and_fail; } @@ -576,12 +687,12 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { unlock_and_fail: pa_threaded_mainloop_unlock(i->mainloop); - + fail: if (i) fd_info_unref(i); - + return NULL; } @@ -609,7 +720,7 @@ static fd_info* fd_info_find(int fd) { fd_info *i; pthread_mutex_lock(&fd_infos_mutex); - + for (i = fd_infos; i; i = i->next) if (i->app_fd == fd && !i->unusable) { fd_info_ref(i); @@ -617,7 +728,7 @@ static fd_info* fd_info_find(int fd) { } pthread_mutex_unlock(&fd_infos_mutex); - + return i; } @@ -626,12 +737,19 @@ static void fix_metrics(fd_info *i) { char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; fs = pa_frame_size(&i->sample_spec); + + /* Don't fix things more than necessary */ + if ((i->fragment_size % fs) == 0 && + i->n_fragments >= 2 && + i->fragment_size > 0) + return; + i->fragment_size = (i->fragment_size/fs)*fs; /* Number of fragments set? */ if (i->n_fragments < 2) { if (i->fragment_size > 0) { - i->n_fragments = pa_bytes_per_second(&i->sample_spec) / 2 / i->fragment_size; + i->n_fragments = (unsigned) (pa_bytes_per_second(&i->sample_spec) / 2 / i->fragment_size); if (i->n_fragments < 2) i->n_fragments = 2; } else @@ -645,8 +763,8 @@ static void fix_metrics(fd_info *i) { i->fragment_size = 1024; } - debug(__FILE__": sample spec: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); - debug(__FILE__": fixated metrics to %i fragments, %li bytes each.\n", i->n_fragments, (long)i->fragment_size); + debug(DEBUG_LEVEL_NORMAL, __FILE__": sample spec: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": fixated metrics to %i fragments, %li bytes each.\n", i->n_fragments, (long)i->fragment_size); } static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { @@ -655,8 +773,37 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { if (i->io_event) { pa_mainloop_api *api; + size_t n; + api = pa_threaded_mainloop_get_api(i->mainloop); - api->io_enable(i->io_event, PA_IO_EVENT_INPUT); + + if (s == i->play_stream) { + n = pa_stream_writable_size(i->play_stream); + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_INPUT; + else + i->io_flags &= ~PA_IO_EVENT_INPUT; + } + + if (s == i->rec_stream) { + n = pa_stream_readable_size(i->rec_stream); + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_OUTPUT; + else + i->io_flags &= ~PA_IO_EVENT_OUTPUT; + } + + api->io_enable(i->io_event, i->io_flags); } } @@ -675,6 +822,7 @@ static void fd_info_shutdown(fd_info *i) { api = pa_threaded_mainloop_get_api(i->mainloop); api->io_free(i->io_event); i->io_event = NULL; + i->io_flags = 0; } if (i->thread_fd >= 0) { @@ -686,50 +834,120 @@ static void fd_info_shutdown(fd_info *i) { static int fd_info_copy_data(fd_info *i, int force) { size_t n; - if (!i->stream) + if (!i->play_stream && !i->rec_stream) return -1; - if ((n = pa_stream_writable_size(i->stream)) == (size_t) -1) { - debug(__FILE__": pa_stream_writable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); - return -1; - } - - while (n >= i->fragment_size || force) { - ssize_t r; - - if (!i->buf) { - if (!(i->buf = malloc(i->fragment_size))) { - debug(__FILE__": malloc() failed.\n"); + if ((i->play_stream) && (pa_stream_get_state(i->play_stream) == PA_STREAM_READY)) { + n = pa_stream_writable_size(i->play_stream); + + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + return -1; + } + + while (n >= i->fragment_size || force) { + ssize_t r; + + if (!i->buf) { + if (!(i->buf = malloc(i->fragment_size))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": malloc() failed.\n"); + return -1; + } + } + + if ((r = read(i->thread_fd, i->buf, i->fragment_size)) <= 0) { + + if (errno == EAGAIN) + break; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", r == 0 ? "EOF" : strerror(errno)); return -1; } - } - - if ((r = read(i->thread_fd, i->buf, i->fragment_size)) <= 0) { - if (errno == EAGAIN) - break; - - debug(__FILE__": read(): %s\n", r == 0 ? "EOF" : strerror(errno)); - return -1; + if (pa_stream_write(i->play_stream, i->buf, (size_t) r, free, 0LL, PA_SEEK_RELATIVE) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_write(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + + i->buf = NULL; + + assert(n >= (size_t) r); + n -= (size_t) r; } - - if (pa_stream_write(i->stream, i->buf, r, free, 0, PA_SEEK_RELATIVE) < 0) { - debug(__FILE__": pa_stream_write(): %s\n", pa_strerror(pa_context_errno(i->context))); + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_INPUT; + else + i->io_flags &= ~PA_IO_EVENT_INPUT; + } + + if ((i->rec_stream) && (pa_stream_get_state(i->rec_stream) == PA_STREAM_READY)) { + n = pa_stream_readable_size(i->rec_stream); + + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); return -1; } - i->buf = NULL; + while (n >= i->fragment_size || force) { + ssize_t r; + const void *data; + const char *buf; + size_t len; + + if (pa_stream_peek(i->rec_stream, &data, &len) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_peek(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + + if (!data) + break; + + buf = (const char*)data + i->rec_offset; + + if ((r = write(i->thread_fd, buf, len - i->rec_offset)) <= 0) { + + if (errno == EAGAIN) + break; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": write(): %s\n", strerror(errno)); + return -1; + } + + assert((size_t)r <= len - i->rec_offset); + i->rec_offset += (size_t) r; + + if (i->rec_offset == len) { + if (pa_stream_drop(i->rec_stream) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drop(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + i->rec_offset = 0; + } + + assert(n >= (size_t) r); + n -= (size_t) r; + } - assert(n >= (size_t) r); - n -= r; + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_OUTPUT; + else + i->io_flags &= ~PA_IO_EVENT_OUTPUT; } if (i->io_event) { pa_mainloop_api *api; + api = pa_threaded_mainloop_get_api(i->mainloop); - api->io_enable(i->io_event, n >= i->fragment_size ? PA_IO_EVENT_INPUT : 0); + api->io_enable(i->io_event, i->io_flags); } + /* So, we emptied the socket now, let's tell dsp_empty_socket() + * about this */ + pa_threaded_mainloop_signal(i->mainloop, 0); + return 0; } @@ -740,11 +958,23 @@ static void stream_state_cb(pa_stream *s, void * userdata) { switch (pa_stream_get_state(s)) { case PA_STREAM_READY: - debug(__FILE__": stream established.\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": stream established.\n"); break; - + case PA_STREAM_FAILED: - debug(__FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + if (s == i->play_stream) { + debug(DEBUG_LEVEL_NORMAL, + __FILE__": pa_stream_connect_playback() failed: %s\n", + pa_strerror(pa_context_errno(i->context))); + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + } else if (s == i->rec_stream) { + debug(DEBUG_LEVEL_NORMAL, + __FILE__": pa_stream_connect_record() failed: %s\n", + pa_strerror(pa_context_errno(i->context))); + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; + } fd_info_shutdown(i); break; @@ -755,52 +985,114 @@ static void stream_state_cb(pa_stream *s, void * userdata) { } } -static int create_stream(fd_info *i) { +static int create_playback_stream(fd_info *i) { pa_buffer_attr attr; - int n; - + int n, flags; + assert(i); fix_metrics(i); - if (!(i->stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) { - debug(__FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + if (!(i->play_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } - pa_stream_set_state_callback(i->stream, stream_state_cb, i); - pa_stream_set_write_callback(i->stream, stream_request_cb, i); - pa_stream_set_latency_update_callback(i->stream, stream_latency_update_cb, i); + pa_stream_set_state_callback(i->play_stream, stream_state_cb, i); + pa_stream_set_write_callback(i->play_stream, stream_request_cb, i); + pa_stream_set_latency_update_callback(i->play_stream, stream_latency_update_cb, i); memset(&attr, 0, sizeof(attr)); - attr.maxlength = i->fragment_size * (i->n_fragments+1); - attr.tlength = i->fragment_size * i->n_fragments; - attr.prebuf = i->fragment_size; - attr.minreq = i->fragment_size; - - if (pa_stream_connect_playback(i->stream, NULL, &attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) { - debug(__FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + attr.maxlength = (uint32_t) (i->fragment_size * (i->n_fragments+1)); + attr.tlength = (uint32_t) (i->fragment_size * i->n_fragments); + attr.prebuf = (uint32_t) i->fragment_size; + attr.minreq = (uint32_t) i->fragment_size; + + flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_EARLY_REQUESTS; + if (i->play_precork) { + flags |= PA_STREAM_START_CORKED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n"); + } + if (pa_stream_connect_playback(i->play_stream, NULL, &attr, flags, NULL, NULL) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } - n = i->fragment_size; + n = (int) i->fragment_size; setsockopt(i->app_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)); - n = i->fragment_size; + n = (int) i->fragment_size; setsockopt(i->thread_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); - + + return 0; + +fail: + return -1; +} + +static int create_record_stream(fd_info *i) { + pa_buffer_attr attr; + int n, flags; + + assert(i); + + fix_metrics(i); + + if (!(i->rec_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + pa_stream_set_state_callback(i->rec_stream, stream_state_cb, i); + pa_stream_set_read_callback(i->rec_stream, stream_request_cb, i); + pa_stream_set_latency_update_callback(i->rec_stream, stream_latency_update_cb, i); + + memset(&attr, 0, sizeof(attr)); + attr.maxlength = (uint32_t) (i->fragment_size * (i->n_fragments+1)); + attr.fragsize = (uint32_t) i->fragment_size; + + flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE; + if (i->rec_precork) { + flags |= PA_STREAM_START_CORKED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n"); + } + if (pa_stream_connect_record(i->rec_stream, NULL, &attr, flags) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + n = (int) i->fragment_size; + setsockopt(i->app_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); + n = (int) i->fragment_size; + setsockopt(i->thread_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)); + return 0; fail: return -1; } -static void free_stream(fd_info *i) { +static void free_streams(fd_info *i) { assert(i); - if (i->stream) { - pa_stream_disconnect(i->stream); - pa_stream_unref(i->stream); - i->stream = NULL; + if (i->play_stream) { + pa_stream_disconnect(i->play_stream); + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + i->io_flags |= PA_IO_EVENT_INPUT; + } + + if (i->rec_stream) { + pa_stream_disconnect(i->rec_stream); + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; + i->io_flags |= PA_IO_EVENT_OUTPUT; + } + + if (i->io_event) { + pa_mainloop_api *api; + + api = pa_threaded_mainloop_get_api(i->mainloop); + api->io_enable(i->io_event, i->io_flags); } } @@ -808,25 +1100,32 @@ static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_even fd_info *i = userdata; pa_threaded_mainloop_signal(i->mainloop, 0); - - if (flags & PA_IO_EVENT_INPUT) { - if (!i->stream) { - api->io_enable(e, 0); + if (flags & PA_IO_EVENT_INPUT) { - if (create_stream(i) < 0) + if (!i->play_stream) { + if (create_playback_stream(i) < 0) + goto fail; + } else { + if (fd_info_copy_data(i, 0) < 0) goto fail; + } + + } else if (flags & PA_IO_EVENT_OUTPUT) { + if (!i->rec_stream) { + if (create_record_stream(i) < 0) + goto fail; } else { if (fd_info_copy_data(i, 0) < 0) goto fail; } - + } else if (flags & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) goto fail; return; - + fail: /* We can't do anything better than removing the event source */ fd_info_shutdown(i); @@ -838,16 +1137,10 @@ static int dsp_open(int flags, int *_errno) { int ret; int f; - if ((flags != O_WRONLY) && (flags != (O_WRONLY|O_NONBLOCK))) { - *_errno = EACCES; - return -1; - } - - if (!(i = fd_info_new(FD_INFO_PLAYBACK, _errno))) - return -1; + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open()\n"); - shutdown(i->thread_fd, SHUT_WR); - shutdown(i->app_fd, SHUT_RD); + if (!(i = fd_info_new(FD_INFO_STREAM, _errno))) + return -1; if ((flags & O_NONBLOCK) == O_NONBLOCK) { if ((f = fcntl(i->app_fd, F_GETFL)) >= 0) @@ -861,17 +1154,36 @@ static int dsp_open(int flags, int *_errno) { pa_threaded_mainloop_lock(i->mainloop); api = pa_threaded_mainloop_get_api(i->mainloop); - if (!(i->io_event = api->io_new(api, i->thread_fd, PA_IO_EVENT_INPUT, io_event_cb, i))) + + switch (flags & O_ACCMODE) { + case O_RDONLY: + i->io_flags = PA_IO_EVENT_OUTPUT; + shutdown(i->thread_fd, SHUT_RD); + shutdown(i->app_fd, SHUT_WR); + break; + case O_WRONLY: + i->io_flags = PA_IO_EVENT_INPUT; + shutdown(i->thread_fd, SHUT_WR); + shutdown(i->app_fd, SHUT_RD); + break; + case O_RDWR: + i->io_flags = PA_IO_EVENT_INPUT | PA_IO_EVENT_OUTPUT; + break; + default: + return -1; + } + + if (!(i->io_event = api->io_new(api, i->thread_fd, i->io_flags, io_event_cb, i))) goto fail; - + pa_threaded_mainloop_unlock(i->mainloop); - debug(__FILE__": dsp_open() succeeded, fd=%i\n", i->app_fd); + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() succeeded, fd=%i\n", i->app_fd); fd_info_add_to_list(i); ret = i->app_fd; fd_info_unref(i); - + return ret; fail: @@ -879,10 +1191,10 @@ fail: if (i) fd_info_unref(i); - + *_errno = EIO; - debug(__FILE__": dsp_open() failed\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() failed\n"); return -1; } @@ -899,16 +1211,38 @@ static void sink_info_cb(pa_context *context, const pa_sink_info *si, int eol, v if (eol) return; - if (!pa_cvolume_equal(&i->volume, &si->volume)) + if (!pa_cvolume_equal(&i->sink_volume, &si->volume)) i->volume_modify_count++; - - i->volume = si->volume; + + i->sink_volume = si->volume; i->sink_index = si->index; i->operation_success = 1; pa_threaded_mainloop_signal(i->mainloop, 0); } +static void source_info_cb(pa_context *context, const pa_source_info *si, int eol, void *userdata) { + fd_info *i = userdata; + + if (!si && eol < 0) { + i->operation_success = 0; + pa_threaded_mainloop_signal(i->mainloop, 0); + return; + } + + if (eol) + return; + + if (!pa_cvolume_equal(&i->source_volume, &si->volume)) + i->volume_modify_count++; + + i->source_volume = si->volume; + i->source_index = si->index; + + i->operation_success = 1; + pa_threaded_mainloop_signal(i->mainloop, 0); +} + static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { fd_info *i = userdata; pa_operation *o = NULL; @@ -920,7 +1254,7 @@ static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, ui return; if (!(o = pa_context_get_sink_info_by_index(i->context, i->sink_index, sink_info_cb, i))) { - debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); return; } @@ -929,18 +1263,20 @@ static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, ui static int mixer_open(int flags, int *_errno) { fd_info *i; - pa_operation *o; + pa_operation *o = NULL; int ret; - if (!(i = fd_info_new(FD_INFO_MIXER, _errno))) + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open()\n"); + + if (!(i = fd_info_new(FD_INFO_MIXER, _errno))) return -1; - + pa_threaded_mainloop_lock(i->mainloop); pa_context_set_subscribe_callback(i->context, subscribe_cb, i); - - if (!(o = pa_context_subscribe(i->context, PA_SUBSCRIPTION_MASK_SINK, context_success_cb, i))) { - debug(__FILE__": Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); + + if (!(o = pa_context_subscribe(i->context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, context_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); *_errno = EIO; goto fail; } @@ -951,17 +1287,42 @@ static int mixer_open(int flags, int *_errno) { CONTEXT_CHECK_DEAD_GOTO(i, fail); } + pa_operation_unref(o); + o = NULL; + if (!i->operation_success) { - debug(__FILE__":Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); *_errno = EIO; goto fail; } /* Get sink info */ - pa_operation_unref(o); if (!(o = pa_context_get_sink_info_by_name(i->context, NULL, sink_info_cb, i))) { - debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(i->mainloop); + CONTEXT_CHECK_DEAD_GOTO(i, fail); + } + + pa_operation_unref(o); + o = NULL; + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + /* Get source info */ + + if (!(o = pa_context_get_source_info_by_name(i->context, NULL, source_info_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context))); *_errno = EIO; goto fail; } @@ -972,49 +1333,55 @@ static int mixer_open(int flags, int *_errno) { CONTEXT_CHECK_DEAD_GOTO(i, fail); } + pa_operation_unref(o); + o = NULL; + if (!i->operation_success) { - debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context))); *_errno = EIO; goto fail; } pa_threaded_mainloop_unlock(i->mainloop); - debug(__FILE__": mixer_open() succeeded, fd=%i\n", i->app_fd); + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() succeeded, fd=%i\n", i->app_fd); fd_info_add_to_list(i); ret = i->app_fd; fd_info_unref(i); - + return ret; fail: + if (o) + pa_operation_unref(o); + pa_threaded_mainloop_unlock(i->mainloop); if (i) fd_info_unref(i); - + *_errno = EIO; - debug(__FILE__": mixer_open() failed\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() failed\n"); return -1; } static int sndstat_open(int flags, int *_errno) { static const char sndstat[] = - "Sound Driver:3.8.1a-980706 (Polypaudio Virtual OSS)\n" + "Sound Driver:3.8.1a-980706 (PulseAudio Virtual OSS)\n" "Kernel: POSIX\n" "Config options: 0\n" "\n" "Installed drivers:\n" - "Type 255: Polypaudio Virtual OSS\n" + "Type 255: PulseAudio Virtual OSS\n" "\n" "Card config:\n" - "Polypaudio Virtual OSS\n" + "PulseAudio Virtual OSS\n" "\n" "Audio devices:\n" - "0: Polypaudio Virtual OSS\n" + "0: PulseAudio Virtual OSS\n" "\n" "Synth devices: NOT ENABLED IN CONFIG\n" "\n" @@ -1023,18 +1390,22 @@ static int sndstat_open(int flags, int *_errno) { "Timers:\n" "\n" "Mixers:\n" - "0: Polypaudio Virtual OSS\n"; + "0: PulseAudio Virtual OSS\n"; char fn[] = "/tmp/padsp-sndstat-XXXXXX"; mode_t u; int fd = -1; int e; - debug(__FILE__": sndstat_open()\n"); - - if (flags != O_RDONLY && flags != (O_RDONLY|O_LARGEFILE)) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": sndstat_open()\n"); + + if (flags != O_RDONLY +#ifdef O_LARGEFILE + && flags != (O_RDONLY|O_LARGEFILE) +#endif + ) { *_errno = EACCES; - debug(__FILE__": bad access!\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": bad access!\n"); goto fail; } @@ -1045,7 +1416,7 @@ static int sndstat_open(int flags, int *_errno) { if (fd < 0) { *_errno = e; - debug(__FILE__": mkstemp() failed: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": mkstemp() failed: %s\n", strerror(errno)); goto fail; } @@ -1053,13 +1424,13 @@ static int sndstat_open(int flags, int *_errno) { if (write(fd, sndstat, sizeof(sndstat) -1) != sizeof(sndstat)-1) { *_errno = errno; - debug(__FILE__": write() failed: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": write() failed: %s\n", strerror(errno)); goto fail; } if (lseek(fd, SEEK_SET, 0) < 0) { *_errno = errno; - debug(__FILE__": lseek() failed: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": lseek() failed: %s\n", strerror(errno)); goto fail; } @@ -1071,194 +1442,239 @@ fail: return -1; } -int open(const char *filename, int flags, ...) { - va_list args; - mode_t mode = 0; +static int real_open(const char *filename, int flags, mode_t mode) { int r, _errno = 0; - debug(__FILE__": open(%s)\n", filename); - - va_start(args, flags); - if (flags & O_CREAT) - mode = va_arg(args, mode_t); - va_end(args); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open(%s)\n", filename?filename:"NULL"); if (!function_enter()) { LOAD_OPEN_FUNC(); return _open(filename, flags, mode); } - if (dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0)) { + if (filename && dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0)) r = dsp_open(flags, &_errno); - } else if (mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0) { + else if (filename && mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0) r = mixer_open(flags, &_errno); - } else if (sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0) { + else if (filename && sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0) r = sndstat_open(flags, &_errno); - } else { + else { function_exit(); LOAD_OPEN_FUNC(); return _open(filename, flags, mode); } function_exit(); - + if (_errno) errno = _errno; - + return r; } +int open(const char *filename, int flags, ...) { + va_list args; + mode_t mode = 0; + + if (flags & O_CREAT) { + va_start(args, flags); + if (sizeof(mode_t) < sizeof(int)) + mode = (mode_t) va_arg(args, int); + else + mode = va_arg(args, mode_t); + va_end(args); + } + + return real_open(filename, flags, mode); +} + static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) { int ret = -1; - + switch (request) { case SOUND_MIXER_READ_DEVMASK : - debug(__FILE__": SOUND_MIXER_READ_DEVMASK\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_DEVMASK\n"); - *(int*) argp = SOUND_MASK_PCM; + *(int*) argp = SOUND_MASK_PCM | SOUND_MASK_IGAIN; break; case SOUND_MIXER_READ_RECMASK : - debug(__FILE__": SOUND_MIXER_READ_RECMASK\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECMASK\n"); - *(int*) argp = 0; + *(int*) argp = SOUND_MASK_IGAIN; break; - + case SOUND_MIXER_READ_STEREODEVS: - debug(__FILE__": SOUND_MIXER_READ_STEREODEVS\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_STEREODEVS\n"); pa_threaded_mainloop_lock(i->mainloop); - *(int*) argp = i->volume.channels > 1 ? SOUND_MASK_PCM : 0; + *(int*) argp = 0; + if (i->sink_volume.channels > 1) + *(int*) argp |= SOUND_MASK_PCM; + if (i->source_volume.channels > 1) + *(int*) argp |= SOUND_MASK_IGAIN; pa_threaded_mainloop_unlock(i->mainloop); - + break; case SOUND_MIXER_READ_RECSRC: - debug(__FILE__": SOUND_MIXER_READ_RECSRC\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECSRC\n"); - *(int*) argp = 0; + *(int*) argp = SOUND_MASK_IGAIN; + break; + + case SOUND_MIXER_WRITE_RECSRC: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_RECSRC\n"); break; - + case SOUND_MIXER_READ_CAPS: - debug(__FILE__": SOUND_MIXER_READ_CAPS\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_CAPS\n"); *(int*) argp = 0; break; - + case SOUND_MIXER_READ_PCM: - - debug(__FILE__": SOUND_MIXER_READ_PCM\n"); - + case SOUND_MIXER_READ_IGAIN: { + pa_cvolume *v; + + if (request == SOUND_MIXER_READ_PCM) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_PCM\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_IGAIN\n"); + pa_threaded_mainloop_lock(i->mainloop); + if (request == SOUND_MIXER_READ_PCM) + v = &i->sink_volume; + else + v = &i->source_volume; + *(int*) argp = - ((i->volume.values[0]*100/PA_VOLUME_NORM)) | - ((i->volume.values[i->volume.channels > 1 ? 1 : 0]*100/PA_VOLUME_NORM) << 8); - + ((v->values[0]*100/PA_VOLUME_NORM)) | + ((v->values[v->channels > 1 ? 1 : 0]*100/PA_VOLUME_NORM) << 8); + pa_threaded_mainloop_unlock(i->mainloop); - + break; + } + + case SOUND_MIXER_WRITE_PCM: + case SOUND_MIXER_WRITE_IGAIN: { + pa_cvolume v, *pv; + + if (request == SOUND_MIXER_WRITE_PCM) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_PCM\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_IGAIN\n"); - case SOUND_MIXER_WRITE_PCM: { - pa_cvolume v; - - debug(__FILE__": SOUND_MIXER_WRITE_PCM\n"); - pa_threaded_mainloop_lock(i->mainloop); - v = i->volume; - - i->volume.values[0] = ((*(int*) argp & 0xFF)*PA_VOLUME_NORM)/100; - i->volume.values[1] = ((*(int*) argp >> 8)*PA_VOLUME_NORM)/100; + if (request == SOUND_MIXER_WRITE_PCM) { + v = i->sink_volume; + pv = &i->sink_volume; + } else { + v = i->source_volume; + pv = &i->source_volume; + } + + pv->values[0] = ((*(int*) argp & 0xFF)*PA_VOLUME_NORM)/100; + pv->values[1] = ((*(int*) argp >> 8)*PA_VOLUME_NORM)/100; - if (!pa_cvolume_equal(&i->volume, &v)) { + if (!pa_cvolume_equal(pv, &v)) { pa_operation *o; - - if (!(o = pa_context_set_sink_volume_by_index(i->context, i->sink_index, &i->volume, NULL, NULL))) - debug(__FILE__":Failed set volume: %s", pa_strerror(pa_context_errno(i->context))); + + if (request == SOUND_MIXER_WRITE_PCM) + o = pa_context_set_sink_volume_by_index(i->context, i->sink_index, pv, context_success_cb, i); + else + o = pa_context_set_source_volume_by_index(i->context, i->source_index, pv, context_success_cb, i); + + if (!o) + debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed set volume: %s", pa_strerror(pa_context_errno(i->context))); else { i->operation_success = 0; while (pa_operation_get_state(o) != PA_OPERATION_DONE) { CONTEXT_CHECK_DEAD_GOTO(i, exit_loop); - + pa_threaded_mainloop_wait(i->mainloop); } exit_loop: - + if (!i->operation_success) - debug(__FILE__": Failed to set volume: %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to set volume: %s\n", pa_strerror(pa_context_errno(i->context))); pa_operation_unref(o); } - + /* We don't wait for completion here */ i->volume_modify_count++; } - + pa_threaded_mainloop_unlock(i->mainloop); - + break; } case SOUND_MIXER_INFO: { mixer_info *mi = argp; + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_INFO\n"); + memset(mi, 0, sizeof(mixer_info)); - strncpy(mi->id, "POLYPAUDIO", sizeof(mi->id)); - strncpy(mi->name, "Polypaudio Virtual OSS", sizeof(mi->name)); + strncpy(mi->id, "PULSEAUDIO", sizeof(mi->id)); + strncpy(mi->name, "PulseAudio Virtual OSS", sizeof(mi->name)); pa_threaded_mainloop_lock(i->mainloop); mi->modify_counter = i->volume_modify_count; pa_threaded_mainloop_unlock(i->mainloop); break; } - + default: - debug(__FILE__": unknown ioctl 0x%08lx\n", request); + debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request); *_errno = EINVAL; goto fail; } ret = 0; - + fail: - + return ret; } static int map_format(int *fmt, pa_sample_spec *ss) { - + switch (*fmt) { case AFMT_MU_LAW: ss->format = PA_SAMPLE_ULAW; break; - + case AFMT_A_LAW: ss->format = PA_SAMPLE_ALAW; break; - + case AFMT_S8: *fmt = AFMT_U8; /* fall through */ case AFMT_U8: ss->format = PA_SAMPLE_U8; break; - + case AFMT_U16_BE: *fmt = AFMT_S16_BE; /* fall through */ case AFMT_S16_BE: ss->format = PA_SAMPLE_S16BE; break; - + case AFMT_U16_LE: *fmt = AFMT_S16_LE; /* fall through */ case AFMT_S16_LE: ss->format = PA_SAMPLE_S16LE; break; - + default: ss->format = PA_SAMPLE_S16NE; *fmt = AFMT_S16_NE; @@ -1280,14 +1696,12 @@ static int map_format_back(pa_sample_format_t format) { } } -static int dsp_flush_socket(fd_info *i) { +static int dsp_flush_fd(int fd) { +#ifdef SIOCINQ int l; - - if (i->thread_fd < 0) - return -1; - if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { - debug(__FILE__": SIOCINQ: %s\n", strerror(errno)); + if (ioctl(fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno)); return -1; } @@ -1296,26 +1710,52 @@ static int dsp_flush_socket(fd_info *i) { size_t k; k = (size_t) l > sizeof(buf) ? sizeof(buf) : (size_t) l; - if (read(i->thread_fd, buf, k) < 0) - debug(__FILE__": read(): %s\n", strerror(errno)); + if (read(fd, buf, k) < 0) + debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", strerror(errno)); l -= k; } + return 0; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." + return 0; +#endif +} + +static int dsp_flush_socket(fd_info *i) { + int res = 0; + + if ((i->thread_fd < 0) && (i->app_fd < 0)) + return -1; + + if (i->thread_fd >= 0) + res = dsp_flush_fd(i->thread_fd); + + if (res < 0) + return res; + + if (i->app_fd >= 0) + res = dsp_flush_fd(i->app_fd); + + if (res < 0) + return res; + return 0; } static int dsp_empty_socket(fd_info *i) { +#ifdef SIOCINQ int ret = -1; - + /* Empty the socket */ for (;;) { int l; - + if (i->thread_fd < 0) break; - + if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { - debug(__FILE__": SIOCINQ: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno)); break; } @@ -1323,11 +1763,15 @@ static int dsp_empty_socket(fd_info *i) { ret = 0; break; } - + pa_threaded_mainloop_wait(i->mainloop); } return ret; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." + return 0; +#endif } static int dsp_drain(fd_info *i) { @@ -1336,40 +1780,40 @@ static int dsp_drain(fd_info *i) { if (!i->mainloop) return 0; - - debug(__FILE__": Draining.\n"); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": Draining.\n"); pa_threaded_mainloop_lock(i->mainloop); if (dsp_empty_socket(i) < 0) goto fail; - - if (!i->stream) + + if (!i->play_stream) goto fail; - debug(__FILE__": Really draining.\n"); - - if (!(o = pa_stream_drain(i->stream, stream_success_cb, i))) { - debug(__FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Really draining.\n"); + + if (!(o = pa_stream_drain(i->play_stream, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } i->operation_success = 0; while (pa_operation_get_state(o) != PA_OPERATION_DONE) { - STREAM_CHECK_DEAD_GOTO(i, fail); - + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + pa_threaded_mainloop_wait(i->mainloop); } if (!i->operation_success) { - debug(__FILE__": pa_stream_drain() 2: %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain() 2: %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } r = 0; - + fail: - + if (o) pa_operation_unref(o); @@ -1382,7 +1826,7 @@ static int dsp_trigger(fd_info *i) { pa_operation *o = NULL; int r = -1; - if (!i->stream) + if (!i->play_stream) return 0; pa_threaded_mainloop_lock(i->mainloop); @@ -1390,29 +1834,67 @@ static int dsp_trigger(fd_info *i) { if (dsp_empty_socket(i) < 0) goto fail; - debug(__FILE__": Triggering.\n"); - - if (!(o = pa_stream_trigger(i->stream, stream_success_cb, i))) { - debug(__FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": Triggering.\n"); + + if (!(o = pa_stream_trigger(i->play_stream, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } i->operation_success = 0; while (!pa_operation_get_state(o) != PA_OPERATION_DONE) { - STREAM_CHECK_DEAD_GOTO(i, fail); - + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + pa_threaded_mainloop_wait(i->mainloop); } if (!i->operation_success) { - debug(__FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); goto fail; } r = 0; - + fail: - + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(i->mainloop); + + return 0; +} + +static int dsp_cork(fd_info *i, pa_stream *s, int b) { + pa_operation *o = NULL; + int r = -1; + + pa_threaded_mainloop_lock(i->mainloop); + + if (!(o = pa_stream_cork(s, b, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + i->operation_success = 0; + while (!pa_operation_get_state(o) != PA_OPERATION_DONE) { + if (s == i->play_stream) + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + else if (s == i->rec_stream) + RECORD_STREAM_CHECK_DEAD_GOTO(i, fail); + + pa_threaded_mainloop_wait(i->mainloop); + } + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + r = 0; + +fail: + if (o) pa_operation_unref(o); @@ -1423,30 +1905,40 @@ fail: static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) { int ret = -1; - + + if (i->thread_fd == -1) { + /* + * We've encountered some fatal error and are just waiting + * for a close. + */ + debug(DEBUG_LEVEL_NORMAL, __FILE__": got ioctl 0x%08lx in fatal error state\n", request); + *_errno = EIO; + return -1; + } + switch (request) { case SNDCTL_DSP_SETFMT: { - debug(__FILE__": SNDCTL_DSP_SETFMT: %i\n", *(int*) argp); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFMT: %i\n", *(int*) argp); + pa_threaded_mainloop_lock(i->mainloop); if (*(int*) argp == AFMT_QUERY) *(int*) argp = map_format_back(i->sample_spec.format); else { map_format((int*) argp, &i->sample_spec); - free_stream(i); + free_streams(i); } pa_threaded_mainloop_unlock(i->mainloop); break; } - + case SNDCTL_DSP_SPEED: { pa_sample_spec ss; int valid; char t[256]; - - debug(__FILE__": SNDCTL_DSP_SPEED: %i\n", *(int*) argp); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SPEED: %i\n", *(int*) argp); pa_threaded_mainloop_lock(i->mainloop); @@ -1455,10 +1947,10 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) if ((valid = pa_sample_spec_valid(&ss))) { i->sample_spec = ss; - free_stream(i); + free_streams(i); } - - debug(__FILE__": ss: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": ss: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); pa_threaded_mainloop_unlock(i->mainloop); @@ -1469,24 +1961,24 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) break; } - + case SNDCTL_DSP_STEREO: - debug(__FILE__": SNDCTL_DSP_STEREO: %i\n", *(int*) argp); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_STEREO: %i\n", *(int*) argp); + pa_threaded_mainloop_lock(i->mainloop); - + i->sample_spec.channels = *(int*) argp ? 2 : 1; - free_stream(i); - + free_streams(i); + pa_threaded_mainloop_unlock(i->mainloop); return 0; case SNDCTL_DSP_CHANNELS: { pa_sample_spec ss; int valid; - - debug(__FILE__": SNDCTL_DSP_CHANNELS: %i\n", *(int*) argp); - + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CHANNELS: %i\n", *(int*) argp); + pa_threaded_mainloop_lock(i->mainloop); ss = i->sample_spec; @@ -1494,9 +1986,9 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) if ((valid = pa_sample_spec_valid(&ss))) { i->sample_spec = ss; - free_stream(i); + free_streams(i); } - + pa_threaded_mainloop_unlock(i->mainloop); if (!valid) { @@ -1508,168 +2000,328 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) } case SNDCTL_DSP_GETBLKSIZE: - debug(__FILE__": SNDCTL_DSP_GETBLKSIZE\n"); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETBLKSIZE\n"); pa_threaded_mainloop_lock(i->mainloop); fix_metrics(i); *(int*) argp = i->fragment_size; - + pa_threaded_mainloop_unlock(i->mainloop); - + break; case SNDCTL_DSP_SETFRAGMENT: - debug(__FILE__": SNDCTL_DSP_SETFRAGMENT: 0x%8x\n", *(int*) argp); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFRAGMENT: 0x%08x\n", *(int*) argp); + pa_threaded_mainloop_lock(i->mainloop); - - i->fragment_size = 1 << (*(int*) argp); + + i->fragment_size = 1 << ((*(int*) argp) & 31); i->n_fragments = (*(int*) argp) >> 16; - - free_stream(i); - + + /* 0x7FFF means that we can set whatever we like */ + if (i->n_fragments == 0x7FFF) + i->n_fragments = 12; + + free_streams(i); + pa_threaded_mainloop_unlock(i->mainloop); - + break; - + case SNDCTL_DSP_GETCAPS: - debug(__FILE__": SNDCTL_DSP_CAPS\n"); - - *(int*) argp = DSP_CAP_MULTI; + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CAPS\n"); + + *(int*) argp = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER +#ifdef DSP_CAP_MULTI + | DSP_CAP_MULTI +#endif + ; break; case SNDCTL_DSP_GETODELAY: { int l; - - debug(__FILE__": SNDCTL_DSP_GETODELAY\n"); - + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETODELAY\n"); + pa_threaded_mainloop_lock(i->mainloop); *(int*) argp = 0; - + for (;;) { pa_usec_t usec; - STREAM_CHECK_DEAD_GOTO(i, exit_loop); + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop); - if (pa_stream_get_latency(i->stream, &usec, NULL) >= 0) { + if (pa_stream_get_latency(i->play_stream, &usec, NULL) >= 0) { *(int*) argp = pa_usec_to_bytes(usec, &i->sample_spec); break; } if (pa_context_errno(i->context) != PA_ERR_NODATA) { - debug(__FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context))); + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context))); break; } pa_threaded_mainloop_wait(i->mainloop); } - + exit_loop: - + +#ifdef SIOCINQ if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) - debug(__FILE__": SIOCINQ failed: %s\n", strerror(errno)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); else *(int*) argp += l; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." +#endif pa_threaded_mainloop_unlock(i->mainloop); - debug(__FILE__": ODELAY: %i\n", *(int*) argp); + debug(DEBUG_LEVEL_NORMAL, __FILE__": ODELAY: %i\n", *(int*) argp); break; } - + case SNDCTL_DSP_RESET: { - debug(__FILE__": SNDCTL_DSP_RESET\n"); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_RESET\n"); + pa_threaded_mainloop_lock(i->mainloop); - free_stream(i); + free_streams(i); dsp_flush_socket(i); - reset_params(i); - + + i->optr_n_blocks = 0; + pa_threaded_mainloop_unlock(i->mainloop); break; } - + case SNDCTL_DSP_GETFMTS: { - debug(__FILE__": SNDCTL_DSP_GETFMTS\n"); - + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETFMTS\n"); + *(int*) argp = AFMT_MU_LAW|AFMT_A_LAW|AFMT_U8|AFMT_S16_LE|AFMT_S16_BE; break; } case SNDCTL_DSP_POST: - debug(__FILE__": SNDCTL_DSP_POST\n"); - - if (dsp_trigger(i) < 0) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_POST\n"); + + if (dsp_trigger(i) < 0) *_errno = EIO; break; - case SNDCTL_DSP_SYNC: - debug(__FILE__": SNDCTL_DSP_SYNC\n"); - - if (dsp_drain(i) < 0) + case SNDCTL_DSP_GETTRIGGER: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETTRIGGER\n"); + + *(int*) argp = 0; + if (!i->play_precork) + *(int*) argp |= PCM_ENABLE_OUTPUT; + if (!i->rec_precork) + *(int*) argp |= PCM_ENABLE_INPUT; + + break; + + case SNDCTL_DSP_SETTRIGGER: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETTRIGGER: 0x%08x\n", *(int*) argp); + + if (!i->io_event) { + *_errno = EIO; + break; + } + + i->play_precork = !((*(int*) argp) & PCM_ENABLE_OUTPUT); + + if (i->play_stream) { + if (dsp_cork(i, i->play_stream, !((*(int*) argp) & PCM_ENABLE_OUTPUT)) < 0) + *_errno = EIO; + if (dsp_trigger(i) < 0) + *_errno = EIO; + } + + i->rec_precork = !((*(int*) argp) & PCM_ENABLE_INPUT); + + if (i->rec_stream) { + if (dsp_cork(i, i->rec_stream, !((*(int*) argp) & PCM_ENABLE_INPUT)) < 0) + *_errno = EIO; + } + + break; + + case SNDCTL_DSP_SYNC: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SYNC\n"); + + if (dsp_drain(i) < 0) *_errno = EIO; break; - case SNDCTL_DSP_GETOSPACE: { + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: { audio_buf_info *bi = (audio_buf_info*) argp; - int l; + int l = 0; size_t k = 0; - debug(__FILE__": SNDCTL_DSP_GETOSPACE\n"); + if (request == SNDCTL_DSP_GETOSPACE) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOSPACE\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETISPACE\n"); pa_threaded_mainloop_lock(i->mainloop); fix_metrics(i); - - if (i->stream) { - if ((k = pa_stream_writable_size(i->stream)) == (size_t) -1) - debug(__FILE__": pa_stream_writable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); - } else - k = i->fragment_size * i->n_fragments; - - if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { - debug(__FILE__": SIOCINQ failed: %s\n", strerror(errno)); - l = 0; + + if (request == SNDCTL_DSP_GETOSPACE) { + if (i->play_stream) { + if ((k = pa_stream_writable_size(i->play_stream)) == (size_t) -1) + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); + } else + k = i->fragment_size * i->n_fragments; + +#ifdef SIOCINQ + if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); + l = 0; + } +#else +# warning "Your platform does not dsp_flush_fd, something might not work as intended." +#endif + + bi->bytes = k > (size_t) l ? k - l : 0; + } else { + if (i->rec_stream) { + if ((k = pa_stream_readable_size(i->rec_stream)) == (size_t) -1) + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); + } else + k = 0; + +#ifdef SIOCINQ + if (ioctl(i->app_fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); + l = 0; + } +#else +# warning "Your platform does not dsp_flush_fd, something might not work as intended." +#endif + bi->bytes = k + l; } bi->fragsize = i->fragment_size; bi->fragstotal = i->n_fragments; - bi->bytes = k > (size_t) l ? k - l : 0; bi->fragments = bi->bytes / bi->fragsize; pa_threaded_mainloop_unlock(i->mainloop); - debug(__FILE__": fragsize=%i, fragstotal=%i, bytes=%i, fragments=%i\n", bi->fragsize, bi->fragstotal, bi->bytes, bi->fragments); + debug(DEBUG_LEVEL_NORMAL, __FILE__": fragsize=%i, fragstotal=%i, bytes=%i, fragments=%i\n", bi->fragsize, bi->fragstotal, bi->bytes, bi->fragments); + + break; + } + + case SOUND_PCM_READ_RATE: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_RATE\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = i->sample_spec.rate; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SOUND_PCM_READ_CHANNELS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_CHANNELS\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = i->sample_spec.channels; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SOUND_PCM_READ_BITS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_BITS\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = pa_sample_size(&i->sample_spec)*8; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SNDCTL_DSP_GETOPTR: { + count_info *info; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOPTR\n"); + + info = (count_info*) argp; + memset(info, 0, sizeof(*info)); + + pa_threaded_mainloop_lock(i->mainloop); + + for (;;) { + pa_usec_t usec; + + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop2); + + if (pa_stream_get_time(i->play_stream, &usec) >= 0) { + size_t k = pa_usec_to_bytes(usec, &i->sample_spec); + int m; + + info->bytes = (int) k; + m = k / i->fragment_size; + info->blocks = m - i->optr_n_blocks; + i->optr_n_blocks = m; + + break; + } + + if (pa_context_errno(i->context) != PA_ERR_NODATA) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context))); + break; + } + + pa_threaded_mainloop_wait(i->mainloop); + } + + exit_loop2: + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": GETOPTR bytes=%i, blocks=%i, ptr=%i\n", info->bytes, info->blocks, info->ptr); break; } - + + case SNDCTL_DSP_GETIPTR: + debug(DEBUG_LEVEL_NORMAL, __FILE__": invalid ioctl SNDCTL_DSP_GETIPTR\n"); + goto inval; + + case SNDCTL_DSP_SETDUPLEX: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETDUPLEX\n"); + /* this is a no-op */ + break; + default: - debug(__FILE__": unknwon ioctl 0x%08lx\n", request); + /* Mixer ioctls are valid on /dev/dsp aswell */ + return mixer_ioctl(i, request, argp, _errno); +inval: *_errno = EINVAL; goto fail; } ret = 0; - + fail: - + return ret; } +#ifdef sun +int ioctl(int fd, int request, ...) { +#else int ioctl(int fd, unsigned long request, ...) { +#endif fd_info *i; va_list args; void *argp; int r, _errno = 0; - debug(__FILE__": ioctl()\n"); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ioctl()\n"); va_start(args, request); argp = va_arg(args, void *); @@ -1690,21 +2342,21 @@ int ioctl(int fd, unsigned long request, ...) { r = mixer_ioctl(i, request, argp, &_errno); else r = dsp_ioctl(i, request, argp, &_errno); - + fd_info_unref(i); if (_errno) errno = _errno; function_exit(); - + return r; } int close(int fd) { fd_info *i; - debug(__FILE__": close()\n"); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": close()\n"); if (!function_enter()) { LOAD_CLOSE_FUNC(); @@ -1719,84 +2371,267 @@ int close(int fd) { fd_info_remove_from_list(i); fd_info_unref(i); - + function_exit(); return 0; } +int access(const char *pathname, int mode) { + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": access(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_ACCESS_FUNC(); + return _access(pathname, mode); + } + + if (mode & (W_OK | X_OK)) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = EACCESS\n", pathname, mode); + errno = EACCES; + return -1; + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = OK\n", pathname, mode); + + return 0; +} + +int stat(const char *pathname, struct stat *buf) { +#ifdef HAVE_OPEN64 + struct stat64 parent; +#else + struct stat parent; +#endif + int ret; + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat(%s)\n", pathname?pathname:"NULL"); + LOAD_STAT_FUNC(); + return _stat(pathname, buf); + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": stat(%s)\n", pathname); + +#ifdef _STAT_VER +#ifdef HAVE_OPEN64 + ret = __xstat64(_STAT_VER, "/dev", &parent); +#else + ret = __xstat(_STAT_VER, "/dev", &parent); +#endif +#else +#ifdef HAVE_OPEN64 + ret = stat64("/dev", &parent); +#else + ret = stat("/dev", &parent); +#endif +#endif + + if (ret) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": unable to stat \"/dev\"\n"); + return -1; + } + + buf->st_dev = parent.st_dev; + buf->st_ino = 0xDEADBEEF; /* FIXME: Can we do this in a safe way? */ + buf->st_mode = S_IFCHR | S_IRUSR | S_IWUSR; + buf->st_nlink = 1; + buf->st_uid = getuid(); + buf->st_gid = getgid(); + buf->st_rdev = 0x0E03; /* FIXME: Linux specific */ + buf->st_size = 0; + buf->st_atime = 1181557705; + buf->st_mtime = 1181557705; + buf->st_ctime = 1181557705; + buf->st_blksize = 1; + buf->st_blocks = 0; + + return 0; +} + +#ifdef HAVE_OPEN64 + +int stat64(const char *pathname, struct stat64 *buf) { + struct stat oldbuf; + int ret; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat64(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_STAT64_FUNC(); + return _stat64(pathname, buf); + } + + ret = stat(pathname, &oldbuf); + if (ret) + return ret; + + buf->st_dev = oldbuf.st_dev; + buf->st_ino = oldbuf.st_ino; + buf->st_mode = oldbuf.st_mode; + buf->st_nlink = oldbuf.st_nlink; + buf->st_uid = oldbuf.st_uid; + buf->st_gid = oldbuf.st_gid; + buf->st_rdev = oldbuf.st_rdev; + buf->st_size = oldbuf.st_size; + buf->st_atime = oldbuf.st_atime; + buf->st_mtime = oldbuf.st_mtime; + buf->st_ctime = oldbuf.st_ctime; + buf->st_blksize = oldbuf.st_blksize; + buf->st_blocks = oldbuf.st_blocks; + + return 0; +} + int open64(const char *filename, int flags, ...) { va_list args; mode_t mode = 0; - debug(__FILE__": open64(%s)\n", filename); - - va_start(args, flags); - if (flags & O_CREAT) - mode = va_arg(args, mode_t); - va_end(args); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open64(%s)\n", filename?filename:"NULL"); - if (strcmp(filename, "/dev/dsp") != 0 && - strcmp(filename, "/dev/adsp") != 0 && - strcmp(filename, "/dev/sndstat") != 0 && - strcmp(filename, "/dev/mixer") != 0) { + if (flags & O_CREAT) { + va_start(args, flags); + if (sizeof(mode_t) < sizeof(int)) + mode = va_arg(args, int); + else + mode = va_arg(args, mode_t); + va_end(args); + } + + if (!filename || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { LOAD_OPEN64_FUNC(); return _open64(filename, flags, mode); } - return open(filename, flags, mode); + return real_open(filename, flags, mode); +} + +#endif + +#ifdef _STAT_VER + +int __xstat(int ver, const char *pathname, struct stat *buf) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_XSTAT_FUNC(); + return ___xstat(ver, pathname, buf); + } + + if (ver != _STAT_VER) { + errno = EINVAL; + return -1; + } + + return stat(pathname, buf); +} + +#ifdef HAVE_OPEN64 + +int __xstat64(int ver, const char *pathname, struct stat64 *buf) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat64(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_XSTAT64_FUNC(); + return ___xstat64(ver, pathname, buf); + } + + if (ver != _STAT_VER) { + errno = EINVAL; + return -1; + } + + return stat64(pathname, buf); } +#endif + +#endif + FILE* fopen(const char *filename, const char *mode) { FILE *f = NULL; int fd; mode_t m; - - debug(__FILE__": fopen(%s)\n", filename); - if (strcmp(filename, "/dev/dsp") == 0 || - strcmp(filename, "/dev/adsp") == 0) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen(%s)\n", filename?filename:"NULL"); - if (strcmp(mode, "wb") != 0) { - errno = EACCES; - return NULL; - } + if (!filename || + !mode || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { + LOAD_FOPEN_FUNC(); + return _fopen(filename, mode); + } + switch (mode[0]) { + case 'r': + m = O_RDONLY; + break; + case 'w': + case 'a': m = O_WRONLY; - } else if (strcmp(filename, "/dev/sndstat") == 0) { - - if (strcmp(mode, "r") != 0) { - errno = EACCES; - return NULL; - } + break; + default: + errno = EINVAL; + return NULL; + } - m = O_RDONLY; - } else if (strcmp(filename, "/dev/mixer") == 0) + if ((((mode[1] == 'b') || (mode[1] == 't')) && (mode[2] == '+')) || (mode[1] == '+')) m = O_RDWR; - else { - LOAD_FOPEN_FUNC(); - return _fopen(filename, mode); - } - if ((fd = open(filename, m)) < 0) + if ((fd = real_open(filename, m, 0)) < 0) return NULL; if (!(f = fdopen(fd, mode))) { close(fd); return NULL; } - + return f; } +#ifdef HAVE_OPEN64 + FILE *fopen64(const char *filename, const char *mode) { - debug(__FILE__": fopen64(%s)\n", filename); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename?filename:"NULL"); - if (strcmp(filename, "/dev/dsp") != 0 && - strcmp(filename, "/dev/adsp") != 0 && - strcmp(filename, "/dev/sndstat") != 0 && - strcmp(filename, "/dev/mixer") != 0) { + if (!filename || + !mode || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { LOAD_FOPEN64_FUNC(); return _fopen64(filename, mode); } @@ -1804,10 +2639,12 @@ FILE *fopen64(const char *filename, const char *mode) { return fopen(filename, mode); } +#endif + int fclose(FILE *f) { fd_info *i; - debug(__FILE__": fclose()\n"); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fclose()\n"); if (!function_enter()) { LOAD_FCLOSE_FUNC(); @@ -1825,9 +2662,9 @@ int fclose(FILE *f) { /* Dirty trick to avoid that the fd is not freed twice, once by us * and once by the real fclose() */ i->app_fd = -1; - + fd_info_unref(i); - + function_exit(); LOAD_FCLOSE_FUNC();