diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.h b/Sources/CSFBAudioEngine/Player/AudioPlayer.h index 2b99e113..a13a026c 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 audio frames than requested + 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..d0c5b156 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,14 @@ 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 +1978,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 +2133,24 @@ 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 %llu", framesRendered, + framesRequested, hostTime); + + return true; +} + void sfb::AudioPlayer::handleRenderingWillStartEvent(Decoder decoder, uint64_t hostTime) noexcept { const auto now = host_time::current(); if (now > hostTime) {