diff --git a/src/duskpsp/audio/audiostreampsp.c b/src/duskpsp/audio/audiostreampsp.c index 86d9156d..8af9ac67 100644 --- a/src/duskpsp/audio/audiostreampsp.c +++ b/src/duskpsp/audio/audiostreampsp.c @@ -185,9 +185,6 @@ void audioStreamPSPTopUp(audiostream_t *stream) { : stream->platform.totalFrames; const size_t framesRemainingInSegment = currentEndFrame - stream->platform.readPosition; const size_t framesToRead = mathMin(room, framesRemainingInSegment); - const bool_t reachesSegmentEnd = framesToRead == framesRemainingInSegment; - const bool_t willLoop = reachesSegmentEnd && - (stream->state & AUDIO_STREAM_STATE_LOOPING); if(framesToRead > 0) { int16_t *scratch = stream->platform.scratch; @@ -199,32 +196,46 @@ void audioStreamPSPTopUp(audiostream_t *stream) { stream->platform.readFailed = true; return; } - if(framesRead < framesToRead) { - // The asset is shorter than its declared header size (corrupt or - // truncated) - pad what's missing with silence. - memoryZero( - scratch + (framesRead * channels), (framesToRead - framesRead) * frameSize - ); - } - threadMutexLock(&stream->platform.ringLock); - for(size_t i = 0; i < framesToRead; i++) { - const size_t writeIndex = (stream->platform.ringWritePos + i) % AUDIO_PSP_RING_FRAMES; - memoryCopy( - stream->platform.ring + (writeIndex * channels), - scratch + (i * channels), - frameSize - ); - } - stream->platform.ringWritePos = - (stream->platform.ringWritePos + framesToRead) % AUDIO_PSP_RING_FRAMES; - stream->platform.ringFilled += framesToRead; - stream->platform.framesEnqueued += framesToRead; - threadMutexUnlock(&stream->platform.ringLock); + if(framesRead > 0) { + threadMutexLock(&stream->platform.ringLock); + for(size_t i = 0; i < framesRead; i++) { + const size_t writeIndex = (stream->platform.ringWritePos + i) % AUDIO_PSP_RING_FRAMES; + memoryCopy( + stream->platform.ring + (writeIndex * channels), + scratch + (i * channels), + frameSize + ); + } + stream->platform.ringWritePos = + (stream->platform.ringWritePos + framesRead) % AUDIO_PSP_RING_FRAMES; + stream->platform.ringFilled += framesRead; + stream->platform.framesEnqueued += framesRead; + threadMutexUnlock(&stream->platform.ringLock); - stream->platform.readPosition += framesToRead; + stream->platform.readPosition += framesRead; + } } + // Reached (or ran past, which can't actually happen since framesToRead + // is itself bounded by framesRemainingInSegment) the segment end only if + // readPosition genuinely got there - never inferred from how much was + // merely requested. A short read (framesRead < framesToRead, including + // 0) does NOT by itself mean the segment is over: unlike PCM (where a + // short read only ever happens at a truly corrupt/truncated file's real + // end), MP3 decode can plausibly produce fewer frames than asked without + // that meaning "no more content" (e.g. a hardware decoder backend + // reporting it has nothing ready *this instant*, not that there's + // nothing left in the file) - trusting the request size instead of + // actual progress here silently let readPosition race ahead of real + // decode progress, confirmed as the cause of premature loop-segment-end + // detection ("racing through content"). A short read that isn't + // genuinely the end just means less got queued this call; the next + // TopUp() call (next engine frame) naturally retries. + const bool_t reachesSegmentEnd = stream->platform.readPosition >= currentEndFrame; + const bool_t willLoop = reachesSegmentEnd && + (stream->state & AUDIO_STREAM_STATE_LOOPING); + if(reachesSegmentEnd) { if(willLoop) { if(errorIsNotOk(audioStreamSeek(stream, stream->platform.loopToFrame))) {