Fix PSP MP3 TopUp trusting requested frame count over actual on short reads
audioStreamPSPTopUp() advanced readPosition/ringFilled/framesEnqueued by the requested framesToRead regardless of how many frames a short read actually produced - a leftover assumption from the WAV/PCM design, where a short read only ever means "truly corrupt file, at the real end." That doesn't hold for MP3: a hardware decoder backend can plausibly report "nothing ready this instant" without that meaning no more content exists. Every such short read silently inflated readPosition ahead of real decode progress, triggering the loop-segment-end check far too early - restarting the pass again and again well short of the real runtime, heard as the clip racing through its own content. Now only ever advances by the actual frames produced (matching dusklinux's audioStreamLinuxFeed(), which already did this correctly), and bases the loop/end decision on real position instead of the originally-requested read size. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -185,9 +185,6 @@ void audioStreamPSPTopUp(audiostream_t *stream) {
|
|||||||
: stream->platform.totalFrames;
|
: stream->platform.totalFrames;
|
||||||
const size_t framesRemainingInSegment = currentEndFrame - stream->platform.readPosition;
|
const size_t framesRemainingInSegment = currentEndFrame - stream->platform.readPosition;
|
||||||
const size_t framesToRead = mathMin(room, framesRemainingInSegment);
|
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) {
|
if(framesToRead > 0) {
|
||||||
int16_t *scratch = stream->platform.scratch;
|
int16_t *scratch = stream->platform.scratch;
|
||||||
@@ -199,32 +196,46 @@ void audioStreamPSPTopUp(audiostream_t *stream) {
|
|||||||
stream->platform.readFailed = true;
|
stream->platform.readFailed = true;
|
||||||
return;
|
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);
|
if(framesRead > 0) {
|
||||||
for(size_t i = 0; i < framesToRead; i++) {
|
threadMutexLock(&stream->platform.ringLock);
|
||||||
const size_t writeIndex = (stream->platform.ringWritePos + i) % AUDIO_PSP_RING_FRAMES;
|
for(size_t i = 0; i < framesRead; i++) {
|
||||||
memoryCopy(
|
const size_t writeIndex = (stream->platform.ringWritePos + i) % AUDIO_PSP_RING_FRAMES;
|
||||||
stream->platform.ring + (writeIndex * channels),
|
memoryCopy(
|
||||||
scratch + (i * channels),
|
stream->platform.ring + (writeIndex * channels),
|
||||||
frameSize
|
scratch + (i * channels),
|
||||||
);
|
frameSize
|
||||||
}
|
);
|
||||||
stream->platform.ringWritePos =
|
}
|
||||||
(stream->platform.ringWritePos + framesToRead) % AUDIO_PSP_RING_FRAMES;
|
stream->platform.ringWritePos =
|
||||||
stream->platform.ringFilled += framesToRead;
|
(stream->platform.ringWritePos + framesRead) % AUDIO_PSP_RING_FRAMES;
|
||||||
stream->platform.framesEnqueued += framesToRead;
|
stream->platform.ringFilled += framesRead;
|
||||||
threadMutexUnlock(&stream->platform.ringLock);
|
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(reachesSegmentEnd) {
|
||||||
if(willLoop) {
|
if(willLoop) {
|
||||||
if(errorIsNotOk(audioStreamSeek(stream, stream->platform.loopToFrame))) {
|
if(errorIsNotOk(audioStreamSeek(stream, stream->platform.loopToFrame))) {
|
||||||
|
|||||||
Reference in New Issue
Block a user