pdeljanov / Symphonia

Pure Rust multimedia format demuxing, tag reading, and audio decoding library
Mozilla Public License 2.0
2.28k stars 133 forks source link

Symphonia MP3 decoding has been very slow. #308

Open GameBakerStudio opened 1 month ago

GameBakerStudio commented 1 month ago

While I don't believe this is an issue related directly to the software of Symphonia, I think this issue will still apply here. (I also can't post on StackOverflow so please).

I attempted to use Symphonia to decode MP3 files in my application. I got it to work "following" the example documentation, but it's taking a very long time. 17 seconds is the minimum amount of time I can find to decode files of even very small duration (1-2 minutes). I tried using Rayon; got it down to 3 seconds, but apparently MP3 must be sequentially processed? I guess I am asking if anyone can help me get this time down? I don't expect it to be instantaneous, but I didn't expect it to take 15-17 seconds.

Here's my code. (All parameters and external software are working correctly/valid)

use std::time::Duration;

use symphonia::core::io::{MediaSourceStream, MediaSourceStreamOptions};
use symphonia::core::probe::Hint;
use symphonia::core::formats::FormatOptions;
use symphonia::core::codecs::DecoderOptions;
use symphonia::core::errors::Error;
use symphonia::default::get_probe;
use symphonia_core::meta::MetadataOptions;
use symphonia_core::audio::{AudioBufferRef, SampleBuffer};
use std::fs::File;
use std::path::Path;
use std::io::BufReader;
use std::thread;

use std::sync::{Arc, Mutex};

use symphonia_core::audio::Signal;

pub(crate) fn decode_mp3_file(file_path: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
    // Path to the input audio file
    let audio_path = Path::new(file_path);

    // Open the input audio file
    let file = File::open(audio_path).expect("Failed to open input audio file");

    // Create the media source stream
    let mss = MediaSourceStream::new(Box::new(file), Default::default());

    // Create a hint to help the format registry guess the format
    let mut hint = Hint::new();

    // Use the file extension to assist in guessing
    hint.with_extension("mp3");

    // Probe the media source
    let probed = get_probe()
        .format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())
        .expect("Failed to probe media source");

    // Get the instantiated format reader
    let mut format = probed.format;

    // Get the default track
    let track = format.default_track().expect("No tracks found");

    // Create a decoder for the track
    let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default()).expect("Failed to create decoder");

    println!("Decoding Each Packet.");

    let start_time = std::time::Instant::now();

    // Store the track identifier, we'll use it to filter packets.
    let track_id = track.id;

    let mut sample_count = 0;

    let mut output_pcm: Vec<f32> = Vec::new();

    loop {

        let packet = match format.next_packet() {
            Ok(packet) => packet,
            Err(symphonia::core::errors::Error::IoError(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
                break;
            }
            Err(err) => return Err(Box::new(err)),
        };

        match decoder.decode(&packet) {

            Ok(audio_buf) => {

                // Get the audio buffer specification.
                let spec = *audio_buf.spec();

                // Get the capacity of the decoded buffer. Note: This is capacity, not length!
                let duration = audio_buf.capacity() as u64;

                // Create the f32 sample buffer.
                let mut sample_buf = Some(SampleBuffer::<f32>::new(duration, spec));

                if let Some(buf) = &mut sample_buf {
                    buf.copy_interleaved_ref(audio_buf);

                    let pcm_data = buf.samples().to_vec();
                    output_pcm.extend(pcm_data);

                }

            },
            Err(Error::DecodeError(_)) => (),
            Err(_) => break,

        }

    }
    println!("Time to decode: {} seconds", std::time::Instant::now().duration_since(start_time).as_secs());

    println!("Done Decoding mp3");
    Ok(output_pcm)
}

Sorry code isn't documented :( I'm new to this.

a1phyr commented 1 week ago

Running the code makes it go from 15s to 300ms for me (which is arguably still a lot), this is probably what you are missing here. Even opt-level = 1 is enough to make to duration acceptable in debug mode.

GameBakerStudio commented 1 week ago

Running the code makes it go from 15s to 300ms for me (which is arguably still a lot), this is probably what you are missing here. Even opt-level = 1 is enough to make to duration acceptable in debug mode.

@a1phyr It seems you found a solution for this? I don't know what you mean by "running the code", as thats.. what I've been doing? The 15 (13-17) seconds is the time it takes in debug mode (my current only option). What is opt-level ?

a1phyr commented 1 week ago

Whoops sorry I forgot some words, I meant "Running the code in release mode".

If you don't want to use release mode but still acceptable performance, you can change the optimization level by adding this to your ̀Cargo.toml`

[profile.dev]
opt-level = 1