2020-04-01 20:55:14 +00:00
|
|
|
// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
|
|
|
|
//
|
|
|
|
// This library is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU Library General Public
|
|
|
|
// License as published by the Free Software Foundation; either
|
|
|
|
// version 2 of the License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// Library General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Library General Public
|
|
|
|
// License along with this library; if not, write to the
|
|
|
|
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
|
|
|
// Boston, MA 02110-1335, USA.
|
2021-06-03 18:20:54 +00:00
|
|
|
|
2020-04-01 20:55:14 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
|
|
|
|
use byte_slice_cast::*;
|
|
|
|
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
|
|
|
fn init() {
|
|
|
|
use std::sync::Once;
|
|
|
|
static INIT: Once = Once::new();
|
|
|
|
|
|
|
|
INIT.call_once(|| {
|
|
|
|
gst::init().unwrap();
|
|
|
|
gstrsaudiofx::plugin_register_static().expect("Failed to register rsaudiofx plugin");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-02 11:53:38 +00:00
|
|
|
fn run_test(
|
|
|
|
first_input: &str,
|
|
|
|
second_input: Option<&str>,
|
|
|
|
num_buffers: u32,
|
|
|
|
samples_per_buffer: u32,
|
|
|
|
channels: u32,
|
|
|
|
expected_loudness: f64,
|
|
|
|
) {
|
2020-04-01 20:55:14 +00:00
|
|
|
init();
|
|
|
|
|
2020-04-02 11:53:38 +00:00
|
|
|
let format = if cfg!(target_endian = "little") {
|
|
|
|
format!("audio/x-raw,format=F64LE,rate=192000,channels={}", channels)
|
|
|
|
} else {
|
|
|
|
format!("audio/x-raw,format=F64BE,rate=192000,channels={}", channels)
|
|
|
|
};
|
|
|
|
|
|
|
|
let pipeline = if let Some(second_input) = second_input {
|
|
|
|
gst::parse_launch(&format!(
|
|
|
|
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! audiomixer name=mixer output-buffer-duration={output_buffer_duration} ! {format} ! rsaudioloudnorm ! appsink name=sink audiotestsrc {second_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! mixer.",
|
|
|
|
first_input = first_input,
|
|
|
|
second_input = second_input,
|
|
|
|
num_buffers = num_buffers,
|
|
|
|
samples_per_buffer = samples_per_buffer,
|
2021-05-25 14:37:48 +00:00
|
|
|
output_buffer_duration = samples_per_buffer as u64 * *gst::ClockTime::SECOND / 192_000,
|
2020-04-02 11:53:38 +00:00
|
|
|
format = format,
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
gst::parse_launch(&format!(
|
|
|
|
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! rsaudioloudnorm ! appsink name=sink",
|
|
|
|
first_input = first_input,
|
|
|
|
num_buffers = num_buffers,
|
|
|
|
samples_per_buffer = samples_per_buffer,
|
|
|
|
format = format,
|
|
|
|
))
|
|
|
|
}
|
2020-04-01 20:55:14 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst::Pipeline>()
|
|
|
|
.unwrap();
|
|
|
|
let sink = pipeline
|
2021-04-20 12:58:11 +00:00
|
|
|
.by_name("sink")
|
2020-04-01 20:55:14 +00:00
|
|
|
.unwrap()
|
|
|
|
.downcast::<gst_app::AppSink>()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
sink.set_property("sync", &false).unwrap();
|
2020-06-25 16:49:07 +00:00
|
|
|
let caps = gst_audio::AudioInfo::builder(gst_audio::AUDIO_FORMAT_F64, 192_000, channels)
|
2020-04-01 20:55:14 +00:00
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.to_caps()
|
|
|
|
.unwrap();
|
|
|
|
sink.set_caps(Some(&caps));
|
|
|
|
|
|
|
|
let samples = Arc::new(Mutex::new(Vec::new()));
|
|
|
|
|
|
|
|
let samples_clone = samples.clone();
|
|
|
|
sink.set_callbacks(
|
2020-06-25 16:49:07 +00:00
|
|
|
gst_app::AppSinkCallbacks::builder()
|
2020-04-01 20:55:14 +00:00
|
|
|
.new_sample(move |sink| {
|
|
|
|
let sample = sink.pull_sample().unwrap();
|
|
|
|
|
|
|
|
let mut samples = samples_clone.lock().unwrap();
|
|
|
|
samples.push(sample);
|
|
|
|
Ok(gst::FlowSuccess::Ok)
|
|
|
|
})
|
|
|
|
.build(),
|
|
|
|
);
|
|
|
|
|
|
|
|
pipeline.set_state(gst::State::Playing).unwrap();
|
|
|
|
|
|
|
|
let mut eos = false;
|
2021-04-12 12:49:54 +00:00
|
|
|
let bus = pipeline.bus().unwrap();
|
2021-05-25 14:37:48 +00:00
|
|
|
while let Some(msg) = bus.timed_pop(gst::ClockTime::NONE) {
|
2020-04-01 20:55:14 +00:00
|
|
|
use gst::MessageView;
|
|
|
|
match msg.view() {
|
|
|
|
MessageView::Eos(..) => {
|
|
|
|
eos = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
MessageView::Error(..) => unreachable!(),
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pipeline.set_state(gst::State::Null).unwrap();
|
|
|
|
|
|
|
|
assert!(eos);
|
|
|
|
let samples = samples.lock().unwrap();
|
|
|
|
|
|
|
|
let mut r128 = ebur128::EbuR128::new(
|
|
|
|
channels,
|
|
|
|
192_000,
|
|
|
|
ebur128::Mode::I | ebur128::Mode::SAMPLE_PEAK,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mut num_samples = 0;
|
2021-05-25 14:37:48 +00:00
|
|
|
let mut expected_ts = gst::ClockTime::ZERO;
|
2020-04-01 20:55:14 +00:00
|
|
|
for sample in samples.iter() {
|
2020-04-14 08:59:08 +00:00
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
let buf = sample.buffer().unwrap();
|
2020-04-01 20:55:14 +00:00
|
|
|
|
2021-05-25 14:37:48 +00:00
|
|
|
let ts = buf.pts().expect("undefined pts");
|
2020-04-14 08:59:08 +00:00
|
|
|
match ts.cmp(&expected_ts) {
|
|
|
|
Ordering::Greater => {
|
|
|
|
assert!(
|
2021-05-25 14:37:48 +00:00
|
|
|
ts - expected_ts <= gst::ClockTime::NSECOND,
|
2020-04-14 08:59:08 +00:00
|
|
|
"TS is {} instead of {}",
|
|
|
|
ts,
|
|
|
|
expected_ts
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ordering::Less => {
|
|
|
|
assert!(
|
2021-05-25 14:37:48 +00:00
|
|
|
expected_ts - ts <= gst::ClockTime::NSECOND,
|
2020-04-14 08:59:08 +00:00
|
|
|
"TS is {} instead of {}",
|
|
|
|
ts,
|
|
|
|
expected_ts
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ordering::Equal => (),
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let map = buf.map_readable().unwrap();
|
|
|
|
let data = map.as_slice_of::<f64>().unwrap();
|
|
|
|
|
|
|
|
num_samples += data.len() / channels as usize;
|
|
|
|
r128.add_frames_f64(data).unwrap();
|
|
|
|
|
2021-05-25 14:37:48 +00:00
|
|
|
expected_ts += gst::ClockTime::from_seconds(data.len() as u64 / channels as u64) / 192_000;
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
num_samples,
|
|
|
|
num_buffers as usize * samples_per_buffer as usize
|
|
|
|
);
|
|
|
|
|
|
|
|
let loudness = r128.loudness_global().unwrap();
|
2020-04-02 11:53:38 +00:00
|
|
|
|
|
|
|
if expected_loudness.classify() == std::num::FpCategory::Infinite && expected_loudness < 0.0 {
|
|
|
|
assert!(
|
|
|
|
loudness.classify() == std::num::FpCategory::Infinite && loudness < 0.0,
|
|
|
|
"Loudness is {} instead of {}",
|
|
|
|
loudness,
|
|
|
|
expected_loudness,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
assert!(
|
|
|
|
f64::abs(loudness - expected_loudness) < 1.0,
|
|
|
|
"Loudness is {} instead of {}",
|
|
|
|
loudness,
|
|
|
|
expected_loudness,
|
|
|
|
);
|
|
|
|
}
|
2020-04-01 20:55:14 +00:00
|
|
|
|
|
|
|
for c in 0..channels {
|
|
|
|
let peak = 20.0 * f64::log10(r128.sample_peak(c).unwrap());
|
2020-04-02 11:53:38 +00:00
|
|
|
assert!(
|
|
|
|
peak <= -2.0,
|
|
|
|
"Peak {} for channel {} is above -2.0",
|
|
|
|
c,
|
|
|
|
peak,
|
|
|
|
);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn basic() {
|
2020-04-02 11:53:38 +00:00
|
|
|
run_test("wave=sine", None, 1000, 1920, 1, -24.0);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn basic_white_noise() {
|
2020-04-02 11:53:38 +00:00
|
|
|
run_test("wave=white-noise", None, 1000, 1920, 1, -24.0);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn remaining_at_eos() {
|
2020-04-02 11:53:38 +00:00
|
|
|
run_test("wave=sine", None, 1000, 1024, 1, -24.0);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn short_input() {
|
2020-04-02 11:53:38 +00:00
|
|
|
run_test("wave=sine", None, 100, 1024, 1, -24.0);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn basic_two_channels() {
|
2020-04-02 11:53:38 +00:00
|
|
|
run_test("wave=sine", None, 1000, 1920, 2, -24.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn silence() {
|
|
|
|
run_test("wave=silence", None, 1000, 1024, 1, std::f64::NEG_INFINITY);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn quiet() {
|
|
|
|
// -6dB
|
|
|
|
run_test("wave=sine volume=0.5", None, 1000, 1024, 1, -24.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn very_quiet() {
|
|
|
|
// -20dB
|
|
|
|
run_test("wave=sine volume=0.1", None, 1000, 1024, 1, -24.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn very_very_quiet() {
|
|
|
|
// -40dB
|
|
|
|
run_test("wave=sine volume=0.01", None, 1000, 1024, 1, -24.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn below_threshold() {
|
|
|
|
// -70dB
|
|
|
|
run_test(
|
|
|
|
"wave=sine volume=0.00045",
|
|
|
|
None,
|
|
|
|
1000,
|
|
|
|
1024,
|
|
|
|
1,
|
|
|
|
std::f64::NEG_INFINITY,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn limiter() {
|
|
|
|
run_test(
|
|
|
|
"wave=sine volume=0.05",
|
|
|
|
Some("wave=ticks sine-periods-per-tick=1 tick-interval=4000000000"),
|
|
|
|
1000,
|
|
|
|
1024,
|
|
|
|
1,
|
|
|
|
-24.0,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn limiter_on_first_frame() {
|
|
|
|
run_test(
|
|
|
|
"wave=sine volume=0.05",
|
|
|
|
Some("wave=ticks sine-periods-per-tick=10 tick-interval=4000000000"),
|
|
|
|
1000,
|
|
|
|
1024,
|
|
|
|
1,
|
|
|
|
-24.0,
|
|
|
|
);
|
2020-04-01 20:55:14 +00:00
|
|
|
}
|