diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.h b/Sources/CSFBAudioEngine/Player/AudioPlayer.h index 83390bb1..25c6bfb5 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.h @@ -240,8 +240,10 @@ class AudioPlayer final { muted = 1u << 2, /// The ring buffer contains stale audio and needs to be emptied during the next render cycle audioStale = 1u << 3, + /// A ring buffer format change is pending + formatChangePending = 1u << 4, /// The event message queue had insufficient space to record a render event - renderEventDropped = 1u << 4, + renderEventDropped = 1u << 5, }; // Enable bitmask operations for `Flags` diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index 063f9be8..2bfca12f 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -1225,6 +1225,7 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re // Clear the format mismatch flag if any decoders were canceled if (anyCanceled && formatMismatch) { formatMismatch = false; + clearFlags(Flags::formatChangePending); } // Get the earliest decoder state that has not completed rendering @@ -1267,6 +1268,7 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re // match. Clear the format mismatch flag so rendering can continue; the flag will be set again when // decoding completes. formatMismatch = false; + clearFlags(Flags::formatChangePending); fetchUpdate( decoderState->flags_, @@ -1439,6 +1441,10 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re // decoding can't start until the processing graph is reconfigured which occurs after // all active decoders complete formatMismatch = true; + + // Ring buffer underruns are expected while waiting for the format change to complete; + // suppress underrun notifications until the processing graph is reconfigured + setFlags(Flags::formatChangePending); } } @@ -1459,8 +1465,8 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re continue; } - clearFlags(Flags::audioStale); formatMismatch = false; + clearFlags(Flags::audioStale | Flags::formatChangePending); // Allocate the buffer that is the intermediary between the decoder state and the ring buffer if (auto format = buffer.format; format.channelCount != renderFormat.channelCount || @@ -1676,7 +1682,9 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re isSilence = YES; } - if (framesRead != frameCount) { + // Suppress underrun notifications while a non-gapless format change is pending; the ring buffer + // is expected to run dry while the decoding thread waits to reconfigure the processing graph + if (framesRead != frameCount && bits::is_clear(flags, Flags::formatChangePending)) { if (!events_.enqueue(EventCommand::renderBufferUnderrun, timestamp.mHostTime, framesRead, frameCount)) { setFlags(Flags::renderEventDropped); }