Skip to content

issues with ogg delay and padding with gapless #418

@bobbens

Description

@bobbens

I've noticed clicking when looping vorbis audio. To determine the cause, I've loaded the PCM data with symphonia and written it to disk, to compare with PCM data written from ffmpeg. I've tried different formats to see what is causing the gaps to appear. Results are below:

# Reference generated with ffmpeg
-rw-r--r-- 1 ess ess 94592 Dec 17 09:38 ref.pcm # ffmpeg conversion to PCM with '-f f32le -acodec pcm_f32le'
# Below generated after loading with symphonia
-rw-r--r-- 1 ess ess 94592 Dec 17 10:09 test_flac.pcm # original flac lossless data
-rw-r--r-- 1 ess ess 94592 Dec 17 10:09 test_ogg_flac.pcm # ogg+flac from encoding flac with '-c:a flac -f ogg'
-rw-r--r-- 1 ess ess 98048 Dec 17 10:05 test_ogg_vorbis.pcm # ogg+vorbis from encoding flac with ogg defaults

enable_gapless is always set.

I don't have a minimal test case, but the relevant loading code is:

enum Frame<T> {
    Mono(T),
    Stereo(T, T),
}
impl<T> Frame<T> {
    /// Loads a Vec of Frame<f32> from an AudioBufferRef
    fn load_frames_from_buffer_ref(
        buffer: &symphonia::core::audio::AudioBufferRef,
    ) -> Result<Vec<Frame<f32>>> {
        fn load_frames_from_buffer<S: Sample>(
            buffer: &symphonia::core::audio::AudioBuffer<S>,
        ) -> Result<Vec<Frame<f32>>>
        where
            f32: FromSample<S>,
        {
            match buffer.spec().channels.count() {
                1 => Ok(buffer
                    .chan(0)
                    .iter()
                    .map(|sample| Frame::Mono((*sample).into_sample()))
                    .collect()),
                2 => Ok(buffer
                    .chan(0)
                    .iter()
                    .zip(buffer.chan(1).iter())
                    .map(|(left, right)| {
                        Frame::Stereo((*left).into_sample(), (*right).into_sample())
                    })
                    .collect()),
                _ => anyhow::bail!("Unsupported channel configuration"),
            }
        }
        use symphonia::core::audio::AudioBufferRef;
        match buffer {
            AudioBufferRef::U8(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::U16(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::U24(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::U32(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::S8(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::S16(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::S24(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::S32(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::F32(buffer) => load_frames_from_buffer(buffer),
            AudioBufferRef::F64(buffer) => load_frames_from_buffer(buffer),
        }
    }
}
...

        // Relevant loading code
        let codecs = &CODECS;
        let probe = symphonia::default::get_probe();
        let mss = MediaSourceStream::new(Box::new(src), Default::default());
        let mut hint = symphonia::core::probe::Hint::new();
        if let Some(ext) = ext {
            hint.with_extension(ext);
        }
        // Enable gapless so encoded padding (e.g. Opus pre-skip/end trim) is removed.
        let mut format = probe
            .format(
                &hint,
                mss,
                &symphonia::core::formats::FormatOptions {
                    enable_gapless: true,
                    ..Default::default()
                },
                &Default::default(),
            )?
            .format;

        let track = format.default_track().context("No default track")?;
        let track_id = track.id;

        let codec_params = &track.codec_params;
        let sample_rate = codec_params.sample_rate.context("Unknown sample rate")?;
        let mut decoder = codecs.make(codec_params, &Default::default())?;

        let channels = track.codec_params.channels.context("no channels")?;
        if !channels.contains(Channels::FRONT_LEFT) {
            anyhow::bail!("no mono channel");
        }
        let stereo = channels.contains(Channels::FRONT_RIGHT);

        let mut frames: Vec<Frame<f32>> = vec![];
        loop {
            // Get the next packet from the media format.
            let packet = match format.next_packet() {
                Ok(packet) => packet,
                Err(e) => match e {
                    symphonia::core::errors::Error::IoError(e) => {
                        if e.kind() == std::io::ErrorKind::UnexpectedEof
                            && e.to_string() == "end of stream"
                        {
                            break;
                        }
                        return Err(symphonia::core::errors::Error::IoError(e).into());
                    }
                    e => anyhow::bail!(e),
                },
            };

            // If the packet does not belong to the selected track, skip over it.
            if packet.track_id() == track_id {
                // Decode the packet into audio samples.
                let buffer = decoder.decode(&packet)?;
                frames.append(&mut Frame::<f32>::load_frames_from_buffer_ref(&buffer)?);
            }
        }
...

Looking into the loop in detail, it seem like the packet is not correct:

[lib.rs:352:17] packet.dur = 0
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 0
[lib.rs:352:17] packet.ts = 0
[lib.rs:352:17] packet.dur = 128
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 128
[lib.rs:352:17] packet.ts = 0
[lib.rs:352:17] packet.dur = 128
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 128
[lib.rs:352:17] packet.ts = 0
[lib.rs:352:17] packet.dur = 128
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 128
[lib.rs:352:17] packet.ts = 0
[lib.rs:352:17] packet.dur = 576
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 576
[lib.rs:352:17] packet.ts = 96
[lib.rs:352:17] packet.dur = 1024
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 1024
[lib.rs:352:17] packet.ts = 1120
[lib.rs:352:17] packet.dur = 1024
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0
[lib.rs:352:17] Frame::<f32>::load_frames_from_buffer_ref(&buffer)?.len() = 1024
[lib.rs:352:17] packet.ts = 2144
[lib.rs:352:17] packet.dur = 1024
[lib.rs:352:17] packet.trim_start = 0
[lib.rs:352:17] packet.trim_end = 0

It seems like the encoder delay is not getting trimmed with gapless_enabled automatically. Nor is information set in trim_start and trim_end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions