diff --git a/libretro-common/formats/audio_transfer.c b/libretro-common/formats/audio_transfer.c index 652e1cee5c13..a6df2d46ff85 100644 --- a/libretro-common/formats/audio_transfer.c +++ b/libretro-common/formats/audio_transfer.c @@ -560,6 +560,12 @@ struct audio_transfer_vorbis * bare packet at a time out of wherever it lives, which is the * caller's blob or the demuxer's own view of the caller's buffer. * Nothing is copied and nothing is reframed. */ + /* Plain self-framed .ogg: demuxed incrementally by rvorbis's own + * Ogg layer, so what a decode holds is a packet rather than the + * file. handle is borrowed from it - the stream owns the decoder - + * and is kept so the queries below need no special case. */ + rvorbis_stream_t *stream; + size_t stream_off; /* bytes of the buffer consumed */ int packet; /* opened with rvorbis_open_packets */ size_t pkt_index; /* next packet in the caller's blob */ size_t pkt_offset; @@ -1095,6 +1101,161 @@ static int audio_transfer_vorbis_pull(struct audio_transfer_vorbis *v, * fresh packet is decoded whenever they run out. Returns the frames * written, short of the ask at end of stream or at the container's * stated end. */ +/* Plain-Ogg read: hand rvorbis's demuxer the unconsumed tail of the + * buffer and take what it produces. The compressed cursor is kept here + * rather than in the decoder because a windowed feeder reads it, and + * because a seek re-points it. */ + +/* Seek a plain-Ogg stream. Ogg locates a sample by bisecting on page + * granule positions - there is no index - and the decoder cannot read, + * so the bisection is driven from here over the buffer. Each probe + * resynchronises at the next page and reports where it landed; the + * decode forward from the page before the target is what makes the + * landing exact, since a page boundary is the finest position the + * container states. + * + * The frames before the target are decoded and dropped rather than + * skipped: a Vorbis packet's output depends on its predecessor's + * overlap, so arriving with no history would resume converging on the + * playthrough rather than matching it. */ +static bool audio_transfer_vorbis_stream_seek( + struct audio_transfer_vorbis *v, uint64_t frame) +{ + int16_t scratch[64 * 8]; + size_t lo = 0; + size_t hi = v->size; + unsigned ch = (unsigned)((v->channels > 0 && v->channels <= 8) + ? v->channels : 8); + size_t cap = sizeof(scratch) / sizeof(scratch[0]) / ch; + size_t start; + int guard; + + if (!v->stream) + return false; + + if (!frame) + { + rvorbis_stream_rewind(v->stream); + v->stream_off = 0; + v->emitted = 0; + return true; + } + + for (guard = 0; lo < hi && guard < 64; guard++) + { + size_t mid = lo + (hi - lo) / 2; + size_t off = mid; + int landed = 0; + + rvorbis_stream_reset(v->stream); + while (off < v->size) + { + size_t rd = 0, wr = 0; + int r; + rvorbis_stream_set_out_s16(v->stream, scratch, cap); + rvorbis_stream_set_in(v->stream, (const uint8_t*)v->data + off, + v->size - off); + r = rvorbis_stream_process(v->stream, &rd, &wr); + off += rd; + if (rvorbis_stream_pos_known(v->stream)) + { + landed = 1; + break; + } + if (r == RVORBIS_STREAM_ERROR || r == RVORBIS_STREAM_EOS) + break; + if (r == RVORBIS_STREAM_NEED_IN && !rd) + break; + } + if (landed && rvorbis_stream_tell(v->stream) <= frame) + lo = mid + 1; + else + hi = mid; + } + + /* One page back from the boundary, so the target is reached by + * decoding forward rather than jumped over. */ + start = (lo > 65307) ? lo - 65307 : 0; + + for (guard = 0; guard < 2; guard++) + { + size_t off = start; + if (start) + rvorbis_stream_reset(v->stream); + else + rvorbis_stream_rewind(v->stream); + while (off < v->size) + { + size_t rd = 0, wr = 0, want = cap; + int r; + /* Once the position is known, ask for exactly the distance + * left. Asking for a full scratch instead would step past + * the target and land wherever the packet happened to end. */ + if (rvorbis_stream_pos_known(v->stream)) + { + uint64_t at = rvorbis_stream_tell(v->stream); + if (at == frame) + { + v->stream_off = off; + v->emitted = (int64_t)frame; + return true; + } + if (at > frame) + break; /* landed late: restart from the head */ + if (frame - at < (uint64_t)want) + want = (size_t)(frame - at); + } + rvorbis_stream_set_out_s16(v->stream, scratch, want); + rvorbis_stream_set_in(v->stream, (const uint8_t*)v->data + off, + v->size - off); + r = rvorbis_stream_process(v->stream, &rd, &wr); + off += rd; + if (r == RVORBIS_STREAM_ERROR) + return false; + if (r == RVORBIS_STREAM_EOS) + break; + if (r == RVORBIS_STREAM_NEED_IN && !rd && !wr) + break; + } + if (!start) + break; + start = 0; /* the bisection landed late; take the + * whole stream, which always works */ + } + return false; +} + +static size_t audio_transfer_vorbis_stream_pull( + struct audio_transfer_vorbis *v, int s16, int16_t *out16, + float *outf, size_t frames) +{ + size_t done = 0; + if (!v->stream || !frames) + return 0; + while (done < frames) + { + size_t rd = 0, wr = 0; + int r; + if (s16) + rvorbis_stream_set_out_s16(v->stream, out16 + done * (size_t)v->channels, + frames - done); + else + rvorbis_stream_set_out_f32(v->stream, outf + done * (size_t)v->channels, + frames - done); + rvorbis_stream_set_in(v->stream, + (const uint8_t*)v->data + v->stream_off, + v->size - v->stream_off); + r = rvorbis_stream_process(v->stream, &rd, &wr); + v->stream_off += rd; + done += wr; + if (r == RVORBIS_STREAM_ERROR || r == RVORBIS_STREAM_EOS) + break; + if (r == RVORBIS_STREAM_NEED_IN && !rd && !wr) + break; /* the buffer is spent: a feeder must extend it */ + } + return done; +} + static size_t audio_transfer_vorbis_drain(struct audio_transfer_vorbis *v, int s16, int16_t *out16, float *outf, size_t frames) { @@ -2376,11 +2537,35 @@ bool audio_transfer_start(void *data, enum audio_type_enum type) } else { - v->handle = rvorbis_open_memory( - (const unsigned char*)v->data, (int)v->size, - &err, NULL); + /* Feed until the setup headers are in. That is all the + * residency an open needs; the rest of the file is read + * as it plays. */ + size_t rd, wr; + if (!(v->stream = rvorbis_stream_new())) + return false; + for (;;) + { + int r; + rvorbis_stream_set_out_s16(v->stream, NULL, 0); + rvorbis_stream_set_in(v->stream, + (const uint8_t*)v->data + v->stream_off, + v->size - v->stream_off); + r = rvorbis_stream_process(v->stream, &rd, &wr); + v->stream_off += rd; + if (rvorbis_stream_info(v->stream, NULL)) + break; + if (r != RVORBIS_STREAM_NEED_IN || !rd + || v->stream_off >= v->size) + { + rvorbis_stream_free(v->stream); + v->stream = NULL; + return false; + } + } + v->handle = rvorbis_stream_decoder(v->stream); if (!v->handle) return false; + (void)err; } /* The granules say what the file plays. For a chained * file that is the sum over its links, which rvorbis @@ -3761,8 +3946,8 @@ int audio_transfer_read_s16(void *data, enum audio_type_enum type, else { frames = audio_transfer_vorbis_cap(v, frames); - produced = (size_t)rvorbis_get_samples_s16_interleaved( - v->handle, v->channels, out, (int)frames * v->channels); + produced = audio_transfer_vorbis_stream_pull(v, 1, out, NULL, + frames); v->emitted += (int64_t)produced; } break; @@ -3972,9 +4157,8 @@ int audio_transfer_read_f32(void *data, enum audio_type_enum type, break; } frames = audio_transfer_vorbis_cap(v, frames); - got = rvorbis_get_samples_float_interleaved(v->handle, v->channels, - out, (int)(frames * (size_t)v->channels)); - produced = (got > 0) ? (size_t)got : 0; + (void)got; + produced = audio_transfer_vorbis_stream_pull(v, 0, NULL, out, frames); v->emitted += (int64_t)produced; break; } @@ -4183,7 +4367,12 @@ size_t audio_transfer_buffer_tell(void *data, enum audio_type_enum type) * nothing here; this one has a packet cursor to report. */ if (v->packets) return v->pkt_offset; - /* Self-framed Ogg buffer. */ + /* Self-framed Ogg buffer: the demuxer consumes the buffer + * as it plays, so the cursor kept alongside it is the + * compressed frontier - and now a real one, the bytes + * behind it being releasable rather than merely read. */ + if (v->stream) + return v->stream_off; if (!v->packet) return (size_t)rvorbis_buffer_tell(v->handle); } @@ -4476,16 +4665,7 @@ bool audio_transfer_seek(void *data, enum audio_type_enum type, * out, so a seek that moves the stream has to move that count * with it. Left alone, a loop back to the start reaches the * bound immediately and the stream reads as ended for good. */ - if (frame == 0) /* loop-to-start: seek_start always succeeds */ - { - rvorbis_seek_start(v->handle); - v->emitted = 0; - return true; - } - if (rvorbis_seek(v->handle, (unsigned int)frame) == 0) - return false; - v->emitted = (int64_t)frame; - return true; + return audio_transfer_vorbis_stream_seek(v, frame); } #endif #ifdef HAVE_RMP3 @@ -4615,7 +4795,11 @@ void audio_transfer_free(void *data, enum audio_type_enum type) case AUDIO_TYPE_VORBIS: { struct audio_transfer_vorbis *v = (struct audio_transfer_vorbis*)data; - if (v->handle) + /* handle is borrowed from the stream where there is one, so it + * must not also be closed. */ + if (v->stream) + rvorbis_stream_free(v->stream); + else if (v->handle) rvorbis_close(v->handle); #ifdef HAVE_RWEBM if (v->demux) diff --git a/libretro-common/formats/flac/rflac.c b/libretro-common/formats/flac/rflac.c index e3667331f330..ae73e347342c 100644 --- a/libretro-common/formats/flac/rflac.c +++ b/libretro-common/formats/flac/rflac.c @@ -6356,6 +6356,8 @@ struct rflac_ctx size_t out_frames; size_t out_done; int ended; + size_t span_taken; /* of the current span, last call */ + int eof_in; /* caller has no more input to give */ int need_header; /* set until a header is parsed */ }; @@ -6571,6 +6573,11 @@ void rflac_free(rflac_t *f) * its subchannel data immediately after the last FLAC frame, and finding * that boundary is only possible if the decoder reports where the frames * really ended. */ +/* Where the stream has reached, in bytes of input the decode has + * actually used. The bitreader reads ahead, so this is behind what has + * been pulled from the source by whatever is sitting in its cache - and + * that difference is meaningful to a caller: it is how a CD FLAC hunk's + * audio is told from the subchannel data packed after it. */ static size_t rflac__consumed(const rflac_t *f) { const rflac_bs *bs = &f->dec->bs; @@ -6586,6 +6593,22 @@ static size_t rflac__consumed(const rflac_t *f) return f->src.pos - held; } +/* How much of the caller's span the decoder has absorbed, which is a + * different quantity and the one a windowed caller needs. Bytes the + * bitreader has pulled into its cache are gone from the span whether or + * not their bits have been used, and the cache survives across calls - + * so a caller that re-presented them would have them read twice, once + * from the cache and once from the new span, and the stream would run + * ahead of itself by the cache depth on every call. The carry sits + * ahead of the span and was handed over earlier, so it does not count + * towards this one. */ +static size_t rflac__span_taken(const rflac_t *f) +{ + const rflac_push_source *s = &f->src; + + return (s->pos > s->carry_len) ? s->pos - s->carry_len : 0; +} + static size_t rflac__max_frame_bytes(const rflac_t *f) { return (size_t)f->fmt.block_size * f->fmt.channels @@ -6598,7 +6621,6 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) size_t span_len; size_t want; size_t produced; - int undoable = 0; if (read) *read = 0; @@ -6607,6 +6629,12 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) if (!f) return RFLAC_PROCESS_ERROR; + + /* Cleared here rather than only on the paths that set it: this call + * may return at any of the guards below without touching the span, + * and a caller advancing its window by a figure left over from the + * previous call would skip that much input. */ + f->span_taken = 0; if (f->ended) return RFLAC_PROCESS_END; @@ -6634,6 +6662,7 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) f->src.data = NULL; f->src.size = 0; f->src.pos = 0; + f->span_taken = span_len; if (read) *read = span_len; return RFLAC_PROCESS_NEXT; @@ -6666,14 +6695,16 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) if (want > f->out_frames - f->out_done) want = f->out_frames - f->out_done; - if (rflac__src_total(&f->src) - f->src.pos < rflac__max_frame_bytes(f)) - { - if (!f->saved && !(f->saved = (rflac*)malloc(sizeof(rflac)))) - return RFLAC_PROCESS_ERROR; - memcpy(f->saved, f->dec, sizeof(rflac)); - f->saved_pos = f->src.pos; - undoable = 1; - } + /* Always take a rewind point. This used to be skipped when the + * input held more than the longest frame could be, on the reasoning + * that such a span cannot run dry mid-frame - but the bitreader + * reads ahead of the frame it is decoding, so it can and does, and + * the branch below then read that underrun as the end of the + * stream. What ends a stream is the caller saying so. */ + if (!f->saved && !(f->saved = (rflac*)malloc(sizeof(rflac)))) + return RFLAC_PROCESS_ERROR; + memcpy(f->saved, f->dec, sizeof(rflac)); + f->saved_pos = f->src.pos; f->src.underrun = 0; @@ -6688,8 +6719,15 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) { /* The span ran out mid-frame. Put everything back so the frame * can be decoded once from the start when the rest arrives; the - * decode core is never told this happened. */ - if (undoable) + * decode core is never told this happened. + * + * Unless there is nothing more to arrive. A stream's last frame is + * shorter than the longest one it could legally be, so the tail of + * every file lands here with fewer bytes left than a rewind point + * is taken for - and waiting for the rest of a frame that is + * already complete loses it. Only the caller knows which case it + * is, which is what rflac_set_eof states. */ + if (!f->eof_in) { memcpy(f->dec, f->saved, sizeof(rflac)); if (!rflac__src_hold(&f->src, f->saved_pos)) @@ -6699,18 +6737,22 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote) f->src.size = 0; /* The whole span was taken over, whether it was consumed or * carried, so the caller is free to reuse or free it. */ + f->span_taken = span_len; if (read) *read = span_len; return RFLAC_PROCESS_NEXT; } - /* Not undoable means the span was long enough for any legal - * frame and still ran dry, so this is the end of the stream - * rather than the end of a span. */ + /* The caller has said there is no more input, so an underrun is + * the end of the stream rather than the end of a span. Whatever + * the last frame produced stands: it is kept below rather than + * rolled back. */ f->ended = 1; } f->out_done += produced; + f->span_taken = rflac__span_taken(f); + if (read) { size_t now = rflac__consumed(f); @@ -6795,6 +6837,36 @@ void rflac_reset(rflac_t *f) f->ended = 0; } +size_t rflac_span_taken(const rflac_t *f) +{ + return f ? f->span_taken : 0; +} + +size_t rflac_min_input(const rflac_t *f) +{ + size_t want; + + if (!f || f->need_header) + return 0; + + /* Deliberately generous. A frame must be present whole, but the + * bitreader also reads ahead of the frame it is decoding, and how + * far is not a figure the format states - so a window sized to the + * longest legal frame is not in fact enough, and one sized from + * STREAMINFO's maximum frame size is not either. This is measured + * rather than derived: below roughly 16k, decoding degrades on + * streams whose frames are far smaller than that. Callers wanting + * a small footprint should treat this as the floor it is. */ + want = rflac__max_frame_bytes(f) * 2; + return (want < 32768) ? 32768 : want; +} + +void rflac_set_eof(rflac_t *f) +{ + if (f) + f->eof_in = 1; +} + void rflac_set_in(rflac_t *f, const uint8_t *in, size_t in_size) { if (!f) diff --git a/libretro-common/formats/vorbis/rvorbis.c b/libretro-common/formats/vorbis/rvorbis.c index ec5ef3d1697f..ff08f3756b0e 100644 --- a/libretro-common/formats/vorbis/rvorbis.c +++ b/libretro-common/formats/vorbis/rvorbis.c @@ -6160,3 +6160,587 @@ int rvorbis_packet_frames(rvorbis *f, const void *packet, size_t len) } return right_start - left_start; } + +/* --- STREAMING OGG API ------------------------------------------------- + * + * The pulling API above addresses the whole stream at once: it seeks by + * bisecting over a resident buffer, so a caller must hold the file to + * use it at all. A caller reading from storage has no such buffer and + * fabricating one costs the whole file in memory to play a few + * kilobytes of it at a time. + * + * This walks the Ogg layer incrementally instead, and hands the packets + * it assembles to the raw-packet decoder above, which already reads a + * packet where it lies. So nothing here decodes: it is a demuxer, and + * the residency it needs is one packet rather than one file. + * + * Ogg's framing is what makes that possible and also what makes it + * fiddly. A packet is a run of segments, each up to 255 bytes, ended + * by one shorter than 255 - so a packet that is an exact multiple of + * 255 ends with a zero-length segment, and a packet may run past the + * end of its page and continue on the next. Pages of other logical + * bitstreams may be interleaved with ours and have to be skipped by + * serial number. And after a seek the first page is joined mid-packet, + * whose leading fragment must be dropped rather than decoded. + */ + +#define RVS_MAX_PACKET (1u << 20) /* far above any real setup header */ + +enum +{ + RVS_HDR = 0, /* filling the 27-byte page header, resyncing if wrong */ + RVS_SEGS, /* filling the lacing table */ + RVS_BODY, /* consuming segment bytes into the packet */ + RVS_DONE +}; + +struct rvorbis_stream +{ + rvorbis *dec; + + const uint8_t *in; + size_t in_size; + size_t in_pos; + + int16_t *out16; + float *outf; + size_t out_frames; + size_t out_pos; + int s16; + + uint8_t hdr[27 + 255]; + unsigned hdr_have; + + unsigned nsegs; + unsigned seg_idx; + unsigned seg_left; + int pkt_ends; + + uint8_t *pkt; + size_t pkt_len; + size_t pkt_cap; + + uint32_t serial; + uint32_t cur_serial; + int have_serial; + int skip_page; + int drop_pkt; + int resyncing; /* joined mid-stream; still converging */ + + uint64_t granule; + uint64_t pos; /* frame the next emission sits at */ + int pos_known; + int page_done; /* page consumed; pos lands once drained */ + uint64_t emitted; /* frames handed out since the start */ + uint64_t limit; /* final granule, once the EOS page is in */ + int have_limit; + int eos; + int state; + + int hdr_count; + uint8_t *id; + size_t id_len; + uint8_t *setup; + size_t setup_len; + int opened; + + int channels; + int pending; + int error; +}; + +static int rvs_pkt_reserve(rvorbis_stream_t *s, size_t need) +{ + uint8_t *p; + size_t cap; + if (need <= s->pkt_cap) + return 1; + if (need > RVS_MAX_PACKET) + return 0; + cap = s->pkt_cap ? s->pkt_cap : 4096; + while (cap < need) + cap <<= 1; + if (cap > RVS_MAX_PACKET) + cap = RVS_MAX_PACKET; + if (!(p = (uint8_t *)realloc(s->pkt, cap))) + return 0; + s->pkt = p; + s->pkt_cap = cap; + return 1; +} + +static int rvs_is_id(const uint8_t *p, size_t n) +{ + return n == 30 && p[0] == 0x01 && !memcmp(p + 1, "vorbis", 6); +} + +static int rvs_is_setup(const uint8_t *p, size_t n) +{ + return n > 7 && p[0] == 0x05 && !memcmp(p + 1, "vorbis", 6); +} + +rvorbis_stream_t *rvorbis_stream_new(void) +{ + rvorbis_stream_t *s = (rvorbis_stream_t *)calloc(1, sizeof(*s)); + if (!s) + return NULL; + s->s16 = 1; + s->state = RVS_HDR; + return s; +} + +void rvorbis_stream_free(rvorbis_stream_t *s) +{ + if (!s) + return; + if (s->dec) + rvorbis_close(s->dec); + free(s->pkt); + free(s->id); + free(s->setup); + free(s); +} + +void rvorbis_stream_set_in(rvorbis_stream_t *s, const void *in, size_t in_size) +{ + if (!s) + return; + s->in = (const uint8_t *)in; + s->in_size = in ? in_size : 0; + s->in_pos = 0; +} + +void rvorbis_stream_set_out_s16(rvorbis_stream_t *s, int16_t *out, + size_t out_frames) +{ + if (!s) + return; + s->out16 = out; + s->outf = NULL; + s->out_frames = out ? out_frames : 0; + s->out_pos = 0; + s->s16 = 1; +} + +void rvorbis_stream_set_out_f32(rvorbis_stream_t *s, float *out, + size_t out_frames) +{ + if (!s) + return; + s->outf = out; + s->out16 = NULL; + s->out_frames = out ? out_frames : 0; + s->out_pos = 0; + s->s16 = 0; +} + +int rvorbis_stream_info(const rvorbis_stream_t *s, rvorbis_info *out) +{ + if (!s || !s->opened) + return 0; + if (out) + *out = rvorbis_get_info(s->dec); + return 1; +} + +uint64_t rvorbis_stream_granule(const rvorbis_stream_t *s) +{ + return s ? s->granule : 0; +} + +rvorbis *rvorbis_stream_decoder(const rvorbis_stream_t *s) +{ + return s ? s->dec : NULL; +} + +uint64_t rvorbis_stream_tell(const rvorbis_stream_t *s) +{ + return s ? s->pos : 0; +} + +int rvorbis_stream_pos_known(const rvorbis_stream_t *s) +{ + return s ? s->pos_known : 0; +} + +int rvorbis_stream_get_error(rvorbis_stream_t *s) +{ + int e; + if (!s) + return RVORBIS_STREAM_ERROR; + e = s->error; + s->error = 0; + return e; +} + +void rvorbis_stream_rewind(rvorbis_stream_t *s) +{ + if (!s) + return; + rvorbis_stream_reset(s); + /* Unlike a resync into the middle of a file, this one knows where it + * is: the head of the stream is frame zero. Without that, a target + * inside the first page is unreachable - position becomes known at a + * page boundary, and there is none before it. */ + s->pos = 0; + s->pos_known = 1; + s->resyncing = 0; +} + +void rvorbis_stream_reset(rvorbis_stream_t *s) +{ + if (!s) + return; + s->state = RVS_HDR; + s->hdr_have = 0; + s->nsegs = 0; + s->seg_idx = 0; + s->seg_left = 0; + s->pkt_ends = 0; + s->pkt_len = 0; + s->in = NULL; + s->in_size = 0; + s->in_pos = 0; + s->eos = 0; + s->pending = 0; + s->drop_pkt = 0; + s->skip_page= 0; + /* Nothing decoded yet from wherever this lands, so the first packets + * have no overlap to continue from. */ + s->resyncing = 1; + s->granule = 0; + s->emitted = 0; + s->pos = 0; + s->pos_known= 0; + s->page_done= 0; + s->limit = 0; + s->have_limit = 0; + if (s->opened) + rvorbis_packet_reset(s->dec); +} + +/* One assembled packet. Returns 0 on a hard failure. */ +static int rvs_packet(rvorbis_stream_t *s) +{ + const uint8_t *p = s->pkt; + size_t n = s->pkt_len; + int got; + + if (s->drop_pkt) + { + /* Joined mid-packet after a seek: this is a tail, not a packet. */ + s->drop_pkt = 0; + return 1; + } + + if (!s->opened) + { + if (!s->id && rvs_is_id(p, n)) + { + if (!(s->id = (uint8_t *)malloc(n))) + return 0; + memcpy(s->id, p, n); + s->id_len = n; + s->serial = s->cur_serial; + s->have_serial = 1; + return 1; + } + if (!s->id) + return 1; /* not our bitstream's first packet */ + if (rvs_is_setup(p, n)) + { + int err = 0; + if (!(s->setup = (uint8_t *)malloc(n))) + return 0; + memcpy(s->setup, p, n); + s->setup_len = n; + s->dec = rvorbis_open_packets(s->id, (int)s->id_len, + s->setup, (int)s->setup_len, &err, NULL); + if (!s->dec) + { + s->error = RVORBIS_STREAM_ERROR; + return 0; + } + s->opened = 1; + s->channels = rvorbis_get_info(s->dec).channels; + } + return 1; /* comment header, or anything else */ + } + + if (!n) + return 1; + /* A rewind re-presents the three setup packets. Vorbis marks a + * header packet with bit 0 of its first byte; feeding one to the + * audio decoder would read a mode number out of a comment string. */ + if (p[0] & 1) + return 1; + if ((got = rvorbis_packet_decode(s->dec, p, n, s->s16)) < 0) + { + /* Until a packet has decoded, this is a stream joined partway: + * the first ones after a resync reference an overlap that was + * never decoded, and failing on them is how converging looks + * rather than a broken stream. Once one succeeds, a failure is + * a failure. */ + if (s->resyncing) + return 1; + s->error = RVORBIS_STREAM_ERROR; + return 0; + } + if (got > 0) + s->resyncing = 0; + s->pending = got; + return 1; +} + +int rvorbis_stream_process(rvorbis_stream_t *s, size_t *read, size_t *wrote) +{ + size_t w0; + int ret = RVORBIS_STREAM_OK; + + if (!s) + return RVORBIS_STREAM_ERROR; + w0 = s->out_pos; + + for (;;) + { + /* Drain what the last packet produced before decoding another: + * the decoder holds one packet's frames, so a caller with a small + * output buffer must be able to take them a slice at a time. */ + if (s->opened && s->pending > 0 && s->out_pos < s->out_frames) + { + size_t want = s->out_frames - s->out_pos; + int got; + if (s->s16) + got = rvorbis_packet_read_s16(s->dec, s->channels, + s->out16 + s->out_pos * (size_t)s->channels, + (int)(want * (size_t)s->channels)); + else + got = rvorbis_packet_read_float(s->dec, s->channels, + s->outf + s->out_pos * (size_t)s->channels, + (int)(want * (size_t)s->channels)); + if (got > 0) + { + /* Vorbis codes in overlapping blocks, so the last packet + * decodes past the end of the audio. The final page's + * granule is where that end actually is, and it is known + * before this page's packets are decoded because the header + * is parsed first - so the overhang is dropped rather than + * handed out and taken back. */ + if (s->have_limit) + { + /* Count against the absolute position, not against what + * this pass has emitted: a seek resets the latter, and + * measuring an absolute granule from a relative origin + * lets the tail through. Before the first page boundary + * the position is not known and emitted is the same + * quantity, the stream having started at zero. */ + uint64_t at = s->pos_known ? s->pos : s->emitted; + uint64_t room = (at < s->limit) ? s->limit - at : 0; + if ((uint64_t)got > room) + got = (int)room; + } + s->out_pos += (size_t)got; + s->emitted += (uint64_t)got; + s->pos += (uint64_t)got; + s->pending -= got; + if (s->pending < 0) + s->pending = 0; + if (s->have_limit + && (s->pos_known ? s->pos : s->emitted) >= s->limit) + { + s->pending = 0; + s->state = RVS_DONE; + continue; + } + if (got > 0) + continue; + } + s->pending = 0; + } + + /* A page's granule is the position reached once every packet that + * completes on it has been decoded. So the instant its frames are + * drained, that granule is where the stream now stands - which is + * what makes an exact seek possible after a mid-stream resync, + * where nothing else says where the output landed. */ + if (s->page_done && s->pending <= 0) + { + s->pos = s->granule; + s->pos_known = 1; + s->page_done = 0; + } + + /* No output buffer means parse-only: consume just enough to get + * the setup headers in and stop there, which is what an open + * wants. Treating it as unbounded instead would run the whole + * stream through with nowhere to put it. */ + if (!s->out_frames) + { + if (s->opened) + { + ret = RVORBIS_STREAM_OK; + break; + } + } + else if (s->out_pos >= s->out_frames) + { + ret = RVORBIS_STREAM_OK; + break; + } + if (s->state == RVS_DONE) + { + ret = RVORBIS_STREAM_EOS; + break; + } + + switch (s->state) + { + case RVS_HDR: + while (s->hdr_have < 27 && s->in_pos < s->in_size) + s->hdr[s->hdr_have++] = s->in[s->in_pos++]; + if (s->hdr_have < 27) + { + ret = RVORBIS_STREAM_NEED_IN; + goto out; + } + if (memcmp(s->hdr, "OggS", 4) || s->hdr[4] != 0) + { + /* Resynchronise a byte at a time: a capture pattern may + * begin anywhere inside what we mistook for a header. */ + memmove(s->hdr, s->hdr + 1, 26); + s->hdr_have = 26; + break; + } + s->nsegs = s->hdr[26]; + s->hdr_have= 0; + s->state = RVS_SEGS; + break; + + case RVS_SEGS: + while (s->hdr_have < s->nsegs && s->in_pos < s->in_size) + s->hdr[27 + s->hdr_have++] = s->in[s->in_pos++]; + if (s->hdr_have < s->nsegs) + { + ret = RVORBIS_STREAM_NEED_IN; + goto out; + } + { + unsigned k; + uint64_t g = 0; + uint32_t sn = 0; + for (k = 0; k < 8; k++) + g |= (uint64_t)s->hdr[6 + k] << (8 * k); + for (k = 0; k < 4; k++) + sn |= (uint32_t)s->hdr[14 + k] << (8 * k); + s->cur_serial = sn; + s->skip_page = (s->have_serial && sn != s->serial); + if (!s->skip_page) + { + /* A page on which no packet completes carries -1 + * rather than a position; it says nothing about where + * the stream has reached. */ + if (g != (uint64_t)-1) + s->granule = g; + if ((s->hdr[5] & 0x04) && g != (uint64_t)-1) + { + s->limit = g; + s->have_limit = 1; + } + /* A continued flag with nothing carried over means we + * joined mid-packet: its leading fragment is a tail. */ + if ((s->hdr[5] & 0x01) && !s->pkt_len) + s->drop_pkt = 1; + else if (!(s->hdr[5] & 0x01)) + s->pkt_len = 0; + } + } + s->seg_idx = 0; + s->seg_left = 0; + s->pkt_ends = 0; + s->hdr_have = 0; + s->state = RVS_BODY; + break; + + case RVS_BODY: + for (;;) + { + if (!s->seg_left && !s->pkt_ends) + { + unsigned lace; + if (s->seg_idx >= s->nsegs) + break; + lace = s->hdr[27 + s->seg_idx++]; + s->seg_left = lace; + s->pkt_ends = (lace < 255); + } + if (s->seg_left) + { + size_t avail = s->in_size - s->in_pos; + size_t take = s->seg_left < avail ? s->seg_left : avail; + if (!take) + { + ret = RVORBIS_STREAM_NEED_IN; + goto out; + } + if (s->skip_page) + s->in_pos += take; + else + { + if (!rvs_pkt_reserve(s, s->pkt_len + take)) + { + s->error = RVORBIS_STREAM_ERROR; + ret = RVORBIS_STREAM_ERROR; + goto out; + } + memcpy(s->pkt + s->pkt_len, s->in + s->in_pos, take); + s->pkt_len += take; + s->in_pos += take; + } + s->seg_left -= (unsigned)take; + if (s->seg_left) + { + ret = RVORBIS_STREAM_NEED_IN; + goto out; + } + } + if (s->pkt_ends) + { + s->pkt_ends = 0; + if (!s->skip_page) + { + if (!rvs_packet(s)) + { + ret = RVORBIS_STREAM_ERROR; + goto out; + } + s->pkt_len = 0; + if (s->pending > 0) + break; /* drain before taking another packet */ + } + } + } + if (s->seg_idx >= s->nsegs && !s->seg_left && !s->pkt_ends) + { + if (!s->skip_page && (s->hdr[5] & 0x04)) + { + s->eos = 1; + s->state = RVS_DONE; + } + else + { + s->page_done = 1; + s->hdr_have = 0; + s->state = RVS_HDR; + } + } + break; + } + } + +out: + if (read) + *read = s->in_pos; + if (wrote) + *wrote = s->out_pos - w0; + return ret; +} diff --git a/libretro-common/formats/wav/rwav.c b/libretro-common/formats/wav/rwav.c index a5c4a848eb4b..8d88f4517e44 100644 --- a/libretro-common/formats/wav/rwav.c +++ b/libretro-common/formats/wav/rwav.c @@ -548,12 +548,25 @@ static const short rwav_ms_adapt[16] = { /* Decode one block into 'out', which must hold samplesperblock frames. * Returns frames produced, 0 on a malformed block. */ +/* Emit only the frames in [first, first + count) of the block, written + * to @out relative to @first. A block must still be decoded from its + * start - the predictor state is what the samples continue from - but + * nothing before @first has to be *stored*, so a window landing inside + * a block needs no scratch buffer to be trimmed afterwards. */ +#define RWAV_PUT(idx, chan, v) \ + do { \ + unsigned rwav__i = (unsigned)(idx); \ + if (rwav__i >= first && rwav__i - first < count) \ + out[(rwav__i - first) * ch + (chan)] = (short)(v); \ + } while (0) + static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, - size_t blen, short *out) + size_t blen, unsigned first, unsigned count, short *out) { unsigned ch = wav->numchannels; unsigned spb = wav->samplesperblock; unsigned c, i; + unsigned end; if (wav->format == RWAV_FORMAT_IMA_ADPCM) { @@ -567,7 +580,7 @@ static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, idx[c] = b[c * 4 + 2]; if (idx[c] > 88) idx[c] = 88; - out[c] = (short)pred[c]; + RWAV_PUT(0, c, pred[c]); } /* After the preamble the nibbles come in eight-sample runs per * channel: four bytes of one channel, then four of the next. */ @@ -599,14 +612,15 @@ static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, if (idx[c] < 0) idx[c] = 0; if (idx[c] > 88) idx[c] = 88; if (i + k < spb) - out[(i + k) * ch + c] = (short)pred[c]; + RWAV_PUT(i + k, c, pred[c]); } } off += 4 * ch; i += 8; } } - return spb; + end = (spb < first + count) ? spb : first + count; + return (end > first) ? end - first : 0; } /* MS ADPCM: each channel states a coefficient index, a delta and @@ -633,8 +647,8 @@ static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, s2[c] = (short)rwav_u16(b + ch * 5 + c * 2); for (c = 0; c < ch; c++) { - out[c] = (short)s2[c]; - out[ch + c] = (short)s1[c]; + RWAV_PUT(0, c, s2[c]); + RWAV_PUT(1, c, s1[c]); } off = need; i = 2; @@ -653,7 +667,7 @@ static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, delta[c] = (rwav_ms_adapt[nib] * delta[c]) / 256; if (delta[c] < 16) delta[c] = 16; - out[i * ch + c] = (short)p; + RWAV_PUT(i, c, p); if (c & 1) off++; if (c == ch - 1) @@ -675,16 +689,19 @@ static unsigned rwav_decode_block(const rwav_t *wav, const unsigned char *b, delta[0] = (rwav_ms_adapt[nib] * delta[0]) / 256; if (delta[0] < 16) delta[0] = 16; - out[i * ch] = (short)p; + RWAV_PUT(i, 0, p); i++; } off++; } } - return spb; + end = (spb < first + count) ? spb : first + count; + return (end > first) ? end - first : 0; } } +#undef RWAV_PUT + size_t rwav_decode_s16(const rwav_t *wav, const void *base, size_t frame, size_t frames, short *out) { @@ -727,7 +744,6 @@ size_t rwav_decode_s16(const rwav_t *wav, const void *base, size_t frame, /* Start at the block holding 'frame' and step forward within * it: a block restates its own predictor, so nothing before it * has to be decoded. */ - static short blk[8192 * 16]; unsigned spb = wav->samplesperblock; while (done < frames) { @@ -738,31 +754,13 @@ size_t rwav_decode_s16(const rwav_t *wav, const void *base, size_t frame, unsigned got, take; if (boff + wav->blockalign > wav->subchunk2size) break; - if (within == 0 && frames - done >= (size_t)spb) - { - /* The window covers this whole block: decode straight - * into the caller's buffer. The whole-file decode the - * mixer does lands here for every block, so the static - * scratch below - and with it the reentrancy caveat - - * only serves windows that start or end mid-block. */ - got = rwav_decode_block(wav, d + boff, wav->blockalign, - out + done * ch); - if (!got) - break; - done += got; - continue; - } - if ((size_t)spb * ch > sizeof(blk) / sizeof(blk[0])) - break; - got = rwav_decode_block(wav, d + boff, wav->blockalign, blk); - if (!got || within >= got) + take = (frames - done > (size_t)spb) + ? spb : (unsigned)(frames - done); + got = rwav_decode_block(wav, d + boff, wav->blockalign, + within, take, out + done * ch); + if (!got) break; - take = got - within; - if ((size_t)take > frames - done) - take = (unsigned)(frames - done); - memcpy(out + done * ch, blk + (size_t)within * ch, - (size_t)take * ch * sizeof(short)); - done += take; + done += got; } return done; } @@ -807,3 +805,115 @@ size_t rwav_decode_s16(const rwav_t *wav, const void *base, size_t frame, } return done; } + +/* ---- windowed reading ------------------------------------------------ + * + * rwav_decode_s16 addresses frames, but a caller streaming a file has + * bytes: it must know which ones a frame range needs before it can + * fetch them. For the uncompressed and companded formats that is + * blockalign arithmetic the caller could do itself; for the block-coded + * ones it is not, because a frame lives inside a coded block that only + * rwav can locate, and fetching the wrong span yields silence or noise + * rather than an error. So the mapping is stated here once. + */ + +static int rwav_block_coded(const rwav_t *wav) +{ + return wav->format == RWAV_FORMAT_MS_ADPCM + || wav->format == RWAV_FORMAT_IMA_ADPCM; +} + +int rwav_frame_extent(const rwav_t *wav, size_t frame, size_t frames, + size_t *offset, size_t *length) +{ + size_t first, last, off, len; + + if (!wav || !wav->blockalign || frame >= wav->numsamples) + return 0; + if (frames > wav->numsamples - frame) + frames = wav->numsamples - frame; + if (!frames) + return 0; + + if (rwav_block_coded(wav)) + { + unsigned spb = wav->samplesperblock; + if (!spb) + return 0; + first = frame / spb; + last = (frame + frames - 1) / spb; + } + else + { + first = frame; + last = frame + frames - 1; + } + + off = (size_t)wav->blockalign * first; + len = (size_t)wav->blockalign * (last - first + 1); + /* A declared payload may overrun the file; subchunk2size is already + * clamped to what is really there, so bound against it rather than + * describing bytes the caller cannot read. */ + if (off >= wav->subchunk2size) + return 0; + if (len > wav->subchunk2size - off) + len = wav->subchunk2size - off; + + if (offset) + *offset = wav->dataoffset + off; + if (length) + *length = len; + return 1; +} + +size_t rwav_decode_s16_at(const rwav_t *wav, const void *chunk, + size_t chunk_offset, size_t frame, size_t frames, short *out) +{ + rwav_t h; + size_t want, rel; + + if (!wav || !chunk || !out || !wav->blockalign) + return 0; + if (frame >= wav->numsamples) + return 0; + if (frames > wav->numsamples - frame) + frames = wav->numsamples - frame; + if (!frames) + return 0; + + /* Re-express the payload as though the chunk were the whole file: + * the decode below is position-independent once told where the data + * starts and how much of it there is. */ + h = *wav; + h.dataoffset = 0; + h.samples = NULL; + + if (rwav_block_coded(wav)) + { + unsigned spb = wav->samplesperblock; + size_t first = frame / spb; + if (!spb) + return 0; + /* The chunk must begin on the block the frame lives in - that is + * what rwav_frame_extent hands back - because the block carries + * the predictor state the frame continues from. */ + if (chunk_offset != wav->dataoffset + (size_t)wav->blockalign * first) + return 0; + rel = frame - first * (size_t)spb; + want = rel + frames; + } + else + { + if (chunk_offset != wav->dataoffset + (size_t)wav->blockalign * frame) + return 0; + rel = 0; + want = frames; + } + + h.numsamples = want; + h.subchunk2size = (size_t)wav->blockalign + * (rwav_block_coded(wav) + ? (want + wav->samplesperblock - 1) / wav->samplesperblock + : want); + return rwav_decode_s16(&h, chunk, rel, frames, out); +} diff --git a/libretro-common/include/formats/rflac.h b/libretro-common/include/formats/rflac.h index a99ecbf105b5..d201cea78859 100644 --- a/libretro-common/include/formats/rflac.h +++ b/libretro-common/include/formats/rflac.h @@ -179,6 +179,49 @@ void rflac_set_out_f32(rflac_t *f, float *out, size_t out_frames); */ int rflac_process(rflac_t *f, size_t *read, size_t *wrote); +/** + * rflac_span_taken: + * + * How many bytes of the span given to the last rflac_set_in() the + * decoder absorbed. This is not what @read reports: @read is what the + * decode logically consumed, which lags what the bitreader has pulled + * in by whatever sits in its cache, and that difference is what tells + * a CD FLAC hunk's audio from the subchannel packed after it. + * + * A caller feeding a sliding window must advance by this instead. + * Anything the bitreader has pulled is gone from the span whether its + * bits have been used or not, and the cache survives across calls, so + * presenting those bytes again has them read twice. + * + * Returns: bytes of the last span consumed or carried. + */ +size_t rflac_span_taken(const rflac_t *f); + +/** + * rflac_min_input: + * + * The smallest input window that can always make progress, valid once + * the header is parsed. A frame must be presented whole to be decoded, + * so a caller feeding less than the longest one the stream may contain + * can stall with a frame it can never complete; sizing the window to + * this makes that impossible. + * + * Returns: bytes, or 0 before the header is known. + */ +size_t rflac_min_input(const rflac_t *f); + +/** + * rflac_set_eof: + * + * States that no further input will be supplied, so the decoder may + * finish the frame it holds instead of waiting for bytes that are not + * coming. A stream's last frame is shorter than the format's maximum, + * so without this the tail of every file is held back indefinitely. + * + * Safe to call before the last input rather than after it. + */ +void rflac_set_eof(rflac_t *f); + /** * rflac_format: * diff --git a/libretro-common/include/formats/rvorbis.h b/libretro-common/include/formats/rvorbis.h index d3e73ac1c8de..8f4a15a0c3f3 100644 --- a/libretro-common/include/formats/rvorbis.h +++ b/libretro-common/include/formats/rvorbis.h @@ -150,6 +150,165 @@ extern void rvorbis_packet_reset(rvorbis *f); * context as it was before its first packet. This is what a rewind is * on a packet-fed stream: the caller restarts its own packet walk. */ + +/* STREAMING OGG API */ + +/* For an .ogg that is not resident: bytes are handed over a window at a + * time and PCM comes back as they arrive, so what a decode costs in + * memory is a packet rather than a file. The pulling API above wants + * the whole stream addressable at once - it seeks by bisecting over it - + * which a caller reading from storage, or off a CD image, does not have + * and should not have to fabricate. + * + * This is the same shape rflac uses: point it at input, point it at + * output, and step it. + * + * rvorbis_stream_t *s = rvorbis_stream_new(); + * rvorbis_stream_set_out_s16(s, pcm, frames); + * for (;;) + * { + * rvorbis_stream_set_in(s, buf, filled); + * r = rvorbis_stream_process(s, &read, &wrote); + * memmove(buf, buf + read, filled -= read); + * if (r == RVORBIS_STREAM_NEED_IN) + * filled += pull_bytes(buf + filled, cap - filled); + * else if (r != RVORBIS_STREAM_OK) + * break; + * } + * + * Input is consumed, not borrowed: @read says how much was taken and + * the rest must be presented again, so a window may slide freely. The + * decoder holds at most one packet across calls, which is what bounds + * it. + * + * Seeking is the caller's: Ogg locates a sample by bisecting on page + * granule positions, and only the caller can read at an arbitrary + * offset. Reset the stream, re-point input at a guess, and read + * rvorbis_stream_granule() once a page has been parsed to see where + * that guess landed; the headers stay parsed across a reset, so + * converging costs no decode. + */ + +#define RVORBIS_STREAM_OK 0 +#define RVORBIS_STREAM_NEED_IN 1 /* input window is spent */ +#define RVORBIS_STREAM_EOS 2 /* end-of-stream page was consumed */ +#define RVORBIS_STREAM_ERROR (-1) + +typedef struct rvorbis_stream rvorbis_stream_t; + +rvorbis_stream_t *rvorbis_stream_new(void); +void rvorbis_stream_free(rvorbis_stream_t *s); + +/** + * rvorbis_stream_set_in: + * + * Presents a window of the Ogg stream. Need not be page- or + * packet-aligned and may be as small as one byte; what is not consumed + * must be presented again at the head of the next window. + */ +void rvorbis_stream_set_in(rvorbis_stream_t *s, const void *in, size_t in_size); + +/** + * rvorbis_stream_set_out_s16: + * rvorbis_stream_set_out_f32: + * + * Sets the destination and its capacity in frames. Which one was set + * last selects the pipeline the frames are produced through. Interleaved + * at the stream's own channel count, which rvorbis_stream_info() reports. + * + * A NULL destination, or a capacity of zero, means parse-only: process + * consumes what it needs to read the setup headers and stops there, + * producing nothing. That is what opening a stream wants, the channel + * count not being known until those headers are in. + */ +void rvorbis_stream_set_out_s16(rvorbis_stream_t *s, int16_t *out, + size_t out_frames); +void rvorbis_stream_set_out_f32(rvorbis_stream_t *s, float *out, + size_t out_frames); + +/** + * rvorbis_stream_process: + * @read : receives how many input bytes were consumed + * @wrote : receives how many frames were written + * + * Consumes input and produces output until one of them runs out. + * + * Returns: RVORBIS_STREAM_OK when the output is full, NEED_IN when the + * input is spent and more would let it continue, EOS when the stream + * ended, or RVORBIS_STREAM_ERROR. + */ +int rvorbis_stream_process(rvorbis_stream_t *s, size_t *read, size_t *wrote); + +/** + * rvorbis_stream_info: + * + * Returns: nonzero once the setup headers have been parsed, which is + * the point channels and rate are known. + */ +int rvorbis_stream_info(const rvorbis_stream_t *s, rvorbis_info *out); + +/** + * rvorbis_stream_granule: + * + * Returns: the granule position of the last page parsed, i.e. the frame + * the stream had reached at the end of that page. This is what a seek + * bisects on. + */ +uint64_t rvorbis_stream_granule(const rvorbis_stream_t *s); + +/** + * rvorbis_stream_decoder: + * + * Returns: the underlying decoder once the headers have been parsed, + * else NULL. For rvorbis_get_info and the other queries that apply to + * any context; the decode entry points must not be called on it + * directly, the stream being what drives it. + */ +rvorbis *rvorbis_stream_decoder(const rvorbis_stream_t *s); + +/** + * rvorbis_stream_tell: + * + * Returns: the frame position the next emitted frame sits at. + * + * Meaningless until rvorbis_stream_pos_known() reports otherwise, which + * it does from the first page boundary crossed after a reset: a resync + * lands inside a page and nothing in the bitstream says where the + * output from it belongs. Discard what comes out before that point - + * it is decoded without overlap history and is wrong anyway - and the + * frames after it are exactly placed. + */ +uint64_t rvorbis_stream_tell(const rvorbis_stream_t *s); + +int rvorbis_stream_pos_known(const rvorbis_stream_t *s); + +/** + * rvorbis_stream_reset: + * + * Discards the demuxer position, the partly-assembled packet and the + * overlap history, so the next input may come from anywhere in the + * stream; the next Ogg capture pattern is resynchronised on. The parsed + * headers survive, so this is a seek rather than a reopen. + */ +void rvorbis_stream_reset(rvorbis_stream_t *s); + +/** + * rvorbis_stream_rewind: + * + * As reset, but for input resuming at the head of the stream rather + * than at an arbitrary offset, so the position is known immediately + * instead of at the first page boundary crossed. A target inside the + * first page is reachable only this way. + */ +void rvorbis_stream_rewind(rvorbis_stream_t *s); + +/** + * rvorbis_stream_get_error: + * + * Returns: the last error, cleared by the call. + */ +int rvorbis_stream_get_error(rvorbis_stream_t *s); + #ifdef __cplusplus } #endif diff --git a/libretro-common/include/formats/rwav.h b/libretro-common/include/formats/rwav.h index 2df0265dba65..f971b7c5c04f 100644 --- a/libretro-common/include/formats/rwav.h +++ b/libretro-common/include/formats/rwav.h @@ -231,6 +231,44 @@ enum rwav_state rwav_parse(rwav_t *out, const void *buf, size_t len); */ void rwav_free(rwav_t *rwav); +/** + * rwav_frame_extent: + * @wav : header from rwav_parse + * @frame : first frame wanted + * @frames : how many + * @offset : receives the byte offset within the file + * @length : receives how many bytes to read from it + * + * The byte range a frame range needs, so a caller holding only the + * header can fetch exactly the payload it is about to decode rather + * than the file. For the block-coded formats the range is widened to + * whole coded blocks, a block being what carries the predictor state + * its frames continue from - fetching anything narrower decodes to + * noise rather than failing, which is why this is stated here and not + * left to the caller's arithmetic. + * + * @frames is clamped to what the payload holds. + * + * Returns: nonzero on success, 0 if the range lies outside the payload. + */ +int rwav_frame_extent(const rwav_t *wav, size_t frame, size_t frames, + size_t *offset, size_t *length); + +/** + * rwav_decode_s16_at: + * @chunk : the bytes rwav_frame_extent described + * @chunk_offset : the file offset they were read from + * + * rwav_decode_s16 against a partial payload rather than a whole one. + * @chunk_offset must be exactly what rwav_frame_extent reported for + * @frame, which is checked rather than assumed: a chunk starting + * anywhere else would decode a block from the middle and yield noise. + * + * Returns: frames produced. + */ +size_t rwav_decode_s16_at(const rwav_t *wav, const void *chunk, + size_t chunk_offset, size_t frame, size_t frames, short *out); + RETRO_END_DECLS #endif