Skip to main content

sound_to_patch/audio/
decode.rs

1//! Decoding with symphonia at the file's native rate.
2
3use std::error::Error;
4use std::fmt;
5use std::fs::File;
6use std::io;
7use std::path::{Path, PathBuf};
8
9use symphonia::core::codecs::audio::AudioCodecId;
10use symphonia::core::codecs::audio::AudioDecoderOptions;
11use symphonia::core::codecs::audio::well_known::{
12    CODEC_ID_AC3, CODEC_ID_ALAC, CODEC_ID_EAC3, CODEC_ID_MONKEYS_AUDIO, CODEC_ID_MP1, CODEC_ID_MP2,
13    CODEC_ID_OPUS, CODEC_ID_WAVPACK, CODEC_ID_WMA,
14};
15use symphonia::core::errors::Error as SymphoniaError;
16use symphonia::core::formats::probe::Hint;
17use symphonia::core::formats::{FormatOptions, TrackType};
18use symphonia::core::io::{MediaSourceStream, MediaSourceStreamOptions};
19use symphonia::core::meta::MetadataOptions;
20use symphonia::core::units::{Duration, TimeBase};
21
22use super::{Audio, AudioError, SampleRate};
23use crate::units::Seconds;
24
25/// The containers this build reads, for the unsupported-container message.
26const CONTAINERS: &str = "WAV, AIFF, FLAC, MP3, AAC (ADTS), MP4 or M4A, Ogg, MKV or WebM";
27
28/// The lowest sample rate the analysis reads: the band-edge rule looks for
29/// its fall above 2 kHz, so it needs a Nyquist frequency well above that.
30pub const MIN_SAMPLE_RATE: SampleRate = SampleRate::from_const(8_000);
31/// The highest sample rate the analysis reads, the top of the standard
32/// export rates. The cost of the analysis grows faster than the rate.
33pub const MAX_SAMPLE_RATE: SampleRate = SampleRate::from_const(192_000);
34
35/// The most packets a file may lose to decode errors.
36///
37/// symphonia reports a corrupt packet as recoverable: the next packet
38/// decodes. A lost packet becomes silence of its duration, so every later
39/// sample keeps its time. More lost packets than this mean a damaged file.
40pub const MAX_SKIPPED_PACKETS: usize = 4;
41
42/// A decoded file: its audio and the packets that became silence.
43#[derive(Clone, Debug, PartialEq)]
44pub struct Decoded {
45    audio: Audio,
46    skipped: Vec<SkippedPacket>,
47}
48
49impl Decoded {
50    /// Returns the audio.
51    #[must_use]
52    pub fn audio(&self) -> &Audio {
53        &self.audio
54    }
55
56    /// Returns the audio and drops the report.
57    #[must_use]
58    pub fn into_audio(self) -> Audio {
59        self.audio
60    }
61
62    /// Returns what the analysis leaves out of the file, one line each,
63    /// without a `warning:` prefix: the channels past the first two.
64    #[must_use]
65    pub fn warnings(&self) -> Vec<String> {
66        match self.audio.channels().len() {
67            count @ 3.. => vec![format!(
68                "the file has {count} channels; the analysis reads the first two and ignores the other {}",
69                count - 2
70            )],
71            _ => Vec::new(),
72        }
73    }
74
75    /// Returns the packets that failed to decode, at most
76    /// [`MAX_SKIPPED_PACKETS`], in file order.
77    #[must_use]
78    pub fn skipped(&self) -> &[SkippedPacket] {
79        &self.skipped
80    }
81}
82
83/// A packet that failed to decode and became silence.
84#[derive(Clone, Debug, PartialEq)]
85pub struct SkippedPacket {
86    index: usize,
87    start: Seconds,
88    frames: usize,
89    message: &'static str,
90}
91
92impl SkippedPacket {
93    /// Returns the packet's position among the track's packets, from 0.
94    #[must_use]
95    pub fn index(&self) -> usize {
96        self.index
97    }
98
99    /// Returns where the silence starts in the decoded audio.
100    #[must_use]
101    pub fn start(&self) -> Seconds {
102        self.start
103    }
104
105    /// Returns the length of the silence in frames.
106    #[must_use]
107    pub fn frames(&self) -> usize {
108        self.frames
109    }
110
111    /// Returns symphonia's message.
112    #[must_use]
113    pub fn message(&self) -> &'static str {
114        self.message
115    }
116}
117
118impl fmt::Display for SkippedPacket {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(
121            f,
122            "packet {} at {} ({})",
123            self.index, self.start, self.message
124        )
125    }
126}
127
128/// Decodes the default audio track of a file at its native rate, with every
129/// channel kept: the audio track the container flags as default, else the
130/// first one with a known codec.
131///
132/// A packet that fails to decode becomes silence and is listed in
133/// [`Decoded::skipped`]; see [`MAX_SKIPPED_PACKETS`].
134///
135/// # Errors
136///
137/// Returns a [`DecodeError`] that names the file, what failed and how to fix
138/// it: an unreadable file, an unsupported container or codec, a malformed
139/// stream or too many corrupt packets, a sample rate outside
140/// [`MIN_SAMPLE_RATE`] to [`MAX_SAMPLE_RATE`], or a NaN or infinite sample
141/// with its offset.
142pub fn decode(path: &Path) -> Result<Decoded, DecodeError> {
143    let file = File::open(path).map_err(|source| DecodeError {
144        path: path.to_path_buf(),
145        kind: Kind::Open(source),
146    })?;
147    decode_source(path, Box::new(file))
148}
149
150/// Reads a stem file into memory. [`decode_bytes`] then decodes the same
151/// bytes that a checksum reads, so a file that allows one read only, such
152/// as a pipe, still decodes.
153///
154/// # Errors
155///
156/// The [`DecodeError`] that [`decode`] gives when the file cannot be opened
157/// or read.
158pub(crate) fn read_stem(path: &Path) -> Result<Vec<u8>, DecodeError> {
159    use std::io::Read as _;
160    let fail = |kind| DecodeError {
161        path: path.to_path_buf(),
162        kind,
163    };
164    let mut file = File::open(path).map_err(|source| fail(Kind::Open(source)))?;
165    let mut bytes = Vec::new();
166    file.read_to_end(&mut bytes)
167        .map_err(|source| fail(Kind::Read(source)))?;
168    Ok(bytes)
169}
170
171/// [`decode`] of a file that [`read_stem`] read; errors name `path`.
172///
173/// # Errors
174///
175/// As [`decode`].
176pub(crate) fn decode_bytes(path: &Path, bytes: Vec<u8>) -> Result<Decoded, DecodeError> {
177    decode_source(path, Box::new(io::Cursor::new(bytes)))
178}
179
180/// [`decode`] of an open source; errors name `path`.
181fn decode_source(
182    path: &Path,
183    source: Box<dyn symphonia::core::io::MediaSource>,
184) -> Result<Decoded, DecodeError> {
185    let fail = |kind| DecodeError {
186        path: path.to_path_buf(),
187        kind,
188    };
189    let stream = MediaSourceStream::new(source, MediaSourceStreamOptions::default());
190    let mut hint = Hint::new();
191    if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
192        hint.with_extension(extension);
193    }
194    let mut format = symphonia::default::get_probe()
195        .probe(
196            &hint,
197            stream,
198            FormatOptions::default(),
199            MetadataOptions::default(),
200        )
201        .map_err(|error| match error {
202            SymphoniaError::Unsupported(_) => fail(Kind::UnsupportedContainer),
203            other => fail(Kind::from_symphonia(other)),
204        })?;
205    let track = format
206        .default_track(TrackType::Audio)
207        .ok_or_else(|| fail(Kind::NoAudioTrack))?;
208    let track_id = track.id;
209    let time_base = track.time_base;
210    let params = track
211        .codec_params
212        .as_ref()
213        .and_then(|params| params.audio())
214        .ok_or_else(|| fail(Kind::NoAudioTrack))?
215        .clone();
216    let sample_rate = params
217        .sample_rate
218        .and_then(|rate| SampleRate::new(rate).ok())
219        .ok_or_else(|| fail(Kind::NoSampleRate))?;
220    // A header rate far outside the range costs gigabytes later on.
221    if !(MIN_SAMPLE_RATE..=MAX_SAMPLE_RATE).contains(&sample_rate) {
222        return Err(fail(Kind::RateOutOfRange(sample_rate)));
223    }
224    let mut decoder = symphonia::default::get_codecs()
225        .make_audio_decoder(&params, &AudioDecoderOptions::default())
226        .map_err(|error| match error {
227            SymphoniaError::Unsupported(_) => fail(Kind::UnsupportedCodec(params.codec)),
228            other => fail(Kind::from_symphonia(other)),
229        })?;
230
231    let mut collected = Collected::default();
232    let mut planes: Vec<Vec<f64>> = Vec::new();
233    let mut index = 0;
234    while let Some(packet) = format
235        .next_packet()
236        .map_err(|error| fail(Kind::from_symphonia(error)))?
237    {
238        if packet.track_id != track_id {
239            continue;
240        }
241        index += 1;
242        match decoder.decode(&packet) {
243            Ok(buffer) => {
244                buffer.copy_to_vecs_planar::<f64>(&mut planes);
245                collected.append(&planes).map_err(fail)?;
246            }
247            Err(SymphoniaError::DecodeError(message)) => {
248                let frames = packet_frames(packet.dur, time_base, sample_rate);
249                collected
250                    .skip(index - 1, frames, message, sample_rate)
251                    .map_err(fail)?;
252            }
253            Err(error) => return Err(fail(Kind::from_symphonia(error))),
254        }
255    }
256    let Collected {
257        channels, skipped, ..
258    } = collected;
259    if channels.is_empty() {
260        return Err(fail(Kind::Empty));
261    }
262    let audio = Audio::new(sample_rate, channels).map_err(|error| match error {
263        AudioError::NonFinite { channel, frame } => fail(Kind::NonFinite { channel, frame }),
264        other => fail(Kind::Audio(other)),
265    })?;
266    Ok(Decoded { audio, skipped })
267}
268
269/// The channels decoded so far and the packets that became silence.
270#[derive(Default)]
271struct Collected {
272    channels: Vec<Vec<f64>>,
273    skipped: Vec<SkippedPacket>,
274    /// Silence owed before the first decoded packet fixes the channel count.
275    leading_silence: usize,
276    /// The length of the last decoded packet, for a lost packet of unknown
277    /// duration.
278    last_frames: usize,
279}
280
281impl Collected {
282    fn append(&mut self, planes: &[Vec<f64>]) -> Result<(), Kind> {
283        if self.channels.is_empty() {
284            self.channels
285                .resize(planes.len(), vec![0.0; self.leading_silence]);
286        } else if planes.len() != self.channels.len() {
287            return Err(Kind::ChannelsChanged {
288                from: self.channels.len(),
289                to: planes.len(),
290            });
291        }
292        for (channel, plane) in self.channels.iter_mut().zip(planes) {
293            channel.extend_from_slice(plane);
294        }
295        self.last_frames = planes.first().map_or(0, Vec::len);
296        Ok(())
297    }
298
299    fn skip(
300        &mut self,
301        index: usize,
302        frames: Option<usize>,
303        message: &'static str,
304        rate: SampleRate,
305    ) -> Result<(), Kind> {
306        let frames = frames.unwrap_or(self.last_frames);
307        let done = self.channels.first().map_or(self.leading_silence, Vec::len);
308        self.skipped.push(SkippedPacket {
309            index,
310            start: rate.seconds(done),
311            frames,
312            message,
313        });
314        if self.skipped.len() > MAX_SKIPPED_PACKETS {
315            return Err(Kind::TooManyBadPackets(self.skipped.swap_remove(0)));
316        }
317        if self.channels.is_empty() {
318            self.leading_silence += frames;
319        }
320        for channel in &mut self.channels {
321            channel.resize(channel.len() + frames, 0.0);
322        }
323        Ok(())
324    }
325}
326
327/// The frames a packet of duration `dur` covers, from the track's time base.
328fn packet_frames(dur: Duration, time_base: Option<TimeBase>, rate: SampleRate) -> Option<usize> {
329    let time_base = time_base?;
330    let ticks = u128::from(dur.get()) * u128::from(time_base.numer.get()) * u128::from(rate.get());
331    let denom = u128::from(time_base.denom.get());
332    usize::try_from((ticks + denom / 2) / denom).ok()
333}
334
335/// A file that does not decode: the path and the cause.
336#[derive(Debug)]
337pub struct DecodeError {
338    path: PathBuf,
339    kind: Kind,
340}
341
342impl DecodeError {
343    /// Returns the file that failed.
344    #[must_use]
345    pub fn path(&self) -> &Path {
346        &self.path
347    }
348}
349
350#[derive(Debug)]
351enum Kind {
352    Open(io::Error),
353    Read(io::Error),
354    UnsupportedContainer,
355    UnsupportedCodec(AudioCodecId),
356    NoAudioTrack,
357    NoSampleRate,
358    RateOutOfRange(SampleRate),
359    Malformed(&'static str),
360    Limit(&'static str),
361    ResetRequired,
362    Unexpected,
363    ChannelsChanged { from: usize, to: usize },
364    TooManyBadPackets(SkippedPacket),
365    Empty,
366    NonFinite { channel: usize, frame: usize },
367    Audio(AudioError),
368}
369
370impl Kind {
371    fn from_symphonia(error: SymphoniaError) -> Self {
372        match error {
373            SymphoniaError::IoError(source) => Self::Read(source),
374            SymphoniaError::DecodeError(message) | SymphoniaError::Unsupported(message) => {
375                Self::Malformed(message)
376            }
377            SymphoniaError::LimitError(message) => Self::Limit(message),
378            // A reset means the stream changed shape. A seek never happens
379            // here, and the enum is non-exhaustive.
380            SymphoniaError::ResetRequired => Self::ResetRequired,
381            _ => Self::Unexpected,
382        }
383    }
384}
385
386/// The name of a codec that this build lacks, and the symphonia Cargo
387/// feature that adds it, if one exists.
388fn codec_name(codec: AudioCodecId) -> (&'static str, Option<&'static str>) {
389    match codec {
390        CODEC_ID_MP1 => ("MPEG audio layer I", Some("mp1")),
391        CODEC_ID_MP2 => ("MPEG audio layer II", Some("mp2")),
392        CODEC_ID_ALAC => ("ALAC", Some("alac")),
393        CODEC_ID_OPUS => ("Opus", None),
394        CODEC_ID_AC3 => ("AC-3", None),
395        CODEC_ID_EAC3 => ("E-AC-3", None),
396        CODEC_ID_WMA => ("WMA", None),
397        CODEC_ID_WAVPACK => ("WavPack", None),
398        CODEC_ID_MONKEYS_AUDIO => ("Monkey's Audio", None),
399        _ => ("this", None),
400    }
401}
402
403impl fmt::Display for DecodeError {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        let path = self.path.display();
406        write!(f, "cannot decode {path}: ")?;
407        match &self.kind {
408            Kind::Open(source) => write!(f, "cannot open the file ({source}). Check the path"),
409            Kind::Read(source) => write!(f, "reading failed ({source}). Check the file"),
410            Kind::UnsupportedContainer => write!(
411                f,
412                "the container is not supported. Export the stem as one of: {CONTAINERS}"
413            ),
414            Kind::UnsupportedCodec(codec) => match codec_name(*codec) {
415                (name, Some(feature)) => write!(
416                    f,
417                    "the {name} codec is not built in. Build with the symphonia Cargo feature `{feature}`, or export the stem as WAV"
418                ),
419                (name, None) => write!(
420                    f,
421                    "the {name} codec is not supported. Export the stem as WAV or FLAC"
422                ),
423            },
424            Kind::NoAudioTrack => write!(f, "the file has no audio track"),
425            Kind::NoSampleRate => write!(
426                f,
427                "the audio track declares no sample rate. Export the stem again as WAV"
428            ),
429            Kind::RateOutOfRange(rate) => write!(
430                f,
431                "the header declares a sample rate of {rate}, outside the {MIN_SAMPLE_RATE} to {MAX_SAMPLE_RATE} that the analysis reads. Export the stem again at 44.1 or 48 kHz"
432            ),
433            Kind::Malformed(message) => write!(
434                f,
435                "the stream is malformed ({message}). Export the stem again"
436            ),
437            Kind::Limit(message) => write!(f, "a decoder limit was reached ({message})"),
438            Kind::ResetRequired => write!(
439                f,
440                "the stream changes its format midway. Export the stem again as one track"
441            ),
442            Kind::Unexpected => {
443                write!(f, "the decoder failed unexpectedly. Export the stem as WAV")
444            }
445            Kind::ChannelsChanged { from, to } => write!(
446                f,
447                "the channel count changes from {from} to {to} midway. Export the stem again"
448            ),
449            Kind::TooManyBadPackets(first) => write!(
450                f,
451                "more than {MAX_SKIPPED_PACKETS} packets do not decode, the first {first}. Export the stem again"
452            ),
453            Kind::Empty => write!(f, "the audio track holds no samples"),
454            Kind::NonFinite { channel, frame } => write!(
455                f,
456                "the sample at frame {frame} of channel {channel} is NaN or infinite. Export the stem again"
457            ),
458            Kind::Audio(error) => write!(f, "{error}"),
459        }
460    }
461}
462
463impl Error for DecodeError {
464    fn source(&self) -> Option<&(dyn Error + 'static)> {
465        match &self.kind {
466            Kind::Open(source) | Kind::Read(source) => Some(source),
467            Kind::Audio(source) => Some(source),
468            _ => None,
469        }
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    fn rate() -> SampleRate {
478        SampleRate::new(44_100).unwrap()
479    }
480
481    fn error(kind: Kind) -> DecodeError {
482        DecodeError {
483            path: PathBuf::from("bass.wav"),
484            kind,
485        }
486    }
487
488    #[test]
489    fn every_symphonia_error_maps_to_its_kind() {
490        let io = || io::Error::new(io::ErrorKind::UnexpectedEof, "short");
491        assert!(matches!(
492            Kind::from_symphonia(SymphoniaError::IoError(io())),
493            Kind::Read(_)
494        ));
495        assert!(matches!(
496            Kind::from_symphonia(SymphoniaError::DecodeError("bad")),
497            Kind::Malformed("bad")
498        ));
499        assert!(matches!(
500            Kind::from_symphonia(SymphoniaError::Unsupported("odd")),
501            Kind::Malformed("odd")
502        ));
503        assert!(matches!(
504            Kind::from_symphonia(SymphoniaError::LimitError("big")),
505            Kind::Limit("big")
506        ));
507        assert!(matches!(
508            Kind::from_symphonia(SymphoniaError::ResetRequired),
509            Kind::ResetRequired
510        ));
511        assert!(matches!(
512            Kind::from_symphonia(SymphoniaError::SeekError(
513                symphonia::core::errors::SeekErrorKind::Unseekable
514            )),
515            Kind::Unexpected
516        ));
517    }
518
519    #[test]
520    fn every_failure_names_the_file_and_what_failed() {
521        let skipped = SkippedPacket {
522            index: 3,
523            start: Seconds::new(0.5).unwrap(),
524            frames: 1152,
525            message: "bad frame",
526        };
527        let io = || io::Error::other("denied");
528        let cases = [
529            (
530                Kind::Open(io()),
531                "cannot open the file (denied). Check the path",
532            ),
533            (Kind::Read(io()), "reading failed (denied). Check the file"),
534            (Kind::NoAudioTrack, "the file has no audio track"),
535            (
536                Kind::RateOutOfRange(SampleRate::new(1).unwrap()),
537                "the header declares a sample rate of 1 Hz, outside the 8000 Hz to 192000 Hz that the analysis reads. Export the stem again at 44.1 or 48 kHz",
538            ),
539            (
540                Kind::NoSampleRate,
541                "the audio track declares no sample rate. Export the stem again as WAV",
542            ),
543            (
544                Kind::Malformed("bad"),
545                "the stream is malformed (bad). Export the stem again",
546            ),
547            (Kind::Limit("big"), "a decoder limit was reached (big)"),
548            (
549                Kind::ResetRequired,
550                "the stream changes its format midway. Export the stem again as one track",
551            ),
552            (
553                Kind::Unexpected,
554                "the decoder failed unexpectedly. Export the stem as WAV",
555            ),
556            (
557                Kind::ChannelsChanged { from: 2, to: 1 },
558                "the channel count changes from 2 to 1 midway. Export the stem again",
559            ),
560            (
561                Kind::TooManyBadPackets(skipped),
562                "more than 4 packets do not decode, the first packet 3 at 0.5 s (bad frame). Export the stem again",
563            ),
564            (Kind::Empty, "the audio track holds no samples"),
565            (
566                Kind::NonFinite {
567                    channel: 1,
568                    frame: 7,
569                },
570                "the sample at frame 7 of channel 1 is NaN or infinite. Export the stem again",
571            ),
572            (
573                Kind::Audio(AudioError::NoChannels),
574                "the audio has no channel; it needs at least one",
575            ),
576            (
577                Kind::UnsupportedCodec(CODEC_ID_OPUS),
578                "the Opus codec is not supported. Export the stem as WAV or FLAC",
579            ),
580            (
581                Kind::UnsupportedCodec(CODEC_ID_ALAC),
582                "the ALAC codec is not built in. Build with the symphonia Cargo feature `alac`, or export the stem as WAV",
583            ),
584        ];
585        for (kind, cause) in cases {
586            assert_eq!(
587                error(kind).to_string(),
588                format!("cannot decode bass.wav: {cause}")
589            );
590        }
591        assert!(error(Kind::Open(io())).source().is_some());
592        assert!(error(Kind::Read(io())).source().is_some());
593        assert!(
594            error(Kind::Audio(AudioError::NoChannels))
595                .source()
596                .is_some()
597        );
598        assert!(error(Kind::Empty).source().is_none());
599        assert_eq!(error(Kind::Empty).path(), Path::new("bass.wav"));
600    }
601
602    #[test]
603    fn a_channel_count_that_changes_midway_is_an_error() {
604        let mut collected = Collected::default();
605        collected.append(&[vec![0.1, 0.2], vec![0.3, 0.4]]).unwrap();
606        let error = collected.append(&[vec![0.5]]).unwrap_err();
607        assert!(matches!(error, Kind::ChannelsChanged { from: 2, to: 1 }));
608    }
609
610    #[test]
611    fn a_lost_first_packet_becomes_leading_silence_in_every_channel() {
612        let mut collected = Collected::default();
613        collected.skip(0, Some(3), "bad", rate()).unwrap();
614        collected.append(&[vec![1.0, 2.0], vec![3.0, 4.0]]).unwrap();
615        assert_eq!(
616            collected.channels,
617            [vec![0.0, 0.0, 0.0, 1.0, 2.0], vec![0.0, 0.0, 0.0, 3.0, 4.0]]
618        );
619        assert_eq!(collected.skipped[0].start().get(), 0.0);
620        assert_eq!(collected.skipped[0].frames(), 3);
621    }
622
623    #[test]
624    fn a_lost_packet_of_unknown_length_lasts_as_long_as_the_last_one() {
625        let mut collected = Collected::default();
626        collected.skip(0, None, "bad", rate()).unwrap();
627        assert_eq!(collected.skipped[0].frames(), 0);
628        collected.append(&[vec![1.0, 2.0, 3.0]]).unwrap();
629        collected.skip(2, None, "bad", rate()).unwrap();
630        assert_eq!(collected.channels, [vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]]);
631        assert_eq!(collected.skipped[1].frames(), 3);
632        assert_eq!(collected.skipped[1].start().get(), 3.0 / 44_100.0);
633    }
634
635    #[test]
636    fn the_packet_after_the_limit_fails_and_names_the_first() {
637        let mut collected = Collected::default();
638        for index in 0..MAX_SKIPPED_PACKETS {
639            collected.skip(index, Some(10), "bad", rate()).unwrap();
640        }
641        let error = collected
642            .skip(MAX_SKIPPED_PACKETS, Some(10), "bad", rate())
643            .unwrap_err();
644        assert!(matches!(error, Kind::TooManyBadPackets(first) if first.index() == 0));
645    }
646
647    #[test]
648    fn a_packet_length_needs_the_time_base() {
649        let dur = Duration::new(1152);
650        assert_eq!(packet_frames(dur, None, rate()), None);
651        let native = TimeBase::try_from_recip(44_100);
652        assert_eq!(packet_frames(dur, native, rate()), Some(1152));
653        // 1152 ticks of 1/48000 s at 44.1 kHz is 1058.4 frames.
654        let other = TimeBase::try_from_recip(48_000);
655        assert_eq!(packet_frames(dur, other, rate()), Some(1058));
656    }
657}