Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for chunked analysis #42

Merged
merged 2 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions benches/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand All @@ -95,7 +95,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand All @@ -159,7 +159,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand Down Expand Up @@ -211,7 +211,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand All @@ -223,7 +223,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand Down Expand Up @@ -275,7 +275,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&*data, 2).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand All @@ -287,7 +287,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
f.process(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut data_out),
black_box(0),
black_box(&channel_map),
Expand Down
16 changes: 8 additions & 8 deletions benches/true_peak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(&mut peaks),
);
})
Expand All @@ -57,7 +57,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut peaks),
);
})
Expand Down Expand Up @@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(&mut peaks),
);
})
Expand All @@ -118,7 +118,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut peaks),
);
})
Expand Down Expand Up @@ -169,7 +169,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(&mut peaks),
);
})
Expand All @@ -179,7 +179,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut peaks),
);
})
Expand Down Expand Up @@ -230,7 +230,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Interleaved", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(ebur128::Interleaved::new(&data, 2).unwrap()),
black_box(&mut peaks),
);
})
Expand All @@ -240,7 +240,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("Rust/Planar", |b| {
b.iter(|| {
tp.check_true_peak(
black_box(&ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(ebur128::Planar::new(&[fst, snd]).unwrap()),
black_box(&mut peaks),
);
})
Expand Down
180 changes: 178 additions & 2 deletions src/ebur128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ impl EbuR128 {
let (current, next) = src.split_at(self.needed_frames);

self.filter.process(
&current,
current,
&mut *self.audio_data,
self.audio_data_index,
&self.channel_map,
Expand Down Expand Up @@ -632,7 +632,7 @@ impl EbuR128 {
let (current, next) = src.split_at(num_frames);

self.filter.process(
&current,
current,
&mut *self.audio_data,
self.audio_data_index,
&self.channel_map,
Expand Down Expand Up @@ -669,6 +669,10 @@ impl EbuR128 {
Ok(())
}

fn seed_frames<'a, T: Sample + 'a, S: crate::Samples<'a, T>>(&mut self, src: S) {
self.filter.seed(src, &self.channel_map);
}

/// Add interleaved frames to be processed.
pub fn add_frames_i16(&mut self, frames: &[i16]) -> Result<(), Error> {
self.add_frames(crate::Interleaved::new(frames, self.channels as usize)?)
Expand Down Expand Up @@ -709,6 +713,62 @@ impl EbuR128 {
self.add_frames(crate::Planar::new(frames)?)
}

/// Add interleaved frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_i16(&mut self, frames: &[i16]) -> Result<(), Error> {
self.seed_frames(crate::Interleaved::new(frames, self.channels as usize)?);
Ok(())
}

/// Add interleaved frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_i32(&mut self, frames: &[i32]) -> Result<(), Error> {
self.seed_frames(crate::Interleaved::new(frames, self.channels as usize)?);
Ok(())
}

/// Add interleaved frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_f32(&mut self, frames: &[f32]) -> Result<(), Error> {
self.seed_frames(crate::Interleaved::new(frames, self.channels as usize)?);
Ok(())
}

/// Add interleaved frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_f64(&mut self, frames: &[f64]) -> Result<(), Error> {
self.seed_frames(crate::Interleaved::new(frames, self.channels as usize)?);
Ok(())
}

/// Add planar frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_planar_i16(&mut self, frames: &[&[i16]]) -> Result<(), Error> {
self.seed_frames(crate::Planar::new(frames)?);
Ok(())
}

/// Add planar frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_planar_i32(&mut self, frames: &[&[i32]]) -> Result<(), Error> {
self.seed_frames(crate::Planar::new(frames)?);
Ok(())
}

/// Add planar frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_planar_f32(&mut self, frames: &[&[f32]]) -> Result<(), Error> {
self.seed_frames(crate::Planar::new(frames)?);
Ok(())
}

/// Add planar frames to warmup filters, but not be considered for measurements.
/// See [`EbuR128::loudness_global_multiple`] for example usage.
pub fn seed_frames_planar_f64(&mut self, frames: &[&[f64]]) -> Result<(), Error> {
self.seed_frames(crate::Planar::new(frames)?);
Ok(())
}

