diff options
-rw-r--r-- | src/audio_out/audio_pulse_out.c | 237 |
1 files changed, 169 insertions, 68 deletions
diff --git a/src/audio_out/audio_pulse_out.c b/src/audio_out/audio_pulse_out.c index 88dc25e0b..8f0c6aab5 100644 --- a/src/audio_out/audio_pulse_out.c +++ b/src/audio_out/audio_pulse_out.c @@ -17,7 +17,7 @@ * 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.8 2007/02/02 23:45:23 dgp85 Exp $ + * $Id: audio_pulse_out.c,v 1.9 2007/02/03 10:41:09 dgp85 Exp $ * * ao plugin for pulseaudio (rename of polypaudio): * http://0pointer.de/lennart/projects/pulsaudio/ @@ -94,9 +94,115 @@ typedef struct { audio_driver_class_t driver_class; xine_t *xine; + + /** Pulseaudio connection context */ + struct pa_context *context; + + /** Main event loop object */ + struct pa_threaded_mainloop *mainloop; } pulse_class_t; -int wait_for_operation(pulse_driver_t *this, pa_operation *o) +/** + * @brief Callback function called when the status of the PulseAudio + * context changes + * @param ctx Context that changed (same as class->context) + * @param class_gen pulse_class_t pointer for the PulseAudio output + * instance. + */ +static void __xine_pa_ctx_state_callback(pa_context *const ctx, void *const class_gen) +{ + pulse_class_t *const class = (pulse_class_t*)class_gen; + + _x_assert(ctx); _x_assert(class); + _x_assert(ctx == class->context); + + pa_threaded_mainloop_signal(class->mainloop, 0); +} + +/** + * @brief Callback function called when the status of the PulseAudio + * stream changes + * @param stream Stream that changed (same as this->stream) + * @param this_gen pulse_driver_t pointer for the PulseAudio output + * instance. + */ +static void __xine_pa_stream_state_callback(pa_stream *const stream, 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->mainloop, 0); +} + +/** + * @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->mainloop, 0); +} + +/** + * @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->context); + + pa_threaded_mainloop_signal(this->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->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->mainloop); @@ -119,6 +225,7 @@ static int ao_pulse_open(ao_driver_t *this_gen, 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); @@ -166,24 +273,11 @@ static int ao_pulse_open(ao_driver_t *this_gen, goto fail; } - this->mainloop = pa_threaded_mainloop_new(); - pa_threaded_mainloop_start(this->mainloop); - _x_assert(this->mainloop); - - this->context = pa_context_new(pa_threaded_mainloop_get_api(this->mainloop), __progname); - _x_assert(this->context); - - pa_context_connect(this->context, this->host, 1, NULL); - - if (pa_context_get_state(this->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->context))); - goto fail; - } - this->stream = pa_stream_new(this->context, "audio stream", &ss, NULL); _x_assert(this->stream); + pa_stream_set_state_callback(this->stream, __xine_pa_stream_state_callback, this); + a.maxlength = pa_bytes_per_second(&ss)*1; a.tlength = a.maxlength*9/10; a.prebuf = a.tlength/2; @@ -194,8 +288,15 @@ static int ao_pulse_open(ao_driver_t *this_gen, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, &this->cvolume, NULL); - if (pa_stream_get_state(this->stream) != PA_STREAM_READY) { - xprintf (this->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: Failed to connect to server: %s\n", + do { + xine_usec_sleep (100); + + streamstate = pa_stream_get_state(this->stream); + fprintf(stderr, "PulseAudio stream state: %d\n", streamstate); + } while (streamstate < PA_STREAM_READY); + + 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->context))); goto fail; } @@ -250,8 +351,6 @@ static int ao_pulse_write(ao_driver_t *this_gen, int16_t *data, pa_stream_write(this->stream, data, l, NULL, 0, PA_SEEK_RELATIVE); data = (int16_t *) ((uint8_t*) data + l); size -= l; - - wait_for_completion(this); } this->frames_written += num_frames; @@ -294,22 +393,11 @@ static void ao_pulse_close(ao_driver_t *this_gen) if (this->stream) { if (pa_stream_get_state(this->stream) == PA_STREAM_READY) - wait_for_operation(this, pa_stream_drain(this->stream, NULL, NULL)); + 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; } - - if (this->context) { - pa_context_disconnect(this->context); - pa_context_unref(this->context); - this->context = NULL; - } - - if (this->mainloop) { - pa_threaded_mainloop_free(this->mainloop); - this->mainloop = NULL; - } } static uint32_t ao_pulse_get_capabilities (ao_driver_t *this_gen) { @@ -324,25 +412,6 @@ static void ao_pulse_exit(ao_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) { - - pulse_driver_t *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->context))); - return; - } - - if (!i) - return; - - this->cvolume = i->volume; -} - - static int ao_pulse_get_property (ao_driver_t *this_gen, int property) { pulse_driver_t *this = (pulse_driver_t *) this_gen; @@ -351,7 +420,8 @@ static int ao_pulse_get_property (ao_driver_t *this_gen, int property) { case AO_PROP_MIXER_VOL: 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)); + pa_context_get_sink_input_info(this->context, pa_stream_get_index(this->stream), + __xine_pa_sink_info_callback, this)); return (int) (pa_sw_volume_to_linear(this->swvolume)*100); break; case AO_PROP_MUTE_VOL: @@ -372,7 +442,7 @@ static int ao_pulse_set_property (ao_driver_t *this_gen, int property, int value 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->context, pa_stream_get_index(this->stream), - &this->cvolume, NULL, NULL)); + &this->cvolume, __xine_pa_context_success_callback, this)); } break; case AO_PROP_MUTE_VOL: @@ -390,25 +460,25 @@ static int ao_pulse_ctrl(ao_driver_t *this_gen, int cmd, ...) { case AO_CTRL_PLAY_PAUSE: _x_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)); + wait_for_operation(this,pa_stream_cork(this->stream, 1, __xine_pa_stream_success_callback, this)); break; case AO_CTRL_PLAY_RESUME: _x_assert(this->stream && this->context); if(pa_stream_get_state(this->stream) == PA_STREAM_READY) { struct pa_operation *o2, *o1; - o1 = pa_stream_prebuf(this->stream, NULL, NULL); - o2 = pa_stream_cork(this->stream, 0, NULL, NULL); - _x_assert(o1 && o2); - wait_for_operation(this,o1); - wait_for_operation(this,o2); + 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); } break; case AO_CTRL_FLUSH_BUFFERS: _x_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)); + wait_for_operation(this,pa_stream_flush(this->stream, __xine_pa_stream_success_callback, this)); this->frames_written = 0; break; } @@ -421,6 +491,7 @@ static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *da pulse_driver_t *this; char hn[128]; char *device; + pa_context_state_t ctxstate; lprintf ("audio_pulse_out: open_plugin called\n"); @@ -480,16 +551,31 @@ static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *da xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: host %s sink %s\n", this->host ? this->host : "(null)", this->sink ? this->sink : "(null)"); - /* test pulseaudio 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_pulse_out: open_plugin failed.\n"); - return NULL; + this->mainloop = class->mainloop; + this->context = class->context; + + if ( pa_context_get_state(this->context) != PA_CONTEXT_READY ){ + pa_context_connect(this->context, this->host, 1, NULL); + + do { + xine_usec_sleep (100); + + ctxstate = pa_context_get_state(this->context); + } while (ctxstate < PA_CONTEXT_READY); + + if (ctxstate != 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->context))); + goto fail; + } } return &this->ao_driver; + + fail: + free(this); + xprintf (class->xine, XINE_VERBOSITY_DEBUG, "audio_pulse_out: open_plugin failed.\n"); + return NULL; } /* @@ -508,6 +594,11 @@ static void dispose_class (audio_driver_class_t *this_gen) { pulse_class_t *this = (pulse_class_t *) this_gen; + pa_context_unref(this->context); + + pa_threaded_mainloop_stop(this->mainloop); + pa_threaded_mainloop_free(this->mainloop); + free (this); } @@ -528,6 +619,16 @@ static void *init_class (xine_t *xine, void *data) { this->xine = xine; + this->mainloop = pa_threaded_mainloop_new(); + _x_assert(this->mainloop); + + pa_threaded_mainloop_start(this->mainloop); + + this->context = pa_context_new(pa_threaded_mainloop_get_api(this->mainloop), __progname); + _x_assert(this->context); + + pa_context_set_state_callback(this->context, __xine_pa_ctx_state_callback, this); + return this; } |