From bc414c8bda19a9e6c9bef8999ead4babd1da81d9 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sat, 1 Aug 2026 03:40:27 +0000 Subject: [PATCH 1/2] rmp3: add f32 output to the streaming API The stream decoded to s16 only, which left a caller serving a float pipeline - audio_transfer's f32 read, and through it the mixer's float voices - unable to move off the resident pull API without a quantisation it never had before. The decoder core already runs both pipelines; what the stream lacked was a way to select one. rmp3_stream_set_out_f32 is that, shaped like its s16 partner, and selecting either converts the decoder's persistent filter state and any undrained frame in place - the same operation the pull API's reads perform, now factored into shared helpers (rmp3d_convert_state, rmp3d_convert_frames) rather than restated. The frame buffer becomes the same s16/f32 union the pull API holds, named (rmp3_frame_buf) so both sides convert through one function with union-member access and no aliasing games. A parse-only call selects nothing: a walk in the middle of a decode must not disturb the pipeline the decode is in. And rmp3_stream_info's doc now says what the code always did - a located frame is what fills the channel count and rate in, so a parse-only walk answers it without anything having been decoded. A mid-stream channel change now adapts instead of erroring: mono duplicated, stereo averaged into the stream's own layout, the same arithmetic the pull API's reads apply, and a rate change decodes through at the stream's stated rate, which is all the pull API ever did with one. The stream refusing what the pull API played would have turned concatenated-rip joins from a wrong-but-playing tail into a truncation, and the caller this API exists to migrate plays such files today. Verified bit-exact against the pull API as oracle on seven fixtures (LAME CBR 128/320, VBR -q0, MPEG-2 mono 22.05k, Layer II, and stereo+mono / 44.1k+22.05k concatenations): f32 and s16 each across 25 combinations of input window (512 bytes to whole file) and output chunk (4 frames to 256K), and 32-segment alternating s16/f32 sequences at frame counts that land the switches mid-frame with samples pending. ASan, UBSan and LeakSan clean; TSan clean with eight concurrent streams in mixed and switching modes. --- libretro-common/formats/mp3/rmp3.c | 186 ++++++++++++++++++------- libretro-common/include/formats/rmp3.h | 31 ++++- 2 files changed, 162 insertions(+), 55 deletions(-) diff --git a/libretro-common/formats/mp3/rmp3.c b/libretro-common/formats/mp3/rmp3.c index 0c5a97e188fb..6b892001e6f3 100644 --- a/libretro-common/formats/mp3/rmp3.c +++ b/libretro-common/formats/mp3/rmp3.c @@ -2622,42 +2622,42 @@ static uint32_t rmp3_decode_next_frame(rmp3* pMP3) * frame is converted in place (the decoder state has already advanced * past it, so re-decoding is not an option); every later frame is * synthesised natively in the new format. */ -static void rmp3_set_output_mode(rmp3* pMP3, uint32_t f32) +/* The two pipelines keep their persistent decoder state in different + * formats: float for the float pipeline, Q28 fixed point for the s16 + * pipeline (the storage is shared through unions). Convert the QMF + * and IMDCT overlap histories so decoding continues seamlessly in the + * new format. Shared by the pull and streaming APIs, whose format + * switches are the same operation on different bookkeeping. */ +static void rmp3d_convert_state(rmp3dec *dec, uint32_t f32) { - uint32_t total; int i; - if (pMP3->f32_mode == f32) - return; - pMP3->f32_mode = f32; - - /* The two pipelines keep their persistent decoder state in - * different formats: float for the float pipeline, Q28 fixed point - * for the s16 pipeline (the storage is shared through unions). - * Convert the QMF and IMDCT overlap histories so decoding continues - * seamlessly in the new format. */ + float *of = &dec->mdct_overlap.f[0][0]; + int32_t *oq = &dec->mdct_overlap.q[0][0]; + if (f32) { - float *of = &pMP3->decoder.mdct_overlap.f[0][0]; - int32_t *oq = &pMP3->decoder.mdct_overlap.q[0][0]; - if (f32) - { - for (i = 0; i < 15*2*32; i++) - pMP3->decoder.qmf_state.f[i] = - pMP3->decoder.qmf_state.q[i] * (1.0f / (float)(1 << RMP3D_QBITS)); - for (i = 0; i < 2*9*32; i++) - of[i] = oq[i] * (1.0f / (float)(1 << RMP3D_QBITS)); - } - else - { - for (i = 0; i < 15*2*32; i++) - pMP3->decoder.qmf_state.q[i] = - rmp3d_float_to_q(pMP3->decoder.qmf_state.f[i]); - for (i = 0; i < 2*9*32; i++) - oq[i] = rmp3d_float_to_q(of[i]); - } + for (i = 0; i < 15*2*32; i++) + dec->qmf_state.f[i] = + dec->qmf_state.q[i] * (1.0f / (float)(1 << RMP3D_QBITS)); + for (i = 0; i < 2*9*32; i++) + of[i] = oq[i] * (1.0f / (float)(1 << RMP3D_QBITS)); } + else + { + for (i = 0; i < 15*2*32; i++) + dec->qmf_state.q[i] = + rmp3d_float_to_q(dec->qmf_state.f[i]); + for (i = 0; i < 2*9*32; i++) + oq[i] = rmp3d_float_to_q(of[i]); + } +} - total = (pMP3->framesConsumed + pMP3->framesRemaining) - * pMP3->frameChannels; +/* Convert 'total' samples of a buffered frame between the two formats, + * in place. The decoder state has already advanced past the frame, so + * re-decoding is not an option; conversion is what keeps a buffered + * frame playable across a format switch. */ +static void rmp3d_convert_frames(rmp3_frame_buf *fb, uint32_t total, + uint32_t f32) +{ if (total == 0) return; @@ -2667,7 +2667,7 @@ static void rmp3_set_output_mode(rmp3* pMP3, uint32_t f32) * backwards only clobbers s16 slots that are already consumed. */ uint32_t i = total; while (i-- > 0) - pMP3->frames.f32[i] = pMP3->frames.s16[i] * (1.0f / 32768.0f); + fb->f32[i] = fb->s16[i] * (1.0f / 32768.0f); } else { @@ -2676,19 +2676,30 @@ static void rmp3_set_output_mode(rmp3* pMP3, uint32_t f32) uint32_t i; for (i = 0; i < total; i++) { - float s = pMP3->frames.f32[i] * 32768.0f; + float s = fb->f32[i] * 32768.0f; int v; - if (s > 32767.0f) { pMP3->frames.s16[i] = 32767; continue; } - if (s < -32768.0f) { pMP3->frames.s16[i] = -32768; continue; } + if (s > 32767.0f) { fb->s16[i] = 32767; continue; } + if (s < -32768.0f) { fb->s16[i] = -32768; continue; } v = (int)(s + .5f); v -= (v < 0); /* round half away from zero, as rmp3d_scale_pcm */ if (v > 32767) v = 32767; if (v < -32768) v = -32768; - pMP3->frames.s16[i] = (int16_t)v; + fb->s16[i] = (int16_t)v; } } } +static void rmp3_set_output_mode(rmp3* pMP3, uint32_t f32) +{ + if (pMP3->f32_mode == f32) + return; + pMP3->f32_mode = f32; + rmp3d_convert_state(&pMP3->decoder, f32); + rmp3d_convert_frames(&pMP3->frames, + (pMP3->framesConsumed + pMP3->framesRemaining) + * pMP3->frameChannels, f32); +} + uint32_t rmp3_init_memory(rmp3* pMP3, const void* pData, size_t dataSize) { if (!pMP3) @@ -2889,9 +2900,14 @@ struct rmp3_stream size_t in_size; size_t in_pos; + /* One destination or the other, the same way the pull API's two + * reads are one decoder: whichever set_out_* ran last names the + * pipeline, and f32_mode says which. */ int16_t *out; + float *outf; size_t out_frames; size_t out_pos; + uint32_t f32_mode; /* A frame straddling two windows is reassembled here rather than * asking the caller to guarantee alignment it cannot know. */ @@ -2902,8 +2918,9 @@ struct rmp3_stream uint64_t frames_in; /* MPEG frames consumed since reset */ /* One decoded frame, drained across as many calls as the caller's - * output takes to accept it. */ - int16_t pcm[RMP3_MAX_SAMPLES_PER_FRAME]; + * output takes to accept it; held in whichever format the synthesis + * ran in, converted in place if the caller switches. */ + rmp3_frame_buf pcm; unsigned pending; unsigned drained; @@ -2936,6 +2953,22 @@ void rmp3_stream_set_in(rmp3_stream_t *s, const void *in, size_t in_size) s->in_pos = 0; } +/* Selecting a pipeline is the same operation as the pull API's reads + * perform: convert the decoder's persistent filter state, and whatever + * of the last decoded frame has not been drained, so decoding + * continues seamlessly in the new format. A parse-only call selects + * nothing - a walk in the middle of a decode must not disturb the + * pipeline the decode is in. */ +static void rmp3_stream_set_mode(rmp3_stream_t *s, uint32_t f32) +{ + if (s->f32_mode == f32) + return; + s->f32_mode = f32; + rmp3d_convert_state(&s->dec, f32); + rmp3d_convert_frames(&s->pcm, + (uint32_t)s->pending * s->channels, f32); +} + void rmp3_stream_set_out_s16(rmp3_stream_t *s, int16_t *out, size_t out_frames) { if (!s) @@ -2943,8 +2976,23 @@ void rmp3_stream_set_out_s16(rmp3_stream_t *s, int16_t *out, size_t out_frames) /* A null destination, or no room in it, means parse-only: frames are * located and counted, nothing is decoded. */ s->out = out; + s->outf = NULL; + s->out_frames = out ? out_frames : 0; + s->out_pos = 0; + if (out && out_frames) + rmp3_stream_set_mode(s, 0); +} + +void rmp3_stream_set_out_f32(rmp3_stream_t *s, float *out, size_t out_frames) +{ + if (!s) + return; + s->out = NULL; + s->outf = out; s->out_frames = out ? out_frames : 0; s->out_pos = 0; + if (out && out_frames) + rmp3_stream_set_mode(s, 1); } int rmp3_stream_info(const rmp3_stream_t *s, unsigned *channels, unsigned *rate) @@ -3008,7 +3056,8 @@ int rmp3_stream_process(rmp3_stream_t *s, size_t *read, size_t *wrote) const uint8_t *src; size_t avail; int samples; - int parse_only = (!s->out || !s->out_frames); + int parse_only = + ((!s->out && !s->outf) || !s->out_frames); /* Hand over what the last frame produced before decoding another: * one frame is up to 1152 PCM frames and a caller's buffer may be @@ -3021,9 +3070,14 @@ int rmp3_stream_process(rmp3_stream_t *s, size_t *read, size_t *wrote) { if ((size_t)take > room) take = (unsigned)room; - memcpy(s->out + s->out_pos * (size_t)s->channels, - s->pcm + (size_t)s->drained * (size_t)s->channels, - (size_t)take * (size_t)s->channels * sizeof(int16_t)); + if (s->f32_mode) + memcpy(s->outf + s->out_pos * (size_t)s->channels, + s->pcm.f32 + (size_t)s->drained * (size_t)s->channels, + (size_t)take * (size_t)s->channels * sizeof(float)); + else + memcpy(s->out + s->out_pos * (size_t)s->channels, + s->pcm.s16 + (size_t)s->drained * (size_t)s->channels, + (size_t)take * (size_t)s->channels * sizeof(int16_t)); s->out_pos += take; } s->drained += take; @@ -3084,7 +3138,8 @@ int rmp3_stream_process(rmp3_stream_t *s, size_t *read, size_t *wrote) memset(&info, 0, sizeof(info)); samples = rmp3dec_decode_frame(&s->dec, src, (int)avail, - parse_only ? NULL : (void*)s->pcm, &info, 0); + parse_only ? NULL : (void*)&s->pcm, &info, + (int)s->f32_mode); if (!samples && !info.frame_bytes) { @@ -3120,11 +3175,46 @@ int rmp3_stream_process(rmp3_stream_t *s, size_t *read, size_t *wrote) s->rate = (unsigned)info.hz; s->started = 1; } - /* A stream that changes geometry partway is not something the - * fixed output layout can express. */ - else if ((unsigned)info.channels != s->channels - || (unsigned)info.hz != s->rate) - return RMP3_STREAM_ERROR; + /* A malformed stream that changes channel count partway is + * adapted to the stream's own layout - mono duplicated, + * stereo averaged, the same arithmetic the pull API's reads + * apply - so the interleave stays consistent. A rate change + * likewise decodes through at the stream's stated rate, which + * is all the pull API ever did with one. */ + else if (!parse_only + && (unsigned)info.channels != s->channels) + { + if (s->channels == 2) + { + /* Widening in place: pair 2i,2i+1 overlays sample i, + * so walking backwards reads each source before its + * slots are clobbered. */ + int i = samples; + if (s->f32_mode) + while (i-- > 0) + s->pcm.f32[2*i] = s->pcm.f32[2*i + 1] + = s->pcm.f32[i]; + else + while (i-- > 0) + s->pcm.s16[2*i] = s->pcm.s16[2*i + 1] + = s->pcm.s16[i]; + } + else + { + /* Narrowing in place: sample i overlays half of the + * pair 2i,2i+1, which is behind the read position + * walking forwards. */ + int i; + if (s->f32_mode) + for (i = 0; i < samples; i++) + s->pcm.f32[i] = (s->pcm.f32[2*i] + + s->pcm.f32[2*i + 1]) * 0.5f; + else + for (i = 0; i < samples; i++) + s->pcm.s16[i] = (int16_t)(((int32_t)s->pcm.s16[2*i] + + (int32_t)s->pcm.s16[2*i + 1]) >> 1); + } + } if (parse_only) { diff --git a/libretro-common/include/formats/rmp3.h b/libretro-common/include/formats/rmp3.h index 00b8fcc103bd..03ae2473c43e 100644 --- a/libretro-common/include/formats/rmp3.h +++ b/libretro-common/include/formats/rmp3.h @@ -68,6 +68,15 @@ typedef struct } scratch; } rmp3dec; +/* One decoded MPEG frame, in whichever format the synthesis ran in. + * The two pipelines share the storage through this union; a format + * switch converts the contents in place. */ +typedef union +{ + int16_t s16[RMP3_MAX_SAMPLES_PER_FRAME]; + float f32[RMP3_MAX_SAMPLES_PER_FRAME]; +} rmp3_frame_buf; + /* Main API (Pull API) * ===================*/ @@ -80,11 +89,7 @@ typedef struct uint32_t framesConsumed; /* PCM frames of the current block already returned. Internal. */ uint32_t framesRemaining; /* PCM frames of the current block still to return. Internal. */ uint32_t f32_mode; /* 0: synthesis outputs s16; 1: native float. Internal. */ - union - { - int16_t s16[RMP3_MAX_SAMPLES_PER_FRAME]; - float f32[RMP3_MAX_SAMPLES_PER_FRAME]; - } frames; /* Current decoded frame in the latched format. */ + rmp3_frame_buf frames; /* Current decoded frame in the latched format. */ const uint8_t* pData; /* Caller's buffer (borrowed, never freed). */ size_t dataSize; size_t readPos; /* Read cursor into pData. */ @@ -177,21 +182,33 @@ void rmp3_stream_set_in(rmp3_stream_t *s, const void *in, size_t in_size); /** * rmp3_stream_set_out_s16: + * rmp3_stream_set_out_f32: * * Sets the destination and its capacity in frames, interleaved at the * stream's own channel count. A null destination, or a capacity of * zero, is parse-only. + * + * The two entry points select the same two pipelines the pull API + * runs: s16 synthesises in Q28 fixed point straight to s16, f32 in + * native float. They may be mixed freely on one stream - the decoder's + * persistent filter state and any undrained frame are converted in + * place at the switch, exactly as the pull API's reads do - but a + * parse-only call selects nothing, so a walk in the middle of a decode + * does not disturb the pipeline the decode is in. */ void rmp3_stream_set_out_s16(rmp3_stream_t *s, int16_t *out, size_t out_frames); +void rmp3_stream_set_out_f32(rmp3_stream_t *s, float *out, size_t out_frames); int rmp3_stream_process(rmp3_stream_t *s, size_t *read, size_t *wrote); /** * rmp3_stream_info: * - * Returns: nonzero once a frame has been decoded, which is the point + * Returns: nonzero once a frame has been located, which is the point * the channel count and rate are known - MPEG audio has no header - * ahead of the stream to read them from. + * ahead of the stream to read them from, so they come off the first + * frame's own header. A parse-only walk locates frames too, so this + * answers there without anything having been decoded. */ int rmp3_stream_info(const rmp3_stream_t *s, unsigned *channels, unsigned *rate); From f949f8ce254fd405745af9b2450a46c135791e22 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sat, 1 Aug 2026 03:52:20 +0000 Subject: [PATCH 2/2] audio_transfer: move the plain-MP3 arm onto rmp3_stream The last arm holding a whole-file decoder over its buffer. rmp3's pull API borrows the pointer and cursors over all of it, so the frontier buffer_tell reported (readPos) named bytes merely read, not bytes releasable, and windowing an MP3 the way the Ogg arm windows a Vorbis file had nothing to stand on. The stream consumes its input - what is behind the cursor is genuinely done with, a frame possibly straddling the boundary held in the stream's own 4 KB reassembly hold - so the cursor kept here is a real frontier, leading the decode position by at most that hold, the safe side for a feeder. Open walks the stream parse-only to the first frame, which is where the channel count and rate come from and decodes nothing, then rewinds for the reads; the Xing/LAME/VBRI parse and the header-walk length fallback are untouched. Reads drain through the stream in either pipeline, freely mixed as before. Seeking adopts the Opus arm's shape: recorded at seek(), performed at the following read as a reset and decode forward in that read's own pipeline, and refused up front where it lies past a known length. That closes two seams the resident decoder carried. Its rewind kept the synthesis filter's state - QMF and overlap history from wherever playback had been - so the first frames after any seek, and after every loop back to the start, deviated from a linear decode; and it walked forward in f32 whatever the caller's format, converting the filter state through a float round-trip on the way. Measured against linear ground truth, the old arm deviated on every fixture at every seek target and on every loop pass (1-342 bytes per 16 K window, the worst on a rate-change join); the new arm is byte-identical to the playthrough everywhere, in both formats. Behaviour otherwise intended to be identical, and verified so against the old arm linked beside the new one: linear decode byte-exact in s16 and f32 across chunk sizes 1 to 64 K frames, mixed-format segment sequences byte-exact, info() identical, on seven fixtures including stereo+mono and 44.1k+22.05k concatenations (which the stream now adapts as the pull API did). What is not identical, beyond the seek correctness above: buffer_tell reports the consumption frontier rather than the pull cursor's scan position; a seek moves nothing until the next read, so a tell between the two still names the old position; and a stream the length walk could not measure (free format) fails a past-the-end seek at the read that performs it rather than at seek() itself. ASan, UBSan and LeakSan clean over open/decode/seek/loop/teardown, short and truncated files, and a garbage buffer whose open is refused without leaking the stream; TSan clean with eight concurrent contexts mixing formats and seeking. --- libretro-common/formats/audio_transfer.c | 247 +++++++++++++++++++---- 1 file changed, 208 insertions(+), 39 deletions(-) diff --git a/libretro-common/formats/audio_transfer.c b/libretro-common/formats/audio_transfer.c index a6df2d46ff85..f6e061d02848 100644 --- a/libretro-common/formats/audio_transfer.c +++ b/libretro-common/formats/audio_transfer.c @@ -286,9 +286,23 @@ static size_t audio_transfer_ogg_page(const uint8_t *buf, size_t size, * only what is wider than that. * * MP3 (rmp3) - * Does: buffer input; s16 (quantised by the synthesis filter, no float - * round trip) and f32; channels and rate from the first frame; seek - * to any PCM frame; buffer_tell, hence windowing. + * Does: buffer input, decoded through rmp3_stream - the buffer is + * consumed incrementally rather than borrowed whole, so + * buffer_tell reports a real frontier, the bytes behind it + * releasable rather than merely read (it leads the decode position + * by at most the stream's reassembly hold, the safe side for a + * feeder); s16 (quantised by the synthesis filter, no float round + * trip) and f32, freely mixed - the stream converts its filter + * state at the switch as the pull API did; channels and rate from + * the first frame's header, read at open by a parse-only walk that + * decodes nothing; seek to any PCM frame, recorded at seek() and + * performed at the following read as a decode forward from the + * head in that read's own pipeline, so the audio after a seek is + * byte-identical to a linear decode's - MP3 frames depend on the + * bit reservoir of their predecessors, so the top is the only + * sample-accurate way in, which is also what the resident decoder + * did, minus its habit of carrying stale synthesis state through + * the rewind. * Length and gapless trim from the Xing/Info or VBRI header in the * first frame: that frame carries no audio but is decoded like any * other, so the priming dropped is its own length plus the encoder @@ -607,8 +621,17 @@ struct audio_transfer_mp3 { const void *data; size_t size; - rmp3 handle; /* dr_mp3 initialises this in place (by value) */ - int inited; /* handle is embedded, so track init state a flag */ + rmp3_stream_t *stream; /* opened decoder, NULL until start() succeeds */ + size_t off; /* consumption cursor: the compressed frontier */ + unsigned channels; /* from the first frame's header, read at start() */ + unsigned rate; + int eof_sent; /* the stream has been told the tail is all there is */ + /* Recorded rather than done: an MP3 seek is a decode forward from + * the head, and which pipeline it decodes in belongs to the read + * that follows, so the output after a seek is byte-identical to a + * linear decode in that read's own format. Same shape as the Opus + * arm's seek_to. */ + int64_t seek_to; /* Gapless, from the Xing/Info or VBRI header in the first frame. * That frame carries no audio but dr_mp3 decodes it like any * other, so the priming to drop is its own length plus the @@ -1742,6 +1765,89 @@ static int audio_transfer_mp3_gapless(const uint8_t *b, size_t len, #endif #ifdef HAVE_RMP3 +/* Drain 'frames' frames out of the stream in one of the two pipelines, + * feeding it the unconsumed tail of the buffer as it asks. The + * compressed cursor is kept here rather than in the decoder because a + * windowed feeder reads it, and because a seek re-points it. Returns + * the frames written, short of the ask at end of stream. */ +static size_t audio_transfer_mp3_pull(struct audio_transfer_mp3 *m, + int s16, int16_t *o16, float *of, size_t frames) +{ + size_t done = 0; + if (!m->stream || !frames) + return 0; + while (done < frames) + { + size_t rd = 0, wr = 0; + int r; + if (s16) + rmp3_stream_set_out_s16(m->stream, + o16 + done * (size_t)m->channels, frames - done); + else + rmp3_stream_set_out_f32(m->stream, + of + done * (size_t)m->channels, frames - done); + rmp3_stream_set_in(m->stream, + (const uint8_t*)m->data + m->off, m->size - m->off); + r = rmp3_stream_process(m->stream, &rd, &wr); + m->off += rd; + done += wr; + if (r == RMP3_STREAM_ERROR || r == RMP3_STREAM_END) + break; + if (r == RMP3_STREAM_NEED_IN && m->off >= m->size) + { + /* A frame must be presented whole, so the tail of the stream + * sits in the hold waiting for a window that will never fill; + * this is what says the short tail is all there is. Latched + * until a seek resets the stream. */ + if (m->eof_sent && !wr) + break; + rmp3_stream_set_eof(m->stream); + m->eof_sent = 1; + } + } + return done; +} + +/* Restart the stream and decode forward to @frame, discarding, in the + * pipeline of the read that asked - which is what makes the output + * after the seek byte-identical to a linear decode's. MP3 frames + * depend on the bit reservoir of their predecessors, so decoding from + * the top is the only sample-accurate way in. Frame numbers here are + * of the played audio; the priming sits before frame 0 and is part of + * the distance. Returns the frame reached, or < 0 if the stream ends + * first. */ +static int64_t audio_transfer_mp3_seek_to(struct audio_transfer_mp3 *m, + int64_t frame, int s16) +{ + /* Somewhere to throw the frames walked past. On the stack, and one + * union rather than two arrays: these were a pair of function-local + * statics, 24 KiB of BSS of which only ever half was in use, and + * shared by every context. audio_mixer runs up to + * AUDIO_MIXER_MAX_VOICES at once, so two MP3 streams priming in the + * same callback wrote over each other's sink. Small because it is + * only a sink - the loop below iterates until the distance is gone. */ + union { int16_t s16[256]; float f32[256]; } skip; + uint64_t left = (uint64_t)frame + m->start_trim; + unsigned ch = m->channels ? m->channels : 1; + + rmp3_stream_reset(m->stream); + m->off = 0; + m->eof_sent = 0; + while (left) + { + size_t cap = (sizeof(skip.s16) / sizeof(skip.s16[0])) / ch; + size_t want = (left < (uint64_t)cap) ? (size_t)left : cap; + size_t n = audio_transfer_mp3_pull(m, s16, + skip.s16, skip.f32, want); + if (!n) + return -1; /* the stream ends before the target */ + left -= n; + } + m->trim_left = 0; + m->emitted = frame; + return frame; +} + /* Read past the priming and stop at the padding. The trim is dropped * by decoding it and throwing it away - there is nowhere else for it * to go, the frames being coded - and the bound is what the tag says @@ -1749,23 +1855,15 @@ static int audio_transfer_mp3_gapless(const uint8_t *b, size_t len, static size_t audio_transfer_mp3_read(struct audio_transfer_mp3 *m, int s16, int16_t *o16, float *of, size_t frames) { - unsigned ch = m->handle.channels ? m->handle.channels : 1; size_t got; - /* Somewhere to throw the primed frames. On the stack, and one - * union rather than two arrays: these were a pair of function-local - * statics, 24 KiB of BSS of which only ever half was in use, and - * shared by every context. audio_mixer runs up to - * AUDIO_MIXER_MAX_VOICES at once, so two MP3 streams priming in the - * same callback wrote over each other's sink. Small because it is - * only a sink - the loop below iterates until the trim is gone. */ union { int16_t s16[256]; float f32[256]; } skip; + unsigned ch = m->channels ? m->channels : 1; while (m->trim_left) { - size_t cap = (size_t)256 / ch; + size_t cap = (sizeof(skip.s16) / sizeof(skip.s16[0])) / ch; size_t want = (m->trim_left < cap) ? (size_t)m->trim_left : cap; - size_t n = s16 - ? (size_t)rmp3_read_s16(&m->handle, (uint64_t)want, skip.s16) - : (size_t)rmp3_read_f32(&m->handle, (uint64_t)want, skip.f32); + size_t n = audio_transfer_mp3_pull(m, s16, + skip.s16, skip.f32, want); if (!n) { m->trim_left = 0; /* stream ended inside the priming */ @@ -1781,8 +1879,7 @@ static size_t audio_transfer_mp3_read(struct audio_transfer_mp3 *m, if ((int64_t)frames > left) frames = (size_t)left; } - got = s16 ? (size_t)rmp3_read_s16(&m->handle, (uint64_t)frames, o16) - : (size_t)rmp3_read_f32(&m->handle, (uint64_t)frames, of); + got = audio_transfer_mp3_pull(m, s16, o16, of, frames); m->emitted += (int64_t)got; return got; } @@ -2707,10 +2804,50 @@ bool audio_transfer_start(void *data, enum audio_type_enum type) struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; if (!m || !m->data) return false; - m->inited = (rmp3_init_memory(&m->handle, m->data, m->size) != 0); - if (!m->inited) + if (!(m->stream = rmp3_stream_new())) return false; - m->limit = -1; + /* Walk parse-only until the first frame is located, which is + * where the channel count and rate come from - MPEG audio has + * no header ahead of the stream to read them from. Locating + * decodes nothing, so the walk costs a scan; a buffer with no + * frame in it fails here, as the resident open did. Then back + * to the head, so the first read decodes from frame one. */ + m->off = 0; + m->eof_sent = 0; + for (;;) + { + size_t rd = 0, wr = 0; + int r; + rmp3_stream_set_out_s16(m->stream, NULL, 0); + rmp3_stream_set_in(m->stream, + (const uint8_t*)m->data + m->off, m->size - m->off); + r = rmp3_stream_process(m->stream, &rd, &wr); + m->off += rd; + if (rmp3_stream_info(m->stream, &m->channels, &m->rate)) + break; + if (r == RMP3_STREAM_NEED_IN && m->off >= m->size + && !m->eof_sent) + { + /* A file shorter than the reassembly hold never fills + * it; say the short tail is all there is, so its frame + * is still found. */ + rmp3_stream_set_eof(m->stream); + m->eof_sent = 1; + continue; + } + if (r == RMP3_STREAM_ERROR || r == RMP3_STREAM_END + || (r == RMP3_STREAM_NEED_IN && m->off >= m->size)) + { + rmp3_stream_free(m->stream); + m->stream = NULL; + return false; + } + } + rmp3_stream_reset(m->stream); + m->off = 0; + m->eof_sent = 0; + m->seek_to = -1; + m->limit = -1; { uint64_t fr = 0, delay = 0; if (!audio_transfer_mp3_gapless((const uint8_t*)m->data, @@ -3141,7 +3278,7 @@ bool audio_transfer_is_valid(void *data, enum audio_type_enum type) case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - return (m && m->inited); + return (m && m->stream); } #endif #ifdef HAVE_RMODTRACKER @@ -3252,12 +3389,12 @@ bool audio_transfer_info(void *data, enum audio_type_enum type, case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (!m || !m->inited) + if (!m || !m->stream) return false; if (channels) - *channels = (unsigned)m->handle.channels; + *channels = m->channels; if (rate) - *rate = (unsigned)m->handle.sampleRate; + *rate = m->rate; /* From the Xing/Info or VBRI count, less the gapless trim * where a LAME tag states one; 0 for a file carrying no * such header, which cannot be measured without a walk. */ @@ -3957,8 +4094,15 @@ int audio_transfer_read_s16(void *data, enum audio_type_enum type, case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (!m || !m->inited) + if (!m || !m->stream) return AUDIO_PROCESS_ERROR; + if (m->seek_to >= 0) + { + int64_t at = audio_transfer_mp3_seek_to(m, m->seek_to, 1); + m->seek_to = -1; + if (at < 0) + return AUDIO_PROCESS_ERROR; + } produced = audio_transfer_mp3_read(m, 1, out, NULL, frames); break; } @@ -4167,8 +4311,15 @@ int audio_transfer_read_f32(void *data, enum audio_type_enum type, case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (!m || !m->inited) + if (!m || !m->stream) return AUDIO_PROCESS_ERROR; + if (m->seek_to >= 0) + { + int64_t at = audio_transfer_mp3_seek_to(m, m->seek_to, 0); + m->seek_to = -1; + if (at < 0) + return AUDIO_PROCESS_ERROR; + } produced = audio_transfer_mp3_read(m, 0, NULL, out, frames); break; } @@ -4418,8 +4569,13 @@ size_t audio_transfer_buffer_tell(void *data, enum audio_type_enum type) { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (m->inited) - return (size_t)m->handle.readPos; + /* The stream consumes the buffer as it plays, so the cursor + * kept alongside it is the compressed frontier - a real one, + * the bytes behind it being releasable rather than merely + * read. It leads the decode position by at most the stream's + * reassembly hold, which is the safe side for a feeder. */ + if (m->stream) + return m->off; return 0; } #endif @@ -4672,15 +4828,28 @@ bool audio_transfer_seek(void *data, enum audio_type_enum type, case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (!m || !m->inited) + if (!m || !m->stream) return false; - /* Frame numbers here are of the played audio, so the - * priming sits before frame 0. */ - if (!rmp3_seek_to_frame(&m->handle, - (uint64_t)frame + m->start_trim)) + /* Where the length is known, refuse to be sent past it rather + * than walk to the end and report failure from there. */ + if (m->limit >= 0 && (int64_t)frame > m->limit) return false; - m->trim_left = 0; - m->emitted = (int64_t)frame; + if (frame == 0 && !m->start_trim) + { + /* Loop-to-start with nothing to prime past: a rewind, done + * here rather than deferred. */ + rmp3_stream_reset(m->stream); + m->off = 0; + m->eof_sent = 0; + m->seek_to = -1; + m->trim_left = 0; + m->emitted = 0; + return true; + } + /* Recorded, not done: see seek_to. Frame numbers here are of + * the played audio, so the priming sits before frame 0 and the + * walk at the read covers both. */ + m->seek_to = (int64_t)frame; return true; } #endif @@ -4812,8 +4981,8 @@ void audio_transfer_free(void *data, enum audio_type_enum type) case AUDIO_TYPE_MP3: { struct audio_transfer_mp3 *m = (struct audio_transfer_mp3*)data; - if (m->inited) - rmp3_uninit(&m->handle); + if (m->stream) + rmp3_stream_free(m->stream); break; } #endif