/// Get global integrated loudness in LUFS.
pub fn loudness_global(&self) -> Result<f64, Error> {
if !self.mode.contains(Mode::I) {
Expand All @@ -719,6 +779,13 @@ impl EbuR128 {
}

/// Get global integrated loudness in LUFS across multiple instances.
///
/// This can be used to allow parallel iteration of long signals, assuming some care is taken:
/// 1. Divide input-signal up in "chunks" of even 100ms samples. Make chunks overlap by 400ms, for example (0-10s, 9.6-20s, 19.6-30s, ...)
/// 2. The first chunk is processed as normal. Then in parallel, for each remaining chunk, create a new instance of `EbuR128`, and in parallel:
/// 1. Feed the first 100ms of the chunk (these are samples overlapping with last chunk) through `seed_frames_*` function. This is sufficient to make filter-states in each instance what they would have been if a single analyzer would have reached this point.
/// 2. Process the remaining samples of each chunk through the analyzer
/// 3. Call [`EbuR128::loudness_global_multiple`] over all the chunks to get the global loudness
// FIXME: Should maybe be IntoIterator? Maybe AsRef<Self>?
pub fn loudness_global_multiple<'a>(
iter: impl Iterator<Item = &'a Self>,
Expand Down Expand Up @@ -935,6 +1002,16 @@ mod tests {
#[cfg(feature = "c-tests")]
use quickcheck_macros::quickcheck;

fn f64_max(mut values: impl Iterator<Item = f64>) -> Option<f64> {
let mut v = values.next()?;
for candidate in values {
if candidate > v {
v = candidate
}
}
Some(v)
}

#[test]
fn sine_stereo_i16() {
let mut data = vec![0i16; 48_000 * 5 * 2];
Expand Down Expand Up @@ -1977,6 +2054,105 @@ mod tests {
);
}

#[test]
fn chunks_queue_with_true_peak() {
let mut data = vec![0.0f32; 48_000 * 3];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(1) {
let val = f32::sin(accumulator);
out[0] = val;
accumulator += step;
}

let mut ebu1 = EbuR128::new(1, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu1.add_frames_f32(&data).unwrap();

let mut ebu_chunks = Vec::new();
for i in 0..3usize {
let mut ebu_chunk = EbuR128::new(1, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
let start_index = std::cmp::max(i as isize * 48_000, 0) as usize;
let stop_index = std::cmp::min(start_index + 48_000 + (48_00 * 3), data.len());
if start_index > 0 {
ebu_chunk
.seed_frames_f32(&data[start_index - 48_00..start_index])
.unwrap();
}
ebu_chunk
.add_frames_f32(&data[start_index..stop_index])
.unwrap();
ebu_chunks.push(ebu_chunk);
}

assert_float_eq!(
ebu1.sample_peak(0).unwrap(),
f64_max(ebu_chunks.iter().map(|meter| meter.sample_peak(0).unwrap())).unwrap(),
abs <= 0.000001
);

assert_float_eq!(
ebu1.true_peak(0).unwrap(),
f64_max(ebu_chunks.iter().map(|meter| meter.true_peak(0).unwrap())).unwrap(),
abs <= 0.000001
);

assert_float_eq!(
ebu1.loudness_global().unwrap(),
EbuR128::loudness_global_multiple(ebu_chunks.iter()).unwrap(),
abs <= 0.000001
);
}

#[test]
fn chunks_histogram_with_true_peak() {
let mut data = vec![0.0f32; 48_000 * 3];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(1) {
let val = f32::sin(accumulator);
out[0] = val;
accumulator += step;
}

let mut ebu1 = EbuR128::new(1, 48_000, Mode::all() | Mode::HISTOGRAM).unwrap();
ebu1.add_frames_f32(&data).unwrap();

let mut ebu_chunks = Vec::new();
for i in 0..3usize {
let mut ebu_chunk =
EbuR128::new(1, 48_000, Mode::all() | Mode::HISTOGRAM & !Mode::HISTOGRAM).unwrap();
let start_index = std::cmp::max(i as isize * 48_000, 0) as usize;
let stop_index = std::cmp::min(start_index + 48_000 + (48_00 * 3), data.len());
if start_index > 0 {
ebu_chunk
.seed_frames_f32(&data[start_index - 48_00..start_index])
.unwrap();
}
ebu_chunk
.add_frames_f32(&data[start_index..stop_index])
.unwrap();
ebu_chunks.push(ebu_chunk);
}

assert_float_eq!(
ebu1.sample_peak(0).unwrap(),
f64_max(ebu_chunks.iter().map(|meter| meter.sample_peak(0).unwrap())).unwrap(),
abs <= 0.000001
);

assert_float_eq!(
ebu1.true_peak(0).unwrap(),
f64_max(ebu_chunks.iter().map(|meter| meter.true_peak(0).unwrap())).unwrap(),
abs <= 0.000001
);

assert_float_eq!(
ebu1.loudness_global().unwrap(),
EbuR128::loudness_global_multiple(ebu_chunks.iter()).unwrap(),
abs <= 0.000001
);
}

#[cfg(feature = "c-tests")]
fn compare_results(ebu: &EbuR128, ebu_c: &ebur128_c::EbuR128, channels: u32) {
assert_float_eq!(
Expand Down
Loading