From 049682f2e3d0d5b6aa40b2773da599898fdbf9ec Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 22 Jun 2026 09:31:44 -0600 Subject: [PATCH 1/4] Add `RenderingEventCommand::bufferUnderrun` --- Sources/CSFBAudioEngine/Player/AudioPlayer.h | 5 +++ Sources/CSFBAudioEngine/Player/AudioPlayer.mm | 37 +++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.h b/Sources/CSFBAudioEngine/Player/AudioPlayer.h index 2b99e113..6f3aa488 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.h @@ -275,6 +275,8 @@ class AudioPlayer final { enum class RenderingEventCommand : uint32_t { /// Audio frames rendered from ring buffer framesRendered = 1, + /// The ring buffer contained fewer than requested audio frames + bufferUnderrun = 2, }; // MARK: - Event Processing @@ -307,6 +309,9 @@ class AudioPlayer final { /// Reads and processes a frames rendered event from `renderingEvents_` bool processFramesRenderedEvent() noexcept; + /// Reads and processes a buffer underrun event from `renderingEvents_` + bool processBufferUnderrunEvent() noexcept; + /// Called when the first audio frame from a decoder will render. void handleRenderingWillStartEvent(Decoder _Nonnull decoder, uint64_t hostTime) noexcept; diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index ebfc3c93..f6073cf9 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -1679,13 +1679,9 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re } // Read audio from the ring buffer - if (const auto framesRead = audioRingBuffer_.read(outputData, frameCount); framesRead > 0) { -#if DEBUG - if (framesRead != frameCount) { - os_log_debug(log_, "Insufficient audio in ring buffer: %zu frames available, %u requested", framesRead, - frameCount); - } -#endif /* DEBUG */ + const auto framesRead = audioRingBuffer_.read(outputData, frameCount); + + if (framesRead > 0) { if (!renderingEvents_.writeAll(RenderingEventCommand::framesRendered, nextEventIdentificationNumber(), timestamp.mHostTime, timestamp.mRateScalar, static_cast(framesRead))) { setFlags(Flags::renderEventDropped); @@ -1694,6 +1690,13 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re isSilence = YES; } + if (framesRead != frameCount) { + if (!renderingEvents_.writeAll(RenderingEventCommand::bufferUnderrun, nextEventIdentificationNumber(), + timestamp.mHostTime, static_cast(framesRead), static_cast(frameCount))) { + setFlags(Flags::renderEventDropped); + } + } + return noErr; } @@ -1974,6 +1977,9 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re case RenderingEventCommand::framesRendered: return processFramesRenderedEvent(); + case RenderingEventCommand::bufferUnderrun: + return processBufferUnderrunEvent(); + default: #if DEBUG assert(false && "Unknown RenderingEventCommand"); @@ -2126,6 +2132,23 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re return true; } +bool sfb::AudioPlayer::processBufferUnderrunEvent() noexcept { + // The host time from the render cycle's timestamp + uint64_t hostTime; + // The number of valid frames rendered + uint32_t framesRendered; + // The number of frames that were requested by the IOProc + uint32_t framesRequested; + if (!renderingEvents_.readAll(hostTime, framesRendered, framesRequested)) { + os_log_error(log_, "Missing host time or frame count for buffer underrun event"); + return false; + } + + os_log_error(log_, "Audio ring buffer underrun: %u/%u frames rendered for host time %lu", framesRendered, framesRequested, hostTime); + + return true; +} + void sfb::AudioPlayer::handleRenderingWillStartEvent(Decoder decoder, uint64_t hostTime) noexcept { const auto now = host_time::current(); if (now > hostTime) { From 50ced337198179f8822559574d3df3ecf96b9b5e Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 22 Jun 2026 09:34:11 -0600 Subject: [PATCH 2/4] Reformat --- Sources/CSFBAudioEngine/Player/AudioPlayer.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index f6073cf9..653424a5 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -1692,7 +1692,8 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re if (framesRead != frameCount) { if (!renderingEvents_.writeAll(RenderingEventCommand::bufferUnderrun, nextEventIdentificationNumber(), - timestamp.mHostTime, static_cast(framesRead), static_cast(frameCount))) { + timestamp.mHostTime, static_cast(framesRead), + static_cast(frameCount))) { setFlags(Flags::renderEventDropped); } } @@ -2144,7 +2145,8 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re return false; } - os_log_error(log_, "Audio ring buffer underrun: %u/%u frames rendered for host time %lu", framesRendered, framesRequested, hostTime); + os_log_error(log_, "Audio ring buffer underrun: %u/%u frames rendered for host time %lu", framesRendered, + framesRequested, hostTime); return true; } From b111c079b38ba624a2342cd74184fdc6ae17a6e6 Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 22 Jun 2026 09:35:20 -0600 Subject: [PATCH 3/4] Fix printf specifier --- Sources/CSFBAudioEngine/Player/AudioPlayer.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index 653424a5..d0c5b156 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -2145,7 +2145,7 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re return false; } - os_log_error(log_, "Audio ring buffer underrun: %u/%u frames rendered for host time %lu", framesRendered, + os_log_error(log_, "Audio ring buffer underrun: %u/%u frames rendered for host time %llu", framesRendered, framesRequested, hostTime); return true; From e6aa9eb3b5aea0442160990ac698f646ea5bd7db Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 22 Jun 2026 18:43:05 -0600 Subject: [PATCH 4/4] Update comment --- Sources/CSFBAudioEngine/Player/AudioPlayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.h b/Sources/CSFBAudioEngine/Player/AudioPlayer.h index 6f3aa488..a13a026c 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.h @@ -275,7 +275,7 @@ class AudioPlayer final { enum class RenderingEventCommand : uint32_t { /// Audio frames rendered from ring buffer framesRendered = 1, - /// The ring buffer contained fewer than requested audio frames + /// The ring buffer contained fewer audio frames than requested bufferUnderrun = 2, };