1use crate::flac::{FlacFrame, FlacInfo, Md5};
3use crate::formats::WavLayout;
4use crate::mp3::{self, Header, Mp3State, Vbr};
5use crate::pcm::{decode_ima_block, read_sample};
6use crate::{Audio, AudioFormat, DecodeInfo, Endian, Error, ErrorCode, Limits, Result};
7
8#[cfg(all(feature = "file-stream", not(target_family = "wasm")))]
9pub(crate) mod file;
10
11#[derive(Clone, Debug)]
13pub struct DecoderOptions {
14 pub format: Option<AudioFormat>,
16 pub limits: Limits,
19 pub chunk_frames: usize,
21}
22impl Default for DecoderOptions {
23 fn default() -> Self {
24 Self {
25 format: None,
26 limits: Limits::default(),
27 chunk_frames: 4096,
28 }
29 }
30}
31
32pub struct Decoder<B> {
77 bytes: B,
78 format: AudioFormat,
79 channels: usize,
80 rate: u32,
81 frames: Option<u64>,
82 chunk_frames: usize,
83 position: u64,
84 block: Block,
85 inner: Inner,
86 failed: Option<(ErrorCode, String)>,
87}
88
89struct Block {
91 data: Vec<Vec<f32>>,
92 start: usize,
93}
94impl Block {
95 fn new(channels: usize, capacity: usize) -> Self {
96 Self {
97 data: (0..channels)
98 .map(|_| Vec::with_capacity(capacity))
99 .collect(),
100 start: 0,
101 }
102 }
103 fn clear(&mut self) {
104 for c in &mut self.data {
105 c.clear();
106 }
107 self.start = 0;
108 }
109 fn available(&self) -> usize {
110 self.data[0].len() - self.start
111 }
112}
113
114enum Inner {
115 Ogg(Box<crate::ogg::Stream>),
116 Wav(WavStream),
117 Flac(Box<FlacStream>),
118 Mp3(Box<Mp3Stream>),
119}
120
121impl<B: AsRef<[u8]>> Decoder<B> {
122 pub fn new(bytes: B) -> Result<Self> {
123 Self::with_options(bytes, &DecoderOptions::default())
124 }
125 pub fn with_options(bytes: B, options: &DecoderOptions) -> Result<Self> {
126 if options.chunk_frames == 0 {
127 return Err(Error::invalid("chunk_frames must be at least 1"));
128 }
129 let input = bytes.as_ref();
130 let format = options
131 .format
132 .or_else(|| crate::sniff(input))
133 .ok_or_else(|| {
134 Error::new(
135 ErrorCode::UnsupportedFormat,
136 "Unknown or unsupported audio format",
137 )
138 })?;
139 let limits = &options.limits;
140 let (inner, channels, rate, frames, capacity) = match format {
141 AudioFormat::Vorbis | AudioFormat::Opus => {
142 let s = crate::ogg::Stream::open(input, format, limits)?;
143 let (channels, rate, frames, capacity) = (s.channels, s.rate, s.frames, s.capacity);
144 (
145 Inner::Ogg(Box::new(s)),
146 channels,
147 rate,
148 Some(frames),
149 capacity,
150 )
151 }
152 AudioFormat::Wav => {
153 let layout = crate::formats::parse_wav(input, limits)?;
154 let (channels, rate, frames) = (layout.channels, layout.rate, layout.frames);
155 let stream = WavStream::new(layout, options.chunk_frames);
156 let capacity = stream.block_frames();
157 (
158 Inner::Wav(stream),
159 channels,
160 rate,
161 Some(frames as u64),
162 capacity,
163 )
164 }
165 AudioFormat::Flac => {
166 let info = crate::flac::parse_info(input, limits)?;
167 let (channels, rate) = (info.channels, info.rate);
168 let frames = (info.total != 0).then_some(info.total as u64);
169 let capacity = info.max;
170 limits.check(capacity, channels * 5, rate)?;
172 let stream = FlacStream::new(info);
173 (
174 Inner::Flac(Box::new(stream)),
175 channels,
176 rate,
177 frames,
178 capacity,
179 )
180 }
181 AudioFormat::Mp3 => {
182 let stream = Mp3Stream::open(input, limits)?;
183 let (channels, rate) = (stream.channels, stream.rate);
184 let frames = (stream.end_raw - stream.start_raw) as u64;
185 (
186 Inner::Mp3(Box::new(stream)),
187 channels,
188 rate,
189 Some(frames),
190 1152,
191 )
192 }
193 _ => {
194 return Err(Error::new(
195 ErrorCode::UnsupportedFormat,
196 "Incremental decoding supports WAV, FLAC and MP3",
197 ));
198 }
199 };
200 limits.check(capacity, channels, rate)?;
201 Ok(Self {
202 bytes,
203 format,
204 channels,
205 rate,
206 frames,
207 chunk_frames: options.chunk_frames,
208 position: 0,
209 block: Block::new(channels, capacity),
210 inner,
211 failed: None,
212 })
213 }
214 pub fn format(&self) -> AudioFormat {
215 self.format
216 }
217 pub fn sample_rate(&self) -> u32 {
218 self.rate
219 }
220 pub fn channels(&self) -> usize {
221 self.channels
222 }
223 pub fn frames(&self) -> Option<u64> {
226 self.frames
227 }
228 pub fn position(&self) -> u64 {
230 self.position
231 }
232 pub fn into_inner(self) -> B {
234 self.bytes
235 }
236 pub fn read(&mut self, out: &mut [f32]) -> Result<usize> {
242 if let Some((code, message)) = &self.failed {
243 return Err(Error::new(*code, message.clone()));
244 }
245 let wanted = out.len() / self.channels;
246 let mut written = 0;
247 while written < wanted {
248 if self.block.available() == 0 {
249 match self.refill() {
250 Ok(true) => continue,
251 Ok(false) => break,
252 Err(e) => {
253 self.position += written as u64;
254 self.failed = Some((e.code, e.message.clone()));
255 return Err(e);
256 }
257 }
258 }
259 let n = self.block.available().min(wanted - written);
260 let start = self.block.start;
261 for (c, data) in self.block.data.iter().enumerate() {
262 for (i, &s) in data[start..start + n].iter().enumerate() {
263 out[(written + i) * self.channels + c] = s;
264 }
265 }
266 self.block.start += n;
267 written += n;
268 }
269 self.position += written as u64;
270 Ok(written)
271 }
272 pub fn next_chunk(&mut self) -> Result<Option<Audio>> {
274 let mut data: Vec<Vec<f32>> = vec![Vec::with_capacity(self.chunk_frames); self.channels];
275 let mut buffer = vec![0.0; self.chunk_frames.min(4096) * self.channels];
276 while data[0].len() < self.chunk_frames {
277 let want = (self.chunk_frames - data[0].len()).min(4096);
278 let n = self.read(&mut buffer[..want * self.channels])?;
279 if n == 0 {
280 break;
281 }
282 for (c, channel) in data.iter_mut().enumerate() {
283 channel.extend((0..n).map(|i| buffer[i * self.channels + c]));
284 }
285 }
286 if data[0].is_empty() {
287 return Ok(None);
288 }
289 let info = DecodeInfo {
290 format: Some(self.format),
291 ..Default::default()
292 };
293 Audio::decoded(data, self.rate, info).map(Some)
294 }
295 pub fn seek(&mut self, frame: u64) -> Result<()> {
298 self.failed = None;
299 let frame = self.frames.map_or(frame, |n| frame.min(n));
300 let bytes = self.bytes.as_ref();
301 let result = match &mut self.inner {
302 Inner::Ogg(s) => s.seek(bytes, frame, &mut self.block.data).map(|offset| {
303 self.block.start = offset;
304 frame
305 }),
306 Inner::Wav(s) => s.seek(bytes, frame, &mut self.block),
307 Inner::Flac(s) => s.seek(bytes, frame, self.position, &mut self.block),
308 Inner::Mp3(s) => s.seek(bytes, frame, &mut self.block),
309 };
310 match result {
311 Ok(position) => {
312 self.position = position;
313 Ok(())
314 }
315 Err(e) => {
316 self.failed = Some((e.code, e.message.clone()));
317 Err(e)
318 }
319 }
320 }
321 pub fn rewind(&mut self) -> Result<()> {
323 self.failed = None;
324 self.block.clear();
325 match &mut self.inner {
326 Inner::Ogg(s) => s.rewind(),
327 Inner::Wav(s) => s.rewind(),
328 Inner::Flac(s) => s.rewind(),
329 Inner::Mp3(s) => s.rewind(),
330 }
331 self.position = 0;
332 Ok(())
333 }
334 fn refill(&mut self) -> Result<bool> {
335 let bytes = self.bytes.as_ref();
336 self.block.clear();
337 match &mut self.inner {
338 Inner::Ogg(s) => s.refill(bytes, &mut self.block.data),
339 Inner::Wav(s) => s.refill(bytes, &mut self.block),
340 Inner::Flac(s) => s.refill(bytes, &mut self.block),
341 Inner::Mp3(s) => s.refill(bytes, &mut self.block),
342 }
343 }
344}
345
346impl<B> std::fmt::Debug for Decoder<B> {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 f.debug_struct("Decoder")
349 .field("format", &self.format)
350 .field("channels", &self.channels)
351 .field("sample_rate", &self.rate)
352 .field("frames", &self.frames)
353 .field("position", &self.position)
354 .finish_non_exhaustive()
355 }
356}
357
358struct WavStream {
359 layout: WavLayout,
360 chunk_frames: usize,
361 next: usize,
363 produced: usize,
365 scratch: Vec<u8>,
366}
367impl WavStream {
368 fn new(layout: WavLayout, chunk_frames: usize) -> Self {
369 let bytes = if layout.code == 17 {
370 layout.align
371 } else {
372 chunk_frames * layout.align
373 };
374 Self {
375 layout,
376 chunk_frames,
377 next: 0,
378 produced: 0,
379 scratch: Vec::with_capacity(bytes),
380 }
381 }
382 fn block_frames(&self) -> usize {
383 if self.layout.code == 17 {
384 self.layout.samples_per_block
385 } else {
386 self.chunk_frames
387 }
388 }
389 fn rewind(&mut self) {
390 self.next = 0;
391 self.produced = 0;
392 }
393 fn gather(&mut self, bytes: &[u8], len: usize) {
395 self.scratch.clear();
396 let mut offset = self.next;
397 for range in &self.layout.payloads {
398 let size = range.end - range.start;
399 if offset >= size {
400 offset -= size;
401 continue;
402 }
403 let take = (size - offset).min(len - self.scratch.len());
404 let from = range.start + offset;
405 self.scratch.extend_from_slice(&bytes[from..from + take]);
406 offset = 0;
407 if self.scratch.len() == len {
408 break;
409 }
410 }
411 }
412 fn refill(&mut self, bytes: &[u8], block: &mut Block) -> Result<bool> {
413 let remaining = self.layout.frames - self.produced;
414 if remaining == 0 {
415 return Ok(false);
416 }
417 let layout = &self.layout;
418 if layout.code == 17 {
419 let align = layout.align;
420 self.gather(bytes, align);
421 decode_ima_block(
422 &self.scratch,
423 self.layout.samples_per_block,
424 &mut block.data,
425 )?;
426 let n = self.layout.samples_per_block.min(remaining);
427 for c in &mut block.data {
428 c.truncate(n);
429 }
430 self.next += align;
431 self.produced += n;
432 } else {
433 let n = self.chunk_frames.min(remaining);
434 let (align, format) = (layout.align, layout.format);
435 self.gather(bytes, n * align);
436 let width = format.bytes();
437 for frame in self.scratch.chunks_exact(align) {
438 for (c, sample) in frame.chunks_exact(width).enumerate() {
439 block.data[c].push(read_sample(sample, format, Endian::Little)?);
440 }
441 }
442 self.next += n * align;
443 self.produced += n;
444 }
445 Ok(true)
446 }
447 fn seek(&mut self, bytes: &[u8], frame: u64, block: &mut Block) -> Result<u64> {
448 block.clear();
449 let frame = frame as usize;
450 if self.layout.code == 17 {
451 let spb = self.layout.samples_per_block;
452 let index = frame / spb;
453 self.next = index * self.layout.align;
454 self.produced = index * spb;
455 if frame > self.produced {
456 self.refill(bytes, block)?;
457 block.start = frame - index * spb;
458 }
459 } else {
460 self.next = frame * self.layout.align;
461 self.produced = frame;
462 }
463 Ok(frame as u64)
464 }
465}
466
467struct FlacStream {
468 info: FlacInfo,
469 frame: FlacFrame,
470 pos: usize,
472 written: usize,
474 md5: Md5,
475 from_start: bool,
477}
478impl FlacStream {
479 fn new(info: FlacInfo) -> Self {
480 Self {
481 frame: FlacFrame::new(&info),
482 pos: info.first_frame,
483 info,
484 written: 0,
485 md5: Md5::new(),
486 from_start: true,
487 }
488 }
489 fn rewind(&mut self) {
490 self.pos = self.info.first_frame;
491 self.written = 0;
492 self.md5 = Md5::new();
493 self.from_start = true;
494 }
495 fn refill(&mut self, bytes: &[u8], block: &mut Block) -> Result<bool> {
496 let total = self.info.total;
497 if self.pos >= bytes.len() {
498 if self.from_start {
499 if total != 0 && self.written != total {
500 return Err(Error::decode("FLAC sample count mismatch"));
501 }
502 let md5 = std::mem::replace(&mut self.md5, Md5::new());
503 if self.info.md5 != [0; 16] && md5.digest() != self.info.md5 {
504 return Err(Error::decode("FLAC PCM MD5 mismatch"));
505 }
506 self.from_start = false;
507 }
508 return Ok(false);
509 }
510 let (n, next_pos) = self.frame.decode(bytes, self.pos, &self.info)?;
511 let next = self
512 .written
513 .checked_add(n)
514 .ok_or_else(|| Error::limit("FLAC frame count overflow"))?;
515 if total != 0 && next > total {
516 return Err(Error::decode("FLAC exceeds declared sample count"));
517 }
518 let bits = self.info.bits;
519 for (c, samples) in block.data.iter_mut().zip(&self.frame.samples) {
520 c.extend(samples[..n].iter().map(|&s| crate::flac::to_float(s, bits)));
521 }
522 if self.from_start {
523 self.md5.update(&self.frame.raw);
524 }
525 self.written = next;
526 self.pos = next_pos;
527 Ok(true)
528 }
529 fn seek_point(&self, bytes: &[u8], frame: u64) -> Option<(usize, usize)> {
531 let table = &bytes[self.info.seek_table.clone()?];
532 let mut best = None;
533 for point in table.chunks_exact(18) {
534 let sample = u64::from_be_bytes(point[..8].try_into().unwrap());
535 let offset = u64::from_be_bytes(point[8..16].try_into().unwrap());
536 if sample == u64::MAX || sample > frame {
537 continue;
538 }
539 let pos = usize::try_from(offset)
540 .ok()
541 .and_then(|o| o.checked_add(self.info.first_frame))
542 .filter(|&p| p < bytes.len())?;
543 if best.is_none_or(|(s, _)| sample as usize > s) {
544 best = Some((sample as usize, pos));
545 }
546 }
547 best
548 }
549 fn seek(&mut self, bytes: &[u8], frame: u64, position: u64, block: &mut Block) -> Result<u64> {
550 let block_start = position - block.start as u64;
551 if frame >= block_start && frame < block_start + block.data[0].len() as u64 {
552 block.start = (frame - block_start) as usize;
553 return Ok(frame);
554 }
555 block.clear();
556 if frame < self.written as u64 || frame == 0 {
557 self.rewind();
558 }
559 if let Some((sample, pos)) = self.seek_point(bytes, frame) {
560 if sample > self.written {
561 self.pos = pos;
562 self.written = sample;
563 self.from_start = false;
564 }
565 }
566 while (self.written as u64) <= frame && frame != 0 {
567 let frame_start = self.written;
568 block.clear();
569 if !self.refill(bytes, block)? {
570 return Ok(self.written as u64);
571 }
572 if self.written as u64 > frame {
573 block.start = (frame - frame_start as u64) as usize;
574 }
575 }
576 Ok(frame)
577 }
578}
579
580const RESERVOIR: usize = 511;
582const RECENT: usize = 64;
584
585#[derive(Clone, Copy, Default)]
586struct FrameMark {
587 pos: usize,
588 payload: usize,
589 raw: usize,
591 counted: usize,
593 history: usize,
595}
596
597struct Mp3Stream {
598 channels: usize,
599 rate: u32,
600 first: usize,
601 tag: Option<Vbr>,
602 start_raw: usize,
604 end_raw: usize,
605 state: Mp3State,
606 buffer: Box<[[f32; 1152]; 2]>,
607 pos: usize,
608 history: usize,
610 raw: usize,
612 discard: usize,
614 flushed: bool,
615}
616impl Mp3Stream {
617 fn open(bytes: &[u8], limits: &Limits) -> Result<Self> {
618 let (first, h) = mp3::find(bytes, mp3::tag_size(bytes))
619 .ok_or_else(|| Error::decode("No readable MPEG Layer III frame"))?;
620 let tag = mp3::vbr(bytes, first, h)?;
621 let (channels, rate) = (h.channels, h.rate);
622 limits.check(0, channels, rate)?;
623 if let Some(tag) = tag {
624 let frames = tag
625 .frames
626 .checked_mul(if h.version == 1 { 1152 } else { 576 })
627 .ok_or_else(|| Error::limit("MP3 sample count overflow"))?;
628 limits.check(frames, channels, rate)?;
629 }
630 let mut stream = Self {
631 channels,
632 rate,
633 first,
634 tag,
635 start_raw: 0,
636 end_raw: 0,
637 state: Mp3State::new(channels, rate),
638 buffer: Box::new([[0.0; 1152]; 2]),
639 pos: first,
640 history: 0,
641 raw: 0,
642 discard: 0,
643 flushed: false,
644 };
645 let mut total = 0;
646 stream.walk_until(bytes, |mark| {
647 total = mark.raw + mark.counted;
648 true
649 })?;
650 if total == 0 {
651 return Err(Error::decode("No decodable MP3 frames"));
652 }
653 let raw_total = total + mp3::FLUSH;
654 limits.check(raw_total, channels, rate)?;
655 let tag = tag.unwrap_or_default();
656 stream.start_raw = (tag.delay + mp3::DECODER_DELAY + mp3::FLUSH).min(raw_total);
657 stream.end_raw = raw_total
658 .saturating_sub(tag.padding.saturating_sub(mp3::DECODER_DELAY))
659 .max(stream.start_raw);
660 stream.discard = stream.start_raw;
661 Ok(stream)
662 }
663 fn walk_until(
666 &self,
667 bytes: &[u8],
668 mut visit: impl FnMut(FrameMark) -> bool,
669 ) -> Result<Option<FrameMark>> {
670 let (mut pos, mut history, mut raw) = (self.first, 0, 0);
671 while let Some(h) = mp3::header(bytes, pos) {
672 if pos + h.length > bytes.len() || h.rate != self.rate || h.channels != self.channels {
673 break;
674 }
675 let mark = self.mark(bytes, pos, h, history, raw)?;
676 if !visit(mark) {
677 return Ok(Some(mark));
678 }
679 history = (history + mark.payload).min(4096);
680 raw += mark.counted;
681 pos += h.length;
682 }
683 Ok(None)
684 }
685 fn mark(
686 &self,
687 bytes: &[u8],
688 pos: usize,
689 h: Header,
690 history: usize,
691 raw: usize,
692 ) -> Result<FrameMark> {
693 let output = !(pos == self.first && self.tag.is_some());
694 let back = mp3::main_data_begin(bytes, pos, h)?;
695 let granules = if h.version == 1 { 2 } else { 1 };
696 Ok(FrameMark {
697 pos,
698 payload: mp3::payload_len(h),
699 raw,
700 counted: if output && back <= history {
701 granules * 576
702 } else {
703 0
704 },
705 history,
706 })
707 }
708 fn rewind(&mut self) {
709 self.state.reset();
710 self.pos = self.first;
711 self.history = 0;
712 self.raw = 0;
713 self.discard = self.start_raw;
714 self.flushed = false;
715 }
716 fn emit(&self, raw: usize, n: usize, block: &mut Block) {
718 let from = raw.max(self.discard).max(self.start_raw);
719 let to = (raw + n).min(self.end_raw);
720 if from < to {
721 for (c, samples) in block.data.iter_mut().zip(self.buffer.iter()) {
722 c.extend_from_slice(&samples[from - raw..to - raw]);
723 }
724 }
725 }
726 fn refill(&mut self, bytes: &[u8], block: &mut Block) -> Result<bool> {
727 loop {
728 if self.flushed {
729 return Ok(false);
730 }
731 let Some(h) = mp3::header(bytes, self.pos) else {
732 self.state.flush(&mut self.buffer);
733 self.emit(self.raw, mp3::FLUSH, block);
734 self.raw += mp3::FLUSH;
735 self.flushed = true;
736 return Ok(block.available() > 0);
737 };
738 if self.pos + h.length > bytes.len() {
739 return Err(Error::decode("Truncated MP3 frame"));
740 }
741 if h.rate != self.rate || h.channels != self.channels {
742 return Err(Error::decode("MP3 stream shape changed"));
743 }
744 let mark = self.mark(bytes, self.pos, h, self.history, self.raw)?;
745 let output = !(self.pos == self.first && self.tag.is_some());
746 let n = self
747 .state
748 .frame(bytes, self.pos, h, output, &mut self.buffer)?;
749 self.pos += h.length;
750 self.history = (self.history + mark.payload).min(4096);
751 self.raw += mark.counted;
752 if n > 0 && mark.counted > 0 {
753 self.emit(mark.raw, n, block);
754 if block.available() > 0 {
755 return Ok(true);
756 }
757 }
758 }
759 }
760 fn seek(&mut self, bytes: &[u8], frame: u64, block: &mut Block) -> Result<u64> {
761 block.clear();
762 let target = (self.start_raw + frame as usize).min(self.end_raw);
763 let mut recent = [FrameMark::default(); RECENT];
765 let mut seen = 0usize;
766 self.walk_until(bytes, |mark| {
767 if mark.counted > 0 && mark.raw + mark.counted > target {
768 return false;
769 }
770 recent[seen % RECENT] = mark;
771 seen += 1;
772 true
773 })?;
774 let mut restart = None;
777 let mut previous_decoded = None;
778 let mut payload = 0;
779 for back in 0..seen.min(RECENT) {
780 let mark = recent[(seen - 1 - back) % RECENT];
781 if previous_decoded.is_none() {
782 if mark.counted > 0 {
783 previous_decoded = Some(back);
784 }
785 continue;
786 }
787 payload += mark.payload;
788 if payload >= RESERVOIR {
789 restart = Some(mark);
790 break;
791 }
792 }
793 self.rewind();
796 if let (Some(_), Some(mark)) = (previous_decoded, restart) {
797 self.pos = mark.pos;
798 self.history = mark.history;
799 self.raw = mark.raw;
800 }
801 self.discard = target.max(self.start_raw);
802 Ok((target - self.start_raw) as u64)
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn mp3_seek_restarts_shortly_before_the_target() {
812 let audio = Audio::new(
815 (0..2)
816 .map(|ch| {
817 (0..30_000)
818 .map(|i| ((i * (ch + 1)) as f32 * 0.02).sin() * 0.3)
819 .collect()
820 })
821 .collect(),
822 44_100,
823 )
824 .unwrap();
825 let bytes = crate::encode(&audio, AudioFormat::Mp3).unwrap();
826 let mut decoder = Decoder::new(bytes.as_slice()).unwrap();
827 let total = decoder.frames().unwrap();
828 decoder.seek(total * 3 / 4).unwrap();
829 let Inner::Mp3(stream) = &decoder.inner else {
830 unreachable!()
831 };
832 let target = stream.start_raw + (total * 3 / 4) as usize;
833 assert!(stream.pos > stream.first, "restarted from the first frame");
835 assert!(
836 stream.raw < target && target - stream.raw < 20 * 1152,
837 "{} vs {target}",
838 stream.raw
839 );
840 assert!(stream.history > 0);
841 }
842
843 #[test]
844 fn flac_seek_without_table_continues_forward() {
845 let tone: Vec<f32> = (0..40_000).map(|i| (i as f32 * 0.02).sin() * 0.3).collect();
846 let bytes =
847 crate::encode(&Audio::new(vec![tone], 8000).unwrap(), AudioFormat::Flac).unwrap();
848 let mut decoder = Decoder::new(bytes.as_slice()).unwrap();
849 decoder.seek(20_000).unwrap();
850 let Inner::Flac(stream) = &decoder.inner else {
851 unreachable!()
852 };
853 assert!(stream.from_start, "decoding forward keeps the MD5 check");
854 let written = stream.written;
855 decoder.seek(30_000).unwrap();
856 let Inner::Flac(stream) = &decoder.inner else {
857 unreachable!()
858 };
859 assert!(stream.written > written && stream.written <= 30_000 + 4096);
860 }
861}