diff options
Diffstat (limited to 'audio/out')
-rw-r--r-- | audio/out/ao.c | 36 | ||||
-rw-r--r-- | audio/out/ao.h | 12 | ||||
-rw-r--r-- | audio/out/ao_alsa.c | 77 | ||||
-rw-r--r-- | audio/out/ao_coreaudio.c | 2 | ||||
-rw-r--r-- | audio/out/ao_coreaudio_exclusive.c | 2 | ||||
-rw-r--r-- | audio/out/ao_coreaudio_properties.c | 2 | ||||
-rw-r--r-- | audio/out/ao_coreaudio_utils.c | 2 | ||||
-rw-r--r-- | audio/out/ao_lavc.c | 2 | ||||
-rw-r--r-- | audio/out/ao_null.c | 16 | ||||
-rw-r--r-- | audio/out/ao_pulse.c | 5 | ||||
-rw-r--r-- | audio/out/ao_wasapi_utils.c | 2 | ||||
-rw-r--r-- | audio/out/internal.h | 4 | ||||
-rw-r--r-- | audio/out/pull.c | 8 | ||||
-rw-r--r-- | audio/out/push.c | 6 |
14 files changed, 129 insertions, 47 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c index c9d8f42b4a..cf66e0c64b 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -158,7 +158,7 @@ error: static struct ao *ao_init(bool probing, struct mpv_global *global, struct input_ctx *input_ctx, - struct encode_lavc_context *encode_lavc_ctx, + struct encode_lavc_context *encode_lavc_ctx, int flags, int samplerate, int format, struct mp_chmap channels, char *dev, char *name, char **args) { @@ -169,6 +169,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, ao->channels = channels; ao->format = format; ao->encode_lavc_ctx = encode_lavc_ctx; + ao->init_flags = flags; if (ao->driver->encode != !!ao->encode_lavc_ctx) goto fail; @@ -182,6 +183,8 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, 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; + int r = ao->driver->init(ao); if (r < 0) { // Silly exception for coreaudio spdif redirection @@ -190,7 +193,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, snprintf(redirect, sizeof(redirect), "%s", ao->redirect); snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : ""); talloc_free(ao); - return ao_init(probing, global, input_ctx, encode_lavc_ctx, + return ao_init(probing, global, input_ctx, encode_lavc_ctx, flags, samplerate, format, channels, rdevice, redirect, NULL); } goto fail; @@ -240,7 +243,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev) } struct ao *ao_init_best(struct mpv_global *global, - bool ao_null_fallback, + int init_flags, struct input_ctx *input_ctx, struct encode_lavc_context *encode_lavc_ctx, int samplerate, int format, struct mp_chmap channels) @@ -283,7 +286,7 @@ struct ao *ao_init_best(struct mpv_global *global, } } - if (ao_null_fallback) { + if (init_flags & AO_INIT_NULL_FALLBACK) { MP_TARRAY_APPEND(tmp, ao_list, ao_num, (struct m_obj_settings){.name = "null"}); } @@ -297,7 +300,7 @@ struct ao *ao_init_best(struct mpv_global *global, dev = pref_dev; mp_verbose(log, "Using preferred device '%s'\n", dev); } - ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, + ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, init_flags, samplerate, format, channels, dev, entry->name, entry->attribs); if (ao) @@ -429,6 +432,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, return r; } +// safe_multichannel=true behaves like ao_chmap_sel_adjust. +// safe_multichannel=false is a helper for callers which do not support safe +// handling of arbitrary channel layouts. If the multichannel layouts are not +// considered "always safe" (e.g. HDMI), then allow only stereo or mono, if +// they are part of the list in *s. +bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map, bool safe_multichannel) +{ + if (!safe_multichannel && (ao->init_flags & AO_INIT_SAFE_MULTICHANNEL_ONLY)) { + struct mp_chmap res = *map; + if (mp_chmap_sel_adjust(s, &res)) { + if (!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_MONO) && + !mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_STEREO)) + { + MP_WARN(ao, "Disabling multichannel output.\n"); + *map = (struct mp_chmap)MP_CHMAP_INIT_STEREO; + } + } + } + + return ao_chmap_sel_adjust(ao, s, map); +} + bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s, struct mp_chmap *map, int num) { diff --git a/audio/out/ao.h b/audio/out/ao.h index e8e64e33eb..3b187e7355 100644 --- a/audio/out/ao.h +++ b/audio/out/ao.h @@ -50,6 +50,16 @@ enum { AO_EVENT_HOTPLUG = 2, }; +enum { + // Allow falling back to ao_null if nothing else works. + AO_INIT_NULL_FALLBACK = 1 << 0, + // Only accept multichannel configurations that are guaranteed to work + // (i.e. not sending arbitrary layouts over HDMI). + AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1, + // Stream silence as long as no audio is playing. + AO_INIT_STREAM_SILENCE = 1 << 2, +}; + typedef struct ao_control_vol { float left; float right; @@ -72,7 +82,7 @@ struct encode_lavc_context; struct mp_audio; struct ao *ao_init_best(struct mpv_global *global, - bool ao_null_fallback, + int init_flags, struct input_ctx *input_ctx, struct encode_lavc_context *encode_lavc_ctx, int samplerate, int format, struct mp_chmap channels); diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c index d09f5fc499..bbe15b4b61 100644 --- a/audio/out/ao_alsa.c +++ b/audio/out/ao_alsa.c @@ -53,6 +53,7 @@ struct priv { bool device_lost; snd_pcm_format_t alsa_fmt; bool can_pause; + bool paused; snd_pcm_sframes_t prepause_frames; double delay_before_pause; snd_pcm_uframes_t buffersize; @@ -360,7 +361,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) snd_pcm_free_chmaps(maps); - return ao_chmap_sel_adjust(ao, &chmap_sel, chmap); + return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false); } // Map back our selected channel layout to an ALSA one. This is done this way so @@ -785,6 +786,14 @@ static int init_device(struct ao *ao, int mode) MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize); MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst); + ao->device_buffer = p->buffersize; + + // ao_alsa implements this by relying on underrun behavior (no data means + // underrun, during which silence is played). Trigger by playing some + // initial silence. + if (ao->stream_silence) + ao_play_silence(ao, p->outburst); + return 0; alsa_error: @@ -874,7 +883,7 @@ static double get_delay(struct ao *ao) struct priv *p = ao->priv; snd_pcm_sframes_t delay; - if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) + if (p->paused) return p->delay_before_pause; if (snd_pcm_delay(p->alsa, &delay) < 0) @@ -888,27 +897,47 @@ static double get_delay(struct ao *ao) 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"); + } + } +} + static void audio_pause(struct ao *ao) { struct priv *p = ao->priv; int err; - if (p->can_pause) { + if (p->paused) + return; + + p->delay_before_pause = get_delay(ao); + p->prepause_frames = p->delay_before_pause * ao->samplerate; + + if (ao->stream_silence) { + soft_reset(ao); + } else if (p->can_pause) { if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) { - p->delay_before_pause = get_delay(ao); err = snd_pcm_pause(p->alsa, 1); CHECK_ALSA_ERROR("pcm pause error"); + p->prepause_frames = 0; } } else { - if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0 - || p->prepause_frames < 0) - p->prepause_frames = 0; - p->delay_before_pause = p->prepause_frames / (double)ao->samplerate; - err = snd_pcm_drop(p->alsa); CHECK_ALSA_ERROR("pcm drop error"); } + p->paused = true; + alsa_error: ; } @@ -930,9 +959,15 @@ static void audio_resume(struct ao *ao) struct priv *p = ao->priv; int err; + if (!p->paused) + return; + resume_device(ao); - if (p->can_pause) { + if (ao->stream_silence) { + p->paused = false; + get_delay(ao); // recovers from underrun (as a side-effect) + } else if (p->can_pause) { if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) { err = snd_pcm_pause(p->alsa, 0); CHECK_ALSA_ERROR("pcm resume error"); @@ -941,11 +976,13 @@ static void audio_resume(struct ao *ao) MP_VERBOSE(ao, "resume not supported by hardware\n"); err = snd_pcm_prepare(p->alsa); CHECK_ALSA_ERROR("pcm prepare error"); - if (p->prepause_frames) - ao_play_silence(ao, p->prepause_frames); } + if (p->prepause_frames) + ao_play_silence(ao, p->prepause_frames); + alsa_error: ; + p->paused = false; } static void reset(struct ao *ao) @@ -953,12 +990,18 @@ static void reset(struct ao *ao) struct priv *p = ao->priv; int err; + p->paused = false; p->prepause_frames = 0; p->delay_before_pause = 0; - err = snd_pcm_drop(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); + + if (ao->stream_silence) { + soft_reset(ao); + } else { + err = snd_pcm_drop(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + } alsa_error: ; } @@ -997,6 +1040,8 @@ static int play(struct ao *ao, void **data, int samples, int flags) } } while (res == 0); + p->paused = false; + return res < 0 ? -1 : res; alsa_error: diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index b0a5dc0da1..fbbacde714 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -419,7 +419,7 @@ const struct ao_driver audio_out_coreaudio = { .uninit = uninit, .init = init, .control = control, - .pause = stop, + .reset = stop, .resume = start, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c index 510fa3a529..49f921cd4c 100644 --- a/audio/out/ao_coreaudio_exclusive.c +++ b/audio/out/ao_coreaudio_exclusive.c @@ -399,7 +399,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = { .name = "coreaudio_exclusive", .uninit = uninit, .init = init, - .pause = audio_pause, + .reset = audio_pause, .resume = audio_resume, .list_devs = ca_get_device_list, .priv_size = sizeof(struct priv), diff --git a/audio/out/ao_coreaudio_properties.c b/audio/out/ao_coreaudio_properties.c index b74cf07289..a66c9dcf1f 100644 --- a/audio/out/ao_coreaudio_properties.c +++ b/audio/out/ao_coreaudio_properties.c @@ -70,7 +70,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector, return err; coreaudio_error_free: - free(*data); + talloc_free(*data); coreaudio_error: return err; } diff --git a/audio/out/ao_coreaudio_utils.c b/audio/out/ao_coreaudio_utils.c index 0bcc0d62a1..76f5402fd1 100644 --- a/audio/out/ao_coreaudio_utils.c +++ b/audio/out/ao_coreaudio_utils.c @@ -147,7 +147,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message) { if (code == noErr) return true; - mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code)); + mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code); return false; } diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c index 6b4279ca87..8ae1317407 100644 --- a/audio/out/ao_lavc.c +++ b/audio/out/ao_lavc.c @@ -123,7 +123,7 @@ static int init(struct ao *ao) struct mp_chmap_sel sel = {0}; mp_chmap_sel_add_any(&sel); - if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) + if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false)) goto fail; mp_chmap_reorder_to_lavc(&ao->channels); ac->codec->channels = ao->channels.num; diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c index 7d45795d9e..ac3f8348b5 100644 --- a/audio/out/ao_null.c +++ b/audio/out/ao_null.c @@ -57,7 +57,7 @@ struct priv { // (This is not needed by the AO API, but many AOs behave this way.) int outburst; // samples - char **channel_layouts; + struct m_channels channel_layouts; }; static void drain(struct ao *ao) @@ -91,15 +91,9 @@ static int init(struct ao *ao) ao->untimed = priv->untimed; struct mp_chmap_sel sel = {.tmp = ao}; - if (priv->channel_layouts) { - for (int n = 0; priv->channel_layouts[n]; n++) { - struct mp_chmap map = {0}; - if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) { - MP_FATAL(ao, "Invalid channel map in option.\n"); - return -1; - } - mp_chmap_sel_add_map(&sel, &map); - } + if (priv->channel_layouts.num_chmaps) { + for (int n = 0; n < priv->channel_layouts.num_chmaps; n++) + mp_chmap_sel_add_map(&sel, &priv->channel_layouts.chmaps[n]); } else { mp_chmap_sel_add_any(&sel); } @@ -244,7 +238,7 @@ const struct ao_driver audio_out_null = { OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100), OPT_FLAG("broken-eof", broken_eof, 0), OPT_FLAG("broken-delay", broken_delay, 0), - OPT_STRINGLIST("channel-layouts", channel_layouts, 0), + OPT_CHANNELS("channel-layouts", channel_layouts, 0), {0} }, }; diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c index d553b6700c..fc0dd0d008 100644 --- a/audio/out/ao_pulse.c +++ b/audio/out/ao_pulse.c @@ -23,6 +23,7 @@ #include <stdbool.h> #include <string.h> #include <stdint.h> +#include <math.h> #include <pthread.h> #include <pulse/pulseaudio.h> @@ -34,8 +35,8 @@ #include "ao.h" #include "internal.h" -#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM) -#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100) +#define VOL_PA2MP(v) ((v) * 100.0 / PA_VOLUME_NORM) +#define VOL_MP2PA(v) lrint((v) * PA_VOLUME_NORM / 100) struct priv { // PulseAudio playback stream object diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 0e7dec885a..6218153bea 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -407,7 +407,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat) } entry = ao->channels; - if (ao_chmap_sel_adjust(ao, &chmap_sel, &entry)){ + if (ao_chmap_sel_adjust2(ao, &chmap_sel, &entry, !state->opt_exclusive)){ change_waveformat_channels(wformat, &entry); return true; } diff --git a/audio/out/internal.h b/audio/out/internal.h index 49131ba293..518661c2bd 100644 --- a/audio/out/internal.h +++ b/audio/out/internal.h @@ -43,6 +43,8 @@ struct ao { struct encode_lavc_context *encode_lavc_ctx; struct input_ctx *input_ctx; struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix + int init_flags; // AO_INIT_* flags + bool stream_silence; // if audio inactive, just play silence // The device as selected by the user, usually using ao_device_desc.name // from an entry from the list returned by driver->list_devices. If the @@ -191,6 +193,8 @@ void ao_wakeup_poll(struct ao *ao); bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, struct mp_chmap *map); +bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map, bool safe_multichannel); bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s, struct mp_chmap *map, int num); diff --git a/audio/out/pull.c b/audio/out/pull.c index 89805809b7..2175a58db0 100644 --- a/audio/out/pull.c +++ b/audio/out/pull.c @@ -185,7 +185,7 @@ static double get_delay(struct ao *ao) static void reset(struct ao *ao) { struct ao_pull_state *p = ao->api_priv; - if (ao->driver->reset) + if (!ao->stream_silence && ao->driver->reset) ao->driver->reset(ao); // assumes the audio callback thread is stopped set_state(ao, AO_STATE_NONE); for (int n = 0; n < ao->num_planes; n++) @@ -195,7 +195,7 @@ static void reset(struct ao *ao) static void pause(struct ao *ao) { - if (ao->driver->reset) + if (!ao->stream_silence && ao->driver->reset) ao->driver->reset(ao); set_state(ao, AO_STATE_NONE); } @@ -244,6 +244,10 @@ static int init(struct ao *ao) p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride); atomic_store(&p->state, AO_STATE_NONE); assert(ao->driver->resume); + + if (ao->stream_silence) + ao->driver->resume(ao); + return 0; } diff --git a/audio/out/push.c b/audio/out/push.c index ac87c62a96..bf5dde46b5 100644 --- a/audio/out/push.c +++ b/audio/out/push.c @@ -497,10 +497,8 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds, bool wakeup = false; if (p_fds[num_fds].revents & POLLIN) { wakeup = true; - // flush the wakeup pipe contents - might "drown" some wakeups, but - // that's ok for our use-case - char buf[100]; - (void)read(p->wakeup_pipe[0], buf, sizeof(buf)); + // might "drown" some wakeups, but that's ok for our use-case + mp_flush_wakeup_pipe(p->wakeup_pipe[0]); } return (r >= 0 || r == -EINTR) ? wakeup : -1; } |