diff options
Diffstat (limited to 'audio/out')
32 files changed, 4258 insertions, 3198 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c index 71c17e03b0..75fcbac6fa 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -29,7 +29,7 @@ #include "audio/format.h" #include "options/options.h" -#include "options/m_config.h" +#include "options/m_config_frontend.h" #include "osdep/endian.h" #include "common/msg.h" #include "common/common.h" @@ -40,7 +40,9 @@ extern const struct ao_driver audio_out_audiotrack; extern const struct ao_driver audio_out_audiounit; extern const struct ao_driver audio_out_coreaudio; extern const struct ao_driver audio_out_coreaudio_exclusive; +extern const struct ao_driver audio_out_avfoundation; extern const struct ao_driver audio_out_rsound; +extern const struct ao_driver audio_out_pipewire; extern const struct ao_driver audio_out_sndio; extern const struct ao_driver audio_out_pulse; extern const struct ao_driver audio_out_jack; @@ -64,6 +66,12 @@ static const struct ao_driver * const audio_out_drivers[] = { #if HAVE_COREAUDIO &audio_out_coreaudio, #endif +#if HAVE_AVFOUNDATION + &audio_out_avfoundation, +#endif +#if HAVE_PIPEWIRE + &audio_out_pipewire, +#endif #if HAVE_PULSE &audio_out_pulse, #endif @@ -98,15 +106,11 @@ static const struct ao_driver * const audio_out_drivers[] = { #endif &audio_out_pcm, &audio_out_lavc, -#if HAVE_RSOUND - &audio_out_rsound, -#endif - NULL }; static bool get_desc(struct m_obj_desc *dst, int index) { - if (index >= MP_ARRAY_SIZE(audio_out_drivers) - 1) + if (index >= MP_ARRAY_SIZE(audio_out_drivers)) return false; const struct ao_driver *ao = audio_out_drivers[index]; *dst = (struct m_obj_desc) { @@ -127,7 +131,6 @@ static bool get_desc(struct m_obj_desc *dst, int index) static const struct m_obj_list ao_obj_list = { .get_desc = get_desc, .description = "audio outputs", - .allow_unknown_entries = true, .allow_trailer = true, .disallow_positional_parameters = true, .use_global_options = true, @@ -136,11 +139,12 @@ static const struct m_obj_list ao_obj_list = { #define OPT_BASE_STRUCT struct ao_opts const struct m_sub_options ao_conf = { .opts = (const struct m_option[]) { - OPT_SETTINGSLIST("ao", audio_driver_list, UPDATE_AUDIO, &ao_obj_list, ), - OPT_STRING("audio-device", audio_device, UPDATE_AUDIO), - OPT_STRING("audio-client-name", audio_client_name, UPDATE_AUDIO), - OPT_DOUBLE("audio-buffer", audio_buffer, - UPDATE_AUDIO | M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 10), + {"ao", OPT_SETTINGSLIST(audio_driver_list, &ao_obj_list), + .flags = UPDATE_AUDIO}, + {"audio-device", OPT_STRING(audio_device), .flags = UPDATE_AUDIO}, + {"audio-client-name", OPT_STRING(audio_client_name), .flags = UPDATE_AUDIO}, + {"audio-buffer", OPT_DOUBLE(audio_buffer), + .flags = UPDATE_AUDIO, M_RANGE(0, 10)}, {0} }, .size = sizeof(OPT_BASE_STRUCT), @@ -210,14 +214,9 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, af_fmt_to_str(ao->format)); ao->device = talloc_strdup(ao, dev); - - ao->api = ao->driver->play ? &ao_api_push : &ao_api_pull; - ao->api_priv = talloc_zero_size(ao, ao->api->priv_size); - assert(!ao->api->priv_defaults && !ao->api->options); - ao->stream_silence = flags & AO_INIT_STREAM_SILENCE; - ao->period_size = 1; + init_buffer_pre(ao); int r = ao->driver->init(ao); if (r < 0) { @@ -226,18 +225,14 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, char redirect[80], rdevice[80]; snprintf(redirect, sizeof(redirect), "%s", ao->redirect); snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : ""); - talloc_free(ao); + ao_uninit(ao); return ao_init(probing, global, wakeup_cb, wakeup_ctx, encode_lavc_ctx, flags, samplerate, format, channels, rdevice, redirect); } goto fail; } - - if (ao->period_size < 1) { - MP_ERR(ao, "Invalid period size set.\n"); - goto fail; - } + ao->driver_initialized = true; ao->sstride = af_fmt_to_bytes(ao->format); ao->num_planes = 1; @@ -248,8 +243,10 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, } ao->bps = ao->samplerate * ao->sstride; - if (!ao->device_buffer && ao->driver->get_space) - ao->device_buffer = ao->driver->get_space(ao); + if (ao->device_buffer <= 0 && ao->driver->write) { + MP_ERR(ao, "Device buffer size not set.\n"); + goto fail; + } if (ao->device_buffer) MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer); ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate); @@ -259,12 +256,12 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, ao->buffer = (ao->buffer + align - 1) / align * align; MP_VERBOSE(ao, "using soft-buffer of %d samples.\n", ao->buffer); - if (ao->api->init(ao) < 0) + if (!init_buffer_post(ao)) goto fail; return ao; fail: - talloc_free(ao); + ao_uninit(ao); return NULL; } @@ -319,7 +316,7 @@ struct ao *ao_init_best(struct mpv_global *global, } if (autoprobe) { - for (int n = 0; audio_out_drivers[n]; n++) { + for (int n = 0; n < MP_ARRAY_SIZE(audio_out_drivers); n++) { const struct ao_driver *driver = audio_out_drivers[n]; if (driver == &audio_out_null) break; @@ -358,86 +355,6 @@ struct ao *ao_init_best(struct mpv_global *global, return ao; } -// Uninitialize and destroy the AO. Remaining audio must be dropped. -void ao_uninit(struct ao *ao) -{ - if (ao) - ao->api->uninit(ao); - talloc_free(ao); -} - -// Queue the given audio data. Start playback if it hasn't started yet. Return -// the number of samples that was accepted (the core will try to queue the rest -// again later). Should never block. -// data: start pointer for each plane. If the audio data is packed, only -// data[0] is valid, otherwise there is a plane for each channel. -// samples: size of the audio data (see ao->sstride) -// flags: currently AOPLAY_FINAL_CHUNK can be set -int ao_play(struct ao *ao, void **data, int samples, int flags) -{ - return ao->api->play(ao, data, samples, flags); -} - -int ao_control(struct ao *ao, enum aocontrol cmd, void *arg) -{ - return ao->api->control ? ao->api->control(ao, cmd, arg) : CONTROL_UNKNOWN; -} - -// Return size of the buffered data in seconds. Can include the device latency. -// Basically, this returns how much data there is still to play, and how long -// it takes until the last sample in the buffer reaches the speakers. This is -// used for audio/video synchronization, so it's very important to implement -// this correctly. -double ao_get_delay(struct ao *ao) -{ - return ao->api->get_delay(ao); -} - -// Return free size of the internal audio buffer. This controls how much audio -// the core should decode and try to queue with ao_play(). -int ao_get_space(struct ao *ao) -{ - return ao->api->get_space(ao); -} - -// Stop playback and empty buffers. Essentially go back to the state after -// ao->init(). -void ao_reset(struct ao *ao) -{ - if (ao->api->reset) - ao->api->reset(ao); - atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN); -} - -// Pause playback. Keep the current buffer. ao_get_delay() must return the -// same value as before pausing. -void ao_pause(struct ao *ao) -{ - if (ao->api->pause) - ao->api->pause(ao); -} - -// Resume playback. Play the remaining buffer. If the driver doesn't support -// pausing, it has to work around this and e.g. use ao_play_silence() to fill -// the lost audio. -void ao_resume(struct ao *ao) -{ - if (ao->api->resume) - ao->api->resume(ao); -} - -// Block until the current audio buffer has played completely. -void ao_drain(struct ao *ao) -{ - if (ao->api->drain) - ao->api->drain(ao); -} - -bool ao_eof_reached(struct ao *ao) -{ - return ao->api->get_eof ? ao->api->get_eof(ao) : true; -} - // Query the AO_EVENT_*s as requested by the events parameter, and return them. int ao_query_and_reset_events(struct ao *ao, int events) { @@ -466,12 +383,6 @@ void ao_hotplug_event(struct ao *ao) ao_add_events(ao, AO_EVENT_HOTPLUG); } -// Returns whether this call actually set a new underrun flag. -bool ao_underrun_event(struct ao *ao) -{ - return ao_add_events(ao, AO_EVENT_UNDERRUN); -} - bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, struct mp_chmap *map) { @@ -545,8 +456,9 @@ struct ao_hotplug { void *wakeup_ctx; // A single AO instance is used to listen to hotplug events. It wouldn't // make much sense to allow multiple AO drivers; all sane platforms have - // a single such audio API. - // This is _not_ the same AO instance as used for playing audio. + // a single audio API providing all events. + // This is _not_ necessarily the same AO instance as used for playing + // audio. struct ao *ao; // cached struct ao_device_list *list; @@ -586,7 +498,8 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp) } // The return value is valid until the next call to this API. -struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp) +struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp, + struct ao *playback_ao) { if (hp->list && !hp->needs_update) return hp->list; @@ -598,7 +511,20 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp) MP_TARRAY_APPEND(list, list->devices, list->num_devices, (struct ao_device_desc){"auto", "Autoselect device"}); - for (int n = 0; audio_out_drivers[n]; n++) { + // Try to use the same AO for hotplug handling as for playback. + // Different AOs may not agree and the playback one is the only one the + // user knows about and may even have configured explicitly. + if (!hp->ao && playback_ao && playback_ao->driver->hotplug_init) { + struct ao *ao = ao_alloc(true, hp->global, hp->wakeup_cb, hp->wakeup_ctx, + (char *)playback_ao->driver->name); + if (playback_ao->driver->hotplug_init(ao) >= 0) { + hp->ao = ao; + } else { + talloc_free(ao); + } + } + + for (int n = 0; n < MP_ARRAY_SIZE(audio_out_drivers); n++) { const struct ao_driver *d = audio_out_drivers[n]; if (d == &audio_out_null) break; // don't add unsafe/special entries @@ -609,10 +535,13 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp) continue; if (ao->driver->hotplug_init) { - if (!hp->ao && ao->driver->hotplug_init(ao) >= 0) - hp->ao = ao; // keep this one - if (hp->ao && hp->ao->driver == d) - get_devices(hp->ao, list); + if (ao->driver->hotplug_init(ao) >= 0) { + get_devices(ao, list); + if (hp->ao) + ao->driver->hotplug_uninit(ao); + else + hp->ao = ao; // keep this one + } } else { get_devices(ao, list); } @@ -661,10 +590,11 @@ static void dummy_wakeup(void *ctx) { } -void ao_print_devices(struct mpv_global *global, struct mp_log *log) +void ao_print_devices(struct mpv_global *global, struct mp_log *log, + struct ao *playback_ao) { struct ao_hotplug *hp = ao_hotplug_create(global, dummy_wakeup, NULL); - struct ao_device_list *list = ao_hotplug_get_device_list(hp); + struct ao_device_list *list = ao_hotplug_get_device_list(hp, playback_ao); mp_info(log, "List of detected audio devices:\n"); for (int n = 0; n < list->num_devices; n++) { struct ao_device_desc *desc = &list->devices[n]; @@ -686,7 +616,7 @@ void ao_set_gain(struct ao *ao, float gain) #define MUL_GAIN_f(d, num_samples, gain) \ for (int n = 0; n < (num_samples); n++) \ - (d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0) + (d)[n] = (d)[n] * (gain) static void process_plane(struct ao *ao, void *data, int num_samples) { @@ -775,7 +705,7 @@ static void convert_plane(int type, void *data, int num_samples) break; } default: - abort(); + MP_ASSERT_UNREACHABLE(); } } diff --git a/audio/out/ao.h b/audio/out/ao.h index da81be103c..18c7cdc02f 100644 --- a/audio/out/ao.h +++ b/audio/out/ao.h @@ -26,8 +26,7 @@ #include "audio/chmap_sel.h" enum aocontrol { - // _VOLUME commands take struct ao_control_vol pointer for input/output. - // If there's only one volume, SET should use average of left/right. + // _VOLUME commands take a pointer to float for input/output. AOCONTROL_GET_VOLUME, AOCONTROL_SET_VOLUME, // _MUTE commands take a pointer to bool @@ -39,13 +38,12 @@ enum aocontrol { // If set, then the queued audio data is the last. Note that after a while, new // data might be written again, instead of closing the AO. -#define AOPLAY_FINAL_CHUNK 1 +#define PLAYER_FINAL_CHUNK 1 enum { AO_EVENT_RELOAD = 1, AO_EVENT_HOTPLUG = 2, AO_EVENT_INITIAL_UNBLOCK = 4, - AO_EVENT_UNDERRUN = 8, }; enum { @@ -58,13 +56,10 @@ enum { AO_INIT_STREAM_SILENCE = 1 << 2, // Force exclusive mode, i.e. lock out the system mixer. AO_INIT_EXCLUSIVE = 1 << 3, + // Initialize with music role. + AO_INIT_MEDIA_ROLE_MUSIC = 1 << 4, }; -typedef struct ao_control_vol { - float left; - float right; -} ao_control_vol_t; - struct ao_device_desc { const char *name; // symbolic name; will be set on ao->device const char *desc; // verbose human readable name @@ -98,16 +93,16 @@ void ao_get_format(struct ao *ao, const char *ao_get_name(struct ao *ao); const char *ao_get_description(struct ao *ao); bool ao_untimed(struct ao *ao); -int ao_play(struct ao *ao, void **data, int samples, int flags); int ao_control(struct ao *ao, enum aocontrol cmd, void *arg); void ao_set_gain(struct ao *ao, float gain); double ao_get_delay(struct ao *ao); -int ao_get_space(struct ao *ao); void ao_reset(struct ao *ao); -void ao_pause(struct ao *ao); -void ao_resume(struct ao *ao); +void ao_start(struct ao *ao); +void ao_set_paused(struct ao *ao, bool paused, bool eof); void ao_drain(struct ao *ao); -bool ao_eof_reached(struct ao *ao); +bool ao_is_playing(struct ao *ao); +struct mp_async_queue; +struct mp_async_queue *ao_get_queue(struct ao *ao); int ao_query_and_reset_events(struct ao *ao, int events); int ao_add_events(struct ao *ao, int events); void ao_unblock(struct ao *ao); @@ -120,8 +115,8 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global, void *wakeup_ctx); void ao_hotplug_destroy(struct ao_hotplug *hp); bool ao_hotplug_check_update(struct ao_hotplug *hp); -struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp); +struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp, struct ao *playback_ao); -void ao_print_devices(struct mpv_global *global, struct mp_log *log); +void ao_print_devices(struct mpv_global *global, struct mp_log *log, struct ao *playback_ao); #endif /* MPLAYER_AUDIO_OUT_H */ diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c index bb67fd84a8..92ea0db237 100644 --- a/audio/out/ao_alsa.c +++ b/audio/out/ao_alsa.c @@ -34,7 +34,6 @@ #include <math.h> #include <string.h> -#include "config.h" #include "options/options.h" #include "options/m_config.h" #include "options/m_option.h" @@ -57,9 +56,9 @@ struct ao_alsa_opts { char *mixer_device; char *mixer_name; int mixer_index; - int resample; - int ni; - int ignore_chmap; + bool resample; + bool ni; + bool ignore_chmap; int buffer_time; int frags; }; @@ -67,21 +66,19 @@ struct ao_alsa_opts { #define OPT_BASE_STRUCT struct ao_alsa_opts static const struct m_sub_options ao_alsa_conf = { .opts = (const struct m_option[]) { - OPT_FLAG("alsa-resample", resample, 0), - OPT_STRING("alsa-mixer-device", mixer_device, 0), - OPT_STRING("alsa-mixer-name", mixer_name, 0), - OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99), - OPT_FLAG("alsa-non-interleaved", ni, 0), - OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0), - OPT_INTRANGE("alsa-buffer-time", buffer_time, 0, 0, INT_MAX), - OPT_INTRANGE("alsa-periods", frags, 0, 0, INT_MAX), + {"alsa-resample", OPT_BOOL(resample)}, + {"alsa-mixer-device", OPT_STRING(mixer_device)}, + {"alsa-mixer-name", OPT_STRING(mixer_name)}, + {"alsa-mixer-index", OPT_INT(mixer_index), M_RANGE(0, 99)}, + {"alsa-non-interleaved", OPT_BOOL(ni)}, + {"alsa-ignore-chmap", OPT_BOOL(ignore_chmap)}, + {"alsa-buffer-time", OPT_INT(buffer_time), M_RANGE(0, INT_MAX)}, + {"alsa-periods", OPT_INT(frags), M_RANGE(0, INT_MAX)}, {0} }, .defaults = &(const struct ao_alsa_opts) { .mixer_device = "default", .mixer_name = "Master", - .mixer_index = 0, - .ni = 0, .buffer_time = 100000, .frags = 4, }, @@ -93,10 +90,6 @@ struct priv { bool device_lost; snd_pcm_format_t alsa_fmt; bool can_pause; - bool paused; - bool final_chunk_written; - snd_pcm_sframes_t prepause_frames; - double delay_before_pause; snd_pcm_uframes_t buffersize; snd_pcm_uframes_t outburst; @@ -121,34 +114,6 @@ struct priv { MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \ } while (0) -// Common code for handling ENODEV, which happens if a device gets "lost", and -// can't be used anymore. Returns true if alsa_err is not ENODEV. -static bool check_device_present(struct ao *ao, int alsa_err) -{ - struct priv *p = ao->priv; - if (alsa_err != -ENODEV) - return true; - if (!p->device_lost) { - MP_WARN(ao, "Device lost, trying to recover...\n"); - ao_request_reload(ao); - p->device_lost = true; - } - return false; -} - -static void handle_underrun(struct ao *ao) -{ - struct priv *p = ao->priv; - - if (!p->final_chunk_written) { - ao_underrun_event(ao); - - int err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - alsa_error: ; - } -} - static int control(struct ao *ao, enum aocontrol cmd, void *arg) { struct priv *p = ao->priv; @@ -200,15 +165,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) switch (cmd) { case AOCONTROL_SET_VOLUME: { - ao_control_vol_t *vol = arg; - set_vol = vol->left / f_multi + pmin + 0.5; + float *vol = arg; + set_vol = *vol / f_multi + pmin + 0.5; err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol); CHECK_ALSA_ERROR("Error setting left channel"); MP_DBG(ao, "left=%li, ", set_vol); - set_vol = vol->right / f_multi + pmin + 0.5; - err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol); CHECK_ALSA_ERROR("Error setting right channel"); MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n", @@ -216,12 +179,14 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) break; } case AOCONTROL_GET_VOLUME: { - ao_control_vol_t *vol = arg; + float *vol = arg; + float left, right; snd_mixer_selem_get_playback_volume(elem, 0, &get_vol); - vol->left = (get_vol - pmin) * f_multi; + left = (get_vol - pmin) * f_multi; snd_mixer_selem_get_playback_volume(elem, 1, &get_vol); - vol->right = (get_vol - pmin) * f_multi; - MP_DBG(ao, "left=%f, right=%f\n", vol->left, vol->right); + right = (get_vol - pmin) * f_multi; + *vol = (left + right) / 2.0; + MP_DBG(ao, "vol=%f\n", *vol); break; } case AOCONTROL_SET_MUTE: { @@ -595,7 +560,7 @@ static char *append_params(void *ta_parent, const char *device, const char *p) /* a simple list of parameters: add it at the end of the list */ return talloc_asprintf(ta_parent, "%s,%s", device, p); } - abort(); + MP_ASSERT_UNREACHABLE(); } static int try_open_device(struct ao *ao, const char *device, int mode) @@ -658,7 +623,8 @@ static void uninit(struct ao *ao) CHECK_ALSA_ERROR("pcm close error"); } -alsa_error: ; +alsa_error: + snd_config_update_free_global(); } #define INIT_DEVICE_ERR_GENERIC -1 @@ -865,9 +831,8 @@ static int init_device(struct ao *ao, int mode) err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary); CHECK_ALSA_ERROR("Unable to get boundary"); - /* start playing when one period has been written */ - err = snd_pcm_sw_params_set_start_threshold - (p->alsa, alsa_swparams, p->outburst); + // Manual trigger; INT_MAX as suggested by ALSA doxygen (they call it MAXINT). + err = snd_pcm_sw_params_set_start_threshold(p->alsa, alsa_swparams, INT_MAX); CHECK_ALSA_ERROR("Unable to set start threshold"); /* play silence when there is an underrun */ @@ -883,10 +848,12 @@ static int init_device(struct ao *ao, int mode) MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst); ao->device_buffer = p->buffersize; - ao->period_size = p->outburst; p->convert.channels = ao->channels.num; + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + return 0; alsa_error: @@ -944,264 +911,194 @@ static int init(struct ao *ao) return r; } -static void drain(struct ao *ao) +// Function for dealing with playback state. This attempts to recover the ALSA +// state (bring it into SND_PCM_STATE_{PREPARED,RUNNING,PAUSED,UNDERRUN}). If +// state!=NULL, fill it after recovery is attempted. +// Returns true if PCM is in one the expected states. +static bool recover_and_get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; - snd_pcm_drain(p->alsa); -} - -static int get_space(struct ao *ao) -{ - struct priv *p = ao->priv; - - // in case of pausing or the device still being configured, - // just return our buffer size. - if (p->paused || snd_pcm_state(p->alsa) == SND_PCM_STATE_SETUP) - return p->buffersize; - - snd_pcm_sframes_t space = snd_pcm_avail(p->alsa); - if (space < 0 && space != -EPIPE) { - MP_ERR(ao, "Error received from snd_pcm_avail " - "(%ld, %s with ALSA state %s)!\n", - space, snd_strerror(space), - snd_pcm_state_name(snd_pcm_state(p->alsa))); - - // request a reload of the AO if device is not present, - // then error out. - check_device_present(ao, space); - goto alsa_error; - } - if (space == -EPIPE) - handle_underrun(ao); - - if (space > p->buffersize || space < 0) // Buffer underrun? - space = p->buffersize; - return space / p->outburst * p->outburst; + int err; -alsa_error: - return 0; -} + snd_pcm_status_t *st; + snd_pcm_status_alloca(&st); + + bool state_ok = false; + snd_pcm_state_t pcmst = SND_PCM_STATE_DISCONNECTED; + + // Give it a number of chances to recover. This tries to deal with the fact + // that the API is asynchronous, and to account for some past cargo-cult + // (where things were retried in a loop). + for (int n = 0; n < 10; n++) { + err = snd_pcm_status(p->alsa, st); + if (err == -EPIPE) { + // ALSA APIs can return -EPIPE when an XRUN happens, + // we skip right to handling it by setting pcmst + // manually. + pcmst = SND_PCM_STATE_XRUN; + } else { + // Otherwise do error checking and query the PCM state properly. + CHECK_ALSA_ERROR("snd_pcm_status"); -/* delay in seconds between first and last sample in buffer */ -static double get_delay(struct ao *ao) -{ - struct priv *p = ao->priv; - snd_pcm_sframes_t delay; + pcmst = snd_pcm_status_get_state(st); + } - if (p->paused) - return p->delay_before_pause; + if (pcmst == SND_PCM_STATE_PREPARED || + pcmst == SND_PCM_STATE_RUNNING || + pcmst == SND_PCM_STATE_PAUSED) + { + state_ok = true; + break; + } - int err = snd_pcm_delay(p->alsa, &delay); - if (err < 0) { - if (err == -EPIPE) - handle_underrun(ao); - return 0; - } + MP_VERBOSE(ao, "attempt %d to recover from state '%s'...\n", + n + 1, snd_pcm_state_name(pcmst)); - if (delay < 0) { - /* underrun - move the application pointer forward to catch up */ - snd_pcm_forward(p->alsa, -delay); - delay = 0; - } - return delay / (double)ao->samplerate; -} - -// For stream-silence mode: replace remaining buffer with silence. -// Tries to cause an instant buffer underrun. -static void soft_reset(struct ao *ao) -{ - struct priv *p = ao->priv; - snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa); - if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) { - frames = snd_pcm_rewind(p->alsa, frames); - if (frames < 0) { - int err = frames; - CHECK_ALSA_WARN("pcm rewind error"); + switch (pcmst) { + // Underrun; recover. (We never use draining.) + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_DRAINING: + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + continue; + // Hardware suspend. + case SND_PCM_STATE_SUSPENDED: + MP_INFO(ao, "PCM in suspend mode, trying to resume.\n"); + err = snd_pcm_resume(p->alsa); + if (err == -EAGAIN) { + // Cargo-cult from decades ago, with a cargo cult timeout. + MP_INFO(ao, "PCM resume EAGAIN - retrying.\n"); + sleep(1); + continue; + } + if (err == -ENOSYS) { + // As suggested by ALSA doxygen. + MP_VERBOSE(ao, "ENOSYS, retrying with snd_pcm_prepare().\n"); + err = snd_pcm_prepare(p->alsa); + } + if (err < 0) + MP_ERR(ao, "resuming from SUSPENDED: %s\n", snd_strerror(err)); + continue; + // Device lost. OPEN/SETUP are states we never enter after init, so + // treat them like DISCONNECTED. + case SND_PCM_STATE_DISCONNECTED: + case SND_PCM_STATE_OPEN: + case SND_PCM_STATE_SETUP: + default: + if (!p->device_lost) { + MP_WARN(ao, "Device lost, trying to recover...\n"); + ao_request_reload(ao); + p->device_lost = true; + } + goto alsa_error; } } -} -static void audio_pause(struct ao *ao) -{ - struct priv *p = ao->priv; - int err; - - if (p->paused) - return; + if (!state_ok) { + MP_ERR(ao, "could not recover\n"); + } - p->delay_before_pause = get_delay(ao); - p->prepause_frames = p->delay_before_pause * ao->samplerate; +alsa_error: - if (ao->stream_silence) { - soft_reset(ao); - } else if (p->can_pause) { - if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) { - err = snd_pcm_pause(p->alsa, 1); - CHECK_ |