Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/CSFBAudioEngine/Player/AudioPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
39 changes: 32 additions & 7 deletions Sources/CSFBAudioEngine/Player/AudioPlayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t>(framesRead))) {
setFlags(Flags::renderEventDropped);
Expand All @@ -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<uint32_t>(framesRead),
static_cast<uint32_t>(frameCount))) {
setFlags(Flags::renderEventDropped);
}
}

return noErr;
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down