diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:13:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:13:14 +0000 |
commit | 60e8a3d404f0640fa5a3f834eae54b4f1fb9127d (patch) | |
tree | 1da89a218d0ecf010c67a87cb2f625c4cb18e7d7 /audio/out/ao_coreaudio.c | |
parent | Adding upstream version 0.37.0. (diff) | |
download | mpv-upstream.tar.xz mpv-upstream.zip |
Adding upstream version 0.38.0.upstream/0.38.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'audio/out/ao_coreaudio.c')
-rw-r--r-- | audio/out/ao_coreaudio.c | 138 |
1 files changed, 129 insertions, 9 deletions
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index 37f1313..ae743c9 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -27,6 +27,11 @@ #include "ao_coreaudio_properties.h" #include "ao_coreaudio_utils.h" +// The timeout for stopping the audio unit after being reset. This allows the +// device to sleep after playback paused. The duration is chosen to match the +// behavior of AVFoundation. +#define IDLE_TIME 7 * NSEC_PER_SEC + struct priv { AudioDeviceID device; AudioUnit audio_unit; @@ -37,6 +42,12 @@ struct priv { AudioStreamID original_asbd_stream; bool change_physical_format; + + // Block that is executed after `IDLE_TIME` to stop audio output unit. + dispatch_block_t idle_work; + dispatch_queue_t queue; + + int hotplug_cb_registration_times; }; static int64_t ca_get_hardware_latency(struct ao *ao) { @@ -78,7 +89,7 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags, int64_t end = mp_time_ns(); end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames); - int samples = ao_read_data_nonblocking(ao, planes, frames, end); + int samples = ao_read_data(ao, planes, frames, end); if (samples == 0) *aflags |= kAudioUnitRenderAction_OutputIsSilence; @@ -128,6 +139,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd); static void init_physical_format(struct ao *ao); +static void reinit_latency(struct ao *ao); +static bool register_hotplug_cb(struct ao *ao); +static void unregister_hotplug_cb(struct ao *ao); static bool reinit_device(struct ao *ao) { struct priv *p = ao->priv; @@ -154,6 +168,9 @@ static int init(struct ao *ao) if (!reinit_device(ao)) goto coreaudio_error; + if (!register_hotplug_cb(ao)) + goto coreaudio_error; + if (p->change_physical_format) init_physical_format(ao); @@ -166,6 +183,11 @@ static int init(struct ao *ao) if (!init_audiounit(ao, asbd)) goto coreaudio_error; + reinit_latency(ao); + + p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle", + DISPATCH_QUEUE_SERIAL); + return CONTROL_OK; coreaudio_error: @@ -295,8 +317,6 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd) CHECK_CA_ERROR_L(coreaudio_error_audiounit, "can't link audio unit to selected device"); - p->hw_latency_ns = ca_get_hardware_latency(ao); - AURenderCallbackStruct render_cb = (AURenderCallbackStruct) { .inputProc = render_cb_lpcm, .inputProcRefCon = ao, @@ -320,24 +340,96 @@ coreaudio_error: return false; } -static void reset(struct ao *ao) +static void reinit_latency(struct ao *ao) +{ + struct priv *p = ao->priv; + + p->hw_latency_ns = ca_get_hardware_latency(ao); +} + +static void stop(struct ao *ao) +{ + struct priv *p = ao->priv; + OSStatus err = AudioOutputUnitStop(p->audio_unit); + CHECK_CA_WARN("can't stop audio unit"); +} + +static void cancel_and_release_idle_work(struct priv *p) +{ + if (!p->idle_work) + return; + + dispatch_block_cancel(p->idle_work); + Block_release(p->idle_work); + p->idle_work = NULL; +} + +static void stop_after_idle_time(struct ao *ao) { struct priv *p = ao->priv; + + cancel_and_release_idle_work(p); + + p->idle_work = dispatch_block_create(0, ^{ + MP_VERBOSE(ao, "Stopping audio unit due to idle timeout\n"); + stop(ao); + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, IDLE_TIME), + p->queue, p->idle_work); +} + +static void _reset(void *_ao) +{ + struct ao *ao = (struct ao *)_ao; + struct priv *p = ao->priv; OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0); CHECK_CA_WARN("can't reset audio unit"); + + // Until the audio unit is stopped the macOS daemon coreaudiod continues to + // consume CPU and prevent macOS from sleeping. Immediately stopping the + // audio unit would be disruptive for short pause/resume cycles as + // restarting the audio unit takes a noticeable amount of time when a + // wireless audio device is being used. Instead the audio unit is stopped + // after a delay if it remains idle. + stop_after_idle_time(ao); } -static void start(struct ao *ao) +static void reset(struct ao *ao) { struct priv *p = ao->priv; + // Must dispatch to serialize reset, start and stop operations. + dispatch_sync_f(p->queue, ao, &_reset); +} + +static void _start(void *_ao) +{ + struct ao *ao = (struct ao *)_ao; + struct priv *p = ao->priv; + + if (p->idle_work) + dispatch_block_cancel(p->idle_work); + OSStatus err = AudioOutputUnitStart(p->audio_unit); CHECK_CA_WARN("can't start audio unit"); } +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + // Must dispatch to serialize reset, start and stop operations. + dispatch_sync_f(p->queue, ao, &_start); +} static void uninit(struct ao *ao) { struct priv *p = ao->priv; + + dispatch_sync(p->queue, ^{ + cancel_and_release_idle_work(p); + }); + dispatch_release(p->queue); + AudioOutputUnitStop(p->audio_unit); AudioUnitUninitialize(p->audio_unit); AudioComponentInstanceDispose(p->audio_unit); @@ -348,6 +440,8 @@ static void uninit(struct ao *ao) &p->original_asbd); CHECK_CA_WARN("could not restore physical stream format"); } + + unregister_hotplug_cb(ao); } static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, @@ -355,8 +449,11 @@ static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, void *ctx) { struct ao *ao = ctx; + struct priv *p = ao->priv; MP_VERBOSE(ao, "Handling potential hotplug event...\n"); reinit_device(ao); + if (p->audio_unit) + reinit_latency(ao); ao_hotplug_event(ao); return noErr; } @@ -369,7 +466,25 @@ static uint32_t hotplug_properties[] = { static int hotplug_init(struct ao *ao) { if (!reinit_device(ao)) - goto coreaudio_error; + return -1; + + if (!register_hotplug_cb(ao)) + return -1; + + return 0; +} + +static void hotplug_uninit(struct ao *ao) +{ + unregister_hotplug_cb(ao); +} + +static bool register_hotplug_cb(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (p->hotplug_cb_registration_times++) + return true; OSStatus err = noErr; for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { @@ -388,14 +503,19 @@ static int hotplug_init(struct ao *ao) } } - return 0; + return true; coreaudio_error: - return -1; + return false; } -static void hotplug_uninit(struct ao *ao) +static void unregister_hotplug_cb(struct ao *ao) { + struct priv *p = ao->priv; + + if (--p->hotplug_cb_registration_times) + return; + OSStatus err = noErr; for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { AudioObjectPropertyAddress addr = { |