From 9793d068c7eed77c76a704bd8c2c8d33675f66f4 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Fri, 2 Dec 2022 17:12:48 +0100 Subject: [PATCH] audiounit: poll latency when updating timings Because AVAudioSessionRouteChangeNotification is not triggered when switching Speaker <-> Airplay2 (but it is triggered for BT <-> Anything). Fixes A/V sync with AirPlay2 when Airplay is changed during playback. --- modules/audio_output/audiounit_ios.m | 34 +++++++++++++++++-------- modules/audio_output/auhal.c | 4 +-- modules/audio_output/coreaudio_common.c | 32 +++++++++++++++-------- modules/audio_output/coreaudio_common.h | 9 ++++--- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/modules/audio_output/audiounit_ios.m b/modules/audio_output/audiounit_ios.m index cdaa039d1eb3..591ea75b7b34 100644 --- a/modules/audio_output/audiounit_ios.m +++ b/modules/audio_output/audiounit_ios.m @@ -138,6 +138,10 @@ - (NSInteger)removeAoutInstance:(AoutWrapper *)wrapperInstance bool b_spatial_audio_supported; enum au_dev au_dev; + /* For debug purpose, to print when specific latency changed */ + vlc_tick_t output_latency_ticks; + vlc_tick_t io_buffer_duration_ticks; + /* sw gain */ float soft_gain; bool soft_mute; @@ -161,17 +165,29 @@ - (NSInteger)removeAoutInstance:(AoutWrapper *)wrapperInstance Float64 unit_s; vlc_tick_t latency_us = 0, us; + bool changed = false; us = vlc_tick_from_sec([p_sys->avInstance outputLatency]); - msg_Dbg(p_aout, "Current device has a outputLatency of %" PRId64 "us", us); + if (us != p_sys->output_latency_ticks) + { + msg_Dbg(p_aout, "Current device has a new outputLatency of %" PRId64 "us", us); + p_sys->output_latency_ticks = us; + changed = true; + } latency_us += us; us = vlc_tick_from_sec([p_sys->avInstance IOBufferDuration]); - msg_Dbg(p_aout, "Current device has a IOBufferDuration of %" PRId64 "us", us); + if (us != p_sys->io_buffer_duration_ticks) + { + msg_Dbg(p_aout, "Current device has a new IOBufferDuration of %" PRId64 "us", us); + p_sys->io_buffer_duration_ticks = us; + changed = true; + } latency_us += us; - msg_Dbg(p_aout, "Current device has a total latency of %" PRId64 "us", - latency_us); + if (changed) + msg_Dbg(p_aout, "Current device has a new total latency of %" PRId64 "us", + latency_us); return latency_us; } @@ -202,10 +218,7 @@ - (void)audioSessionRouteChange:(NSNotification *)notification || routeChangeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) aout_RestartRequest(p_aout, AOUT_RESTART_OUTPUT); else - { - const vlc_tick_t latency_us = GetLatency(p_aout); - ca_SetDeviceLatency(p_aout, latency_us); - } + ca_ResetDeviceLatency(p_aout); } - (void)handleInterruption:(NSNotification *)notification @@ -539,6 +552,8 @@ static int role2policy_cmp(const void *key, const void *val) aout_FormatPrint(p_aout, "VLC is looking for:", fmt); p_sys->au_unit = NULL; + p_sys->output_latency_ticks = 0; + p_sys->io_buffer_duration_ticks = 0; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:p_sys->aoutWrapper @@ -599,8 +614,7 @@ static int role2policy_cmp(const void *key, const void *val) if (err != noErr) ca_LogWarn("failed to set IO mode"); - const vlc_tick_t latency_us = GetLatency(p_aout); - ret = au_Initialize(p_aout, p_sys->au_unit, fmt, NULL, latency_us, NULL); + ret = au_Initialize(p_aout, p_sys->au_unit, fmt, NULL, 0, GetLatency, NULL); if (ret != VLC_SUCCESS) goto error; diff --git a/modules/audio_output/auhal.c b/modules/audio_output/auhal.c index 28153b5d95da..ade1db9d5384 100644 --- a/modules/audio_output/auhal.c +++ b/modules/audio_output/auhal.c @@ -1092,7 +1092,7 @@ StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt, /* Do the last VLC aout setups */ bool warn_configuration; int ret = au_Initialize(p_aout, p_sys->au_unit, fmt, layout, latency_us, - &warn_configuration); + NULL, &warn_configuration); if (ret != VLC_SUCCESS) goto error; @@ -1365,7 +1365,7 @@ StartSPDIF(audio_output_t * p_aout, audio_sample_format_t *fmt) return VLC_EGENERIC; } - ret = ca_Initialize(p_aout, fmt, 0); + ret = ca_Initialize(p_aout, fmt, 0, NULL); if (ret != VLC_SUCCESS) { AudioDeviceDestroyIOProcID(p_sys->i_selected_dev, p_sys->i_procID); diff --git a/modules/audio_output/coreaudio_common.c b/modules/audio_output/coreaudio_common.c index 5239363f114f..fdd21db7884e 100644 --- a/modules/audio_output/coreaudio_common.c +++ b/modules/audio_output/coreaudio_common.c @@ -136,6 +136,14 @@ ca_Open(audio_output_t *p_aout) return VLC_SUCCESS; } +static vlc_tick_t +GetLatency(audio_output_t *p_aout) +{ + struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys; + return p_sys->get_latency != NULL ? p_sys->get_latency(p_aout) + : p_sys->i_dev_latency_ticks; +} + /* Called from render callbacks. No lock, wait, and IO here */ void ca_Render(audio_output_t *p_aout, uint64_t host_time, @@ -148,9 +156,7 @@ ca_Render(audio_output_t *p_aout, uint64_t host_time, const vlc_tick_t bytes_ticks = BytesToTicks(p_sys, bytes); const vlc_tick_t now_ticks = vlc_tick_now(); - const vlc_tick_t end_ticks = now_ticks + bytes_ticks - + host_delay_ticks - + p_sys->i_dev_latency_ticks; + const vlc_tick_t end_ticks = now_ticks + bytes_ticks + host_delay_ticks; lock_lock(p_sys); @@ -168,7 +174,7 @@ ca_Render(audio_output_t *p_aout, uint64_t host_time, { /* Write silence to reach the first play date */ vlc_tick_t silence_ticks = p_sys->first_play_date - end_ticks - + bytes_ticks; + - GetLatency(p_aout) + bytes_ticks; if (silence_ticks > 0) { tocopy = TicksToBytes(p_sys, silence_ticks); @@ -208,7 +214,7 @@ ca_Render(audio_output_t *p_aout, uint64_t host_time, { p_sys->timing_report_last_written_bytes = 0; vlc_tick_t pos_ticks = BytesToTicks(p_sys, p_sys->i_total_bytes); - aout_TimingReport(p_aout, end_ticks, pos_ticks); + aout_TimingReport(p_aout, end_ticks + GetLatency(p_aout), pos_ticks); } else p_sys->timing_report_last_written_bytes += tocopy; @@ -329,7 +335,7 @@ ca_Play(audio_output_t * p_aout, block_t * p_block, vlc_tick_t date) int ca_Initialize(audio_output_t *p_aout, const audio_sample_format_t *fmt, - vlc_tick_t i_dev_latency_ticks) + vlc_tick_t i_dev_latency_ticks, get_latency_cb get_latency) { struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys; @@ -346,7 +352,10 @@ ca_Initialize(audio_output_t *p_aout, const audio_sample_format_t *fmt, p_sys->i_bytes_per_frame = fmt->i_bytes_per_frame; p_sys->i_frame_length = fmt->i_frame_length; - p_sys->i_dev_latency_ticks = i_dev_latency_ticks; + if (get_latency != NULL) + p_sys->get_latency = get_latency; + else + p_sys->i_dev_latency_ticks = i_dev_latency_ticks; p_sys->timing_report_delay_bytes = TicksToBytes(p_sys, TIMING_REPORT_DELAY_TICKS); ca_ClearOutBuffers(p_aout); @@ -373,12 +382,13 @@ ca_SetAliveState(audio_output_t *p_aout, bool alive) lock_unlock(p_sys); } -void ca_SetDeviceLatency(audio_output_t *p_aout, vlc_tick_t i_dev_latency_ticks) +void ca_ResetDeviceLatency(audio_output_t *p_aout) { struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys; lock_lock(p_sys); - p_sys->i_dev_latency_ticks = i_dev_latency_ticks; + /* Trigger aout_TimingReport() to be called from the next render callback */ + p_sys->timing_report_last_written_bytes = p_sys->timing_report_delay_bytes; lock_unlock(p_sys); } @@ -713,7 +723,7 @@ MapInputLayout(audio_output_t *p_aout, const audio_sample_format_t *fmt, int au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt, const AudioChannelLayout *outlayout, vlc_tick_t i_dev_latency_ticks, - bool *warn_configuration) + get_latency_cb get_latency, bool *warn_configuration) { int ret; AudioChannelLayout *inlayout_buf = NULL; @@ -836,7 +846,7 @@ au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt, return VLC_EGENERIC; } - ret = ca_Initialize(p_aout, fmt, i_dev_latency_ticks); + ret = ca_Initialize(p_aout, fmt, i_dev_latency_ticks, get_latency); if (ret != VLC_SUCCESS) { AudioUnitUninitialize(au); diff --git a/modules/audio_output/coreaudio_common.h b/modules/audio_output/coreaudio_common.h index ed35a1457b72..7aee47d3f692 100644 --- a/modules/audio_output/coreaudio_common.h +++ b/modules/audio_output/coreaudio_common.h @@ -45,6 +45,8 @@ #define ca_LogErr(fmt) msg_Err(p_aout, fmt ", OSStatus: %d", (int) err) #define ca_LogWarn(fmt) msg_Warn(p_aout, fmt ", OSStatus: %d", (int) err) +typedef vlc_tick_t (*get_latency_cb)(audio_output_t *); + struct aout_sys_common { /* The following is owned by common.c (initialized from ca_Open) */ @@ -86,6 +88,7 @@ struct aout_sys_common uint8_t chan_table[AOUT_CHAN_MAX]; /* ca_TimeGet extra latency, in vlc ticks */ vlc_tick_t i_dev_latency_ticks; + get_latency_cb get_latency; }; int ca_Open(audio_output_t *p_aout); @@ -104,20 +107,20 @@ void ca_MuteSet(audio_output_t * p_aout, bool mute); void ca_Play(audio_output_t * p_aout, block_t * p_block, vlc_tick_t date); int ca_Initialize(audio_output_t *p_aout, const audio_sample_format_t *fmt, - vlc_tick_t i_dev_latency_ticks); + vlc_tick_t i_dev_latency_ticks, get_latency_cb get_latency); void ca_Uninitialize(audio_output_t *p_aout); void ca_SetAliveState(audio_output_t *p_aout, bool alive); -void ca_SetDeviceLatency(audio_output_t *p_aout, vlc_tick_t i_dev_latency_ticks); +void ca_ResetDeviceLatency(audio_output_t *p_aout); AudioUnit au_NewOutputInstance(audio_output_t *p_aout, OSType comp_sub_type); int au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt, const AudioChannelLayout *outlayout, vlc_tick_t i_dev_latency_ticks, - bool *warn_configuration); + get_latency_cb get_latency, bool *warn_configuration); void au_Uninitialize(audio_output_t *p_aout, AudioUnit au);