diff options
Diffstat (limited to 'audio/out/ao_coreaudio.c')
-rw-r--r-- | audio/out/ao_coreaudio.c | 166 |
1 files changed, 143 insertions, 23 deletions
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index d5936a51f6..4abfac38ee 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -17,7 +17,6 @@ #include <CoreAudio/HostTime.h> -#include "config.h" #include "ao.h" #include "internal.h" #include "audio/format.h" @@ -28,16 +27,27 @@ #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; - uint64_t hw_latency_us; + uint64_t hw_latency_ns; AudioStreamBasicDescription original_asbd; AudioStreamID original_asbd_stream; - int change_physical_format; + 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) { @@ -54,13 +64,13 @@ static int64_t ca_get_hardware_latency(struct ao *ao) { &size); CHECK_CA_ERROR("cannot get audio unit latency"); - uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6; - uint64_t device_latency_us = ca_get_device_latency_us(ao, p->device); + uint64_t audiounit_latency_ns = MP_TIME_S_TO_NS(audiounit_latency_sec); + uint64_t device_latency_ns = ca_get_device_latency_ns(ao, p->device); - MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us); - MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us); + MP_VERBOSE(ao, "audiounit latency [ns]: %lld\n", audiounit_latency_ns); + MP_VERBOSE(ao, "device latency [ns]: %lld\n", device_latency_ns); - return audiounit_latency_us + device_latency_us; + return audiounit_latency_ns + device_latency_ns; coreaudio_error: return 0; @@ -77,13 +87,14 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags, for (int n = 0; n < ao->num_planes; n++) planes[n] = buffer_list->mBuffers[n].mData; - int64_t end = mp_time_us(); - end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames); + int64_t end = mp_time_ns(); + end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames); + // don't use the returned sample count since CoreAudio always expects full frames ao_read_data(ao, planes, frames, end); return noErr; } -static int get_volume(struct ao *ao, struct ao_control_vol *vol) { +static int get_volume(struct ao *ao, float *vol) { struct priv *p = ao->priv; float auvol; OSStatus err = @@ -91,15 +102,15 @@ static int get_volume(struct ao *ao, struct ao_control_vol *vol) { kAudioUnitScope_Global, 0, &auvol); CHECK_CA_ERROR("could not get HAL output volume"); - vol->left = vol->right = auvol * 100.0; + *vol = auvol * 100.0; return CONTROL_TRUE; coreaudio_error: return CONTROL_ERROR; } -static int set_volume(struct ao *ao, struct ao_control_vol *vol) { +static int set_volume(struct ao *ao, float *vol) { struct priv *p = ao->priv; - float auvol = (vol->left + vol->right) / 200.0; + float auvol = *vol / 100.0; OSStatus err = AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, auvol, 0); @@ -122,6 +133,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; @@ -148,6 +162,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); @@ -160,6 +177,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: @@ -289,8 +311,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_us = ca_get_hardware_latency(ao); - AURenderCallbackStruct render_cb = (AURenderCallbackStruct) { .inputProc = render_cb_lpcm, .inputProcRefCon = ao, @@ -314,6 +334,13 @@ coreaudio_error: return false; } +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; @@ -321,17 +348,82 @@ static void stop(struct ao *ao) CHECK_CA_WARN("can't stop audio unit"); } -static void start(struct ao *ao) +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 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); @@ -342,6 +434,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, @@ -349,8 +443,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; } @@ -363,7 +460,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++) { @@ -382,14 +497,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 = { @@ -415,14 +535,14 @@ const struct ao_driver audio_out_coreaudio = { .uninit = uninit, .init = init, .control = control, - .reset = stop, + .reset = reset, .start = start, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, .list_devs = ca_get_device_list, .priv_size = sizeof(struct priv), .options = (const struct m_option[]){ - {"change-physical-format", OPT_FLAG(change_physical_format)}, + {"change-physical-format", OPT_BOOL(change_physical_format)}, {0} }, .options_prefix = "coreaudio", |