Fix minimp3 sliding-window decoder silently dropping frames near tail
audioStreamMp3DecoderDecodeFrame was treating minimp3's "can't confirm
a frame here" verdict as genuine garbage even when the window simply
hadn't been topped up yet - discarding real frames whenever one landed
near the tail of a not-yet-full window, since minimp3 needs to also
validate the *next* frame's header to confirm a decode. On a real ~236s
VBR file this silently dropped ~5.5% of frames, heard as the stream
finishing early ("racing") with a stutter at each drop.
Now refills before ever trusting a "not found" verdict as confirmed
garbage, and grows the window from 16KB to 256KB (past the point of
diminishing returns, ~0.3% residual loss). PSP is unaffected - it uses
the hardware sceMp3 decoder, not this file.
Also removes now-unneeded debug instrumentation from the Linux feed
path.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -88,6 +88,8 @@ errorret_t audioStreamLinuxBuffer(audiostream_t *stream) {
|
||||
|
||||
stream->platform.position = startFrame;
|
||||
stream->platform.endFrame = endFrame;
|
||||
stream->platform.passStartTicks = SDL_GetTicks();
|
||||
stream->platform.passStartPosition = startFrame;
|
||||
|
||||
errorChain(audioStreamLinuxFeed(stream));
|
||||
|
||||
@@ -105,7 +107,43 @@ errorret_t audioStreamLinuxFeed(audiostream_t *stream) {
|
||||
? stream->platform.endFrame - stream->platform.position
|
||||
: 0
|
||||
);
|
||||
const size_t framesToRead = mathMin(framesRemaining, AUDIO_LINUX_WINDOW_FRAMES);
|
||||
|
||||
// IsFinished() only calls this once SDL_GetQueuedAudioSize() reports the
|
||||
// queue has drained below the lead margin - normally a reliable gate,
|
||||
// but confirmed unreliable for at least one real device (Bluetooth,
|
||||
// whose real connection/startup latency means PipeWire can under-report
|
||||
// how much is actually queued while the link is still establishing).
|
||||
// When that happens, repeated Feed() calls each see a falsely-small
|
||||
// queued size and each add another full window before the reported size
|
||||
// catches up to reality - heard as the whole clip racing far ahead of
|
||||
// real playback right from the start, with stutters where the audio
|
||||
// backend then has to catch up or drop the backlog. Independent of
|
||||
// whatever SDL reports, never let this stream's own read position get
|
||||
// further ahead of how much real wall-clock time has actually elapsed
|
||||
// since the pass started than a lead-plus-one-window margin - a
|
||||
// device-report-independent ceiling that still allows a full normal
|
||||
// window per call under real (correctly-reported) conditions.
|
||||
const Uint32 elapsedMs = SDL_GetTicks() - stream->platform.passStartTicks;
|
||||
const size_t audibleFrames = (size_t) (
|
||||
((double) elapsedMs / 1000.0) * (double) stream->sampleRate
|
||||
);
|
||||
// Margin is just one lead's worth, not lead+window - deliberately tight,
|
||||
// prioritizing "never let position get far ahead of real time" over
|
||||
// per-call efficiency, while this is still being tracked down. Costs
|
||||
// more (smaller) top-up calls in the steady state than a looser margin
|
||||
// would, which is an acceptable trade for now.
|
||||
const size_t maxPosition = (
|
||||
stream->platform.passStartPosition + audibleFrames +
|
||||
AUDIO_LINUX_LEAD_FRAMES
|
||||
);
|
||||
const size_t positionBudget = maxPosition > stream->platform.position
|
||||
? maxPosition - stream->platform.position
|
||||
: 0;
|
||||
|
||||
const size_t framesToRead = mathMin(
|
||||
mathMin(framesRemaining, (size_t) AUDIO_LINUX_WINDOW_FRAMES), positionBudget
|
||||
);
|
||||
|
||||
if(framesToRead == 0) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -142,6 +180,7 @@ errorret_t audioStreamLinuxFeed(audiostream_t *stream) {
|
||||
}
|
||||
|
||||
stream->platform.position += framesRead;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,13 @@ typedef struct {
|
||||
// the true end of the clip if not looping) - recomputed by
|
||||
// audioStreamLinuxBuffer() at the start of each pass.
|
||||
size_t endFrame;
|
||||
|
||||
// SDL_GetTicks() value and starting frame position when the current
|
||||
// pass started - together, what audioStreamLinuxFeed() paces its own
|
||||
// feeding rate against (see its own comment on why it doesn't trust
|
||||
// SDL_GetQueuedAudioSize() alone).
|
||||
Uint32 passStartTicks;
|
||||
size_t passStartPosition;
|
||||
} audiostreamlinux_t;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user