sdroege / ebur128

Implementation of the EBU R128 loudness standard
MIT License
93 stars 15 forks source link

Simple example #56

Closed thewh1teagle closed 1 month ago

thewh1teagle commented 1 month ago

Hi there, Thanks for providing this crate!

I’m using cpal to record audio from the microphone in Rust, but the sound is coming through too quietly. I’m looking to normalize the audio, but I haven’t found a straightforward example in your repository. Could you clarify what the input and output should be for normalization? Specifically, should I use a sample array and sample rate?

It would be great if you could add a simple example with cpal or provide some guidance on how to achieve this.

Thank you!

altunenes commented 1 month ago

I'm also looking for it. it seems a little bit complex to me to understand what going on in the source code :-)

sdroege commented 1 month ago

Not really a "simple" example, but maybe https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/blob/main/audio/audiofx/src/ebur128level/imp.rs helps.

Note that this crate only measures the loudness. Normalization has to be done separately based on the results.

thewh1teagle commented 1 month ago

@sdroege

Thanks.

Does the following looks correct? It works but maybe can be improved / has some mistakes.

/*
wget https://github.com/thewh1teagle/vibe/raw/main/samples/multi.wav
cargo run --example normalize_wav multi.wav multi.wav normalized.wav
*/
use ebur128::{EbuR128, Mode};
use hound::{WavReader, WavWriter, WavSpec, SampleFormat};
use std::env;

fn main() {
    let input_path = env::args().nth(1).expect("Please specify input wav path");
    let output_path = env::args().nth(2).expect("Please specify output wav path");

    let mut reader = WavReader::open(&input_path).expect("Failed to open WAV file");

    let spec = reader.spec();
    let channels = spec.channels as usize;
    let rate = spec.sample_rate as u32;
    let mut ebur128 = EbuR128::new(channels as u32, rate, Mode::all()).expect("Failed to create ebur128");

    let samples: Vec<f32> = reader
        .samples::<f32>()
        .map(|s| s.unwrap())
        .collect();
    let chunk_size = rate * 1; // 1s

    // Analyze loudness
    for chunk in samples.chunks(chunk_size as usize * channels) {
        ebur128.add_frames_f32(chunk);
        let loudness = ebur128
            .loudness_global()
            .expect("Failed to get global loudness");
        println!("Loudness: {:?}", loudness);
    }

    // Get the global loudness and calculate the gain needed
    let global_loudness = ebur128.loudness_global().expect("Failed to get global loudness");
    let target_loudness = -23.0; // EBU R128 target loudness
    let gain = 10f32.powf(((target_loudness - global_loudness) / 20.0) as f32);

    // Apply gain and write to the output file
    let mut writer = WavWriter::create(output_path, WavSpec {
        channels: spec.channels,
        sample_rate: spec.sample_rate,
        bits_per_sample: 16,
        sample_format: SampleFormat::Int,
    }).expect("Failed to create WAV writer");

    for sample in samples {
        let normalized_sample = (sample * gain).clamp(-1.0, 1.0);
        writer.write_sample((normalized_sample * i16::MAX as f32) as i16).expect("Failed to write sample");
    }
}
sdroege commented 1 month ago

Yes, something like that should work. Afterwards the global loudness should be about -23.

Do you want to put this into a PR and add it to the examples subdirectory here?