0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 03:52:22 +02:00

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>
This commit is contained in:
Vilius 2023-05-09 01:06:22 +03:00 committed by der richter
parent 801306acdf
commit ab419a6660

View File

@ -27,6 +27,11 @@
#include "ao_coreaudio_properties.h" #include "ao_coreaudio_properties.h"
#include "ao_coreaudio_utils.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 { struct priv {
AudioDeviceID device; AudioDeviceID device;
AudioUnit audio_unit; AudioUnit audio_unit;
@ -37,6 +42,10 @@ struct priv {
AudioStreamID original_asbd_stream; AudioStreamID original_asbd_stream;
bool 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;
}; };
static int64_t ca_get_hardware_latency(struct ao *ao) { 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)) if (!init_audiounit(ao, asbd))
goto coreaudio_error; goto coreaudio_error;
p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle",
DISPATCH_QUEUE_SERIAL);
return CONTROL_OK; return CONTROL_OK;
coreaudio_error: coreaudio_error:
@ -320,24 +332,89 @@ coreaudio_error:
return false; 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; struct priv *p = ao->priv;
OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0); OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0);
CHECK_CA_WARN("can't reset audio unit"); 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) static void start(struct ao *ao)
{ {
struct priv *p = ao->priv; struct priv *p = ao->priv;
OSStatus err = AudioOutputUnitStart(p->audio_unit); // Must dispatch to serialize reset, start and stop operations.
CHECK_CA_WARN("can't start audio unit"); dispatch_sync_f(p->queue, ao, &_start);
} }
static void uninit(struct ao *ao) static void uninit(struct ao *ao)
{ {
struct priv *p = ao->priv; struct priv *p = ao->priv;
dispatch_sync(p->queue, ^{
cancel_and_release_idle_work(p);
});
dispatch_release(p->queue);
AudioOutputUnitStop(p->audio_unit); AudioOutputUnitStop(p->audio_unit);
AudioUnitUninitialize(p->audio_unit); AudioUnitUninitialize(p->audio_unit);
AudioComponentInstanceDispose(p->audio_unit); AudioComponentInstanceDispose(p->audio_unit);