X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fpulse.c;h=26f8679080167f346d8303c93c63844547745069;hb=2078838d5e2f389d86435b1ddb39c9cf381fc728;hp=c09b3f63baeaa2becbdeb73045147ff5e86c8c6e;hpb=145903d05b94462eaa309575e46c3cbafb853a42;p=vlc diff --git a/modules/audio_output/pulse.c b/modules/audio_output/pulse.c index c09b3f63ba..26f8679080 100644 --- a/modules/audio_output/pulse.c +++ b/modules/audio_output/pulse.c @@ -2,6 +2,7 @@ * pulse.c : Pulseaudio output plugin for vlc ***************************************************************************** * Copyright (C) 2008 the VideoLAN team + * Copyright (C) 2009-2011 Rémi Denis-Courmont * * Authors: Martin Hamrle * @@ -20,492 +21,608 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -/***************************************************************************** - * Preamble - *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include - #include #include #include -#include - -/***************************************************************************** - * aout_sys_t: Pulseaudio output method descriptor - ***************************************************************************** - * This structure is part of the audio output thread descriptor. - * It describes the specific properties of an audio device. - *****************************************************************************/ -struct aout_sys_t -{ - /** PulseAudio playback stream object */ - struct pa_stream *stream; - - /** PulseAudio connection context */ - struct pa_context *context; - - /** Main event loop object */ - struct pa_threaded_mainloop *mainloop; - - int started; - size_t buffer_size; - mtime_t start_date; -}; - -#define PULSE_CLIENT_NAME N_("VLC media player") - -#if 0 -#define PULSE_DEBUG( ...) \ - msg_Dbg( p_aout, __VA_ARGS__ ) -#else -#define PULSE_DEBUG( ...) \ - (void) 0 -#endif - - -#define CHECK_DEAD_GOTO(label) do { \ -if (!p_sys->context || pa_context_get_state(p_sys->context) != PA_CONTEXT_READY || \ - !p_sys->stream || pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { \ - msg_Err(p_aout, "Connection died: %s", p_sys->context ? pa_strerror(pa_context_errno(p_sys->context)) : "NULL"); \ - goto label; \ - } \ -} while(0); - -/***************************************************************************** - * Local prototypes - *****************************************************************************/ static int Open ( vlc_object_t * ); static void Close ( vlc_object_t * ); -static void Play ( aout_instance_t * ); -static void context_state_cb(pa_context *c, void *userdata); -static void stream_state_cb(pa_stream *s, void * userdata); -static void stream_request_cb(pa_stream *s, size_t length, void *userdata); -static void stream_latency_update_cb(pa_stream *s, void *userdata); -static void success_cb(pa_stream *s, int sucess, void *userdata); -static void uninit(aout_instance_t *p_aout); - -/***************************************************************************** - * Module descriptor - *****************************************************************************/ vlc_module_begin () - set_shortname( "Pulse Audio" ) + set_shortname( "PulseAudio" ) set_description( N_("Pulseaudio audio output") ) set_capability( "audio output", 160 ) set_category( CAT_AUDIO ) set_subcategory( SUBCAT_AUDIO_AOUT ) - add_shortcut( "pulseaudio" ) - add_shortcut( "pa" ) + add_shortcut( "pulseaudio", "pa" ) set_callbacks( Open, Close ) - linked_with_a_crap_library_which_uses_atexit() vlc_module_end () -/***************************************************************************** - * Open: open the audio device - *****************************************************************************/ -static int Open ( vlc_object_t *p_this ) -{ - aout_instance_t *p_aout = (aout_instance_t *)p_this; - struct aout_sys_t * p_sys; - struct pa_sample_spec ss; - const struct pa_buffer_attr *buffer_attr; - struct pa_buffer_attr a; - struct pa_channel_map map; - - /* Allocate structures */ - p_aout->output.p_sys = p_sys = calloc( 1, sizeof( aout_sys_t ) ); - if( p_sys == NULL ) - return VLC_ENOMEM; - - PULSE_DEBUG( "Pulse start initialization"); +/* TODO: single static mainloop */ - ss.channels = aout_FormatNbChannels( &p_aout->output.output ); /* Get the input stream channel count */ +/* NOTE: + * Be careful what you do when the PulseAudio mainloop is held, which is to say + * within PulseAudio callbacks, or after pa_threaded_mainloop_lock(). + * In particular, a VLC variable callback cannot be triggered nor deleted with + * the PulseAudio mainloop lock held, if the callback acquires the lock. */ - /* Setup the pulse audio stream based on the input stream count */ - switch(ss.channels) - { - case 8: - p_aout->output.output.i_physical_channels - = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER - | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT - | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT - | AOUT_CHAN_LFE; - break; - case 6: - p_aout->output.output.i_physical_channels - = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER - | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT - | AOUT_CHAN_LFE; - break; - - case 4: - p_aout->output.output.i_physical_channels - = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT - | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT; - break; - - case 2: - p_aout->output.output.i_physical_channels - = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; - break; +struct aout_sys_t +{ + pa_stream *stream; /**< PulseAudio playback stream object */ + pa_context *context; /**< PulseAudio connection context */ + pa_threaded_mainloop *mainloop; /**< PulseAudio event loop */ + pa_volume_t base_volume; /**< 0dB reference volume */ + pa_cvolume cvolume; /**< actual sink input volume */ + //uint32_t byterate; /**< bytes per second */ +}; - case 1: - p_aout->output.output.i_physical_channels = AOUT_CHAN_CENTER; - break; +/* Context helpers */ +static void context_state_cb(pa_context *c, void *userdata) +{ + pa_threaded_mainloop *mainloop = userdata; + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(mainloop, 0); default: - msg_Err(p_aout,"Invalid number of channels"); - goto fail; - } - - /* Add a quick command line info message */ - msg_Dbg(p_aout, "%d audio channels", ss.channels); - - ss.rate = p_aout->output.output.i_rate; - if (HAVE_FPU) - { - ss.format = PA_SAMPLE_FLOAT32NE; - p_aout->output.output.i_format = VLC_CODEC_FL32; - } - else - { - ss.format = PA_SAMPLE_S16NE; - p_aout->output.output.i_format = VLC_CODEC_S16N; - } - - if (!pa_sample_spec_valid(&ss)) { - msg_Err(p_aout,"Invalid sample spec"); - goto fail; - } - - /* Reduce overall latency to 200mS to reduce audible clicks - * Also pulse minreq and internal buffers are now 20mS which reduces resampling - */ - a.tlength = pa_bytes_per_second(&ss)/5; - a.maxlength = a.tlength * 2; - a.prebuf = a.tlength / 2; - a.minreq = a.tlength / 10; - - /* Buffer size is 20mS */ - p_sys->buffer_size = a.minreq; - - /* Initialise the speaker map setup above */ - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - - if (!(p_sys->mainloop = pa_threaded_mainloop_new())) { - msg_Err(p_aout, "Failed to allocate main loop"); - goto fail; - } - - if (!(p_sys->context = pa_context_new(pa_threaded_mainloop_get_api(p_sys->mainloop), _( PULSE_CLIENT_NAME )))) { - msg_Err(p_aout, "Failed to allocate context"); - goto fail; + break; } +} - pa_context_set_state_callback(p_sys->context, context_state_cb, p_aout); - - PULSE_DEBUG( "Pulse before context connect"); +static bool context_wait(pa_threaded_mainloop *mainloop, pa_context *context) +{ + pa_context_state_t state; - if (pa_context_connect(p_sys->context, NULL, 0, NULL) < 0) { - msg_Err(p_aout, "Failed to connect to server: %s", pa_strerror(pa_context_errno(p_sys->context))); - goto fail; + while ((state = pa_context_get_state(context)) != PA_CONTEXT_READY) { + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) + return -1; + pa_threaded_mainloop_wait(mainloop); } + return 0; +} - PULSE_DEBUG( "Pulse after context connect"); - - pa_threaded_mainloop_lock(p_sys->mainloop); - - if (pa_threaded_mainloop_start(p_sys->mainloop) < 0) { - msg_Err(p_aout, "Failed to start main loop"); - goto unlock_and_fail; - } +static void error(aout_instance_t *aout, const char *msg, pa_context *context) +{ + msg_Err(aout, "%s: %s", msg, pa_strerror(pa_context_errno(context))); +} - msg_Dbg(p_aout, "Pulse mainloop started"); +/* Sink */ +static void sink_list_cb(pa_context *c, const pa_sink_info *i, int eol, + void *userdata) +{ + aout_instance_t *aout = userdata; + vlc_value_t val, text; + + if (eol) + return; + (void) c; + + msg_Dbg(aout, "listing sink %s (%"PRIu32"): %s", i->name, i->index, + i->description); + val.i_int = i->index; + text.psz_string = (char *)i->description; + var_Change(aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); +} - /* Wait until the context is ready */ - pa_threaded_mainloop_wait(p_sys->mainloop); +static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, + void *userdata) +{ + aout_instance_t *aout = userdata; + aout_sys_t *sys = aout->output.p_sys; + + if (eol) + return; + (void) c; + + /* PulseAudio flat volume NORM / 100% / 0dB corresponds to no software + * amplification and maximum hardware amplification. + * VLC maps DEFAULT / 100% to no gain at all (software/hardware). + * Thus we need to use the sink base_volume as a multiplier, + * if and only if flat volume is active for our current sink. */ + if (i->flags & PA_SINK_FLAT_VOLUME) + sys->base_volume = i->base_volume; + else + sys->base_volume = PA_VOLUME_NORM; + msg_Dbg(aout, "base volume: %f", pa_sw_volume_to_linear(sys->base_volume)); +} - if (pa_context_get_state(p_sys->context) != PA_CONTEXT_READY) { - msg_Err(p_aout, "Failed to connect to server: %s", pa_strerror(pa_context_errno(p_sys->context))); - goto unlock_and_fail; - } +/* Stream helpers */ +static void stream_state_cb(pa_stream *s, void *userdata) +{ + pa_threaded_mainloop *mainloop = userdata; - if (!(p_sys->stream = pa_stream_new(p_sys->context, "audio stream", &ss, &map))) { - msg_Err(p_aout, "Failed to create stream: %s", pa_strerror(pa_context_errno(p_sys->context))); - goto unlock_and_fail; + switch (pa_stream_get_state(s)) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(mainloop, 0); + default: + break; } +} - PULSE_DEBUG( "Pulse after new stream"); - - pa_stream_set_state_callback(p_sys->stream, stream_state_cb, p_aout); - pa_stream_set_write_callback(p_sys->stream, stream_request_cb, p_aout); - pa_stream_set_latency_update_callback(p_sys->stream, stream_latency_update_cb, p_aout); - - if (pa_stream_connect_playback(p_sys->stream, NULL, &a, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0) { - msg_Err(p_aout, "Failed to connect stream: %s", pa_strerror(pa_context_errno(p_sys->context))); - goto unlock_and_fail; - } +static void stream_moved_cb(pa_stream *s, void *userdata) +{ + aout_instance_t *aout = userdata; + aout_sys_t *sys = aout->output.p_sys; + pa_operation *op; + uint32_t idx = pa_stream_get_device_index(s); + + msg_Dbg(aout, "connected to sink %"PRIu32": %s", idx, + pa_stream_get_device_name(s)); + op = pa_context_get_sink_info_by_index(sys->context, idx, + sink_info_cb, aout); + if (likely(op != NULL)) + pa_operation_unref(op); + + /* Update the variable if someone else moved our stream */ + var_Change(aout, "audio-device", VLC_VAR_SETVALUE, + &(vlc_value_t){ .i_int = idx }, NULL); +} - PULSE_DEBUG("Pulse stream connect"); +static void stream_overflow_cb(pa_stream *s, void *userdata) +{ + aout_instance_t *aout = userdata; - /* Wait until the stream is ready */ - pa_threaded_mainloop_wait(p_sys->mainloop); + msg_Err(aout, "overflow"); + (void) s; +} - msg_Dbg(p_aout,"Pulse stream connected"); +static void stream_started_cb(pa_stream *s, void *userdata) +{ + aout_instance_t *aout = userdata; - if (pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { - msg_Err(p_aout, "Failed to connect to server: %s", pa_strerror(pa_context_errno(p_sys->context))); - goto unlock_and_fail; - } + msg_Dbg(aout, "started"); + (void) s; +} +static void stream_suspended_cb(pa_stream *s, void *userdata) +{ + aout_instance_t *aout = userdata; - PULSE_DEBUG("Pulse after stream get status"); + msg_Dbg(aout, "suspended"); + (void) s; +} - pa_threaded_mainloop_unlock(p_sys->mainloop); +static void stream_underflow_cb(pa_stream *s, void *userdata) +{ + aout_instance_t *aout = userdata; - buffer_attr = pa_stream_get_buffer_attr(p_sys->stream); - p_aout->output.i_nb_samples = buffer_attr->minreq / pa_frame_size(&ss); - p_aout->output.pf_play = Play; - aout_VolumeSoftInit(p_aout); - msg_Dbg(p_aout, "Pulse initialized successfully"); - { - char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + msg_Dbg(aout, "underflow"); + (void) s; +} - msg_Dbg(p_aout, "Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u", buffer_attr->maxlength, buffer_attr->tlength, buffer_attr->prebuf, buffer_attr->minreq); - msg_Dbg(p_aout, "Using sample spec '%s', channel map '%s'.", - pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(p_sys->stream)), - pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(p_sys->stream))); +static int stream_wait(pa_threaded_mainloop *mainloop, pa_stream *stream) +{ + pa_stream_state_t state; - msg_Dbg(p_aout, "Connected to device %s (%u, %ssuspended).", - pa_stream_get_device_name(p_sys->stream), - pa_stream_get_device_index(p_sys->stream), - pa_stream_is_suspended(p_sys->stream) ? "" : "not "); + while ((state = pa_stream_get_state(stream)) != PA_STREAM_READY) { + if (state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED) + return -1; + pa_threaded_mainloop_wait(mainloop); } - - return VLC_SUCCESS; - -unlock_and_fail: - msg_Dbg(p_aout, "Pulse initialization unlock and fail"); - - if (p_sys->mainloop) - pa_threaded_mainloop_unlock(p_sys->mainloop); -fail: - msg_Err(p_aout, "Pulse initialization failed"); - uninit(p_aout); - return VLC_EGENERIC; + return 0; } -/***************************************************************************** - * Play: play a sound samples buffer - *****************************************************************************/ -static void Play( aout_instance_t * p_aout ) +/* Memory free callback. The block_t address is in front of the data. */ +static void data_free(void *data) { - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; - - pa_operation *o; + block_t **pp = data, *block; - if(!p_sys->started){ - msg_Dbg(p_aout, "Pulse stream started"); - p_sys->start_date = - aout_FifoFirstDate( p_aout, &p_aout->output.fifo ); - p_sys->started = 1; - - pa_threaded_mainloop_lock(p_sys->mainloop); - if((o = pa_stream_flush(p_sys->stream, success_cb, p_aout))){ - pa_operation_unref(o); - } - pa_threaded_mainloop_unlock(p_sys->mainloop); + memcpy(&block, pp - 1, sizeof (block)); + block_Release(block); +} - pa_threaded_mainloop_signal(p_sys->mainloop, 0); - } +static void *data_convert(block_t **pp) +{ + block_t *block = *pp; + /* In most cases, there is enough head room, and this is really cheap: */ + block = block_Realloc(block, sizeof (block), block->i_buffer); + *pp = block; + if (unlikely(block == NULL)) + return NULL; + + memcpy(block->p_buffer, &block, sizeof (block)); + block->p_buffer += sizeof (block); + block->i_buffer -= sizeof (block); + return block->p_buffer; } /***************************************************************************** - * Close: close the audio device + * Play: play a sound samples buffer *****************************************************************************/ -static void Close ( vlc_object_t *p_this ) +static void Play(aout_instance_t *aout) { - aout_instance_t *p_aout = (aout_instance_t *)p_this; - struct aout_sys_t * p_sys = p_aout->output.p_sys; + aout_sys_t *sys = aout->output.p_sys; + pa_stream *s = sys->stream; + + /* Note: The core already holds the output FIFO lock at this point. + * Therefore we must not under any circumstances (try to) acquire the + * output FIFO lock while the PulseAudio threaded main loop lock is held + * (including from PulseAudio stream callbacks). Otherwise lock inversion + * will take place, and sooner or later a deadlock. */ + pa_threaded_mainloop_lock(sys->mainloop); + + if (pa_stream_is_corked(sys->stream) > 0) { + pa_operation *op = pa_stream_cork(s, 0, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + msg_Dbg(aout, "uncorking"); + } - msg_Dbg(p_aout, "Pulse Close"); +#if 0 + /* This function should be called by the LibVLC core a header of time, + * but not more than AOUT_MAX_PREPARE. The PulseAudio latency should be + * shorter than that (though it might not be the case with some evil piece + * of audio output hardware). So we may need to trigger playback early, + * (that is to say, short cut the PulseAudio prebuffering). Otherwise, + * audio and video may be out of synchronization. */ + pa_usec_t latency; + int negative; + if (pa_stream_get_latency(s, &latency, &negative) < 0) { + /* Especially at start of stream, latency may not be known (yet). */ + if (pa_context_errno(sys->context) != PA_ERR_NODATA) + error(aout, "cannot determine latency", sys->context); + } else { + mtime_t gap = aout_FifoFirstDate(aout, &aout->output.fifo) - mdate() + - latency; + + if (gap > AOUT_PTS_TOLERANCE) + msg_Dbg(aout, "buffer too early (%"PRId64" us)", gap); + else if (gap < -AOUT_PTS_TOLERANCE) + msg_Err(aout, "buffer too late (%"PRId64" us)", -gap); + } +#endif +#if 0 /* Fault injector to test underrun recovery */ + static unsigned u = 0; + if ((++u % 500) == 0) { + msg_Err(aout, "fault injection"); + msleep(CLOCK_FREQ*2); + } +#endif - if(p_sys->stream){ - pa_operation *o; - pa_threaded_mainloop_lock(p_sys->mainloop); - pa_stream_set_write_callback(p_sys->stream, NULL, NULL); + /* This function is called exactly once per block in the output FIFO, so + * this for-loop is not necessary. + * If this function is changed to not always dequeue blocks, be sure to + * limit the queue size to a reasonable limit to avoid huge leaks. */ + for (;;) { + block_t *block = aout_FifoPop(aout, &aout->output.fifo); + if (block == NULL) + break; - if((o = pa_stream_drain(p_sys->stream, success_cb, p_aout))){ - while (pa_operation_get_state(o) != PA_OPERATION_DONE) { - CHECK_DEAD_GOTO(fail); - pa_threaded_mainloop_wait(p_sys->mainloop); - } + const void *ptr = data_convert(&block); + if (unlikely(ptr == NULL)) + break; - fail: + size_t len = block->i_buffer; + //mtime_t pts = block->i_pts, duration = block->i_length; - pa_operation_unref(o); + if (pa_stream_write(s, ptr, len, data_free, 0, PA_SEEK_RELATIVE) < 0) + { + error(aout, "cannot write", sys->context); + block_Release(block); } - - pa_threaded_mainloop_unlock(p_sys->mainloop); - } - uninit(p_aout); -} - -static void uninit(aout_instance_t *p_aout){ - struct aout_sys_t * p_sys = p_aout->output.p_sys; - - if (p_sys->mainloop) - pa_threaded_mainloop_stop(p_sys->mainloop); - - if (p_sys->stream) { - pa_stream_disconnect(p_sys->stream); - pa_stream_unref(p_sys->stream); - p_sys->stream = NULL; } - if (p_sys->context) { - pa_context_disconnect(p_sys->context); - pa_context_unref(p_sys->context); - p_sys->context = NULL; - } + pa_threaded_mainloop_unlock(sys->mainloop); +} - if (p_sys->mainloop) { - pa_threaded_mainloop_free(p_sys->mainloop); - p_sys->mainloop = NULL; - } +static int VolumeSet(aout_instance_t *aout, audio_volume_t vol, bool mute) +{ + aout_sys_t *sys = aout->output.p_sys; + pa_threaded_mainloop *mainloop = sys->mainloop; + pa_operation *op; + + uint32_t idx = pa_stream_get_index(sys->stream); + pa_volume_t volume = pa_sw_volume_from_linear(vol / (float)AOUT_VOLUME_DEFAULT); + pa_cvolume cvolume; + + /* TODO: do not ruin the channel balance (if set outside VLC) */ + /* TODO: notify UI about volume changes by other PulseAudio clients */ + pa_cvolume_set(&sys->cvolume, sys->cvolume.channels, volume); + pa_sw_cvolume_multiply_scalar(&cvolume, &sys->cvolume, sys->base_volume); + assert(pa_cvolume_valid(&cvolume)); + + pa_threaded_mainloop_lock(mainloop); + op = pa_context_set_sink_input_volume(sys->context, idx, &cvolume, NULL, NULL); + if (likely(op != NULL)) + pa_operation_unref(op); + op = pa_context_set_sink_input_mute(sys->context, idx, mute, NULL, NULL); + if (likely(op != NULL)) + pa_operation_unref(op); + pa_threaded_mainloop_unlock(mainloop); + + return 0; +} - free(p_sys); - p_aout->output.p_sys = NULL; +static int StreamMove(vlc_object_t *obj, const char *varname, vlc_value_t old, + vlc_value_t val, void *userdata) +{ + aout_instance_t *aout = (aout_instance_t *)obj; + aout_sys_t *sys = aout->output.p_sys; + pa_stream *s = userdata; + pa_operation *op; + uint32_t idx = pa_stream_get_index(s); + uint32_t sink_idx = val.i_int; + + (void) varname; (void) old; + + pa_threaded_mainloop_lock(sys->mainloop); + op = pa_context_move_sink_input_by_index(sys->context, idx, sink_idx, + NULL, NULL); + if (likely(op != NULL)) { + pa_operation_unref(op); + msg_Dbg(aout, "moving to sink %"PRIu32, sink_idx); + } else + error(aout, "cannot move sink", sys->context); + pa_threaded_mainloop_unlock(sys->mainloop); + + return (op != NULL) ? VLC_SUCCESS : VLC_EGENERIC; } -static void context_state_cb(pa_context *c, void *userdata) { - aout_instance_t *p_aout = (aout_instance_t *)userdata; - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; - assert(c); +/***************************************************************************** + * Open: open the audio device + *****************************************************************************/ +static int Open(vlc_object_t *obj) +{ + aout_instance_t *aout = (aout_instance_t *)obj; + pa_operation *op; - PULSE_DEBUG( "Pulse context state changed"); + /* Sample format specification */ + struct pa_sample_spec ss; - switch (pa_context_get_state(c)) { - case PA_CONTEXT_READY: - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - PULSE_DEBUG( "Pulse context state changed signal"); - pa_threaded_mainloop_signal(p_sys->mainloop, 0); + switch(aout->output.output.i_format) + { + case VLC_CODEC_F64B: + aout->output.output.i_format = VLC_CODEC_F32B; + case VLC_CODEC_F32B: + ss.format = PA_SAMPLE_FLOAT32BE; break; - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - PULSE_DEBUG( "Pulse context state changed no signal"); + case VLC_CODEC_F64L: + aout->output.output.i_format = VLC_CODEC_F32L; + case VLC_CODEC_F32L: + ss.format = PA_SAMPLE_FLOAT32LE; break; - } -} - -static void stream_state_cb(pa_stream *s, void * userdata) { - aout_instance_t *p_aout = (aout_instance_t *)userdata; - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; - - assert(s); - - PULSE_DEBUG( "Pulse stream state changed"); - - switch (pa_stream_get_state(s)) { - - case PA_STREAM_READY: - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - pa_threaded_mainloop_signal(p_sys->mainloop, 0); + case VLC_CODEC_FI32: + aout->output.output.i_format = VLC_CODEC_FL32; + ss.format = PA_SAMPLE_FLOAT32NE; break; - - case PA_STREAM_UNCONNECTED: - case PA_STREAM_CREATING: + case VLC_CODEC_S32B: + ss.format = PA_SAMPLE_S32BE; + break; + case VLC_CODEC_S32L: + ss.format = PA_SAMPLE_S32LE; + break; + case VLC_CODEC_S24B: + ss.format = PA_SAMPLE_S24BE; + break; + case VLC_CODEC_S24L: + ss.format = PA_SAMPLE_S24LE; + break; + case VLC_CODEC_S16B: + ss.format = PA_SAMPLE_S16BE; + break; + case VLC_CODEC_S16L: + ss.format = PA_SAMPLE_S16LE; + break; + case VLC_CODEC_S8: + aout->output.output.i_format = VLC_CODEC_U8; + case VLC_CODEC_U8: + ss.format = PA_SAMPLE_U8; + break; + default: + if (HAVE_FPU) + { + aout->output.output.i_format = VLC_CODEC_FL32; + ss.format = PA_SAMPLE_FLOAT32NE; + } + else + { + aout->output.output.i_format = VLC_CODEC_S16N; + ss.format = PA_SAMPLE_S16NE; + } break; } -} - -static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { - aout_instance_t *p_aout = (aout_instance_t *)userdata; - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; - mtime_t next_date; - - assert(s); - assert(p_sys); - size_t buffer_size = p_sys->buffer_size; - - PULSE_DEBUG( "Pulse stream request %d", length); + ss.rate = aout->output.output.i_rate; + ss.channels = aout_FormatNbChannels(&aout->output.output); + if (!pa_sample_spec_valid(&ss)) { + msg_Err(aout, "unsupported sample specification"); + return VLC_EGENERIC; + } - do{ - aout_buffer_t * p_buffer = NULL; - if(p_sys->started){ - pa_usec_t latency; - int negative; - if(pa_stream_get_latency(p_sys->stream, &latency, &negative)<0){ - if (pa_context_errno(p_sys->context) != PA_ERR_NODATA) { - msg_Err(p_aout, "pa_stream_get_latency() failed: %s", pa_strerror(pa_context_errno(p_sys->context))); - } - latency = 0; + /* Channel mapping (order defined in vlc_aout.h) */ + struct pa_channel_map map; + map.channels = 0; + + if (aout->output.output.i_physical_channels & AOUT_CHAN_LEFT) + map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_RIGHT) + map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLELEFT) + map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_LEFT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_MIDDLERIGHT) + map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_RIGHT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_REARLEFT) + map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_REARRIGHT) + map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT; + if (aout->output.output.i_physical_channels & AOUT_CHAN_REARCENTER) + map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER; + if (aout->output.output.i_physical_channels & AOUT_CHAN_CENTER) + { + if (ss.channels == 1) + map.map[map.channels++] = PA_CHANNEL_POSITION_MONO; + else + map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER; + } + if (aout->output.output.i_physical_channels & AOUT_CHAN_LFE) + map.map[map.channels++] = PA_CHANNEL_POSITION_LFE; - } + for (unsigned i = 0; map.channels < ss.channels; i++) { + map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0 + i; + msg_Warn(aout, "mapping channel %"PRIu8" to AUX%u", map.channels, i); + } - PULSE_DEBUG( "Pulse stream request latency=%"PRId64"", latency); - next_date = mdate() + latency; + if (!pa_channel_map_valid(&map)) { + msg_Err(aout, "unsupported channel map"); + return VLC_EGENERIC; + } else { + const char *name = pa_channel_map_to_pretty_name(&map); + msg_Dbg(aout, "using %s channel map", (name != NULL) ? name : "?"); + } - if(p_sys->start_date < next_date + AOUT_PTS_TOLERANCE ){ - p_buffer = aout_OutputNextBuffer( p_aout, next_date, 0); - } - } + /* Stream parameters */ + const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING + | PA_STREAM_AUTO_TIMING_UPDATE + | PA_STREAM_ADJUST_LATENCY + | PA_STREAM_START_CORKED; + + const uint32_t byterate = pa_bytes_per_second(&ss); + struct pa_buffer_attr attr; + /* no point in larger buffers on PA side than VLC */ + attr.maxlength = -1; + attr.tlength = byterate * AOUT_MAX_ADVANCE_TIME / CLOCK_FREQ; + attr.prebuf = byterate * AOUT_MAX_PREPARE_TIME / CLOCK_FREQ; + attr.minreq = -1; + attr.fragsize = 0; /* not used for output */ - if ( p_buffer != NULL ) - { - PULSE_DEBUG( "Pulse stream request write buffer %d", p_buffer->i_buffer); - pa_stream_write(p_sys->stream, p_buffer->p_buffer, p_buffer->i_buffer, NULL, 0, PA_SEEK_RELATIVE); - length -= p_buffer->i_buffer; - aout_BufferFree( p_buffer ); - } - else - { - PULSE_DEBUG( "Pulse stream request write zeroes"); - void *data = pa_xmalloc(buffer_size); - bzero(data, buffer_size); - pa_stream_write(p_sys->stream, data, buffer_size, pa_xfree, 0, PA_SEEK_RELATIVE); - length -= buffer_size; - } - }while(length > buffer_size); + /* Allocate structures */ + aout_sys_t *sys = malloc(sizeof(*sys)); + if (unlikely(sys == NULL)) + return VLC_ENOMEM; + aout->output.p_sys = sys; + sys->context = NULL; + sys->stream = NULL; + //sys->byterate = byterate; + + /* Channel volume */ + sys->base_volume = PA_VOLUME_NORM; + pa_cvolume_set(&sys->cvolume, ss.channels, PA_VOLUME_NORM); + + /* Allocate threaded main loop */ + pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new(); + if (unlikely(mainloop == NULL)) { + free(sys); + return VLC_ENOMEM; + } + sys->mainloop = mainloop; - pa_threaded_mainloop_signal(p_sys->mainloop, 0); -} + if (pa_threaded_mainloop_start(mainloop) < 0) { + pa_threaded_mainloop_free(mainloop); + free(sys); + return VLC_ENOMEM; + } + pa_threaded_mainloop_lock(mainloop); + + /* Connect to PulseAudio server */ + char *user_agent = var_InheritString(aout, "user-agent"); + pa_context *ctx = pa_context_new(pa_threaded_mainloop_get_api(mainloop), + user_agent); + free(user_agent); + if (unlikely(ctx == NULL)) + goto fail; + sys->context = ctx; -static void stream_latency_update_cb(pa_stream *s, void *userdata) { - aout_instance_t *p_aout = (aout_instance_t *)userdata; - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + pa_context_set_state_callback(ctx, context_state_cb, mainloop); + if (pa_context_connect(ctx, NULL, 0, NULL) < 0 + || context_wait(mainloop, ctx)) { + error(aout, "cannot connect to server", ctx); + goto fail; + } - assert(s); + /* Create a playback stream */ + pa_stream *s = pa_stream_new(ctx, "audio stream", &ss, &map); + if (s == NULL) { + error(aout, "cannot create stream", ctx); + goto fail; + } + sys->stream = s; + pa_stream_set_state_callback(s, stream_state_cb, mainloop); + pa_stream_set_moved_callback(s, stream_moved_cb, aout); + pa_stream_set_overflow_callback(s, stream_overflow_cb, aout); + pa_stream_set_started_callback(s, stream_started_cb, aout); + pa_stream_set_suspended_callback(s, stream_suspended_cb, aout); + pa_stream_set_underflow_callback(s, stream_underflow_cb, aout); + + if (pa_stream_connect_playback(s, NULL, &attr, flags, NULL, NULL) < 0 + || stream_wait(mainloop, s)) { + error(aout, "cannot connect stream", ctx); + goto fail; + } - PULSE_DEBUG( "Pulse stream latency update"); + const struct pa_buffer_attr *pba = pa_stream_get_buffer_attr(s); + msg_Dbg(aout, "using buffer metrics: maxlength=%u, tlength=%u, " + "prebuf=%u, minreq=%u", + pba->maxlength, pba->tlength, pba->prebuf, pba->minreq); + + aout->output.i_nb_samples = pba->minreq / pa_frame_size(&ss); + + var_Create(aout, "audio-device", VLC_VAR_INTEGER|VLC_VAR_HASCHOICE); + var_Change(aout, "audio-device", VLC_VAR_SETTEXT, + &(vlc_value_t){ .psz_string = (char *)_("Audio device") }, + NULL); + var_AddCallback (aout, "audio-device", StreamMove, s); + op = pa_context_get_sink_info_list(ctx, sink_list_cb, aout); + /* We may need to wait for completion... once LibVLC supports this */ + if (op != NULL) + pa_operation_unref(op); + stream_moved_cb(s, aout); + pa_threaded_mainloop_unlock(mainloop); + + aout->output.pf_play = Play; + aout->output.pf_volume_set = VolumeSet; + return VLC_SUCCESS; - pa_threaded_mainloop_signal(p_sys->mainloop, 0); +fail: + pa_threaded_mainloop_unlock(mainloop); + Close(obj); + return VLC_EGENERIC; } -static void success_cb(pa_stream *s, int sucess, void *userdata) +/***************************************************************************** + * Close: close the audio device + *****************************************************************************/ +static void Close (vlc_object_t *obj) { - aout_instance_t *p_aout = (aout_instance_t *)userdata; - struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; - - VLC_UNUSED(sucess); - - assert(s); + aout_instance_t *aout = (aout_instance_t *)obj; + aout_sys_t *sys = aout->output.p_sys; + pa_threaded_mainloop *mainloop = sys->mainloop; + pa_context *ctx = sys->context; + pa_stream *s = sys->stream; + + if (s != NULL) { + /* The callback takes mainloop lock, so it CANNOT be held here! */ + var_DelCallback (aout, "audio-device", StreamMove, s); + var_Destroy (aout, "audio-device"); + } - pa_threaded_mainloop_signal(p_sys->mainloop, 0); + pa_threaded_mainloop_lock(mainloop); + if (s != NULL) { + pa_operation *op; + + op = pa_stream_flush(s, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + op = pa_stream_drain(s, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + pa_stream_disconnect(s); + pa_stream_unref(s); + } + if (ctx != NULL) + pa_context_unref(ctx); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_free(mainloop); + free(sys); } - -#undef PULSE_DEBUG