summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVilius <orion1vi@protonmail.com>2023-05-09 01:06:22 +0300
committerder richter <der.richter@gmx.de>2024-03-16 15:00:46 +0100
commitab419a6660c6f8f78b30ba0838ab3c274746af89 (patch)
treec1917ef6dcb101bc2bd1fab5f0cf2e77c23cc2b7
parent801306acdf83e9fa5e13198bc3cd46a19d87edbd (diff)
downloadmpv-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>
-rw-r--r--audio/out/ao_coreaudio.c81
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);