/* * Copyright (C) 2000-2007 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_pulse_out.c,v 1.17 2007/04/01 00:32:29 dgp85 Exp $ * * ao plugin for pulseaudio (rename of polypaudio): * http://0pointer.de/lennart/projects/pulsaudio/ * * 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 #include #include #include #include #include #include #include #include #include #include "xine_internal.h" #include "xineutils.h" #include "audio_out.h" #include "bswap.h" #define GAP_TOLERANCE AO_MAX_GAP /* CHECKME: should this be conditional on autotools? */ extern const char *__progname; typedef struct { audio_driver_class_t driver_class; xine_t *xine; pthread_mutex_t pa_mutex; /*< Mutex controlling PulseAudio access. */ struct pa_context *context; /*< Pulseaudio connection context */ struct pa_threaded_mainloop *mainloop; /*< Main event loop object */ } pulse_class_t; typedef struct pulse_driver_s { ao_driver_t ao_driver; xine_t *xine; pulse_class_t *pa_class; char *host; /*< The host to connect to */ char *sink; /*< The sink to connect to */ struct pa_stream *stream; /*< Pulseaudio playback stream object */ pa_volume_t swvolume; pa_cvolume cvolume; 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; } pulse_driver_t; /** * @brief Callback function called when a stream operation succeed * @param stream Stream which operation has succeeded * @param success The success value for the operation (ignored) * @param this_Gen pulse_driver_t pointer for the PulseAudio output * instance. */ static void __xine_pa_stream_success_callback(pa_stream *const stream, const int success, void *const this_gen) { pulse_driver_t *const this = (pulse_driver_t*)this_gen; _x_assert(stream); _x_assert(this); _x_assert(stream == this->stream); pa_threaded_mainloop_signal(this->pa_class->mainloop, 0); } /** * @brief Callback function called when the state of the context is changed * @param ctx Context which operation has succeeded * @param this_gen pulse_driver_t pointer for the PulseAudio output * instance. */ static void __xine_pa_context_status_callback(pa_context *const ctx, void *const this_gen) { pulse_driver_t *const this = (pulse_driver_t*)this_gen; _x_assert(ctx); _x_assert(this); _x_assert(ctx == this->pa_class->context); switch (pa_context_get_state(ctx)) { case PA_CONTEXT_READY: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: pa_threaded_mainloop_signal(this->pa_class->mainloop, 0); break; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; } } /** * @brief Callback function called when a context operation succeed * @param ctx Context which operation has succeeded * @param success The success value for the operation (ignored) * @param this_gen pulse_driver_t pointer for the PulseAudio output * instance. */ static void __xine_pa_context_success_callback(pa_context *const ctx, const int success, void *const this_gen) { pulse_driver_t *const this = (pulse_driver_t*)this_gen; _x_assert(ctx); _x_assert(this); _x_assert(ctx == this->pa_class->context); pa_threaded_mainloop_signal(this->pa_class->mainloop, 0); } /** * @brief Callback function called when the information on the * context's sink is retrieved. * @param ctx Context which operation has succeeded * @param info Structure containing the sink's information * @param this_gen pulse_driver_t pointer for the PulseAudio output * instance. * * This function saves the volume field of the passed structure to the * @c cvolume variable of the output instance. */ static void __xine_pa_sink_info_callback(pa_context *const ctx, const pa_sink_input_info *const info, const int is_last, void *const userdata) { pulse_driver_t *const this = (pulse_driver_t *) userdata; if (is_last < 0) { xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: Failed to get sink input info: %s\n", pa_strerror(pa_context_errno(this->pa_class->context))); return; } if (!info) return; this->cvolume = info->volume; __xine_pa_context_success_callback(ctx, 0, this); } static int wait_for_operation(pulse_driver_t *this, pa_operation *o) { _x_assert(this && o && this->pa_class->mainloop); pa_threaded_mainloop_lock(this->pa_class->mainloop); while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(this->pa_class->mainloop); pa_threaded_mainloop_unlock(this->pa_class->mainloop); return 0; } /* * open the audio device for writing to */ static int ao_pulse_open(ao_driver_t *this_gen, uint32_t bits, uint32_t rate, int mode) { pulse_driver_t *this = (pulse_driver_t *) this_gen; struct pa_sample_spec ss; struct pa_buffer_attr a; pa_stream_state_t streamstate; xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_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_pulse_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: ss.format = PA_SAMPLE_S16NE; break; case 32: ss.format = PA_SAMPLE_FLOAT32NE; break; } if (!pa_sample_spec_valid(&ss)) { xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: Invalid sample spec\n"); goto fail; } pthread_mutex_lock(&this->pa_class->pa_mutex); if ( this->pa_class->context && pa_context_get_state(this->pa_class->context) > PA_CONTEXT_READY ) { pa_context_unref(this->pa_class->context); this->pa_class->context = NULL; } if ( this->pa_class->context == NULL ) { this->pa_class->context = pa_context_new(pa_threaded_mainloop_get_api(this->pa_class->mainloop), __progname); } pa_context_ref(this->pa_class->context); if ( pa_context_get_state(this->pa_class->context) == PA_CONTEXT_UNCONNECTED ) { int ret; pa_threaded_mainloop_lock(this->pa_class->mainloop); ret = pa_context_connect(this->pa_class->context, this->host, 1, NULL); if ( ret < 0 ) goto fail; pa_context_set_state_callback(this->pa_class->context, __xine_pa_context_status_callback, this); pa_threaded_mainloop_wait(this->pa_class->mainloop); pa_threaded_mainloop_unlock(this->pa_class->mainloop); } if (pa_context_get_state(this->pa_class->context) != PA_CONTEXT_READY) { xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(this->pa_class->context))); goto fail; } this->stream = pa_stream_new(this->pa_class->context, "audio stream", &ss, NULL); _x_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_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); do { xine_usec_sleep (100); streamstate = pa_stream_get_state(this->stream); } while (streamstate < PA_STREAM_READY); pthread_mutex_unlock(&this->pa_class->pa_mutex); if (streamstate != PA_STREAM_READY) { xprintf (this->xine, XINE_VERBOSITY_LOG, "audio_pulse_out: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(this->pa_class->context))); goto fail; } this->frames_written = 0; this->ao_driver.set_property(this, AO_PROP_PCM_VOL, 100); return this->sample_rate; fail: pa_threaded_mainloop_unlock(this->pa_class->mainloop); pthread_mutex_unlock(&this->pa_class->pa_mutex); this_gen->close(this_gen); return 0; } static int ao_pulse_num_channels(ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; return this->num_channels; } static int ao_pulse_bytes_per_frame(ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; return this->bytes_per_frame; } static int ao_pulse_get_gap_tolerance (ao_driver_t *this_gen) { return GAP_TOLERANCE; } static int ao_pulse_write(ao_driver_t *this_gen, int16_t *data, uint32_t num_frames) { pulse_driver_t *this = (pulse_driver_t *) this_gen; int size = num_frames * this->bytes_per_frame; int ret = 0; if ( !this->stream || !this->pa_class->context) return -1; switch( pa_stream_get_state(this->stream) ) { case PA_STREAM_READY: while (size > 0) { size_t l; while (!(l = pa_stream_writable_size(this->stream))) { xine_usec_sleep (10000); } if (l > size) l = size; pa_stream_write(this->stream, data, l, NULL, 0, PA_SEEK_RELATIVE); data = (int16_t *) ((uint8_t*) data + l); size -= l; } this->frames_written += num_frames; if (pa_stream_get_state(this->stream) == PA_STREAM_READY) ret = 1; break; } return ret; } static int ao_pulse_delay (ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; pa_usec_t latency = 0; int delay_frames; if ( ! this->stream ) return this->frames_written; pthread_mutex_lock(&this->pa_class->pa_mutex); if (pa_stream_get_latency(this->stream, &latency, NULL) < 0) { pa_context_unref(this->pa_class->context); this->pa_class->context = NULL; pa_stream_disconnect(this->stream); pa_stream_unref(this->stream); this->stream = NULL; pthread_mutex_unlock(&this->pa_class->pa_mutex); return 0; } pthread_mutex_unlock(&this->pa_class->pa_mutex); /* 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_pulse_close(ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; if (this->stream) { pthread_mutex_lock(&this->pa_class->pa_mutex); if (pa_stream_get_state(this->stream) == PA_STREAM_READY) wait_for_operation(this, pa_stream_drain(this->stream, __xine_pa_stream_success_callback, this)); pa_stream_disconnect(this->stream); pa_stream_unref(this->stream); this->stream = NULL; pa_context_unref(this->pa_class->context); pthread_mutex_unlock(&this->pa_class->pa_mutex); } } static uint32_t ao_pulse_get_capabilities (ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; return this->capabilities; } static void ao_pulse_exit(ao_driver_t *this_gen) { pulse_driver_t *this = (pulse_driver_t *) this_gen; free (this); } static int ao_pulse_get_property (ao_driver_t *this_gen, int property) { pulse_driver_t *this = (pulse_driver_t *) this_gen; int result = 0; if ( ! this->stream || ! this->pa_class->context ) return 0; pthread_mutex_lock(&this->pa_class->pa_mutex); switch(property) { case AO_PROP_PCM_VOL: case AO_PROP_MIXER_VOL: { pa_operation *o = pa_context_get_sink_input_info(this->pa_class->context, pa_stream_get_index(this->stream), __xine_pa_sink_info_callback, this); if ( ! o ) return 0; wait_for_operation(this, o); result = (pa_sw_volume_to_linear(this->swvolume)*100); } break; case AO_PROP_MUTE_VOL: result = pa_cvolume_is_muted(&this->cvolume); break; } pthread_mutex_unlock(&this->pa_class->pa_mutex); return result; } static int ao_pulse_set_property (ao_driver_t *this_gen, int property, int value) { pulse_driver_t *this = (pulse_driver_t *) this_gen; int result = ~value; if ( ! this->stream || ! this->pa_class->context ) return result; pthread_mutex_lock(&this->pa_class->pa_mutex); switch(property) { case AO_PROP_PCM_VOL: case AO_PROP_MIXER_VOL: this->swvolume = pa_sw_volume_from_linear((double)value/100.0); pa_cvolume_set(&this->cvolume, pa_stream_get_sample_spec(this->stream)->channels, this->swvolume); wait_for_operation(this, pa_context_set_sink_input_volume(this->pa_class->context, pa_stream_get_index(this->stream), &this->cvolume, __xine_pa_context_success_callback, this)); result = value; break; case AO_PROP_MUTE_VOL: if ( value ) pa_cvolume_mute(&this->cvolume, pa_stream_get_sample_spec(this->stream)->channels); else pa_cvolume_set(&this->cvolume, pa_stream_get_sample_spec(this->stream)->channels, this->swvolume); wait_for_operation(this, pa_context_set_sink_input_volume(this->pa_class->context, pa_stream_get_index(this->stream), &this->cvolume, __xine_pa_context_success_callback, this)); result = value; break; } pthread_mutex_unlock(&this->pa_class->pa_mutex); return result; } static int ao_pulse_ctrl(ao_driver_t *this_gen, int cmd, ...) { pulse_driver_t *this = (pulse_driver_t *) this_gen; if ( ! this->stream ) return 0; switch (cmd) { case AO_CTRL_PLAY_PAUSE: _x_assert(this->stream && this->pa_class->context ); pthread_mutex_lock(&this->pa_class->pa_mutex); if(pa_stream_get_state(this->stream) == PA_STREAM_READY) wait_for_operation(this,pa_stream_cork(this->stream, 1, __xine_pa_stream_success_callback, this)); pthread_mutex_unlock(&this->pa_class->pa_mutex); break; case AO_CTRL_PLAY_RESUME: _x_assert(this->stream && this->pa_class->context); pthread_mutex_lock(&this->pa_class->pa_mutex); if(pa_stream_get_state(this->stream) == PA_STREAM_READY) { struct pa_operation *o2, *o1; o1 = pa_stream_prebuf(this->stream, __xine_pa_stream_success_callback, this); _x_assert(o1); wait_for_operation(this, o1); o2 = pa_stream_cork(this->stream, 0, __xine_pa_stream_success_callback, this); _x_assert(o2); wait_for_operation(this,o2); } pthread_mutex_unlock(&this->pa_class->pa_mutex); break; case AO_CTRL_FLUSH_BUFFERS: _x_assert(this->stream && this->pa_class->context); pthread_mutex_lock(&this->pa_class->pa_mutex); if(pa_stream_get_state(this->stream) == PA_STREAM_READY) wait_for_operation(this,pa_stream_flush(this->stream, __xine_pa_stream_success_callback, this)); pthread_mutex_unlock(&this->pa_class->pa_mutex); this->frames_written = 0; break; } return 0; } static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *data) { pulse_class_t *class = (pulse_class_t *) class_gen; pulse_driver_t *this; char *device; lprintf ("audio_pulse_out: open_plugin called\n"); this = (pulse_driver_t *) xine_xmalloc (sizeof (pulse_driver_t)); if (!this) return NULL; this->xine = class->xine; /* * set capabilities */ this->capabilities = AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO | AO_CAP_MODE_4CHANNEL | AO_CAP_MODE_4_1CHANNEL | AO_CAP_MODE_5CHANNEL | AO_CAP_MODE_5_1CHANNEL | 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->host = NULL; this->sink = NULL; this->ao_driver.get_capabilities = ao_pulse_get_capabilities; this->ao_driver.get_property = ao_pulse_get_property; this->ao_driver.set_property = ao_pulse_set_property; this->ao_driver.open = ao_pulse_open; this->ao_driver.num_channels = ao_pulse_num_channels; this->ao_driver.bytes_per_frame = ao_pulse_bytes_per_frame; this->ao_driver.delay = ao_pulse_delay; this->ao_driver.write = ao_pulse_write; this->ao_driver.close = ao_pulse_close; this->ao_driver.exit = ao_pulse_exit; this->ao_driver.get_gap_tolerance = ao_pulse_get_gap_tolerance; this->ao_driver.control = ao_pulse_ctrl; device = this->xine->config->register_string(this->xine->config, "audio.pulseaudio_device", "", _("device used for pulseaudio"), _("use 'server[:sink]' for setting the " "pulseaudio sink device."), 10, NULL, NULL); if (device && *device) { char *sep = strchr(device, ':'); if ( sep ) { this->host = strndup(device, sep-device); this->sink = strdup(&sep[1]); } else this->host = strdup(device); } xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: host %s sink %s\n", this->host ? this->host : "(null)", this->sink ? this->sink : "(null)"); this->pa_class = class; return &this->ao_driver; fail: pthread_mutex_unlock(&this->pa_class->pa_mutex); free(this); xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: open_plugin failed.\n"); return NULL; } /* * class functions */ static char* get_identifier (audio_driver_class_t *this_gen) { return "pulseaudio"; } static char* get_description (audio_driver_class_t *this_gen) { return _("xine audio output plugin using pulseaudio sound server"); } static void dispose_class (audio_driver_class_t *this_gen) { pulse_class_t *this = (pulse_class_t *) this_gen; pthread_mutex_lock(&this->pa_mutex); if ( this->context ) pa_context_unref(this->context); pa_threaded_mainloop_stop(this->mainloop); pa_threaded_mainloop_free(this->mainloop); pthread_mutex_destroy(&this->pa_mutex); free (this); } static void *init_class (xine_t *xine, void *data) { pulse_class_t *this; lprintf ("audio_pulse_out: init class\n"); this = (pulse_class_t *) xine_xmalloc (sizeof (pulse_class_t)); if (!this) return NULL; 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; pthread_mutex_init(&this->pa_mutex, NULL); pthread_mutex_lock(&this->pa_mutex); this->mainloop = pa_threaded_mainloop_new(); _x_assert(this->mainloop); pa_threaded_mainloop_start(this->mainloop); this->context = NULL; pthread_mutex_unlock(&this->pa_mutex); return this; } static const ao_info_t ao_info_pulse = { 6 }; /* * exported plugin catalog entry */ const plugin_info_t xine_plugin_info[] EXPORTED = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_AUDIO_OUT, 8, "pulseaudio", XINE_VERSION_CODE, &ao_info_pulse, init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };