From dab1e2dafa8628fead93cb3590b6c5ff07994e9e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 20 May 2006 00:55:56 +0000 Subject: update for polypaudio 0.9 git-svn-id: file:///home/lennart/svn/public/xmms-pulse/trunk@36 ef929aba-56e2-0310-84e0-b7573d389508 --- bootstrap.sh | 41 ++- configure.ac | 14 +- src/Makefile.am | 10 +- src/plugin.c | 998 +++++++++++++++++++++++++++++++------------------------- 4 files changed, 599 insertions(+), 464 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index dd5d53b..597fad8 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -17,31 +17,48 @@ # along with xmms-polyp; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +VERSION=1.9 + run_versioned() { local P - type -p "$1-$2" &> /dev/null && P="$1-$2" || local P="$1" + local V + + V=$(echo "$2" | sed -e 's,\.,,g') + + if [ -e "`which $1$V`" ] ; then + P="$1$V" + else + if [ -e "`which $1-$2`" ] ; then + P="$1-$2" + else + P="$1" + fi + fi shift 2 "$P" "$@" } +set -ex + if [ "x$1" = "xam" ] ; then - set -ex - run_versioned automake 1.7 -a -c --foreign + run_versioned automake "$VERSION" -a -c --foreign ./config.status else - set -ex - rm -rf autom4te.cache rm -f config.cache - run_versioned aclocal 1.7 - libtoolize -c --force - autoheader - run_versioned automake 1.7 -a -c --foreign - autoconf -Wall + test "x$LIBTOOLIZE" = "x" && LIBTOOLIZE=libtoolize - CFLAGS="-g -O0" ./configure --sysconfdir=/etc "$@" + "$LIBTOOLIZE" -c --force + run_versioned aclocal "$VERSION" + libtoolize -c --force + run_versioned autoconf 2.59 -Wall + run_versioned autoheader 2.59 + run_versioned automake "$VERSION" -a -c --foreign - make clean + if test "x$NOCONFIGURE" = "x"; then + CFLAGS="-g -O0" ./configure --sysconfdir=/etc "$@" + make clean + fi fi diff --git a/configure.ac b/configure.ac index abf3727..0be433d 100644 --- a/configure.ac +++ b/configure.ac @@ -20,10 +20,10 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. AC_PREREQ(2.57) -AC_INIT([xmms-polyp],[0.5],[mzkzzfcbylc (at) 0pointer (dot) de]) +AC_INIT([xmms-polyp],[0.9.0],[mzkzzfcbylc (at) 0pointer (dot) de]) AC_CONFIG_SRCDIR([src/plugin.c]) AC_CONFIG_HEADERS([config.h]) -AM_INIT_AUTOMAKE([foreign -Wall]) +AM_INIT_AUTOMAKE([foreign 1.9 -Wall]) AC_SUBST(PACKAGE_URL, [http://0pointer.de/lennart/projects/xmms-polyp/]) @@ -43,9 +43,15 @@ AC_C_CONST AC_FUNC_MALLOC AC_TYPE_SIZE_T -ACX_PTHREAD +PKG_PROG_PKG_CONFIG -PKG_CHECK_MODULES(POLYP, [ polyplib >= 0.8 ]) +if test -d ../polypaudio ; then + POLYP_CFLAGS='-I$(top_srcdir)/../polypaudio/src' + POLYP_LIBS='-L$(top_srcdir)/../polypaudio/src/.libs -lpolyp-simple' + echo "*** Found polypaudio in ../polypaudio, using that version ***" +else + PKG_CHECK_MODULES(POLYP, [ polyplib-simple >= 0.9.0 ]) +fi AC_SUBST(POLYP_LIBS) AC_SUBST(POLYP_CFLAGS) diff --git a/src/Makefile.am b/src/Makefile.am index 7ff61a4..192b72d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,9 +22,9 @@ AM_LIBADD=$(PTHREAD_LIBS) xmmsplugindir=$(XMMS_OUTPUTPLUGINDIR) -xmmsplugin_LTLIBRARIES=libpolyp.la +xmmsplugin_LTLIBRARIES=libxmms-polyp.la -libpolyp_la_SOURCES=plugin.c -libpolyp_la_LDFLAGS=-module -avoid-version -libpolyp_la_LIBADD=$(AM_LIBADD) $(POLYP_LIBS) $(XMMS_LIBS) -libpolyp_la_CFLAGS=$(AM_CFLAGS) $(POLYP_CFLAGS) $(XMMS_CFLAGS) +libxmms_polyp_la_SOURCES=plugin.c +libxmms_polyp_la_LDFLAGS=-module -avoid-version +libxmms_polyp_la_LIBADD=$(AM_LIBADD) $(POLYP_LIBS) $(XMMS_LIBS) +libxmms_polyp_la_CFLAGS=$(AM_CFLAGS) $(POLYP_CFLAGS) $(XMMS_CFLAGS) diff --git a/src/plugin.c b/src/plugin.c index 95ed252..47b93c7 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -31,64 +31,42 @@ #include #include #include -#include + #include -static pthread_cond_t request_cond = PTHREAD_COND_INITIALIZER; -static pthread_mutex_t request_mutex = PTHREAD_MUTEX_INITIALIZER; - -struct request { - enum { - MESSAGE_OPEN, - MESSAGE_CLOSE, - MESSAGE_WRITE, - MESSAGE_FLUSH, - MESSAGE_PAUSE, - MESSAGE_UNPAUSE, - MESSAGE_LATENCY, - MESSAGE_WRITABLE, - MESSAGE_TRIGGER, - MESSAGE_GETVOLUME, - MESSAGE_SETVOLUME - } message; - void *data; - struct pa_sample_spec ss; - size_t length; - int success, done; - uint32_t value; - pa_usec_t latency; - pa_cvolume volume; -}; - -static struct request* current_request = NULL; - -static int pipe_fds[2] = { -1, -1}; - -static pthread_t thread_id; -static int thread_running = 0; -static struct pa_context *context = NULL; -static struct pa_stream *stream = NULL; -static struct pa_mainloop_api *mainloop_api = NULL; -static int failed = 0; +static pa_context *context = NULL; +static pa_stream *stream = NULL; +static pa_threaded_mainloop *mainloop = NULL; + static pa_cvolume volume; -static size_t written = 0; -static int do_trigger = 0, triggered = 0; -static struct pa_sample_spec sample_spec; -static int locked_by_thread = 0; -static pa_cvolume saved_volume; +static int volume_valid = 0; -/* This function is from xmms' core */ -gint ctrlsocket_get_session_id(void); +static int do_trigger = 0; +static uint64_t written = 0; +static int time_offset_msec = 0; +static int just_flushed = 0; -static void *memdup(void *p, size_t l) { - void *r = malloc(l); - memcpy(r, p, l); - return r; -} +static int connected = 0; + +static pa_time_event *volume_time_event = NULL; -#ifndef HOST_NAME_MAX -#define HOST_NAME_MAX 255 -#endif +#define CHECK_DEAD_GOTO(label, warn) do { \ +if (!mainloop || \ + !context || pa_context_get_state(context) != PA_CONTEXT_READY || \ + !stream || pa_stream_get_state(stream) != PA_STREAM_READY) { \ + if (warn) \ + g_warning("Connection died: %s", context ? pa_strerror(pa_context_errno(context)) : "NULL"); \ + goto label; \ + } \ +} while(0); + +#define CHECK_CONNECTED(retval) \ +do { \ + if (!connected) return retval; \ +} while (0); + +/* This function is from xmms' core */ +gint ctrlsocket_get_session_id(void); static const char* get_song_name(void) { static char t[256]; @@ -98,489 +76,616 @@ static const char* get_song_name(void) { session = ctrlsocket_get_session_id(); pos = xmms_remote_get_playlist_pos(session); if (!(str = xmms_remote_get_playlist_title(session, pos))) - return "xmms"; + return "Playback Stream"; - snprintf(t, sizeof(t), "XMMS [%s]", str); + snprintf(t, sizeof(t), "%s", str); return t; } -static const char* get_host(void) { - static char t[HOST_NAME_MAX+1]; - gethostname(t, sizeof(t)); - t[HOST_NAME_MAX] = 0; - return t; -} - -static void lock_request(void) { - assert(thread_running && pthread_equal(pthread_self(), thread_id)); - - if (!(locked_by_thread++)) - pthread_mutex_lock(&request_mutex); -} - -static void unlock_request(void) { - assert(thread_running && pthread_equal(pthread_self(), thread_id) && locked_by_thread > 0); - - if (!(--locked_by_thread)) - pthread_mutex_unlock(&request_mutex); -} - -static void finish_request(int success) { - if (!success) - failed = 1; - - lock_request(); - - if (current_request) { - current_request->done = 1; - current_request->success = success; - pthread_cond_broadcast(&request_cond); - } - - unlock_request(); -} - -static void info_callback(struct pa_context *c, const struct pa_sink_input_info *i, int is_last, void *userdata) { - assert(c && c == context); +static void info_cb(struct pa_context *c, const struct pa_sink_input_info *i, int is_last, void *userdata) { + assert(c); if (!i) return; volume = i->volume; + volume_valid = 1; } -static void subscribe_callback(struct pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) { - assert(c && c == context); +static void subscribe_cb(struct pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) { + pa_operation *o; + + assert(c); - if (!stream || index != pa_stream_get_index(stream) || t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + if (!stream || + index != pa_stream_get_index(stream) || + (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))) return; - pa_operation_unref(pa_context_get_sink_input_info(c, index, info_callback, NULL)); -} - -static void stream_state_callback(struct pa_stream *s, void *userdata) { - assert(stream == s); - - switch(pa_stream_get_state(s)) { - case PA_STREAM_CREATING: - break; - case PA_STREAM_READY: - assert(current_request && current_request->message == MESSAGE_OPEN); - pa_operation_unref(pa_context_get_sink_input_info(context, pa_stream_get_index(s), info_callback, NULL)); - finish_request(1); - break; - default: - finish_request(0); + if (!(o = pa_context_get_sink_input_info(c, index, info_cb, NULL))) { + g_warning("pa_context_get_sink_input_info() failed: %s", pa_strerror(pa_context_errno(c))); + return; } + + pa_operation_unref(o); } -static void context_state_callback(struct pa_context *c, void *userdata) { - assert(c && c == context); +static void context_state_cb(pa_context *c, void *userdata) { + assert(c); switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; + } +} - case PA_CONTEXT_READY : - assert(!stream && current_request); - - pa_context_set_subscribe_callback(context, subscribe_callback, NULL); - pa_operation_unref(pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL)); +static void stream_state_cb(pa_stream *s, void * userdata) { + assert(s); - stream = pa_stream_new(c, get_song_name(), ¤t_request->ss, NULL); - assert(stream); + switch (pa_stream_get_state(s)) { - pa_stream_set_state_callback(stream, stream_state_callback, NULL); - pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_LATENCY, ¤t_request->volume, NULL); + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(mainloop, 0); break; - default: - finish_request(0); + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; } } -static void context_drain_callback(struct pa_context *c, void *userdata) { - assert(c && c == context); - mainloop_api->quit(mainloop_api, 0); - finish_request(1); +static void stream_success_cb(pa_stream *s, int success, void *userdata) { + assert(s); + + if (userdata) + *(int*) userdata = success; + + pa_threaded_mainloop_signal(mainloop, 0); } -static void stream_success_callback(struct pa_stream *s, int success, void *userdata) { - assert(s == stream && s); - finish_request(!!success); +static void context_success_cb(pa_context *c, int success, void *userdata) { + assert(c); + + if (userdata) + *(int*) userdata = success; + + pa_threaded_mainloop_signal(mainloop, 0); } -static void context_success_callback(struct pa_context *c, int success, void *userdata) { - assert(c == context && c); - finish_request(!!success); +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + assert(s); + + pa_threaded_mainloop_signal(mainloop, 0); } -static void request_func(struct pa_mainloop_api*api, struct pa_io_event *io, int fd, enum pa_io_event_flags f, void *userdata) { - char x; - - /*fprintf(stderr, "REQUEST %p\n", current_request);*/ - - assert(api && io && f == PA_IO_EVENT_INPUT); - - read(fd, &x, 1); - - lock_request(); - - if (current_request) { - if (failed && current_request->message != MESSAGE_CLOSE) { - finish_request(0); - } else { - - /*fprintf(stderr, "req: %i\n", current_request->message);*/ - - switch (current_request->message) { - case MESSAGE_OPEN: { - char t[64]; - assert(!context && !stream); - - snprintf(t, sizeof(t), "XMMS (PID %lu on %s)", (unsigned long) getpid(), get_host()); - context = pa_context_new(api, t); - assert(context); - pa_context_set_state_callback(context, context_state_callback, NULL); - pa_context_connect(context, NULL, 1, NULL); - break; - - } - case MESSAGE_CLOSE: { - struct pa_operation *o; - - assert(context); - if (stream) - pa_stream_disconnect(stream); - if ((o = pa_context_drain(context, context_drain_callback, NULL))) - pa_operation_unref(o); - else { - api->quit(mainloop_api, 0); - finish_request(1); - } - break; - } - - case MESSAGE_WRITE: - assert(context && stream && current_request->data && current_request->length > 0); - pa_stream_write(stream, current_request->data, current_request->length, free, 0, 0); - current_request->data = NULL; - finish_request(1); - break; - - case MESSAGE_FLUSH: - assert(context && stream); - pa_operation_unref(pa_stream_flush(stream, stream_success_callback, NULL)); - break; - - case MESSAGE_PAUSE: - case MESSAGE_UNPAUSE: - assert(context && stream); - pa_operation_unref(pa_stream_cork(stream, current_request->message == MESSAGE_PAUSE, stream_success_callback, NULL)); - break; - - case MESSAGE_LATENCY: - current_request->latency = context && stream ? pa_stream_get_interpolated_latency(stream, NULL) : 0; - finish_request(1); - break; - - case MESSAGE_WRITABLE: - assert(context && stream); - current_request->value = pa_stream_writable_size(stream); - finish_request(1); - break; - - case MESSAGE_GETVOLUME: - assert(context && stream); - current_request->volume = volume; - finish_request(1); - break; - - case MESSAGE_SETVOLUME: - assert(context && stream); - - if (pa_cvolume_equal(¤t_request->volume, &volume)) - finish_request(1); - else - pa_operation_unref(pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), ¤t_request->volume, context_success_callback, NULL)); - break; - - case MESSAGE_TRIGGER: - assert(context && stream); - pa_operation_unref(pa_stream_trigger(stream, stream_success_callback, NULL)); - break; - - } - } - } - - unlock_request(); +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + assert(s); - /*fprintf(stderr, "REQ_DONE\n");*/ + pa_threaded_mainloop_signal(mainloop, 0); } -static void* thread_func(void *t) { - struct pa_mainloop *m; - struct pa_io_event *io; +static void polyp_get_volume(int *l, int *r) { + pa_cvolume v; + int b = 0; - assert(pipe_fds[0] >= 0 && !mainloop_api); - - failed = locked_by_thread = 0; - thread_running = 1; +/* g_message("get_volume"); */ + + *l = *r = 100; + + if (connected) { + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); + + v = volume; + b = volume_valid; + + fail: + pa_threaded_mainloop_unlock(mainloop); + } else { + v = volume; + b = volume_valid; + } - m = pa_mainloop_new(); - assert(m); - mainloop_api = pa_mainloop_get_api(m); - assert(mainloop_api); + if (b) { + if (v.channels == 2) { + *l = (int) ((v.values[0]*100)/PA_VOLUME_NORM); + *r = (int) ((v.values[1]*100)/PA_VOLUME_NORM); + } else + *l = *r = (int) ((pa_cvolume_avg(&v)*100)/PA_VOLUME_NORM); + } +} - io = mainloop_api->io_new(mainloop_api, pipe_fds[0], PA_IO_EVENT_INPUT, &request_func, NULL); - assert(io); +static void volume_time_cb(pa_mainloop_api *api, pa_time_event *e, const struct timeval *tv, void *userdata) { + pa_operation *o; - pa_mainloop_run(m, NULL); + if (!(o = pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), &volume, NULL, NULL))) + g_warning("pa_context_set_sink_input_volume() failed: %s", pa_strerror(pa_context_errno(context))); + else + pa_operation_unref(o); - if (context) { - pa_context_unref(context); - context = NULL; + /* We don't wait for completion of this command */ + + api->time_free(volume_time_event); + volume_time_event = NULL; +} + +static void polyp_set_volume(int l, int r) { + +/* g_message("set_volume"); */ + + if (connected) { + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); } - if (stream) { - pa_stream_unref(stream); - stream = NULL; + if (!volume_valid || volume.channels != 1) { + volume.values[0] = ((pa_volume_t) l * PA_VOLUME_NORM)/100; + volume.values[1] = ((pa_volume_t) r * PA_VOLUME_NORM)/100; + volume.channels = 2; + } else { + volume.values[0] = ((pa_volume_t) l * PA_VOLUME_NORM)/100; + volume.channels = 1; } - mainloop_api->io_free(io); - pa_mainloop_free(m); + volume_valid = 1; - mainloop_api = NULL; - thread_running = 0; + if (connected && !volume_time_event) { + struct timeval tv; + pa_mainloop_api *api = pa_threaded_mainloop_get_api(mainloop); + volume_time_event = api->time_new(api, pa_timeval_add(pa_gettimeofday(&tv), 100000), volume_time_cb, NULL); + } - pthread_cond_broadcast(&request_cond); - - return NULL; +fail: + if (connected) + pa_threaded_mainloop_unlock(mainloop); } -static void start_thread(void) { - int r; - assert(!thread_running); +static void polyp_pause(short b) { + pa_operation *o = NULL; + int success = 0; - r = pipe(pipe_fds); - assert(r >= 0 && pipe_fds[0] >= 0 && pipe_fds[1] >= 0); +/* g_message("pause"); */ - current_request = NULL; + CHECK_CONNECTED(); - r = pthread_create(&thread_id, NULL, thread_func, NULL); - assert(!r); + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); - thread_running = 1; -} + if (!(o = pa_stream_cork(stream, b, stream_success_cb, &success))) { + g_warning("pa_stream_cork() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } + + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } -static void stop_thread(void) { - pthread_join(thread_id, NULL); - thread_running = 0; - close(pipe_fds[0]); - close(pipe_fds[1]); - pipe_fds[0] = pipe_fds[1] = -1; -} + if (!success) + g_warning("pa_stream_cork() failed: %s", pa_strerror(pa_context_errno(context))); -static void execute_request(struct request *r) { - char x = 'x'; - assert(r); +fail: - r->success = r->done = 0; + if (o) + pa_operation_unref(o); - pthread_mutex_lock(&request_mutex); + pa_threaded_mainloop_unlock(mainloop); +} - while (current_request && thread_running) - pthread_cond_wait(&request_cond, &request_mutex); - - if (!thread_running) { - r->success = 0; - r->done = 1; - } else { - current_request = r; - - assert(pipe_fds[1] >= 0); - write(pipe_fds[1], &x, sizeof(x)); +static int polyp_free(void) { + size_t l = 0; + pa_operation *o = NULL; - while (!r->done && thread_running) - pthread_cond_wait(&request_cond, &request_mutex); +/* g_message("free"); */ - if (!thread_running) { - r->success = 0; - r->done = 1; - } + CHECK_CONNECTED(0); - current_request = NULL; + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); - /* Notify other waiting threads that the request was completed */ - pthread_cond_broadcast(&request_cond); + if ((l = pa_stream_writable_size(stream)) == (size_t) -1) { + g_warning("pa_stream_writable_size() failed: %s", pa_strerror(pa_context_errno(context))); + l = 0; + goto fail; + } + + /* If this function is called twice with no polyp_write() call in + * between this means we should trigger the playback */ + if (do_trigger) { + int success = 0; + + if (!(o = pa_stream_trigger(stream, stream_success_cb, &success))) { + g_warning("pa_stream_trigger() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } + + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } + + if (!success) + g_warning("pa_stream_trigger() failed: %s", pa_strerror(pa_context_errno(context))); } - pthread_mutex_unlock(&request_mutex); +fail: + pa_threaded_mainloop_unlock(mainloop); + + do_trigger = !!l; + return (int) l; } -static void polyp_get_volume(int *l, int *r) { - struct request req; - pa_cvolume v; +static int polyp_get_written_time(void) { + int r = 0; + +/* g_message("get_written_time"); */ - req.message = MESSAGE_GETVOLUME; - execute_request(&req); + CHECK_CONNECTED(0); - if (!req.success) - v = saved_volume; - else - saved_volume = v = req.volume; + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); + + r = (int) (((double) written*1000) / pa_bytes_per_second(pa_stream_get_sample_spec(stream))); - *l = (int) (pa_sw_volume_to_linear(v.values[0]) * 100); - if (sample_spec.channels > 1) - *r = (int) (pa_sw_volume_to_linear(v.values[1]) * 100); - else - *r = *l; +/* g_message("written_time = %i", r); */ + +fail: + pa_threaded_mainloop_unlock(mainloop); + + return r; } -void polyp_set_volume(int l, int r) { - struct request req; +static int polyp_get_output_time(void) { + int r = 0; + pa_usec_t t; + +/* g_message("get_output_time"); */ + + CHECK_CONNECTED(0); - req.message = MESSAGE_SETVOLUME; - pa_cvolume_reset(&req.volume, sample_spec.channels); - req.volume.values[0] = pa_sw_volume_from_linear(l/100.0); - if (sample_spec.channels > 1) - req.volume.values[1] = pa_sw_volume_from_linear(r/100.0); - execute_request(&req); + pa_threaded_mainloop_lock(mainloop); - saved_volume = req.volume; -} + for (;;) { + CHECK_DEAD_GOTO(fail, 1); + + if (pa_stream_get_time(stream, &t) >= 0) + break; -static void polyp_pause(short b) { - struct request r; - r.message = b ? MESSAGE_PAUSE : MESSAGE_UNPAUSE; - execute_request(&r); -} + if (pa_context_errno(context) != PA_ERR_NODATA) { + g_warning("pa_stream_get_time() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } -static int polyp_free(void) { - int ret; - struct request r; + pa_threaded_mainloop_wait(mainloop); + } - r.message = MESSAGE_WRITABLE; - execute_request(&r); + r = (int) (t / 1000); - if (!r.success) - return 0; + if (just_flushed) { + time_offset_msec -= r; + just_flushed = 0; + } - ret = r.value; + r += time_offset_msec; + +/* g_message("output_time = %i", r); */ - if (do_trigger && !triggered) { - r.message = MESSAGE_TRIGGER; - execute_request(&r); - triggered = 1; - } +fail: + pa_threaded_mainloop_unlock(mainloop); - do_trigger = !!ret; - return ret; + return r; } -static int polyp_get_written_time(void) { - return (int) ((((double) written/pa_frame_size(&sample_spec))*1000)/sample_spec.rate); -} +static int polyp_playing(void) { + int r = 0; + const pa_timing_info *i; -static int polyp_get_output_time(void) { - int t, ms; - struct request r; + CHECK_CONNECTED(0); + +/* g_message("playing"); */ - r.message = MESSAGE_LATENCY; - execute_request(&r); + pa_threaded_mainloop_lock(mainloop); - if (!r.success) - return 0; + for (;;) { + CHECK_DEAD_GOTO(fail, 1); - t = polyp_get_written_time(); - ms = (int) r.latency/1000; - - return ms > t ? 0 : t-ms; -} + if ((i = pa_stream_get_timing_info(stream))) + break; + + if (pa_context_errno(context) != PA_ERR_NODATA) { + g_warning("pa_stream_get_timing_info() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } -static int polyp_playing(void) { + pa_threaded_mainloop_wait(mainloop); + } + + r = i->playing; +fail: + pa_threaded_mainloop_unlock(mainloop); - return polyp_get_output_time() >= polyp_get_written_time(); + return r; } static void polyp_flush(int time) { - struct request r; + pa_operation *o = NULL; + int success = 0; + +/* g_message("flush"); */ + + CHECK_CONNECTED(); - r.message = MESSAGE_FLUSH; - execute_request(&r); + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); - triggered = 0; + if (!(o = pa_stream_flush(stream, stream_success_cb, &success))) { + g_warning("pa_stream_flush() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } + + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } - written = (size_t) (((double)time*sample_spec.rate/1000)*pa_frame_size(&sample_spec)); + if (!success) + g_warning("pa_stream_flush() failed: %s", pa_strerror(pa_context_errno(context))); + + written = (uint64_t) (((double) time * pa_bytes_per_second(pa_stream_get_sample_spec(stream))) / 1000); + just_flushed = 1; + time_offset_msec = time; + +fail: + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(mainloop); } static void polyp_write(void* ptr, int length) { - struct request r; - r.message = MESSAGE_WRITE; - r.data = memdup(ptr, length); - r.length = length; +/* g_message("write"); */ - execute_request(&r); + CHECK_CONNECTED(); - written += length; + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 1); + + if (pa_stream_write(stream, ptr, length, NULL, PA_SEEK_RELATIVE, 0) < 0) { + g_warning("pa_stream_write() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } + do_trigger = 0; + written += length; + +fail: + + pa_threaded_mainloop_unlock(mainloop); +} + +static void drain(void) { + pa_operation *o = NULL; + int success = 0; + + CHECK_CONNECTED(); + + pa_threaded_mainloop_lock(mainloop); + CHECK_DEAD_GOTO(fail, 0); + + if (!(o = pa_stream_drain(stream, stream_success_cb, &success))) { + g_warning("pa_stream_drain() failed: %s", pa_strerror(pa_context_errno(context))); + goto fail; + } + + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } + + if (!success) + g_warning("pa_stream_drain() failed: %s", pa_strerror(pa_context_errno(context))); + +fail: + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(mainloop); } static void polyp_close(void) { - struct request r; - assert(thread_running); - r.message = MESSAGE_CLOSE; - execute_request(&r); +/* g_message("close"); */ + + drain(); + + connected = 0; - stop_thread(); + if (mainloop) + pa_threaded_mainloop_stop(mainloop); + + if (stream) { + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = NULL; + } + + if (context) { + pa_context_disconnect(context); + pa_context_unref(context); + context = NULL; + } + + if (mainloop) { + pa_threaded_mainloop_free(mainloop); + mainloop = NULL; + } + + volume_time_event = NULL; } static int polyp_open(AFormat fmt, int rate, int nch) { - struct request r; + pa_sample_spec ss; + pa_operation *o = NULL; + int success; + +/* g_message("open"); */ + g_assert(!mainloop); + g_assert(!context); + g_assert(!stream); + g_assert(!connected); + if (fmt == FMT_U8) - r.ss.format = PA_SAMPLE_U8; + ss.format = PA_SAMPLE_U8; else if (fmt == FMT_S16_LE) - r.ss.format = PA_SAMPLE_S16LE; + ss.format = PA_SAMPLE_S16LE; else if (fmt == FMT_S16_BE) - r.ss.format = PA_SAMPLE_S16BE; + ss.format = PA_SAMPLE_S16BE; else if (fmt == FMT_S16_NE) - r.ss.format = PA_SAMPLE_S16NE; + ss.format = PA_SAMPLE_S16NE; else - return 0; + return FALSE; + + ss.rate = rate; + ss.channels = nch; - r.ss.rate = rate; - r.ss.channels = nch; + if (!pa_sample_spec_valid(&ss)) + return FALSE; - if (!pa_sample_spec_valid(&r.ss)) - return 0; + if (!volume_valid) { + pa_cvolume_reset(&volume, ss.channels); + volume_valid = 1; + } else if (volume.channels != ss.channels) + pa_cvolume_set(&volume, ss.channels, pa_cvolume_avg(&volume)); - sample_spec = r.ss; + if (!(mainloop = pa_threaded_mainloop_new())) { + g_warning("Failed to allocate main loop"); + goto fail; + } - pa_cvolume_reset(&volume, nch); - pa_cvolume_reset(&saved_volume, nch); + pa_threaded_mainloop_lock(mainloop); - start_thread(); + if (!(context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "XMMS"))) { + g_warning("Failed to allocate context"); + goto unlock_and_fail; + } + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_set_subscribe_callback(context, subscribe_cb, NULL); + + if (pa_context_connect(context, NULL, 0, NULL) < 0) { + g_warning("Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + if (pa_threaded_mainloop_start(mainloop) < 0) { + g_warning("Failed to start main loop"); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(mainloop); + + if (pa_context_get_state(context) != PA_CONTEXT_READY) { + g_warning("Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + if (!(stream = pa_stream_new(context, get_song_name(), &ss, NULL))) { + g_warning("Failed to create stream: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } - r.message = MESSAGE_OPEN; - r.volume = saved_volume; - execute_request(&r); + pa_stream_set_state_callback(stream, stream_state_cb, NULL); + pa_stream_set_write_callback(stream, stream_request_cb, NULL); + pa_stream_set_latency_update_callback(stream, stream_latency_update_cb, NULL); - if (!r.success) { - polyp_close(); - return 0; + if (pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, &volume, NULL) < 0) { + g_warning("Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; } - written = do_trigger = triggered = failed = 0; + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(mainloop); - return 1; + if (pa_stream_get_state(stream) != PA_STREAM_READY) { + g_warning("Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + /* Now subscribe to events */ + if (!(o = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, context_success_cb, &success))) { + g_warning("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } + + if (!success) { + g_warning("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + pa_operation_unref(o); + + /* Now request the initial stream info */ + if (!(o = pa_context_get_sink_input_info(context, pa_stream_get_index(stream), info_cb, NULL))) { + g_warning("pa_context_get_sink_input_info() failed: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail, 1); + pa_threaded_mainloop_wait(mainloop); + } + + if (!volume_valid) { + g_warning("pa_context_get_sink_input_info() failed: %s", pa_strerror(pa_context_errno(context))); + goto unlock_and_fail; + } + + do_trigger = 0; + written = 0; + time_offset_msec = 0; + just_flushed = 0; + connected = 1; + volume_time_event = NULL; + + pa_threaded_mainloop_unlock(mainloop); + + return TRUE; + +unlock_and_fail: + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(mainloop); + +fail: + + polyp_close(); + + return FALSE; } static void polyp_init(void) { @@ -593,48 +698,55 @@ static void polyp_about(void) { return; dialog = xmms_show_message( - "About XMMS Polypaudio Output Plugin", - "XMMS Polypaudio Output Plugin\n\n " - "This program is free software; you can redistribute it and/or modify\n" - "it under the terms of the GNU General Public License as published by\n" - "the Free Software Foundation; either version 2 of the License, or\n" - "(at your option) any later version.\n" - "\n" - "This program is distributed in the hope that it will be useful,\n" - "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" - "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" - "GNU General Public License for more details.\n" - "\n" - "You should have received a copy of the GNU General Public License\n" - "along with this program; if not, write to the Free Software\n" - "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,\n" - "USA.", "OK", FALSE, NULL, NULL); - gtk_signal_connect(GTK_OBJECT(dialog), "destroy", - GTK_SIGNAL_FUNC(gtk_widget_destroyed), - &dialog); + "About XMMS Polypaudio Output Plugin", + "XMMS Polypaudio Output Plugin\n\n " + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, write to the Free Software\n" + "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,\n" + "USA.", + "OK", + FALSE, + NULL, + NULL); + + gtk_signal_connect( + GTK_OBJECT(dialog), + "destroy", + GTK_SIGNAL_FUNC(gtk_widget_destroyed), + &dialog); } -static OutputPlugin polyp_plugin = { - NULL, - NULL, - "Polypaudio Output Plugin", - polyp_init, - polyp_about, - NULL, /* polyp_configure, */ - polyp_get_volume, - polyp_set_volume, - polyp_open, - polyp_write, - polyp_close, - polyp_flush, - polyp_pause, - polyp_free, - polyp_playing, - polyp_get_output_time, - polyp_get_written_time, -}; - OutputPlugin *get_oplugin_info(void) { + static OutputPlugin polyp_plugin = { + NULL, + NULL, + "Polypaudio Output Plugin", + polyp_init, + polyp_about, + NULL, /* polyp_configure, */ + polyp_get_volume, + polyp_set_volume, + polyp_open, + polyp_write, + polyp_close, + polyp_flush, + polyp_pause, + polyp_free, + polyp_playing, + polyp_get_output_time, + polyp_get_written_time, + }; + return &polyp_plugin; } -- cgit