--- a/audio/out/ao_coreaudio.c 2025-12-22 03:08:20.000000000 +0800 +++ b/audio/out/ao_coreaudio.c 2026-03-03 20:11:15.000000000 +0800 @@ -29,11 +29,38 @@ #include "ao_coreaudio_utils.h" #include "osdep/mac/compat.h" +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 +typedef Component AudioComponent; +typedef ComponentDescription AudioComponentDescription; +#define AudioComponentFindNext FindNextComponent +#define AudioComponentInstanceNew OpenAComponent +#define AudioComponentInstanceDispose CloseComponent +#endif + +#ifndef kAudioObjectPropertyElementMain +#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 +#define USE_DISPATCH 1 +#else +#define USE_DISPATCH 0 +#endif + +#if USE_DISPATCH // 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 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 +#define DISPATCH_QUEUE_SERIAL NULL +#define SOURCE_TIMER_MASK 0 +#else +#define SOURCE_TIMER_MASK DISPATCH_TIMER_STRICT +#endif +#endif // USE_DISPATCH + struct priv { // This must be put in the front struct coreaudio_cb_sem sem; @@ -48,9 +75,13 @@ bool change_physical_format; - // Block that is executed after `IDLE_TIME` to stop audio output unit. - dispatch_block_t idle_work; +#if USE_DISPATCH + // Timer source that fires after `IDLE_TIME` to stop audio output unit. + // We use dispatch_source_t instead of dispatch_block_t for better + // compatibility with GCC (blocks require clang runtime). + dispatch_source_t idle_timer; dispatch_queue_t queue; +#endif int hotplug_cb_registration_times; }; @@ -142,6 +173,74 @@ static bool register_hotplug_cb(struct ao *ao); static void unregister_hotplug_cb(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"); +} + +#if USE_DISPATCH +// Idle timer handler - called when the timer fires +static void idle_timer_handler(void *ctx) +{ + struct ao *ao = ctx; + MP_VERBOSE(ao, "Stopping audio unit due to idle timeout\n"); + stop(ao); +} + +// Initialize the dispatch queue and idle timer +static void init_idle_stop_timer(struct ao *ao) +{ + struct priv *p = ao->priv; + + p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle", + DISPATCH_QUEUE_SERIAL); + + p->idle_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, + SOURCE_TIMER_MASK, p->queue); + + dispatch_source_set_event_handler_f(p->idle_timer, idle_timer_handler); + dispatch_set_context(p->idle_timer, ao); + + // macOS 10.9+ requires dispatch_activate, earlier versions use dispatch_resume +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 + dispatch_activate(p->idle_timer); +#else + dispatch_resume(p->idle_timer); +#endif +} + +// Cancel the idle timer (set it to never fire) +static void cancel_idle_timer(dispatch_source_t timer) +{ + dispatch_source_set_timer(timer, DISPATCH_TIME_FOREVER, + DISPATCH_TIME_FOREVER, 0); +} + +// Schedule the idle timer to fire after IDLE_TIME +static void schedule_idle_timer(dispatch_source_t timer) +{ + dispatch_source_set_timer(timer, + dispatch_time(DISPATCH_TIME_NOW, IDLE_TIME), + DISPATCH_TIME_FOREVER, 0); +} + +// Clean up the idle timer +static void cleanup_idle_timer(struct priv *p) +{ + if (p->idle_timer) { + dispatch_source_cancel(p->idle_timer); + dispatch_release(p->idle_timer); + p->idle_timer = NULL; + } + if (p->queue) { + dispatch_release(p->queue); + p->queue = NULL; + } +} +#endif // USE_DISPATCH + static bool reinit_device(struct ao *ao) { struct priv *p = ao->priv; @@ -190,8 +289,9 @@ reinit_latency(ao); ao->device_buffer = av_rescale(p->hw_latency_ns, ao->samplerate, 1000000000) * 2; - p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle", - DISPATCH_QUEUE_SERIAL); +#if USE_DISPATCH + init_idle_stop_timer(ao); +#endif return CONTROL_OK; @@ -358,38 +458,7 @@ 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); -} - +#if USE_DISPATCH static void _reset(void *_ao) { struct ao *ao = (struct ao *)_ao; @@ -403,14 +472,7 @@ // 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 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); + schedule_idle_timer(p->idle_timer); } static void _start(void *_ao) @@ -418,28 +480,49 @@ struct ao *ao = (struct ao *)_ao; struct priv *p = ao->priv; - if (p->idle_work) - dispatch_block_cancel(p->idle_work); + cancel_idle_timer(p->idle_timer); OSStatus err = AudioOutputUnitStart(p->audio_unit); CHECK_CA_WARN("can't start audio unit"); } +#endif // USE_DISPATCH + +static void reset(struct ao *ao) +{ +#if USE_DISPATCH + struct priv *p = ao->priv; + // Must dispatch to serialize reset, start and stop operations. + dispatch_sync_f(p->queue, ao, &_reset); +#else + // Without dispatch, just reset and stop immediately + struct priv *p = ao->priv; + OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0); + CHECK_CA_WARN("can't reset audio unit"); + stop(ao); +#endif +} static void start(struct ao *ao) { +#if USE_DISPATCH struct priv *p = ao->priv; // Must dispatch to serialize reset, start and stop operations. dispatch_sync_f(p->queue, ao, &_start); +#else + // Without dispatch, just start immediately + struct priv *p = ao->priv; + OSStatus err = AudioOutputUnitStart(p->audio_unit); + CHECK_CA_WARN("can't start audio unit"); +#endif } 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); +#if USE_DISPATCH + cleanup_idle_timer(p); +#endif AudioOutputUnitStop(p->audio_unit); AudioUnitUninitialize(p->audio_unit); --- a/audio/out/ao_coreaudio_chmap.c 2025-03-26 01:33:27.000000000 +0800 +++ b/audio/out/ao_coreaudio_chmap.c 2025-05-12 05:09:09.000000000 +0800 @@ -22,7 +22,10 @@ #include "ao_coreaudio_utils.h" #include "ao_coreaudio_chmap.h" -#include + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 + #include +#endif static const int speaker_map[][2] = { { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },