summaryrefslogtreecommitdiffstats
path: root/audio/out
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out')
-rw-r--r--audio/out/ao.c176
-rw-r--r--audio/out/ao.h27
-rw-r--r--audio/out/ao_alsa.c426
-rw-r--r--audio/out/ao_audiotrack.c588
-rw-r--r--audio/out/ao_audiounit.m102
-rw-r--r--audio/out/ao_avfoundation.m372
-rw-r--r--audio/out/ao_coreaudio.c169
-rw-r--r--audio/out/ao_coreaudio_chmap.c232
-rw-r--r--audio/out/ao_coreaudio_chmap.h6
-rw-r--r--audio/out/ao_coreaudio_exclusive.c28
-rw-r--r--audio/out/ao_coreaudio_properties.c2
-rw-r--r--audio/out/ao_coreaudio_utils.c64
-rw-r--r--audio/out/ao_coreaudio_utils.h16
-rw-r--r--audio/out/ao_jack.c22
-rw-r--r--audio/out/ao_lavc.c239
-rw-r--r--audio/out/ao_null.c117
-rw-r--r--audio/out/ao_openal.c134
-rw-r--r--audio/out/ao_opensles.c19
-rw-r--r--audio/out/ao_oss.c401
-rw-r--r--audio/out/ao_pcm.c52
-rw-r--r--audio/out/ao_pipewire.c934
-rw-r--r--audio/out/ao_pulse.c254
-rw-r--r--audio/out/ao_sdl.c7
-rw-r--r--audio/out/ao_sndio.c323
-rw-r--r--audio/out/ao_wasapi.c149
-rw-r--r--audio/out/ao_wasapi.h9
-rw-r--r--audio/out/ao_wasapi_utils.c162
-rw-r--r--audio/out/buffer.c753
-rw-r--r--audio/out/internal.h162
-rw-r--r--audio/out/pull.c340
-rw-r--r--audio/out/push.c572
31 files changed, 4510 insertions, 2347 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 480dad69e0..75fcbac6fa 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -35,11 +35,15 @@
#include "common/common.h"
#include "common/global.h"
+extern const struct ao_driver audio_out_oss;
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;
extern const struct ao_driver audio_out_openal;
@@ -62,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
@@ -71,6 +81,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_WASAPI
&audio_out_wasapi,
#endif
+#if HAVE_OSS_AUDIO
+ &audio_out_oss,
+#endif
// wrappers:
#if HAVE_JACK
&audio_out_jack,
@@ -84,18 +97,20 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_SDL2_AUDIO
&audio_out_sdl,
#endif
+#if HAVE_SNDIO
+ &audio_out_sndio,
+#endif
&audio_out_null,
#if HAVE_COREAUDIO
&audio_out_coreaudio_exclusive,
#endif
&audio_out_pcm,
&audio_out_lavc,
- 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) {
@@ -116,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,
@@ -200,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) {
@@ -216,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;
@@ -238,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);
@@ -249,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;
}
@@ -309,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;
@@ -348,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)
{
@@ -456,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)
{
@@ -535,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;
@@ -576,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;
@@ -588,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
@@ -599,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);
}
@@ -651,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];
@@ -676,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)
{
@@ -765,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 b4fa18891b..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,12 +66,12 @@ 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[]) {
- {"alsa-resample", OPT_FLAG(resample)},
+ {"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_FLAG(ni)},
- {"alsa-ignore-chmap", OPT_FLAG(ignore_chmap)},
+ {"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}
@@ -80,8 +79,6 @@ static const struct m_sub_options ao_alsa_conf = {
.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_ALSA_ERROR("pcm pause error");
- p->prepause_frames = 0;
- }
- } else {
- err = snd_pcm_drop(p->alsa);
- CHECK_ALSA_ERROR("pcm drop error");
+ if (state) {
+ snd_pcm_sframes_t del = state_ok ? snd_pcm_status_get_delay(st) : 0;
+ state->delay = MPMAX(del, 0) / (double)ao->samplerate;
+ state->free_samples = state_ok ? snd_pcm_status_get_avail(st) : 0;
+ state->free_samples = MPCLAMP(state->free_samples, 0, ao->device_buffer);
+ // Align to period size.
+ state->free_samples = state->free_samples / p->outburst * p->outburst;
+ state->queued_samples = ao->device_buffer - state->free_samples;
+ state->playing = pcmst == SND_PCM_STATE_RUNNING ||
+ pcmst == SND_PCM_STATE_PAUSED;
}
- p->paused = true;
-
-alsa_error: ;
+ return state_ok;
}
-static void resume_device(struct ao *ao)
+static void audio_get_state