Skip to main content

zvidlib_core/
audio.rs

1//! Exact audio sample reads with gapless and edit-list timeline mapping.
2//!
3//! [`AudioSampleReader`] is codec-neutral: it maps presentation sample ranges
4//! through priming, padding and edits onto a track's packets and asks an
5//! [`AudioDecoder`] for exactly the packets it needs. AAC, Opus and Vorbis
6//! tracks all read through it.
7
8use crate::{
9    AudioBuffer, CancellationToken, Error, ErrorKind, FrameIndex, Limits, Result, SampleRange,
10    Timeline,
11};
12use std::collections::BTreeMap;
13
14/// One compressed audio packet - an AAC access unit, an Opus packet or a
15/// Vorbis packet - and the decoded interval it covers.
16///
17/// The interval may be empty: a Vorbis stream's first audio packet only primes
18/// the decoder's overlap and decodes no samples.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct EncodedAudioSample {
21    pub decoded_range: SampleRange,
22    pub data: Vec<u8>,
23}
24
25/// A stateful audio decoder. Output must use the packet's decoded sample
26/// clock: the buffer for a packet covers exactly its `decoded_range`, which is
27/// empty for a packet that decodes nothing.
28pub trait AudioDecoder {
29    fn decode(
30        &mut self,
31        sample: &EncodedAudioSample,
32        cancellation: &CancellationToken,
33    ) -> Result<AudioBuffer>;
34    fn reset(&mut self) -> Result<()>;
35}
36
37/// The name [`AudioDecoder`] had while AAC was the only audio codec.
38pub use self::AudioDecoder as AacDecoder;
39
40impl<D: AudioDecoder + ?Sized> AudioDecoder for Box<D> {
41    fn decode(
42        &mut self,
43        sample: &EncodedAudioSample,
44        cancellation: &CancellationToken,
45    ) -> Result<AudioBuffer> {
46        (**self).decode(sample, cancellation)
47    }
48
49    fn reset(&mut self) -> Result<()> {
50        (**self).reset()
51    }
52}
53
54/// One MP4 edit in the presentation sample clock.
55///
56/// `media_start == None` is an empty edit and produces silence. A media edit's
57/// source interval has the same length as its presentation interval.
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub struct AudioEdit {
60    pub presentation: SampleRange,
61    pub media_start: Option<u64>,
62}
63
64/// Preserved timing metadata needed to expose gapless presentation samples.
65#[derive(Clone, Debug, Default, Eq, PartialEq)]
66pub struct AudioTrackTiming {
67    pub priming: u32,
68    pub padding: u32,
69    /// Positive values delay the track; negative values trim its media start.
70    pub track_offset: i64,
71    pub edits: Vec<AudioEdit>,
72}
73
74/// Exact presentation-range access over a track's sequential audio packets.
75pub struct AudioSampleReader<D> {
76    decoder: D,
77    packets: Vec<EncodedAudioSample>,
78    decoded: BTreeMap<u64, AudioBuffer>,
79    /// Inclusive packet-index bounds of the contiguous run held in `decoded`.
80    ///
81    /// The run is exactly what the decoder has produced since its last reset,
82    /// minus any packets evicted from its front, so `resident.1 + 1` is the
83    /// packet the decoder is positioned to decode next without a reset.
84    resident: Option<(usize, usize)>,
85    resident_bytes: u64,
86    timing: AudioTrackTiming,
87    sample_rate: u32,
88    channels: u16,
89    decoded_length: u64,
90    presentation_length: u64,
91    preroll_packets: usize,
92    limits: Limits,
93}
94
95/// The name [`AudioSampleReader`] had while AAC was the only audio codec.
96pub type AacSampleReader<D> = AudioSampleReader<D>;
97
98impl<D: AudioDecoder> AudioSampleReader<D> {
99    /// `preroll_packets` is how many packets before the first one a request
100    /// needs are decoded first after a reset, so the decoder's state has
101    /// converged by the time it reaches the samples that are returned.
102    pub fn new(
103        decoder: D,
104        packets: Vec<EncodedAudioSample>,
105        sample_rate: u32,
106        channels: u16,
107        timing: AudioTrackTiming,
108        preroll_packets: usize,
109        limits: Limits,
110    ) -> Result<Self> {
111        if packets.is_empty() {
112            return Err(invalid("an audio reader requires at least one packet"));
113        }
114        if sample_rate == 0 || sample_rate > limits.max_sample_rate {
115            return Err(limit("audio sample rate is outside configured limits"));
116        }
117        if channels == 0 || channels > limits.max_audio_channels {
118            return Err(limit("audio channel count is outside configured limits"));
119        }
120        let mut expected = 0;
121        for packet in &packets {
122            if packet.decoded_range.start != expected {
123                return Err(invalid("audio packet sample intervals must be contiguous"));
124            }
125            expected = packet.decoded_range.end;
126        }
127        if expected == 0 {
128            return Err(invalid("audio packets decode no samples"));
129        }
130        let trimmed = expected
131            .checked_sub(u64::from(timing.priming) + u64::from(timing.padding))
132            .ok_or_else(|| invalid("audio priming and padding exceed decoded duration"))?;
133        validate_edits(&timing, expected)?;
134        let presentation_length = if timing.edits.is_empty() {
135            if timing.track_offset >= 0 {
136                trimmed.checked_add(timing.track_offset.unsigned_abs())
137            } else {
138                trimmed.checked_sub(timing.track_offset.unsigned_abs())
139            }
140            .ok_or_else(|| invalid("track offset exceeds gapless audio duration"))?
141        } else {
142            timing.edits.iter().try_fold(0, |length, edit| {
143                Ok::<_, Error>(
144                    length.max(
145                        shifted_edit(*edit, timing.track_offset)?
146                            .map_or(0, |shifted| shifted.presentation.end),
147                    ),
148                )
149            })?
150        };
151        Ok(Self {
152            decoder,
153            packets,
154            decoded: BTreeMap::new(),
155            resident: None,
156            resident_bytes: 0,
157            timing,
158            sample_rate,
159            channels,
160            decoded_length: expected,
161            presentation_length,
162            preroll_packets,
163            limits,
164        })
165    }
166
167    pub const fn sample_rate(&self) -> u32 {
168        self.sample_rate
169    }
170    pub const fn channels(&self) -> u16 {
171        self.channels
172    }
173    pub const fn presentation_length(&self) -> u64 {
174        self.presentation_length
175    }
176    pub fn timing(&self) -> &AudioTrackTiming {
177        &self.timing
178    }
179
180    /// Returns exactly the requested contiguous half-open presentation range.
181    pub fn get_range(
182        &mut self,
183        range: SampleRange,
184        cancellation: &CancellationToken,
185    ) -> Result<AudioBuffer> {
186        let (mut output, mappings) = self.start_request(range, cancellation)?;
187        for mapping in mappings {
188            let Some(media) = mapping.media else { continue };
189            if let Some(plan) = self.plan_decode(media)? {
190                if plan.reset {
191                    self.decoder.reset()?;
192                }
193                for index in plan.from..=plan.last {
194                    if cancellation.is_cancelled() {
195                        return Err(cancelled());
196                    }
197                    let buffer = self.decoder.decode(&self.packets[index], cancellation)?;
198                    self.accept_decoded(&plan, index, buffer)?;
199                }
200                self.evict_behind(plan.first);
201            }
202            self.copy_media(media, mapping.output_offset, &mut output)?;
203        }
204        AudioBuffer::new(range, self.sample_rate, self.channels, output, &self.limits)
205    }
206
207    /// [`Self::get_range`] with the packets it needs decoded by `decode`
208    /// rather than the reader's own decoder, for a decoder that can only be
209    /// driven asynchronously.
210    ///
211    /// `decode` is handed whether it must reset its state first, and the
212    /// packets to decode in order; it returns one buffer per packet, each
213    /// covering exactly its packet's interval. The reader's own decoder is not
214    /// used.
215    #[doc(hidden)]
216    pub async fn get_range_with<F>(
217        &mut self,
218        range: SampleRange,
219        cancellation: &CancellationToken,
220        mut decode: F,
221    ) -> Result<AudioBuffer>
222    where
223        F: AsyncFnMut(bool, &[EncodedAudioSample]) -> Result<Vec<AudioBuffer>>,
224    {
225        let (mut output, mappings) = self.start_request(range, cancellation)?;
226        for mapping in mappings {
227            let Some(media) = mapping.media else { continue };
228            if let Some(plan) = self.plan_decode(media)? {
229                let buffers = decode(plan.reset, &self.packets[plan.from..=plan.last]).await?;
230                if cancellation.is_cancelled() {
231                    return Err(cancelled());
232                }
233                if buffers.len() != plan.last - plan.from + 1 {
234                    return Err(Error::new(
235                        ErrorKind::Codec,
236                        "audio decoder returned the wrong number of packets",
237                    ));
238                }
239                for (index, buffer) in (plan.from..=plan.last).zip(buffers) {
240                    self.accept_decoded(&plan, index, buffer)?;
241                }
242                self.evict_behind(plan.first);
243            }
244            self.copy_media(media, mapping.output_offset, &mut output)?;
245        }
246        AudioBuffer::new(range, self.sample_rate, self.channels, output, &self.limits)
247    }
248
249    /// Validates a request and allocates its silent output.
250    fn start_request(
251        &self,
252        range: SampleRange,
253        cancellation: &CancellationToken,
254    ) -> Result<(Vec<f32>, Vec<Mapping>)> {
255        if range.end > self.presentation_length {
256            return Err(invalid("audio request exceeds the presentation duration"));
257        }
258        if cancellation.is_cancelled() {
259            return Err(cancelled());
260        }
261        let sample_count = range
262            .len()
263            .checked_mul(u64::from(self.channels))
264            .ok_or_else(|| limit("audio request size overflow"))?;
265        let bytes = sample_count
266            .checked_mul(std::mem::size_of::<f32>() as u64)
267            .ok_or_else(|| limit("audio request allocation overflow"))?;
268        if bytes > self.limits.max_allocation_bytes {
269            return Err(limit("audio request exceeds the allocation limit"));
270        }
271        let output = vec![
272            0.0;
273            usize::try_from(sample_count)
274                .map_err(|_| limit("audio request cannot be represented"))?
275        ];
276        Ok((output, self.mappings(range)?))
277    }
278
279    pub fn get(
280        &mut self,
281        timeline: Timeline,
282        frame: FrameIndex,
283        cancellation: &CancellationToken,
284    ) -> Result<AudioBuffer> {
285        if timeline.audio_sample_rate() != self.sample_rate {
286            return Err(invalid("timeline and audio sample rates do not match"));
287        }
288        self.get_range(timeline.audio_interval_for_frame(frame)?, cancellation)
289    }
290
291    /// Cancels are caller-owned; reset drops queued decode state before a seek.
292    pub fn reset(&mut self) -> Result<()> {
293        self.decoder.reset()?;
294        self.discard_resident();
295        Ok(())
296    }
297
298    fn discard_resident(&mut self) {
299        self.decoded.clear();
300        self.resident = None;
301        self.resident_bytes = 0;
302    }
303
304    /// Packets retained behind the request are dropped once the resident run
305    /// exceeds either bound `Limits` already defines for a decode: at most
306    /// `max_decode_samples_per_seek` packets, holding at most
307    /// `max_allocation_bytes` of decoded samples. Only packets before
308    /// `keep_from` are eligible, so a request never evicts what it just
309    /// decoded for itself.
310    fn evict_behind(&mut self, keep_from: usize) {
311        let Some((mut start, end)) = self.resident else {
312            return;
313        };
314        let max_packets = self.limits.max_decode_samples_per_seek as usize;
315        while start < keep_from
316            && (end - start + 1 > max_packets
317                || self.resident_bytes > self.limits.max_allocation_bytes)
318        {
319            // An empty packet's buffer was never cached, and its start is the
320            // key of the packet after it.
321            let range = self.packets[start].decoded_range;
322            if !range.is_empty()
323                && let Some(buffer) = self.decoded.remove(&range.start)
324            {
325                self.resident_bytes = self.resident_bytes.saturating_sub(buffer_bytes(&buffer));
326            }
327            start += 1;
328        }
329        self.resident = Some((start, end));
330    }
331
332    fn mappings(&self, request: SampleRange) -> Result<Vec<Mapping>> {
333        let mut mappings = Vec::new();
334        if self.timing.edits.is_empty() {
335            let presentation_start = self.timing.track_offset.max(0) as u64;
336            let media_start = u64::from(self.timing.priming)
337                .checked_add(self.timing.track_offset.min(0).unsigned_abs())
338                .ok_or_else(|| limit("audio media offset overflow"))?;
339            let playable_end = self.decoded_length - u64::from(self.timing.padding);
340            let length = playable_end.saturating_sub(media_start);
341            let presentation_end = presentation_start
342                .checked_add(length)
343                .ok_or_else(|| limit("audio presentation range overflow"))?;
344            push_intersection(
345                &mut mappings,
346                request,
347                SampleRange::new(presentation_start, presentation_end)?,
348                Some(media_start),
349            )?;
350        } else {
351            for edit in &self.timing.edits {
352                let Some(edit) = shifted_edit(*edit, self.timing.track_offset)? else {
353                    continue;
354                };
355                push_intersection(&mut mappings, request, edit.presentation, edit.media_start)?;
356            }
357        }
358        Ok(mappings)
359    }
360
361    /// Decides what `range` of the media needs decoded, or `None` when every
362    /// packet it covers is already resident.
363    ///
364    /// A request whose window sits inside the resident run needs nothing. A
365    /// request that starts inside it, or immediately after it, and reaches
366    /// past its end is the forward-sequential playback case: the decoder is
367    /// already positioned on the next packet, so the run is extended in place
368    /// rather than resetting and re-decoding the preroll. Anything else -
369    /// cold, backwards, or separated by a gap - takes the reset path, which is
370    /// what a real seek needs, and discards the resident run.
371    fn plan_decode(&mut self, range: SampleRange) -> Result<Option<DecodePlan>> {
372        let first = self
373            .packets
374            .iter()
375            .position(|packet| packet.decoded_range.end > range.start)
376            .ok_or_else(|| invalid("audio edit maps beyond decoded samples"))?;
377        let last = self
378            .packets
379            .iter()
380            .rposition(|packet| packet.decoded_range.start < range.end)
381            .ok_or_else(|| invalid("audio edit maps before decoded samples"))?;
382        let seek_start = first.saturating_sub(self.preroll_packets);
383        if last - seek_start + 1 > self.limits.max_decode_samples_per_seek as usize {
384            return Err(limit(
385                "audio request exceeded the configured decode-work limit",
386            ));
387        }
388        match self.resident {
389            Some((resident_start, resident_end))
390                if first >= resident_start && first <= resident_end + 1 =>
391            {
392                if last <= resident_end {
393                    self.evict_behind(first);
394                    return Ok(None);
395                }
396                Ok(Some(DecodePlan {
397                    reset: false,
398                    from: resident_end + 1,
399                    last,
400                    first,
401                    preroll_end: resident_end + 1,
402                }))
403            }
404            _ => {
405                self.discard_resident();
406                Ok(Some(DecodePlan {
407                    reset: true,
408                    from: seek_start,
409                    last,
410                    first,
411                    preroll_end: first,
412                }))
413            }
414        }
415    }
416
417    /// Takes in the buffer `plan` decoded for packet `index`.
418    fn accept_decoded(
419        &mut self,
420        plan: &DecodePlan,
421        index: usize,
422        buffer: AudioBuffer,
423    ) -> Result<()> {
424        let packet = &self.packets[index];
425        if buffer.range != packet.decoded_range
426            || buffer.sample_rate != self.sample_rate
427            || buffer.channels != self.channels
428        {
429            return Err(Error::new(
430                ErrorKind::Codec,
431                "audio decoder output does not match its packet interval or format",
432            ));
433        }
434        // Preroll only brings a reset decoder's state up to date. What it
435        // decodes to is not the stream's audio - a Vorbis packet with no block
436        // before it to overlap decodes to silence - so it is never kept for a
437        // later request to read.
438        if index < plan.preroll_end {
439            return Ok(());
440        }
441        if !buffer.range.is_empty() {
442            self.resident_bytes = self.resident_bytes.saturating_add(buffer_bytes(&buffer));
443            self.decoded.insert(buffer.range.start, buffer);
444        }
445        self.resident = Some((self.resident.map_or(index, |(start, _)| start), index));
446        Ok(())
447    }
448
449    fn copy_media(&self, range: SampleRange, output_offset: u64, output: &mut [f32]) -> Result<()> {
450        let channels = usize::from(self.channels);
451        // Walk back from the last packet that can overlap. The resident run
452        // now spans more than one request, so visiting every buffer would make
453        // each read cost the size of the cache instead of the size of the read.
454        for buffer in self
455            .decoded
456            .range(..range.end)
457            .rev()
458            .map(|(_, buffer)| buffer)
459        {
460            if buffer.range.end <= range.start {
461                break;
462            }
463            let start = range.start.max(buffer.range.start);
464            let end = range.end.min(buffer.range.end);
465            if start >= end {
466                continue;
467            }
468            let input_start =
469                usize::try_from((start - buffer.range.start) * u64::from(self.channels))
470                    .map_err(|_| limit("audio input offset overflow"))?;
471            let output_start =
472                usize::try_from((output_offset + start - range.start) * u64::from(self.channels))
473                    .map_err(|_| limit("audio output offset overflow"))?;
474            let count = usize::try_from(end - start)
475                .map_err(|_| limit("audio copy size overflow"))?
476                .checked_mul(channels)
477                .ok_or_else(|| limit("audio copy size overflow"))?;
478            output[output_start..output_start + count]
479                .copy_from_slice(&buffer.samples[input_start..input_start + count]);
480        }
481        Ok(())
482    }
483}
484
485struct Mapping {
486    media: Option<SampleRange>,
487    output_offset: u64,
488}
489
490/// The packets one media range of a request needs decoded.
491struct DecodePlan {
492    /// Whether the decoder has to be reset before `from`.
493    reset: bool,
494    from: usize,
495    last: usize,
496    /// The first packet holding samples the request reads.
497    first: usize,
498    /// Packets before this one are preroll, decoded but not kept.
499    preroll_end: usize,
500}
501
502fn push_intersection(
503    out: &mut Vec<Mapping>,
504    request: SampleRange,
505    presentation: SampleRange,
506    media_start: Option<u64>,
507) -> Result<()> {
508    let start = request.start.max(presentation.start);
509    let end = request.end.min(presentation.end);
510    if start >= end {
511        return Ok(());
512    }
513    let media = media_start
514        .map(|base| {
515            let source_start = base
516                .checked_add(start - presentation.start)
517                .ok_or_else(|| limit("audio edit mapping overflow"))?;
518            let source_end = base
519                .checked_add(end - presentation.start)
520                .ok_or_else(|| limit("audio edit mapping overflow"))?;
521            SampleRange::new(source_start, source_end)
522        })
523        .transpose()?;
524    out.push(Mapping {
525        media,
526        output_offset: start - request.start,
527    });
528    Ok(())
529}
530
531fn validate_edits(timing: &AudioTrackTiming, decoded_length: u64) -> Result<()> {
532    let mut end = 0;
533    for edit in &timing.edits {
534        if edit.presentation.start < end || edit.presentation.is_empty() {
535            return Err(invalid(
536                "audio edits must be nonempty and ordered without overlap",
537            ));
538        }
539        if let Some(media_start) = edit.media_start {
540            let media_end = media_start
541                .checked_add(edit.presentation.len())
542                .ok_or_else(|| limit("audio edit range overflow"))?;
543            if media_start < u64::from(timing.priming)
544                || media_end > decoded_length - u64::from(timing.padding)
545            {
546                return Err(invalid(
547                    "audio edit includes priming, padding, or samples outside the track",
548                ));
549            }
550        }
551        end = edit.presentation.end;
552    }
553    Ok(())
554}
555
556fn shifted_edit(edit: AudioEdit, offset: i64) -> Result<Option<AudioEdit>> {
557    let shifted_start = i128::from(edit.presentation.start) + i128::from(offset);
558    let shifted_end = i128::from(edit.presentation.end) + i128::from(offset);
559    if shifted_end <= 0 {
560        return Ok(None);
561    }
562    let clipped = shifted_start.min(0).unsigned_abs();
563    let start = u64::try_from(shifted_start.max(0))
564        .map_err(|_| limit("shifted audio edit start cannot be represented"))?;
565    let end = u64::try_from(shifted_end)
566        .map_err(|_| limit("shifted audio edit end cannot be represented"))?;
567    let media_start = edit
568        .media_start
569        .map(|value| {
570            value
571                .checked_add(
572                    u64::try_from(clipped)
573                        .map_err(|_| limit("shifted audio edit clip cannot be represented"))?,
574                )
575                .ok_or_else(|| limit("shifted audio edit media offset overflow"))
576        })
577        .transpose()?;
578    Ok(Some(AudioEdit {
579        presentation: SampleRange::new(start, end)?,
580        media_start,
581    }))
582}
583
584fn buffer_bytes(buffer: &AudioBuffer) -> u64 {
585    buffer.samples.len() as u64 * std::mem::size_of::<f32>() as u64
586}
587
588fn invalid(message: &str) -> Error {
589    Error::new(ErrorKind::InvalidInput, message)
590}
591fn limit(message: &str) -> Error {
592    Error::new(ErrorKind::ResourceLimit, message)
593}
594fn cancelled() -> Error {
595    Error::new(ErrorKind::Cancelled, "audio decode cancelled")
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601
602    #[derive(Clone, Default)]
603    struct DecoderCounts {
604        resets: std::rc::Rc<std::cell::Cell<usize>>,
605        decodes: std::rc::Rc<std::cell::Cell<usize>>,
606    }
607
608    #[derive(Default)]
609    struct FixtureDecoder {
610        counts: DecoderCounts,
611    }
612
613    impl AudioDecoder for FixtureDecoder {
614        fn decode(
615            &mut self,
616            sample: &EncodedAudioSample,
617            cancellation: &CancellationToken,
618        ) -> Result<AudioBuffer> {
619            if cancellation.is_cancelled() {
620                return Err(cancelled());
621            }
622            self.counts.decodes.set(self.counts.decodes.get() + 1);
623            let values = (sample.decoded_range.start..sample.decoded_range.end)
624                .map(|value| value as f32)
625                .collect();
626            AudioBuffer::new(sample.decoded_range, 48_000, 1, values, &Limits::default())
627        }
628
629        fn reset(&mut self) -> Result<()> {
630            self.counts.resets.set(self.counts.resets.get() + 1);
631            Ok(())
632        }
633    }
634
635    fn packets_of(count: u64) -> Vec<EncodedAudioSample> {
636        (0..count)
637            .map(|index| EncodedAudioSample {
638                decoded_range: SampleRange::new(index * 4, index * 4 + 4).unwrap(),
639                data: vec![index as u8],
640            })
641            .collect()
642    }
643
644    /// A counted reader over `count` four-sample packets and no gapless trims.
645    fn counted_reader(
646        count: u64,
647        preroll: usize,
648        limits: Limits,
649    ) -> (AudioSampleReader<FixtureDecoder>, DecoderCounts) {
650        let decoder = FixtureDecoder::default();
651        let counts = decoder.counts.clone();
652        let reader = AudioSampleReader::new(
653            decoder,
654            packets_of(count),
655            48_000,
656            1,
657            AudioTrackTiming::default(),
658            preroll,
659            limits,
660        )
661        .unwrap();
662        (reader, counts)
663    }
664
665    fn packets() -> Vec<EncodedAudioSample> {
666        (0..3)
667            .map(|index| EncodedAudioSample {
668                decoded_range: SampleRange::new(index * 4, index * 4 + 4).unwrap(),
669                data: vec![index as u8],
670            })
671            .collect()
672    }
673
674    #[test]
675    fn gapless_offset_reads_return_exact_contiguous_ranges() {
676        let timing = AudioTrackTiming {
677            priming: 2,
678            padding: 2,
679            track_offset: 1,
680            edits: Vec::new(),
681        };
682        let mut reader = AudioSampleReader::new(
683            FixtureDecoder::default(),
684            packets(),
685            48_000,
686            1,
687            timing.clone(),
688            1,
689            Limits::default(),
690        )
691        .unwrap();
692        assert_eq!(reader.timing(), &timing);
693        assert_eq!(reader.presentation_length(), 9);
694        let buffer = reader
695            .get_range(SampleRange::new(0, 9).unwrap(), &CancellationToken::new())
696            .unwrap();
697        assert_eq!(buffer.range, SampleRange { start: 0, end: 9 });
698        assert_eq!(
699            buffer.samples,
700            vec![0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
701        );
702    }
703
704    #[test]
705    fn edit_lists_preserve_empty_edits_and_media_mapping() {
706        let timing = AudioTrackTiming {
707            priming: 1,
708            padding: 1,
709            track_offset: 1,
710            edits: vec![
711                AudioEdit {
712                    presentation: SampleRange::new(0, 2).unwrap(),
713                    media_start: None,
714                },
715                AudioEdit {
716                    presentation: SampleRange::new(2, 5).unwrap(),
717                    media_start: Some(4),
718                },
719            ],
720        };
721        let mut reader = AudioSampleReader::new(
722            FixtureDecoder::default(),
723            packets(),
724            48_000,
725            1,
726            timing,
727            0,
728            Limits::default(),
729        )
730        .unwrap();
731        let buffer = reader
732            .get_range(SampleRange::new(0, 6).unwrap(), &CancellationToken::new())
733            .unwrap();
734        assert_eq!(buffer.samples, vec![0.0, 0.0, 0.0, 4.0, 5.0, 6.0]);
735    }
736
737    #[test]
738    fn cancellation_stops_decode_before_queued_work_runs() {
739        let mut reader = AudioSampleReader::new(
740            FixtureDecoder::default(),
741            packets(),
742            48_000,
743            1,
744            AudioTrackTiming::default(),
745            0,
746            Limits::default(),
747        )
748        .unwrap();
749        let cancellation = CancellationToken::new();
750        cancellation.cancel();
751        let error = reader
752            .get_range(SampleRange::new(0, 1).unwrap(), &cancellation)
753            .unwrap_err();
754        assert_eq!(error.kind(), ErrorKind::Cancelled);
755    }
756
757    #[test]
758    fn forward_sequential_reads_decode_each_packet_once() {
759        let (mut reader, counts) = counted_reader(8, 2, Limits::default());
760        let cancellation = CancellationToken::new();
761        // Start past the preroll depth so the first read pays for it, then
762        // walk forward one packet at a time.
763        for step in 3..8 {
764            let range = SampleRange::new(step * 4, step * 4 + 4).unwrap();
765            let buffer = reader.get_range(range, &cancellation).unwrap();
766            assert_eq!(
767                buffer.samples,
768                (step * 4..step * 4 + 4)
769                    .map(|v| v as f32)
770                    .collect::<Vec<_>>()
771            );
772        }
773        // Packets 1 through 7: the two preroll units ahead of packet 3 plus
774        // each requested unit, decoded once each and never re-decoded.
775        assert_eq!(counts.decodes.get(), 7);
776        assert_eq!(counts.resets.get(), 1);
777    }
778
779    #[test]
780    fn backward_and_distant_requests_still_reset_with_preroll() {
781        let (mut reader, counts) = counted_reader(8, 1, Limits::default());
782        let cancellation = CancellationToken::new();
783        reader
784            .get_range(SampleRange::new(12, 16).unwrap(), &cancellation)
785            .unwrap();
786        assert_eq!(counts.resets.get(), 1);
787        // Backwards of the resident run.
788        reader
789            .get_range(SampleRange::new(0, 4).unwrap(), &cancellation)
790            .unwrap();
791        assert_eq!(counts.resets.get(), 2);
792        // Forward but separated by a gap from the resident run.
793        let buffer = reader
794            .get_range(SampleRange::new(28, 32).unwrap(), &cancellation)
795            .unwrap();
796        assert_eq!(counts.resets.get(), 3);
797        assert_eq!(buffer.samples, vec![28.0, 29.0, 30.0, 31.0]);
798    }
799
800    #[test]
801    fn resident_packets_stay_within_the_configured_bound() {
802        let limits = Limits {
803            max_decode_samples_per_seek: 3,
804            ..Limits::default()
805        };
806        let (mut reader, _) = counted_reader(16, 0, limits);
807        let cancellation = CancellationToken::new();
808        for step in 0..16 {
809            reader
810                .get_range(
811                    SampleRange::new(step * 4, step * 4 + 4).unwrap(),
812                    &cancellation,
813                )
814                .unwrap();
815            assert!(reader.decoded.len() <= 3, "resident run grew unbounded");
816        }
817        // The allocation bound evicts too, independently of the packet count.
818        let limits = Limits {
819            // One four-sample packet is 16 bytes, so the request itself fits
820            // but a second resident packet does not.
821            max_allocation_bytes: 16,
822            ..Limits::default()
823        };
824        let (mut reader, _) = counted_reader(16, 0, limits);
825        for step in 0..16 {
826            reader
827                .get_range(
828                    SampleRange::new(step * 4, step * 4 + 4).unwrap(),
829                    &cancellation,
830                )
831                .unwrap();
832            assert!(reader.decoded.len() <= 2, "allocation bound did not evict");
833        }
834    }
835
836    /// A Vorbis stream's first packet decodes nothing: an empty interval
837    /// sharing its start with the packet after it.
838    fn packets_after_an_empty_one(count: u64) -> Vec<EncodedAudioSample> {
839        std::iter::once(EncodedAudioSample {
840            decoded_range: SampleRange::new(0, 0).unwrap(),
841            data: vec![0xff],
842        })
843        .chain(packets_of(count))
844        .collect()
845    }
846
847    #[test]
848    fn a_packet_that_decodes_nothing_is_decoded_but_never_cached() {
849        let decoder = FixtureDecoder::default();
850        let counts = decoder.counts.clone();
851        let mut reader = AudioSampleReader::new(
852            decoder,
853            packets_after_an_empty_one(4),
854            48_000,
855            1,
856            AudioTrackTiming::default(),
857            1,
858            Limits::default(),
859        )
860        .unwrap();
861        assert_eq!(reader.presentation_length(), 16);
862        let cancellation = CancellationToken::new();
863        let buffer = reader
864            .get_range(SampleRange::new(0, 6).unwrap(), &cancellation)
865            .unwrap();
866        assert_eq!(buffer.samples, vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
867        // The empty packet was the preroll ahead of the first packet with
868        // samples, and it holds no entry of its own.
869        assert_eq!(counts.decodes.get(), 3);
870        assert_eq!(reader.decoded.len(), 2);
871    }
872
873    /// Decodes the first packet after a reset wrong, as a decoder with no
874    /// earlier state to continue from does.
875    #[derive(Default)]
876    struct ColdStartDecoder {
877        cold: bool,
878    }
879
880    impl AudioDecoder for ColdStartDecoder {
881        fn decode(
882            &mut self,
883            sample: &EncodedAudioSample,
884            _: &CancellationToken,
885        ) -> Result<AudioBuffer> {
886            let cold = std::mem::take(&mut self.cold);
887            let values = (sample.decoded_range.start..sample.decoded_range.end)
888                .map(|value| if cold { -1.0 } else { value as f32 })
889                .collect();
890            AudioBuffer::new(sample.decoded_range, 48_000, 1, values, &Limits::default())
891        }
892
893        fn reset(&mut self) -> Result<()> {
894            self.cold = true;
895            Ok(())
896        }
897    }
898
899    #[test]
900    fn preroll_output_is_never_served_to_a_later_request() {
901        let mut reader = AudioSampleReader::new(
902            ColdStartDecoder::default(),
903            packets_of(4),
904            48_000,
905            1,
906            AudioTrackTiming::default(),
907            1,
908            Limits::default(),
909        )
910        .unwrap();
911        let cancellation = CancellationToken::new();
912        let later = reader
913            .get_range(SampleRange::new(8, 12).unwrap(), &cancellation)
914            .unwrap();
915        assert_eq!(later.samples, vec![8.0, 9.0, 10.0, 11.0]);
916        // Packet 1 was this decode's preroll. Reading it now must decode it
917        // again behind its own preroll rather than return the cold output.
918        let earlier = reader
919            .get_range(SampleRange::new(4, 8).unwrap(), &cancellation)
920            .unwrap();
921        assert_eq!(earlier.samples, vec![4.0, 5.0, 6.0, 7.0]);
922    }
923
924    fn block_on<T>(future: impl std::future::Future<Output = T>) -> T {
925        let mut future = std::pin::pin!(future);
926        let mut context = std::task::Context::from_waker(std::task::Waker::noop());
927        match future.as_mut().poll(&mut context) {
928            std::task::Poll::Ready(value) => value,
929            std::task::Poll::Pending => panic!("the decode closure never suspends"),
930        }
931    }
932
933    #[test]
934    fn an_asynchronous_decoder_is_driven_as_the_readers_own_would_be() {
935        let (mut reader, _) = counted_reader(8, 1, Limits::default());
936        let mut fixture = FixtureDecoder::default();
937        let mut calls = Vec::new();
938        let cancellation = CancellationToken::new();
939        let mut read = |reader: &mut AudioSampleReader<FixtureDecoder>, start: u64, end: u64| {
940            block_on(reader.get_range_with(
941                SampleRange::new(start, end).unwrap(),
942                &cancellation,
943                async |reset, packets: &[EncodedAudioSample]| {
944                    calls.push((reset, packets.len()));
945                    packets
946                        .iter()
947                        .map(|packet| fixture.decode(packet, &CancellationToken::new()))
948                        .collect()
949                },
950            ))
951            .unwrap()
952            .samples
953        };
954        // Cold: reset, one preroll packet and the requested one.
955        assert_eq!(read(&mut reader, 12, 16), vec![12.0, 13.0, 14.0, 15.0]);
956        // Sequential: continues without a reset or preroll.
957        assert_eq!(read(&mut reader, 16, 20), vec![16.0, 17.0, 18.0, 19.0]);
958        // Already resident: nothing to decode.
959        assert_eq!(
960            read(&mut reader, 13, 18),
961            vec![13.0, 14.0, 15.0, 16.0, 17.0]
962        );
963        // Backwards: a seek.
964        assert_eq!(read(&mut reader, 0, 2), vec![0.0, 1.0]);
965        assert_eq!(calls, vec![(true, 2), (false, 1), (true, 1)]);
966    }
967
968    #[test]
969    fn packets_that_decode_nothing_at_all_are_rejected() {
970        let error = AudioSampleReader::new(
971            FixtureDecoder::default(),
972            vec![EncodedAudioSample {
973                decoded_range: SampleRange::new(0, 0).unwrap(),
974                data: vec![0],
975            }],
976            48_000,
977            1,
978            AudioTrackTiming::default(),
979            0,
980            Limits::default(),
981        )
982        .err()
983        .unwrap();
984        assert_eq!(error.kind(), ErrorKind::InvalidInput);
985    }
986}