summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_coreaudio.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:13:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:13:14 +0000
commit60e8a3d404f0640fa5a3f834eae54b4f1fb9127d (patch)
tree1da89a218d0ecf010c67a87cb2f625c4cb18e7d7 /audio/out/ao_coreaudio.c
parentAdding upstream version 0.37.0. (diff)
downloadmpv-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.c138
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 = {