diff options
author | Vilius <orion1vi@protonmail.com> | 2023-05-09 01:06:22 +0300 |
---|---|---|
committer | der richter <der.richter@gmx.de> | 2024-03-16 15:00:46 +0100 |
commit | ab419a6660c6f8f78b30ba0838ab3c274746af89 (patch) | |
tree | c1917ef6dcb101bc2bd1fab5f0cf2e77c23cc2b7 /audio | |
parent | 801306acdf83e9fa5e13198bc3cd46a19d87edbd (diff) | |
download | mpv-ab419a6660c6f8f78b30ba0838ab3c274746af89.tar.bz2 mpv-ab419a6660c6f8f78b30ba0838ab3c274746af89.tar.xz |
ao_coreaudio: stop audio unit after idle timeout
Commit 39f7f83 changed ao_driver.reset to use AudioUnitReset instead of
AudioOutputUnitStop. The problem with calling AudioOutputUnitStop was
that AudioOutputUnitStart takes a significant amount of time after a
stop when a wireless audio device is being used. This resulted in
lagging that was noticeable to users during seeking and short
pause/resume cycles. Switching to AudioUnitReset eliminated this
lagging.
However with the switch to AudioUnitReset the macOS daemon coreaudiod
continued to consume CPU time and did not release a powerd assertion
that it created on behalf of mpv, preventing macOS from sleeping.
This commit will change ao_coreaudio.reset to call AudioOutputUnitStop
after a delay if playback has not resumed. This preserves the faster
restart of playback for seeking and short pause/resume cycles and avoids
preventing sleep and needless CPU consumption.
Fixes #11617
The code changes were authored by @orion1vi and @lhc70000.
Co-authored-by: Collider LI <lhc199652@gmail.com>
Diffstat (limited to 'audio')
-rw-r--r-- | audio/out/ao_coreaudio.c | 81 |
1 files changed, 79 insertions, 2 deletions
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index 37f1313ca5..4d983faba3 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,10 @@ 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; }; static int64_t ca_get_hardware_latency(struct ao *ao) { @@ -166,6 +175,9 @@ static int init(struct ao *ao) if (!init_audiounit(ao, asbd)) goto coreaudio_error; + p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle", + DISPATCH_QUEUE_SERIAL); + return CONTROL_OK; coreaudio_error: @@ -320,24 +332,89 @@ coreaudio_error: return false; } -static void reset(struct ao *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); |