X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fpulse.c;h=f1af0976340cadff246fc3a3b856de738db0689d;hb=c72c363f0344ac9bfb280fd4c48299c862561eff;hp=c7bd67dbbe211d828aba01b31d5b2e01364c6b3e;hpb=82f58f3282b637706d27cde26479b9f3c5a5c702;p=vlc diff --git a/modules/audio_output/pulse.c b/modules/audio_output/pulse.c index c7bd67dbbe..f1af097634 100644 --- a/modules/audio_output/pulse.c +++ b/modules/audio_output/pulse.c @@ -25,6 +25,7 @@ # include "config.h" #endif +#include #include #include #include @@ -32,6 +33,9 @@ #include #include +#if !PA_CHECK_VERSION(0,9,22) +# include +#endif static int Open ( vlc_object_t * ); static void Close ( vlc_object_t * ); @@ -66,6 +70,7 @@ struct aout_sys_t pa_context *context; /**< PulseAudio connection context */ pa_volume_t base_volume; /**< 0dB reference volume */ pa_cvolume cvolume; /**< actual sink input volume */ + mtime_t paused; /**< Time when (last) paused */ mtime_t pts; /**< Play time of buffer write offset */ mtime_t desync; /**< Measured desynchronization */ unsigned rate; /**< Current stream sample rate */ @@ -147,10 +152,25 @@ static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, 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)); + msg_Dbg(aout, "base volume: %"PRIu32, sys->base_volume); +} + + +/*** Latency management and lip synchronization ***/ +static mtime_t vlc_pa_get_latency(audio_output_t *aout, + pa_context *ctx, pa_stream *s) +{ + pa_usec_t latency; + int negative; + + if (pa_stream_get_latency(s, &latency, &negative)) { + if (pa_context_errno (ctx) != PA_ERR_NODATA) + vlc_pa_error(aout, "unknown latency", ctx); + return VLC_TS_INVALID; + } + return negative ? -latency : +latency; } -/*** Stream helpers ***/ static void stream_reset_sync(pa_stream *s, audio_output_t *aout) { aout_sys_t *sys = aout->sys; @@ -165,26 +185,62 @@ static void stream_reset_sync(pa_stream *s, audio_output_t *aout) sys->rate = rate; } -static void stream_state_cb(pa_stream *s, void *userdata) +/** + * Starts or resumes the playback stream. + * Tries start playing back audio samples at the most accurate time + * in order to minimize desync and resampling during early playback. + * @note PulseAudio lock required. + */ +static void stream_resync(audio_output_t *aout, pa_stream *s) { - switch (pa_stream_get_state(s)) { - case PA_STREAM_READY: - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - vlc_pa_signal(0); - default: - break; - } - (void) userdata; + aout_sys_t *sys = aout->sys; + pa_operation *op; + mtime_t delta; + + assert (pa_stream_is_corked(s) > 0); + assert (sys->pts != VLC_TS_INVALID); + + delta = vlc_pa_get_latency(aout, sys->context, s); + if (unlikely(delta == VLC_TS_INVALID)) + delta = 0; /* screwed */ + + delta = (sys->pts - mdate()) - delta; + + /* TODO: adjust prebuf instead of padding? */ + if (delta > 0) { + size_t nb = (delta * sys->rate) / CLOCK_FREQ; + size_t size = aout->format.i_bytes_per_frame; + float *zeroes = calloc (nb, size); + + msg_Dbg(aout, "starting with %zu zeroes (%"PRId64" us)", nb, + delta); +#if 0 /* Fault injector: add delay */ + pa_stream_write(s, zeroes, nb * size, NULL, 0, PA_SEEK_RELATIVE); + pa_stream_write(s, zeroes, nb * size, NULL, 0, PA_SEEK_RELATIVE); +#endif + if (likely(zeroes != NULL)) + if (pa_stream_write(s, zeroes, nb * size, free, 0, + PA_SEEK_RELATIVE) < 0) + free(zeroes); + } else + msg_Warn(aout, "starting late (%"PRId64" us)", delta); + + op = pa_stream_cork(s, 0, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + op = pa_stream_trigger(s, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); } -/* Latency management and lip synchronization */ static void stream_latency_cb(pa_stream *s, void *userdata) { audio_output_t *aout = userdata; aout_sys_t *sys = aout->sys; mtime_t delta, change; + if (pa_stream_is_corked(s)) + return; if (sys->pts == VLC_TS_INVALID) { msg_Dbg(aout, "missing latency from input"); @@ -192,54 +248,49 @@ static void stream_latency_cb(pa_stream *s, void *userdata) } /* Compute lip desynchronization */ - { - pa_usec_t latency; - int negative; - - if (pa_stream_get_latency(s, &latency, &negative)) { - vlc_pa_error(aout, "missing latency", sys->context); - return; - } - delta = sys->pts - mdate(); - if (unlikely(negative)) - delta += latency; - else - delta -= latency; - } + delta = vlc_pa_get_latency(aout, sys->context, s); + if (delta == VLC_TS_INVALID) + return; + delta = (sys->pts - mdate()) - delta; change = delta - sys->desync; sys->desync = delta; //msg_Dbg(aout, "desync: %+"PRId64" us (variation: %+"PRId64" us)", // delta, change); + const unsigned inrate = aout->format.i_rate; + unsigned outrate = sys->rate; + bool sync = false; + if (delta < -AOUT_MAX_PTS_DELAY) msg_Warn(aout, "too late by %"PRId64" us", -delta); else if (delta > +AOUT_MAX_PTS_ADVANCE) msg_Warn(aout, "too early by %"PRId64" us", delta); + else if (outrate == inrate) + return; /* In sync, do not add unnecessary disturbance! */ + else + sync = true; /* Compute playback sample rate */ - const unsigned inrate = aout->format.i_rate; - -#define ADJUST_FACTOR 4 -#define ADJUST_MAX 1000 /* Hz (max rate variation per call) */ /* This is empirical. Feel free to define something smarter. */ - int adj = sys->rate * (delta + change) / (CLOCK_FREQ * ADJUST_FACTOR); - - /* This avoids too fast rate variation. They sound ugly as hell and they - * make the algorithm unstable (e.g. oscillation around inrate). */ - if (adj > +ADJUST_MAX) - adj = +ADJUST_MAX; - if (adj < -ADJUST_MAX) - adj = -ADJUST_MAX; - - unsigned outrate = sys->rate - adj; - /* Favor native rate to avoid resampling (FIXME: really a good idea?) */ - if (abs(outrate - inrate) < (inrate >> 10)) - outrate = inrate; + int adj = sync ? (outrate - inrate) + : outrate * (delta + change) / (CLOCK_FREQ << 4); + /* This avoids too quick rate variation. It sounds really bad and + * causes unstability (e.g. oscillation around the correct rate). */ + int limit = inrate >> 10; + /* However, to improve stability and try to converge, closing to the + * nominal rate is favored over drifting from it. */ + if ((adj > 0) == (sys->rate > inrate)) + limit *= 2; + if (adj > +limit) + adj = +limit; + if (adj < -limit) + adj = -limit; + outrate -= adj; /* This keeps the effective rate within specified range * (+/-AOUT_MAX_RESAMPLING% - see ) of the nominal rate. */ - const int limit = inrate * AOUT_MAX_RESAMPLING / 100; + limit = inrate * AOUT_MAX_RESAMPLING / 100; if (outrate > inrate + limit) outrate = inrate + limit; if (outrate < inrate - limit) @@ -258,6 +309,21 @@ static void stream_latency_cb(pa_stream *s, void *userdata) sys->rate = outrate; } + +/*** Stream helpers ***/ +static void stream_state_cb(pa_stream *s, void *userdata) +{ + switch (pa_stream_get_state(s)) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + vlc_pa_signal(0); + default: + break; + } + (void) userdata; +} + static void stream_moved_cb(pa_stream *s, void *userdata) { audio_output_t *aout = userdata; @@ -325,16 +391,6 @@ static int stream_wait(pa_stream *stream) return 0; } -#ifdef LIBPULSE_GETS_A_CLUE -static void stream_success_cb(pa_stream *s, int success, void *userdata) -{ - vlc_pa_signal(0); - (void) s; (void) success; (void) userdata; -} -#else -# define stream_success_cb NULL -#endif - /*** Sink input ***/ static void sink_input_info_cb(pa_context *ctx, const pa_sink_input_info *i, @@ -349,7 +405,7 @@ static void sink_input_info_cb(pa_context *ctx, const pa_sink_input_info *i, (void) ctx; sys->cvolume = i->volume; - volume = pa_sw_volume_to_linear(pa_cvolume_max(&i->volume)); + volume = pa_cvolume_max(&i->volume) / (float)PA_VOLUME_NORM; aout_VolumeHardSet(aout, volume, i->mute); } @@ -383,15 +439,11 @@ static void *data_convert(block_t **pp) /** * Queue one audio frame to the playabck stream */ -static void Play(audio_output_t *aout) +static void Play(audio_output_t *aout, block_t *block) { aout_sys_t *sys = aout->sys; pa_stream *s = sys->stream; - /* This function is called exactly once per block in the output FIFO. */ - block_t *block = aout_FifoPop(&aout->fifo); - assert (block != NULL); - const void *ptr = data_convert(&block); if (unlikely(ptr == NULL)) return; @@ -406,50 +458,9 @@ static void Play(audio_output_t *aout) * will take place, and sooner or later a deadlock. */ vlc_pa_lock(); - if (pa_stream_is_corked(s) > 0) { - /* Start or resume the stream. Zeroes are prepended to sync. - * This does not really work because PulseAudio latency measurement is - * garbage at start. */ - pa_operation *op; - pa_usec_t latency; - int negative; - - if (pa_stream_get_latency(s, &latency, &negative) == 0) - msg_Dbg(aout, "starting with %c%"PRIu64" us latency", - negative ? '-' : '+', latency); - else - latency = negative = 0; - - mtime_t advance = block->i_pts - mdate(); - if (negative) - advance += latency; - else - advance -= latency; - - if (advance > 0) { - size_t nb = (advance * aout->format.i_rate) / CLOCK_FREQ; - size_t size = aout->format.i_bytes_per_frame; - float *zeroes = calloc (nb, size); - - msg_Dbg(aout, "prepending %zu zeroes", nb); -#if 0 /* Fault injector: add delay */ - pa_stream_write(s, zeroes, nb * size, NULL, 0, PA_SEEK_RELATIVE); - pa_stream_write(s, zeroes, nb * size, NULL, 0, PA_SEEK_RELATIVE); -#endif - if (likely(zeroes != NULL)) - if (pa_stream_write(s, zeroes, nb * size, free, 0, - PA_SEEK_RELATIVE) < 0) - free(zeroes); - } - - op = pa_stream_cork(s, 0, NULL, NULL); - if (op != NULL) - pa_operation_unref(op); - op = pa_stream_trigger(s, NULL, NULL); - if (op != NULL) - pa_operation_unref(op); - msg_Dbg(aout, "uncorking"); - } + sys->pts = pts; + if (pa_stream_is_corked(s) > 0) + stream_resync(aout, s); #if 0 /* Fault injector to test underrun recovery */ static volatile unsigned u = 0; @@ -463,7 +474,6 @@ static void Play(audio_output_t *aout) vlc_pa_error(aout, "cannot write", sys->context); block_Release(block); } - sys->pts = pts; vlc_pa_unlock(); } @@ -471,23 +481,50 @@ static void Play(audio_output_t *aout) /** * Cork or uncork the playback stream */ -static void Pause(audio_output_t *aout, bool b_paused, mtime_t i_date) +static void Pause(audio_output_t *aout, bool paused, mtime_t date) { aout_sys_t *sys = aout->sys; pa_stream *s = sys->stream; + pa_operation *op; + + vlc_pa_lock(); + + if (paused) { + sys->paused = date; + op = pa_stream_cork(s, paused, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + } else { + assert (sys->paused != VLC_TS_INVALID); + date -= sys->paused; + msg_Dbg(aout, "resuming after %"PRId64" us", date); + sys->paused = VLC_TS_INVALID; + sys->pts += date; + stream_resync(aout, s); + } - if (!b_paused) - return; /* nothing to do - yet */ + vlc_pa_unlock(); +} + +/** + * Flush or drain the playback stream + */ +static void Flush(audio_output_t *aout, bool wait) +{ + aout_sys_t *sys = aout->sys; + pa_stream *s = sys->stream; + pa_operation *op; vlc_pa_lock(); - pa_operation *op = pa_stream_cork(s, 1, NULL, NULL); + if (wait) + op = pa_stream_drain(s, NULL, NULL); + /* TODO: wait for drain completion*/ + else + op = pa_stream_flush(s, NULL, NULL); if (op != NULL) pa_operation_unref(op); - stream_reset_sync(s, aout); - vlc_pa_unlock(); - (void) i_date; } static int VolumeSet(audio_output_t *aout, float vol, bool mute) @@ -497,11 +534,19 @@ static int VolumeSet(audio_output_t *aout, float vol, bool mute) uint32_t idx = pa_stream_get_index(sys->stream); pa_cvolume cvolume = sys->cvolume; - pa_volume_t volume = pa_sw_volume_multiply(pa_sw_volume_from_linear(vol), - sys->base_volume); + pa_volume_t volume = sys->base_volume; pa_cvolume_scale(&cvolume, PA_VOLUME_NORM); /* preserve balance */ + + /* VLC provides the software volume so convert directly to PulseAudio + * software volume, pa_volume_t. This is not a linear amplification factor + * so do not use PulseAudio linear amplification! */ + vol *= PA_VOLUME_NORM; + if (unlikely(vol >= PA_VOLUME_MAX)) + vol = PA_VOLUME_MAX; + volume = pa_sw_volume_multiply(volume, lround(vol)); pa_sw_cvolume_multiply_scalar(&cvolume, &cvolume, volume); + assert(pa_cvolume_valid(&cvolume)); vlc_pa_lock(); @@ -547,6 +592,11 @@ static int StreamMove(vlc_object_t *obj, const char *varname, vlc_value_t old, */ static int Open(vlc_object_t *obj) { +#if !PA_CHECK_VERSION(0,9,22) + if (!vlc_xlib_init(obj)) + return VLC_EGENERIC; +#endif + audio_output_t *aout = (audio_output_t *)obj; pa_operation *op; @@ -689,6 +739,7 @@ static int Open(vlc_object_t *obj) aout->sys = sys; sys->stream = NULL; sys->context = ctx; + sys->paused = VLC_TS_INVALID; sys->pts = VLC_TS_INVALID; sys->desync = 0; sys->rate = ss.rate; @@ -732,8 +783,6 @@ static int Open(vlc_object_t *obj) "prebuf=%u, minreq=%u", pba->maxlength, pba->tlength, pba->prebuf, pba->minreq); - aout->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") }, @@ -749,7 +798,8 @@ static int Open(vlc_object_t *obj) aout->format.i_format = format; aout->pf_play = Play; aout->pf_pause = Pause; - aout->pf_volume_set = VolumeSet; + aout->pf_flush = Flush; + aout_VolumeHardInit (aout, VolumeSet); return VLC_SUCCESS; fail: @@ -772,30 +822,22 @@ static void Close (vlc_object_t *obj) /* The callback takes mainloop lock, so it CANNOT be held here! */ var_DelCallback (aout, "audio-device", StreamMove, s); var_Destroy (aout, "audio-device"); - } - vlc_pa_lock(); - if (s != NULL) { - pa_operation *op; + vlc_pa_lock (); + pa_stream_disconnect(s); - if (pa_stream_is_corked(s) > 0) - /* Stream paused: discard all buffers */ - op = pa_stream_flush(s, stream_success_cb, NULL); - else - /* Stream playing: wait until buffers are played */ - op = pa_stream_drain(s, stream_success_cb, NULL); - if (likely(op != NULL)) { -#ifdef LIBPULSE_GETS_A_CLUE - while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) - vlc_pa_wait(); -#endif - pa_operation_unref(op); - } + /* Clear all callbacks */ + pa_stream_set_state_callback(s, NULL, NULL); + pa_stream_set_latency_update_callback(s, NULL, NULL); + pa_stream_set_moved_callback(s, NULL, NULL); + pa_stream_set_overflow_callback(s, NULL, NULL); + pa_stream_set_started_callback(s, NULL, NULL); + pa_stream_set_suspended_callback(s, NULL, NULL); + pa_stream_set_underflow_callback(s, NULL, NULL); - pa_stream_disconnect(s); pa_stream_unref(s); + vlc_pa_unlock (); } - vlc_pa_unlock(); vlc_pa_disconnect(obj, ctx); free(sys);