summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_coreaudio.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_coreaudio.c')
-rw-r--r--audio/out/ao_coreaudio.c169
1 files changed, 144 insertions, 25 deletions
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index 2e02c6842b..4abfac38ee 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -17,28 +17,37 @@
#include <CoreAudio/HostTime.h>
-#include "config.h"
#include "ao.h"
#include "internal.h"
#include "audio/format.h"
#include "osdep/timer.h"
#include "options/m_option.h"
-#include "misc/ring.h"
#include "common/msg.h"
#include "ao_coreaudio_chmap.h"
#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) {
@@ -55,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;
@@ -78,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 =
@@ -92,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);
@@ -123,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;
@@ -149,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);
@@ -161,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:
@@ -290,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,
@@ -315,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;
@@ -322,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);
@@ -343,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,
@@ -350,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;
}
@@ -364,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++) {
@@ -383,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 = {
@@ -416,14 +535,14 @@ const struct ao_driver audio_out_coreaudio = {
.uninit = uninit,
.init = init,
.control = control,
- .reset = stop,
- .resume = start,
+ .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[]){
- OPT_FLAG("change-physical-format", change_physical_format, 0),
+ {"change-physical-format", OPT_BOOL(change_physical_format)},
{0}
},
.options_prefix = "coreaudio",