diff options
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | configure.ac | 28 | ||||
-rw-r--r-- | src/audio_out/Makefile.am | 13 | ||||
-rw-r--r-- | src/audio_out/audio_polyp_out.c | 565 |
4 files changed, 606 insertions, 3 deletions
@@ -1,7 +1,8 @@ xine-lib (1-xxx) * Fixed segfault when seeking with the "xvmc" and "xxmc" plugins playing files with IDCT / mocomp XvMC acceleration. - + * polypaudio sound server support + xine-lib (1-rc7) * Build system improvements: replacement functions, better work with headers * Set the codec name for Real Media even if we can't play the files diff --git a/configure.ac b/configure.ac index 16ffc2016..cc4bb7da7 100644 --- a/configure.ac +++ b/configure.ac @@ -871,6 +871,31 @@ AC_SUBST(LIBSTK_CFLAGS) AC_SUBST(LIBSTK_LIBS) AM_CONDITIONAL(HAVE_STK, [test x"$have_stk" = x"yes"]) +dnl --------------------------------------------- +dnl check for polypaudio +dnl --------------------------------------------- + +AC_MSG_CHECKING(for polypaudio) +dnl do some actual testing here +if test x$PKG_CONFIG = xno ; then + AC_MSG_RESULT(no) + echo "*** pkg-config not found. See http://pkgconfig.sourceforge.net" + echo "*** All of polypaudio dependent parts will be disabled" +else + POLYPAUDIO_REQUIRED_VERSION=0.6 + if $PKG_CONFIG --atleast-version $POLYPAUDIO_REQUIRED_VERSION polyplib ; then + POLYPAUDIO_CFLAGS=`$PKG_CONFIG --cflags polyplib` + POLYPAUDIO_LIBS=`$PKG_CONFIG --libs polyplib` + have_polypaudio="yes" + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + AC_MSG_RESULT([*** All of polypaudio dependent parts will be disabled ***]) + fi +fi +AC_SUBST(POLYPAUDIO_CFLAGS) +AC_SUBST(POLYPAUDIO_LIBS) +AM_CONDITIONAL(HAVE_POLYPAUDIO, [test x"$have_polypaudio" = x"yes"]) dnl --------------------------------------------- dnl check for DirectX @@ -2557,6 +2582,9 @@ fi if test x"$have_coreaudio" = "xyes"; then echo " - CoreAudio (Mac OS X audio driver)" fi +if test x"$have_polypaudio" = "xyes"; then + echo " - polypaudio sound server" +fi echo "---" diff --git a/src/audio_out/Makefile.am b/src/audio_out/Makefile.am index 7bae7c850..13b36b36f 100644 --- a/src/audio_out/Makefile.am +++ b/src/audio_out/Makefile.am @@ -1,6 +1,7 @@ include $(top_srcdir)/misc/Makefile.common -AM_CFLAGS = -DXINE_COMPILE $(ALSA_CFLAGS) $(ESD_CFLAGS) $(IRIXAL_CFLAGS) $(ARTS_CFLAGS) +AM_CFLAGS = -DXINE_COMPILE $(ALSA_CFLAGS) $(ESD_CFLAGS) $(IRIXAL_CFLAGS) $(ARTS_CFLAGS) \ + $(POLYPAUDIO_CFLAGS) EXTRA_DIST = audio_irixal_out.c @@ -40,6 +41,10 @@ if HAVE_COREAUDIO coreaudio_module = xineplug_ao_out_coreaudio.la endif +if HAVE_POLYPAUDIO +polypaudio_module = xineplug_ao_out_polypaudio.la +endif + ## # IMPORTANT: # --------- @@ -53,7 +58,8 @@ lib_LTLIBRARIES = xineplug_ao_out_none.la xineplug_ao_out_file.la \ $(arts_module) \ $(esd_module) \ $(directx_module) \ - $(coreaudio_module) + $(coreaudio_module) \ + $(polypaudio_module) #lib_LTLIBRARIES = \ # $(alsa_module) \ @@ -109,3 +115,6 @@ xineplug_ao_out_coreaudio_la_LDFLAGS = \ -avoid-version -module @XINE_PLUGIN_MIN_SYMS@ xineplug_ao_out_coreaudio_la_CFLAGS = -framework CoreAudio -framework AudioUnit +xineplug_ao_out_polypaudio_la_SOURCES = audio_polyp_out.c +xineplug_ao_out_polypaudio_la_LIBADD = $(POLYPAUDIO_LIBS) $(XINE_LIB) +xineplug_ao_out_polypaudio_la_LDFLAGS = -avoid-version -module @XINE_PLUGIN_MIN_SYMS@ diff --git a/src/audio_out/audio_polyp_out.c b/src/audio_out/audio_polyp_out.c new file mode 100644 index 000000000..a522f59ed --- /dev/null +++ b/src/audio_out/audio_polyp_out.c @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2000-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: audio_polyp_out.c,v 1.1 2004/11/15 03:58:55 miguelfreitas Exp $ + * + * ao plugin for polypaudio: + * http://0pointer.de/lennart/projects/polypaudio/ + * + * originally written for polypaudio simple api. Lennart then suggested + * using the async api for better control (such as volume), therefore, a lot + * of this code comes from Lennart's patch to mplayer. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#include <unistd.h> +#include <inttypes.h> +#include <assert.h> +#include <pthread.h> + +#include <polyp/polyplib.h> +#include <polyp/polyplib-error.h> +#include <polyp/mainloop.h> + +#include "xine_internal.h" +#include "xineutils.h" +#include "audio_out.h" +#include "bswap.h" + +#define GAP_TOLERANCE AO_MAX_GAP + +typedef struct polyp_driver_s { + + ao_driver_t ao_driver; + + xine_t *xine; + + /** The host to connect to */ + char *host; + + /** The sink to connect to */ + char *sink; + + /** Polypaudio playback stream object */ + struct pa_stream *stream; + + /** Polypaudio connection context */ + struct pa_context *context; + + /** Main event loop object */ + struct pa_mainloop *mainloop; + + pa_volume_t volume; + + int capabilities; + int mode; + + int32_t sample_rate; + uint32_t num_channels; + uint32_t bits_per_sample; + uint32_t bytes_per_frame; + + uint32_t frames_written; + + pthread_mutex_t lock; + +} polyp_driver_t; + +typedef struct { + audio_driver_class_t driver_class; + + xine_t *xine; +} polyp_class_t; + + +/** Wait until no further actions are pending on the connection context */ +static void wait_for_completion(polyp_driver_t *this) { + assert(this->context && this->mainloop); + + while (pa_context_is_pending(this->context)) + pa_mainloop_iterate(this->mainloop, 1, NULL); +} + +#if 0 +/** Make sure that the connection context doesn't starve to death */ +static void keep_alive(polyp_driver_t *this) { + assert(this->context && this->mainloop); + + while (pa_mainloop_iterate(this->mainloop, 0, NULL) > 0); +} +#endif + +/** Wait until the specified operation completes */ +static void wait_for_operation(polyp_driver_t *this, struct pa_operation *o) { + assert(o && this->context && this->mainloop); + + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_mainloop_iterate(this->mainloop, 1, NULL); + + pa_operation_unref(o); +} + + +/* + * open the audio device for writing to + */ +static int ao_polyp_open(ao_driver_t *this_gen, + uint32_t bits, uint32_t rate, int mode) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + struct pa_sample_spec ss; + struct pa_buffer_attr a; + + xprintf (this->xine, XINE_VERBOSITY_DEBUG, + "audio_polyp_out: ao_open bits=%d rate=%d, mode=%d\n", bits, rate, mode); + + if ( (mode & this->capabilities) == 0 ) { + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: unsupported mode %08x\n", mode); + return 0; + } + + if (this->stream) { + + if ( mode == this->mode && rate == this->sample_rate && + bits == this->bits_per_sample ) + return this->sample_rate; + + this_gen->close(this_gen); + } + + this->mode = mode; + this->sample_rate = rate; + this->bits_per_sample = bits; + this->num_channels = _x_ao_mode2channels( mode ); + this->bytes_per_frame = (this->bits_per_sample*this->num_channels)/8; + + ss.rate = rate; + ss.channels = this->num_channels; + switch (bits) { + case 8: + ss.format = PA_SAMPLE_U8; + break; + case 16: +#ifdef WORDS_BIGENDIAN + ss.format = PA_SAMPLE_S16BE; +#else + ss.format = PA_SAMPLE_S16LE; +#endif + break; + case 32: + ss.format = PA_SAMPLE_FLOAT32; + break; + } + + pthread_mutex_lock(&this->lock); + + if (!pa_sample_spec_valid(&ss)) { + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: Invalid sample spec\n"); + goto fail; + } + + this->mainloop = pa_mainloop_new(); + assert(this->mainloop); + + this->context = pa_context_new(pa_mainloop_get_api(this->mainloop), "xine"); + assert(this->context); + + pa_context_connect(this->context, this->host, 1, NULL); + + wait_for_completion(this); + + if (pa_context_get_state(this->context) != PA_CONTEXT_READY) { + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: Failed to connect to server: %s\n", + pa_strerror(pa_context_errno(this->context))); + goto fail; + } + + this->stream = pa_stream_new(this->context, "audio stream", &ss); + assert(this->stream); + + a.maxlength = pa_bytes_per_second(&ss)*1; + a.tlength = a.maxlength*9/10; + a.prebuf = a.tlength/2; + a.minreq = a.tlength/10; + + pa_stream_connect_playback(this->stream, this->sink, &a, PA_STREAM_INTERPOLATE_LATENCY, PA_VOLUME_NORM); + + wait_for_completion(this); + + if (pa_stream_get_state(this->stream) != PA_STREAM_READY) { + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: Failed to connect to server: %s\n", + pa_strerror(pa_context_errno(this->context))); + goto fail; + } + pthread_mutex_unlock(&this->lock); + + this->frames_written = 0; + + return this->sample_rate; + +fail: + pthread_mutex_unlock(&this->lock); + this_gen->close(this_gen); + return 0; +} + + +static int ao_polyp_num_channels(ao_driver_t *this_gen) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + return this->num_channels; +} + +static int ao_polyp_bytes_per_frame(ao_driver_t *this_gen) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + return this->bytes_per_frame; +} + +static int ao_polyp_get_gap_tolerance (ao_driver_t *this_gen) +{ + return GAP_TOLERANCE; +} + +static int ao_polyp_write(ao_driver_t *this_gen, int16_t *data, + uint32_t num_frames) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + int size = num_frames * this->bytes_per_frame; + int ret = 0; + + assert(this->stream && this->context); + + pthread_mutex_lock(&this->lock); + + if (pa_stream_get_state(this->stream) == PA_STREAM_READY) { + + while (size > 0) { + size_t l; + + l = pa_stream_writable_size(this->stream); + + if (l > size) + l = size; + + if (!l) { + pthread_mutex_unlock(&this->lock); + xine_usec_sleep (10000); + pthread_mutex_lock(&this->lock); + pa_mainloop_iterate(this->mainloop, 1, NULL); + } else + pa_stream_write(this->stream, data, l, NULL, 0); + + data = (int16_t *) ((uint8_t*) data + l); + size -= l; + } + + wait_for_completion(this); + + this->frames_written += num_frames; + + if (pa_stream_get_state(this->stream) == PA_STREAM_READY) + ret = 1; + } + pthread_mutex_unlock(&this->lock); + + return ret; +} + + +static int ao_polyp_delay (ao_driver_t *this_gen) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + pa_usec_t latency; + int delay_frames; + + pthread_mutex_lock(&this->lock); + latency = pa_stream_get_interpolated_latency(this->stream, NULL); + pthread_mutex_unlock(&this->lock); + + /* convert latency (us) to frame units. */ + delay_frames = (int)(latency * this->sample_rate / 1000000); + + if( delay_frames > this->frames_written ) + return this->frames_written; + else + return delay_frames; +} + +static void ao_polyp_close(ao_driver_t *this_gen) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + + pthread_mutex_lock(&this->lock); + if (this->stream) { + if (pa_stream_get_state(this->stream) == PA_STREAM_READY) + wait_for_operation(this, pa_stream_drain(this->stream, NULL, NULL)); + + pa_stream_unref(this->stream); + this->stream = NULL; + } + + if (this->context) { + pa_context_unref(this->context); + this->context = NULL; + } + + if (this->mainloop) { + pa_mainloop_free(this->mainloop); + this->mainloop = NULL; + } + pthread_mutex_unlock(&this->lock); +} + +static uint32_t ao_polyp_get_capabilities (ao_driver_t *this_gen) { + polyp_driver_t *this = (polyp_driver_t *) this_gen; + return this->capabilities; +} + +static void ao_polyp_exit(ao_driver_t *this_gen) +{ + polyp_driver_t *this = (polyp_driver_t *) this_gen; + + free (this); +} + +/** A callback function that is called when the + * pa_context_get_sink_input_info() operation completes. Saves the + * volume field of the specified structure to the global variable volume. */ +static void info_func(struct pa_context *c, const struct pa_sink_input_info *i, int is_last, void *userdata) { + + polyp_driver_t *this = (polyp_driver_t *) userdata; + if (is_last < 0) { + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: Failed to get sink input info: %s\n", + pa_strerror(pa_context_errno(this->context))); + return; + } + + if (!i) + return; + + this->volume = i->volume; +} + + +static int ao_polyp_get_property (ao_driver_t *this_gen, int property) { + polyp_driver_t *this = (polyp_driver_t *) this_gen; + + switch(property) { + case AO_PROP_PCM_VOL: + case AO_PROP_MIXER_VOL: + pthread_mutex_lock(&this->lock); + if( this->stream && this->context ) + wait_for_operation(this, + pa_context_get_sink_input_info(this->context, pa_stream_get_index(this->stream), info_func, this)); + pthread_mutex_unlock(&this->lock); + return (int) (pa_volume_to_user(this->volume)*100); + break; + case AO_PROP_MUTE_VOL: + break; + } + + return 0; +} + +static int ao_polyp_set_property (ao_driver_t *this_gen, int property, int value) { + polyp_driver_t *this = (polyp_driver_t *) this_gen; + + switch(property) { + case AO_PROP_PCM_VOL: + case AO_PROP_MIXER_VOL: + pthread_mutex_lock(&this->lock); + if( this->stream && this->context ) + wait_for_operation(this, + pa_context_set_sink_input_volume(this->context, pa_stream_get_index(this->stream), + pa_volume_from_user((double)value/100), NULL, NULL)); + pthread_mutex_unlock(&this->lock); + break; + case AO_PROP_MUTE_VOL: + break; + } + + return 0; +} + +static int ao_polyp_ctrl(ao_driver_t *this_gen, int cmd, ...) { + polyp_driver_t *this = (polyp_driver_t *) this_gen; + + pthread_mutex_lock(&this->lock); + switch (cmd) { + + case AO_CTRL_PLAY_PAUSE: + assert(this->stream && this->context ); + if(pa_stream_get_state(this->stream) == PA_STREAM_READY) + wait_for_operation(this,pa_stream_cork(this->stream, 1, NULL, NULL)); + break; + + case AO_CTRL_PLAY_RESUME: + assert(this->stream && this->context); + if(pa_stream_get_state(this->stream) == PA_STREAM_READY) + wait_for_operation(this,pa_stream_cork(this->stream, 0, NULL, NULL)); + break; + + case AO_CTRL_FLUSH_BUFFERS: + assert(this->stream && this->context); + if(pa_stream_get_state(this->stream) == PA_STREAM_READY) + wait_for_operation(this,pa_stream_flush(this->stream, NULL, NULL)); + this->frames_written = 0; + break; + } + pthread_mutex_unlock(&this->lock); + + return 0; +} + +static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *data) { + polyp_class_t *class = (polyp_class_t *) class_gen; + polyp_driver_t *this; + char hn[128]; + char *device; + + lprintf ("audio_polyp_out: open_plugin called\n"); + + this = (polyp_driver_t *) xine_xmalloc (sizeof (polyp_driver_t)); + this->xine = class->xine; + + /* + * set capabilities + */ + this->capabilities = AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO | AO_CAP_MIXER_VOL | + AO_CAP_PCM_VOL | AO_CAP_MUTE_VOL | AO_CAP_8BITS | + AO_CAP_16BITS | AO_CAP_FLOAT32; + + this->sample_rate = 0; + this->volume = PA_VOLUME_NORM; + + this->ao_driver.get_capabilities = ao_polyp_get_capabilities; + this->ao_driver.get_property = ao_polyp_get_property; + this->ao_driver.set_property = ao_polyp_set_property; + this->ao_driver.open = ao_polyp_open; + this->ao_driver.num_channels = ao_polyp_num_channels; + this->ao_driver.bytes_per_frame = ao_polyp_bytes_per_frame; + this->ao_driver.delay = ao_polyp_delay; + this->ao_driver.write = ao_polyp_write; + this->ao_driver.close = ao_polyp_close; + this->ao_driver.exit = ao_polyp_exit; + this->ao_driver.get_gap_tolerance = ao_polyp_get_gap_tolerance; + this->ao_driver.control = ao_polyp_ctrl; + + device = this->xine->config->register_string(this->xine->config, + "audio.polypaudio_device", + "", + _("device used for polypaudio"), + _("use 'server[:sink]' for setting the " + "polypaudio sink device."), + 10, NULL, + NULL); + + if (device && strlen(device)) { + int i = strcspn(device, ":"); + if (i >= sizeof(hn)) + i = sizeof(hn)-1; + + if (i > 0) { + strncpy(this->host = hn, device, i); + hn[i] = 0; + } + + if (device[i] == ':') + this->sink = device+i+1; + } + + xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: host %s sink %s\n", + this->host ? this->host : "(null)", this->sink ? this->sink : "(null)"); + + pthread_mutex_init (&this->lock, NULL); + + /* test polypaudio connection */ + if( this->ao_driver.open(&this->ao_driver, 16, 44100, AO_CAP_MODE_STEREO) != 0 ) { + this->ao_driver.close(&this->ao_driver); + } else { + free(this); + xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_polyp_out: open_plugin failed.\n"); + return NULL; + } + + return &this->ao_driver; +} + +/* + * class functions + */ + +static char* get_identifier (audio_driver_class_t *this_gen) { + return "polypaudio"; +} + +static char* get_description (audio_driver_class_t *this_gen) { + return _("xine audio output plugin using polypaudio sound server"); +} + +static void dispose_class (audio_driver_class_t *this_gen) { + + polyp_class_t *this = (polyp_class_t *) this_gen; + + free (this); +} + +static void *init_class (xine_t *xine, void *data) { + + polyp_class_t *this; + + lprintf ("audio_polyp_out: init class\n"); + + this = (polyp_class_t *) xine_xmalloc (sizeof (polyp_class_t)); + + this->driver_class.open_plugin = open_plugin; + this->driver_class.get_identifier = get_identifier; + this->driver_class.get_description = get_description; + this->driver_class.dispose = dispose_class; + + this->xine = xine; + + return this; +} + +static ao_info_t ao_info_polyp = { + 6 +}; + +/* + * exported plugin catalog entry + */ + +plugin_info_t xine_plugin_info[] = { + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_AUDIO_OUT, 8, "polypaudio", XINE_VERSION_CODE, &ao_info_polyp, init_class }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +}; + |