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:
parent
801306acdf
commit
ab419a6660
@ -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,10 @@ 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;
|
||||
};
|
||||
|
||||
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))
|
||||
goto coreaudio_error;
|
||||
|
||||
p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
@ -320,24 +332,89 @@ coreaudio_error:
|
||||
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;
|
||||
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;
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
// 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);
|
||||
|
Loading…
Reference in New Issue
Block a user