X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fpulse.c;h=cd15f84f57ed53b8540de5fe35013b1e8773d133;hb=2b1a7345d574af9dd993c410aaa56575c447ac62;hp=a94cea05b8e613b85454a35a4459fe8c49a24e6d;hpb=2516061e583b6789c403bb9c8d73e5197c76b0f0;p=vlc diff --git a/modules/audio_output/pulse.c b/modules/audio_output/pulse.c index a94cea05b8..cd15f84f57 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 * ); @@ -64,13 +68,72 @@ struct aout_sys_t { pa_stream *stream; /**< PulseAudio playback stream object */ pa_context *context; /**< PulseAudio connection context */ + pa_time_event *trigger; /**< Deferred stream trigger */ 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 */ }; +static void sink_list_cb(pa_context *, const pa_sink_info *, int, void *); +static void sink_input_info_cb(pa_context *, const pa_sink_input_info *, + int, void *); + +/*** Context ***/ +static void context_cb(pa_context *ctx, pa_subscription_event_type_t type, + uint32_t idx, void *userdata) +{ + audio_output_t *aout = userdata; + aout_sys_t *sys = aout->sys; + pa_operation *op; + + switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { + case PA_SUBSCRIPTION_EVENT_SINK: + switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) + { + case PA_SUBSCRIPTION_EVENT_NEW: + case PA_SUBSCRIPTION_EVENT_CHANGE: + op = pa_context_get_sink_info_by_index(ctx, idx, sink_list_cb, aout); + if (likely(op != NULL)) + pa_operation_unref(op); + break; + + case PA_SUBSCRIPTION_EVENT_REMOVE: + var_Change(aout, "audio-device", VLC_VAR_DELCHOICE, + &(vlc_value_t){ .i_int = idx }, NULL); + break; + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if (idx != pa_stream_get_index(sys->stream)) + break; /* only interested in our sink input */ + + /* Gee... PA will not provide the infos directly in the event. */ + switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) + { + case PA_SUBSCRIPTION_EVENT_REMOVE: + msg_Err(aout, "sink input killed!"); + break; + + default: + op = pa_context_get_sink_input_info(ctx, idx, sink_input_info_cb, + aout); + if (likely(op != NULL)) + pa_operation_unref(op); + break; + } + break; + + default: /* unsubscribed facility?! */ + assert(0); + } +} + + /*** Sink ***/ static void sink_list_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) @@ -86,6 +149,7 @@ static void sink_list_cb(pa_context *c, const pa_sink_info *i, int eol, i->description); val.i_int = i->index; text.psz_string = (char *)i->description; + var_Change(aout, "audio-device", VLC_VAR_DELCHOICE, &val, NULL); var_Change(aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); } @@ -108,10 +172,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; @@ -126,26 +205,91 @@ 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) +static void stream_start(pa_stream *s, audio_output_t *aout) { - switch (pa_stream_get_state(s)) { - case PA_STREAM_READY: - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - vlc_pa_signal(0); - default: - break; + aout_sys_t *sys = aout->sys; + pa_operation *op; + + if (sys->trigger != NULL) { + vlc_pa_rttime_free(sys->trigger); + sys->trigger = NULL; + } + + op = pa_stream_cork(s, 0, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); + op = pa_stream_trigger(s, NULL, NULL); + if (likely(op != NULL)) + pa_operation_unref(op); +} + +static void stream_stop(pa_stream *s, audio_output_t *aout) +{ + aout_sys_t *sys = aout->sys; + pa_operation *op; + + if (sys->trigger != NULL) { + vlc_pa_rttime_free(sys->trigger); + sys->trigger = NULL; + } + + op = pa_stream_cork(s, 1, NULL, NULL); + if (op != NULL) + pa_operation_unref(op); +} + +static void stream_trigger_cb(pa_mainloop_api *api, pa_time_event *e, + const struct timeval *tv, void *userdata) +{ + audio_output_t *aout = userdata; + aout_sys_t *sys = aout->sys; + + msg_Dbg(aout, "starting deferred"); + assert (sys->trigger == e); + stream_start(sys->stream, aout); + (void) api; (void) e; (void) tv; +} + +/** + * 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) +{ + aout_sys_t *sys = aout->sys; + 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; + if (delta > 0) { + if (sys->trigger == NULL) { + msg_Dbg(aout, "deferring start (%"PRId64" us)", delta); + delta += pa_rtclock_now(); + sys->trigger = pa_context_rttime_new(sys->context, delta, + stream_trigger_cb, aout); + } + } else { + msg_Warn(aout, "starting late (%"PRId64" us)", delta); + stream_start(s, aout); } - (void) userdata; } -/* 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"); @@ -153,54 +297,50 @@ 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; + /* This is empirical (especially the shift values). + * Feel free to define something smarter. */ + int adj = sync ? (outrate - inrate) + : outrate * ((delta >> 4) + change) / (CLOCK_FREQ << 2); + /* 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) @@ -219,6 +359,41 @@ 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_event_cb(pa_stream *s, const char *name, pa_proplist *pl, + void *userdata) +{ + audio_output_t *aout = userdata; + +#if PA_CHECK_VERSION(1,0,0) + /* FIXME: expose aout_Restart() directly */ + if (!strcmp(name, PA_STREAM_EVENT_FORMAT_LOST)) { + vlc_value_t dummy = { .i_int = 0 }; + + msg_Dbg (aout, "format lost"); + aout_ChannelsRestart (VLC_OBJECT(aout), "audio-device", + dummy, dummy, NULL); + } else +#endif + msg_Warn (aout, "unhandled event %s", name); + (void) s; + (void) pl; +} + static void stream_moved_cb(pa_stream *s, void *userdata) { audio_output_t *aout = userdata; @@ -236,6 +411,16 @@ static void stream_moved_cb(pa_stream *s, void *userdata) /* Update the variable if someone else moved our stream */ var_Change(aout, "audio-device", VLC_VAR_SETVALUE, &(vlc_value_t){ .i_int = idx }, NULL); + + /* Sink unknown as yet, create stub choice for it */ + if (var_GetInteger(aout, "audio-device") != idx) + { + var_Change(aout, "audio-device", VLC_VAR_ADDCHOICE, + &(vlc_value_t){ .i_int = idx }, + &(vlc_value_t){ .psz_string = (char *)"?" }); + var_Change(aout, "audio-device", VLC_VAR_SETVALUE, + &(vlc_value_t){ .i_int = idx }, NULL); + } } static void stream_overflow_cb(pa_stream *s, void *userdata) @@ -265,12 +450,9 @@ static void stream_suspended_cb(pa_stream *s, void *userdata) static void stream_underflow_cb(pa_stream *s, void *userdata) { audio_output_t *aout = userdata; - pa_operation *op; msg_Warn(aout, "underflow"); - op = pa_stream_cork(s, 1, NULL, NULL); - if (op != NULL) - pa_operation_unref(op); + stream_stop(s, aout); stream_reset_sync(s, aout); } @@ -286,15 +468,26 @@ 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) + +/*** Sink input ***/ +static void sink_input_info_cb(pa_context *ctx, const pa_sink_input_info *i, + int eol, void *userdata) { - vlc_pa_signal(0); - (void) s; (void) success; (void) userdata; + audio_output_t *aout = userdata; + aout_sys_t *sys = aout->sys; + float volume; + + if (eol) + return; + (void) ctx; + + sys->cvolume = i->volume; + volume = pa_cvolume_max(&i->volume) / (float)PA_VOLUME_NORM; + aout_VolumeHardSet(aout, volume, i->mute); } -#else -# define stream_success_cb NULL -#endif + + +/*** VLC audio output callbacks ***/ /* Memory free callback. The block_t address is in front of the data. */ static void data_free(void *data) @@ -323,15 +516,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; @@ -346,50 +535,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; @@ -403,7 +551,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(); } @@ -411,38 +558,69 @@ 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; - if (!b_paused) - return; /* nothing to do - yet */ + vlc_pa_lock(); + + if (paused) { + sys->paused = date; + stream_stop(s, aout); + } 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); + } + + 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) { aout_sys_t *sys = aout->sys; pa_operation *op; - uint32_t idx = pa_stream_get_index(sys->stream); - pa_volume_t volume = pa_sw_volume_from_linear(vol); - 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); + pa_cvolume cvolume = sys->cvolume; + 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(); @@ -488,26 +666,35 @@ 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; /* Sample format specification */ struct pa_sample_spec ss; + vlc_fourcc_t format = aout->format.i_format; +#if PA_CHECK_VERSION(1,0,0) + pa_encoding_t encoding = PA_ENCODING_INVALID; +#endif - switch(aout->format.i_format) + switch(format) { case VLC_CODEC_F64B: - aout->format.i_format = VLC_CODEC_F32B; + format = VLC_CODEC_F32B; case VLC_CODEC_F32B: ss.format = PA_SAMPLE_FLOAT32BE; break; case VLC_CODEC_F64L: - aout->format.i_format = VLC_CODEC_F32L; + format = VLC_CODEC_F32L; case VLC_CODEC_F32L: ss.format = PA_SAMPLE_FLOAT32LE; break; case VLC_CODEC_FI32: - aout->format.i_format = VLC_CODEC_FL32; + format = VLC_CODEC_FL32; ss.format = PA_SAMPLE_FLOAT32NE; break; case VLC_CODEC_S32B: @@ -529,19 +716,41 @@ static int Open(vlc_object_t *obj) ss.format = PA_SAMPLE_S16LE; break; case VLC_CODEC_S8: - aout->format.i_format = VLC_CODEC_U8; + format = VLC_CODEC_U8; case VLC_CODEC_U8: ss.format = PA_SAMPLE_U8; break; +#if PA_CHECK_VERSION(1,0,0) + case VLC_CODEC_A52: + format = VLC_CODEC_SPDIFL; + encoding = PA_ENCODING_AC3_IEC61937; + ss.format = HAVE_FPU ? PA_SAMPLE_FLOAT32NE : PA_SAMPLE_S16NE; + break; + /*case VLC_CODEC_EAC3: + format = VLC_CODEC_SPDIFL FIXME; + encoding = PA_ENCODING_EAC3_IEC61937; + ss.format = HAVE_FPU ? PA_SAMPLE_FLOAT32NE : PA_SAMPLE_S16NE; + break; + case VLC_CODEC_MPGA: + format = VLC_CODEC_SPDIFL FIXME; + encoding = PA_ENCODING_MPEG_IEC61937; + ss.format = HAVE_FPU ? PA_SAMPLE_FLOAT32NE : PA_SAMPLE_S16NE; + break;*/ + case VLC_CODEC_DTS: + format = VLC_CODEC_SPDIFL; + encoding = PA_ENCODING_DTS_IEC61937; + ss.format = HAVE_FPU ? PA_SAMPLE_FLOAT32NE : PA_SAMPLE_S16NE; + break; +#endif default: if (HAVE_FPU) { - aout->format.i_format = VLC_CODEC_FL32; + format = VLC_CODEC_FL32; ss.format = PA_SAMPLE_FLOAT32NE; } else { - aout->format.i_format = VLC_CODEC_S16N; + format = VLC_CODEC_S16N; ss.format = PA_SAMPLE_S16NE; } break; @@ -629,23 +838,65 @@ static int Open(vlc_object_t *obj) aout->sys = sys; sys->stream = NULL; sys->context = ctx; + sys->trigger = NULL; + sys->paused = VLC_TS_INVALID; sys->pts = VLC_TS_INVALID; sys->desync = 0; sys->rate = ss.rate; + /* Context events */ + const pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SINK + | PA_SUBSCRIPTION_MASK_SINK_INPUT; + + pa_context_set_subscribe_callback(ctx, context_cb, aout); + op = pa_context_subscribe(ctx, mask, NULL, NULL); + if (likely(op != NULL)) + pa_operation_unref(op); + /* Channel volume */ sys->base_volume = PA_VOLUME_NORM; pa_cvolume_set(&sys->cvolume, ss.channels, PA_VOLUME_NORM); - vlc_pa_lock(); +#if PA_CHECK_VERSION(1,0,0) + pa_format_info *formatv[2]; + unsigned formatc = 0; + + /* Favor digital pass-through if available*/ + if (encoding != PA_ENCODING_INVALID) { + formatv[formatc] = pa_format_info_new(); + formatv[formatc]->encoding = encoding; + pa_format_info_set_rate(formatv[formatc], ss.rate); + pa_format_info_set_channels(formatv[formatc], ss.channels); + formatc++; + } + + /* Fallback to PCM */ + formatv[formatc] = pa_format_info_new(); + formatv[formatc]->encoding = PA_ENCODING_PCM; + pa_format_info_set_sample_format(formatv[formatc], ss.format); + pa_format_info_set_rate(formatv[formatc], ss.rate); + pa_format_info_set_channels(formatv[formatc], ss.channels); + formatc++; + /* Create a playback stream */ + pa_stream *s; + + vlc_pa_lock(); + s = pa_stream_new_extended(ctx, "audio stream", formatv, formatc, NULL); + + for (unsigned i = 0; i < formatc; i++) + pa_format_info_free(formatv[i]); +#else + vlc_pa_lock(); pa_stream *s = pa_stream_new(ctx, "audio stream", &ss, &map); +#endif if (s == NULL) { vlc_pa_error(obj, "stream creation failure", ctx); goto fail; } sys->stream = s; pa_stream_set_state_callback(s, stream_state_cb, NULL); + pa_stream_set_event_callback(s, stream_event_cb, aout); pa_stream_set_latency_update_callback(s, stream_latency_cb, aout); pa_stream_set_moved_callback(s, stream_moved_cb, aout); pa_stream_set_overflow_callback(s, stream_overflow_cb, aout); @@ -659,13 +910,26 @@ static int Open(vlc_object_t *obj) goto fail; } +#if PA_CHECK_VERSION(1,0,0) + if (encoding != PA_ENCODING_INVALID) { + const pa_format_info *info = pa_stream_get_format_info(s); + + assert (info != NULL); + if (pa_format_info_is_pcm (info)) { + msg_Dbg(aout, "digital pass-through not available"); + format = HAVE_FPU ? VLC_CODEC_FL32 : VLC_CODEC_S16N; + } else { + msg_Dbg(aout, "digital pass-through enabled"); + pa_stream_set_latency_update_callback(s, NULL, NULL); + } + } +#endif + 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->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") }, @@ -678,9 +942,11 @@ static int Open(vlc_object_t *obj) stream_moved_cb(s, aout); vlc_pa_unlock(); + 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: @@ -703,30 +969,25 @@ 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 (); + if (unlikely(sys->trigger != NULL)) + vlc_pa_rttime_free(sys->trigger); + 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_event_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);