use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use std::mem;
use std::sync::Mutex;
use std::u64;
use byte_slice_cast::*;
use once_cell::sync::Lazy;
use atomic_refcell::AtomicRefCell;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"audioloudnorm",
gst::DebugColorFlags::empty(),
Some("Audio Loudless Normalization Filter"),
)
});
const DEFAULT_LOUDNESS_TARGET: f64 = -24.0;
const DEFAULT_LOUDNESS_RANGE_TARGET: f64 = 7.0;
const DEFAULT_MAX_TRUE_PEAK: f64 = -2.0;
const DEFAULT_OFFSET: f64 = 0.0;
#[derive(Debug, Clone, Copy)]
struct Settings {
pub loudness_target: f64,
pub loudness_range_target: f64,
pub max_true_peak: f64,
pub offset: f64,
}
impl Default for Settings {
fn default() -> Self {
Settings {
loudness_target: DEFAULT_LOUDNESS_TARGET,
loudness_range_target: DEFAULT_LOUDNESS_RANGE_TARGET,
max_true_peak: DEFAULT_MAX_TRUE_PEAK,
offset: DEFAULT_OFFSET,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FrameType {
First,
Inner,
Final,
Linear,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LimiterState {
Out,
Attack,
Sustain,
Release,
}
struct State {
info: gst_audio::AudioInfo,
adapter: gst_base::UniqueAdapter,
current_samples_per_frame: usize,
offset: f64,
target_i: f64,
target_lra: f64,
target_tp: f64,
buf: Box<[f64]>,
buf_index: usize,
prev_buf_index: usize,
weights: [f64; 21],
delta: [f64; 30],
index: usize,
prev_delta: f64,
gain_reduction: [f64; 2],
limiter_buf: Box<[f64]>,
limiter_buf_index: usize,
prev_smp: Box<[f64]>,
limiter_state: LimiterState,
env_cnt: usize,
sustain_cnt: Option<usize>,
frame_type: FrameType,
above_threshold: bool,
r128_in: ebur128::EbuR128,
r128_out: ebur128::EbuR128,
}
impl State {
fn new(settings: &Settings, info: gst_audio::AudioInfo) -> Self {
let r128_in = ebur128::EbuR128::new(
info.channels(),
info.rate(),
ebur128::Mode::HISTOGRAM
| ebur128::Mode::I
| ebur128::Mode::S
| ebur128::Mode::LRA
| ebur128::Mode::SAMPLE_PEAK,
)
.unwrap();
let r128_out = ebur128::EbuR128::new(
info.channels(),
info.rate(),
ebur128::Mode::HISTOGRAM
| ebur128::Mode::I
| ebur128::Mode::S
| ebur128::Mode::LRA
| ebur128::Mode::SAMPLE_PEAK,
)
.unwrap();
let buf_size = GAIN_LOOKAHEAD * info.channels() as usize;
let buf = vec![0.0; buf_size].into_boxed_slice();
let limiter_buf_size = (2 * FRAME_SIZE + LIMITER_LOOKAHEAD) * info.channels() as usize;
let limiter_buf = vec![0.0; limiter_buf_size].into_boxed_slice();
let prev_smp = vec![0.0; info.channels() as usize].into_boxed_slice();
let current_samples_per_frame = GAIN_LOOKAHEAD;
let buf_index = 0;
let prev_buf_index = 0;
let limiter_buf_index = 0;
let index = 1;
let limiter_state = LimiterState::Out;
let offset = f64::powf(10., settings.offset / 20.);
let target_tp = f64::powf(10., settings.max_true_peak / 20.);
State {
info,
adapter: gst_base::UniqueAdapter::new(),
current_samples_per_frame,
offset,
target_i: settings.loudness_target,
target_lra: settings.loudness_range_target,
target_tp,
buf,
buf_index,
prev_buf_index,
delta: [0.0; 30],
weights: init_gaussian_filter(),
prev_delta: 0.0,
index,
gain_reduction: [0.0; 2],
limiter_buf,
prev_smp,
limiter_buf_index,
limiter_state,
env_cnt: 0,
sustain_cnt: None,
frame_type: FrameType::First,
above_threshold: false,
r128_in,
r128_out,
}
}
}
pub struct AudioLoudNorm {
srcpad: gst::Pad,
sinkpad: gst::Pad,
settings: Mutex<Settings>,
state: AtomicRefCell<Option<State>>,
}
const GAIN_LOOKAHEAD: usize = 3 * 192_000; const FRAME_SIZE: usize = 19_200;
const LIMITER_ATTACK_WINDOW: usize = 1920; const LIMITER_RELEASE_WINDOW: usize = 19_200; const LIMITER_LOOKAHEAD: usize = 1920;
impl State {
fn drain_full_frames(
&mut self,
imp: &AudioLoudNorm,
) -> Result<Vec<gst::Buffer>, gst::FlowError> {
let mut outbufs = vec![];
while self.adapter.available() >= self.info.bpf() as usize * self.current_samples_per_frame
{
let (pts, distance) = self.adapter.prev_pts();
let distance_samples = distance / self.info.bpf() as u64;
let distance_ts = distance_samples
.mul_div_floor(*gst::ClockTime::SECOND, self.info.rate() as u64)
.map(gst::ClockTime::from_nseconds);
let pts = pts
.opt_checked_add(distance_ts)
.map_err(|_| gst::FlowError::Error)?;
let inbuf = self
.adapter
.take_buffer(self.info.bpf() as usize * self.current_samples_per_frame)
.unwrap();
let src = inbuf.map_readable().map_err(|_| gst::FlowError::Error)?;
let src = src
.as_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?;
let (mut outbuf, pts) = self.process(imp, src, pts)?;
{
let outbuf = outbuf.get_mut().unwrap();
outbuf.set_pts(pts);
outbuf.set_duration(
(outbuf.size() as u64)
.mul_div_floor(
*gst::ClockTime::SECOND,
(self.info.bpf() * self.info.rate()) as u64,
)
.map(gst::ClockTime::from_nseconds),
);
}
outbufs.push(outbuf);
}
Ok(outbufs)
}
fn drain(&mut self, imp: &AudioLoudNorm) -> Result<gst::Buffer, gst::FlowError> {
gst::debug!(CAT, imp: imp, "Draining");
let (pts, distance) = self.adapter.prev_pts();
let distance_samples = distance / self.info.bpf() as u64;
let distance_ts = distance_samples
.mul_div_floor(*gst::ClockTime::SECOND, self.info.rate() as u64)
.map(gst::ClockTime::from_nseconds);
let pts = pts
.opt_checked_add(distance_ts)
.map_err(|_| gst::FlowError::Error)?;
let mut _mapped_inbuf = None;
let src = if self.adapter.available() > 0 {
let inbuf = self.adapter.take_buffer(self.adapter.available()).unwrap();
let inbuf = inbuf
.into_mapped_buffer_readable()
.map_err(|_| gst::FlowError::Error)?;
_mapped_inbuf = Some(inbuf);
_mapped_inbuf
.as_ref()
.unwrap()
.as_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?
} else {
&[]
};
if self.current_samples_per_frame == FRAME_SIZE {
self.frame_type = FrameType::Final;
} else if src.is_empty() {
gst::debug!(CAT, imp: imp, "No data to drain");
return Err(gst::FlowError::Eos);
}
let (mut outbuf, pts) = self.process(imp, src, pts)?;
{
let outbuf = outbuf.get_mut().unwrap();
outbuf.set_pts(pts);
outbuf.set_duration(
(outbuf.size() as u64)
.mul_div_floor(
*gst::ClockTime::SECOND,
(self.info.bpf() * self.info.rate()) as u64,
)
.map(gst::ClockTime::from_nseconds),
);
}
Ok(outbuf)
}
fn process_first_frame_is_last(&mut self, imp: &AudioLoudNorm) -> Result<(), gst::FlowError> {
let global = self
.r128_in
.loudness_global()
.map_err(|_| gst::FlowError::Error)?;
let mut true_peak = 0.0;
for c in 0..(self.info.channels()) {
let peak = self
.r128_in
.sample_peak(c)
.map_err(|_| gst::FlowError::Error)?;
if c == 0 || peak > true_peak {
true_peak = peak;
}
}
gst::debug!(
CAT,
imp: imp,
"Calculated global loudness for first frame {} with peak {}",
global,
true_peak
);
let offset = f64::powf(10., (self.target_i - global) / 20.);
let offset_tp = true_peak * offset;
self.offset = if offset_tp < self.target_tp {
offset
} else {
self.target_tp / true_peak
};
self.frame_type = FrameType::Linear;
Ok(())
}
fn process_first_frame(
&mut self,
imp: &AudioLoudNorm,
src: &[f64],
pts: impl Into<Option<gst::ClockTime>>,
) -> Result<(gst::Buffer, Option<gst::ClockTime>), gst::FlowError> {
self.buf.copy_from_slice(src);
let shortterm = self
.r128_in
.loudness_shortterm()
.map_err(|_| gst::FlowError::Error)?;
let env_shortterm = if shortterm < -70.0 {
self.above_threshold = false;
0.
} else {
self.above_threshold = true;
self.target_i - shortterm
};
for delta in self.delta.iter_mut() {
*delta = f64::powf(10.0, env_shortterm / 20.);
}
self.prev_delta = self.delta[self.index];
gst::debug!(
CAT,
imp: imp,
"Initializing for first frame with gain adjustment of {}",
self.prev_delta
);
for (limiter_buf, sample) in self.limiter_buf.iter_mut().zip(self.buf.iter()) {
*limiter_buf = sample * self.prev_delta * self.offset;
}
self.buf_index = self.limiter_buf.len();
self.limiter_buf_index = 0;
let mut outbuf = gst::Buffer::with_size(FRAME_SIZE * self.info.bpf() as usize)
.map_err(|_| gst::FlowError::Error)?;
{
let outbuf = outbuf.get_mut().unwrap();
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
let dst = dst
.as_mut_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?;
self.true_peak_limiter(imp, dst);
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
}
self.current_samples_per_frame = FRAME_SIZE;
self.frame_type = FrameType::Inner;
Ok((outbuf, pts.into()))
}
fn process_fill_inner_frame(&mut self, imp: &AudioLoudNorm, src: &[f64]) {
let gain = self.gaussian_filter(if self.index + 10 < 30 {
self.index + 10
} else {
self.index + 10 - 30
});
let gain_next = self.gaussian_filter(if self.index + 11 < 30 {
self.index + 11
} else {
self.index + 11 - 30
});
gst::debug!(
CAT,
imp: imp,
"Applying gain adjustment {}-{}",
gain,
gain_next
);
let channels = self.info.channels() as usize;
assert!(src.len() / channels <= FRAME_SIZE);
for (n, samples) in src.chunks_exact(channels).enumerate() {
let (buf_read, buf_write, limiter_buf) = unsafe {
let buf = &mut &mut *self.buf as *mut &mut [f64];
let buf_read = (*buf).get_unchecked(self.buf_index..(self.buf_index + channels));
let buf_write =
(*buf).get_unchecked_mut(self.prev_buf_index..(self.prev_buf_index + channels));
let limiter_buf = self
.limiter_buf
.get_unchecked_mut(self.limiter_buf_index..(self.limiter_buf_index + channels));
(buf_read, buf_write, limiter_buf)
};
buf_write.copy_from_slice(samples);
let current_gain =
(gain + ((n as f64 / FRAME_SIZE as f64) * (gain_next - gain))) * self.offset;
for (o, i) in limiter_buf.iter_mut().zip(buf_read.iter()) {
*o = *i * current_gain;
}
self.limiter_buf_index += channels;
if self.limiter_buf_index >= self.limiter_buf.len() {
self.limiter_buf_index -= self.limiter_buf.len();
}
self.prev_buf_index += channels;
if self.prev_buf_index >= self.buf.len() {
self.prev_buf_index -= self.buf.len();
}
self.buf_index += channels;
if self.buf_index >= self.buf.len() {
self.buf_index -= self.buf.len();
}
}
}
fn process_update_gain_inner_frame(
&mut self,
imp: &AudioLoudNorm,
) -> Result<(), gst::FlowError> {
let global = self
.r128_in
.loudness_global()
.map_err(|_| gst::FlowError::Error)?;
let shortterm = self
.r128_in
.loudness_shortterm()
.map_err(|_| gst::FlowError::Error)?;
let relative_threshold = self
.r128_in
.relative_threshold()
.map_err(|_| gst::FlowError::Error)?;
gst::debug!(
CAT,
imp: imp,
"Calculated global loudness {}, short term loudness {} and relative threshold {}",
global,
shortterm,
relative_threshold
);
if !self.above_threshold {
if shortterm > -70.0 {
self.prev_delta *= 1.0058;
}
let shortterm_out = self
.r128_out
.loudness_shortterm()
.map_err(|_| gst::FlowError::Error)?;
if shortterm_out >= self.target_i {
self.above_threshold = true;
gst::debug!(
CAT,
imp: imp,
"Above threshold now ({} >= {}, {} > -70)",
shortterm_out,
self.target_i,
shortterm
);
}
}
if shortterm < relative_threshold || shortterm <= -70. || !self.above_threshold {
self.delta[self.index] = self.prev_delta;
} else {
let env_global = if (shortterm - global).abs() < (self.target_lra / 2.) {
shortterm - global
} else if (self.target_lra / 2.) * (shortterm - global) < 0.0 {
-1.
} else {
1.
};
let env_shortterm = self.target_i - shortterm;
self.delta[self.index] = f64::powf(10., (env_global + env_shortterm) / 20.);
}
self.prev_delta = self.delta[self.index];
gst::debug!(
CAT,
imp: imp,
"Calculated new gain adjustment {}",
self.prev_delta
);
self.index += 1;
if self.index >= 30 {
self.index -= 30;
}
Ok(())
}
fn process_inner_frame(
&mut self,
imp: &AudioLoudNorm,
src: &[f64],
pts: impl Into<Option<gst::ClockTime>>,
) -> Result<(gst::Buffer, Option<gst::ClockTime>), gst::FlowError> {
self.process_fill_inner_frame(imp, src);
let mut outbuf =
gst::Buffer::with_size(self.current_samples_per_frame * self.info.bpf() as usize)
.map_err(|_| gst::FlowError::Error)?;
{
let outbuf = outbuf.get_mut().unwrap();
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
let dst = dst
.as_mut_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?;
self.true_peak_limiter(imp, dst);
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
}
self.process_update_gain_inner_frame(imp)?;
let pts = pts.into().map(|pts| pts + 100.mseconds() - 3.seconds());
Ok((outbuf, pts))
}
fn process_fill_final_frame(&mut self, _imp: &AudioLoudNorm, idx: usize, num_samples: usize) {
let channels = self.info.channels() as usize;
let gain = self.gaussian_filter(if self.index + 10 < 30 {
self.index + 10
} else {
self.index + 10 - 30
});
let gain_next = self.gaussian_filter(if self.index + 11 < 30 {
self.index + 11
} else {
self.index + 11 - 30
});
for n in idx..num_samples {
let (buf_read, limiter_buf) = unsafe {
let buf_read = self
.buf
.get_unchecked(self.buf_index..(self.buf_index + channels));
let limiter_buf = self
.limiter_buf
.get_unchecked_mut(self.limiter_buf_index..(self.limiter_buf_index + channels));
(buf_read, limiter_buf)
};
let current_gain =
(gain + ((n as f64 / num_samples as f64) * (gain_next - gain))) * self.offset;
for (o, i) in limiter_buf.iter_mut().zip(buf_read.iter()) {
*o = *i * current_gain;
}
self.limiter_buf_index += channels;
if self.limiter_buf_index >= self.limiter_buf.len() {
self.limiter_buf_index -= self.limiter_buf.len();
}
self.buf_index += channels;
if self.buf_index >= self.buf.len() {
self.buf_index -= self.buf.len();
}
}
}
fn process_final_frame(
&mut self,
imp: &AudioLoudNorm,
src: &[f64],
pts: impl Into<Option<gst::ClockTime>>,
) -> Result<(gst::Buffer, Option<gst::ClockTime>), gst::FlowError> {
let channels = self.info.channels() as usize;
let num_samples = src.len() / channels;
self.process_fill_inner_frame(imp, src);
if num_samples != FRAME_SIZE {
self.process_fill_final_frame(imp, num_samples, FRAME_SIZE);
}
let out_num_samples = 30 * FRAME_SIZE - (FRAME_SIZE - num_samples);
let mut outbuf = gst::Buffer::with_size(out_num_samples * self.info.bpf() as usize)
.map_err(|_| gst::FlowError::Error)?;
{
let outbuf = outbuf.get_mut().unwrap();
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
let dst = dst
.as_mut_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?;
let mut smp_cnt = 0;
while smp_cnt < out_num_samples {
let frame_size = std::cmp::min(out_num_samples - smp_cnt, FRAME_SIZE);
let dst = &mut dst[(smp_cnt * channels)..((smp_cnt + frame_size) * channels)];
self.true_peak_limiter(imp, dst);
smp_cnt += frame_size;
if smp_cnt == out_num_samples {
break;
}
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
self.process_update_gain_inner_frame(imp)?;
let next_frame_size = std::cmp::min(out_num_samples - smp_cnt, FRAME_SIZE);
self.process_fill_final_frame(imp, 0, next_frame_size);
if next_frame_size < FRAME_SIZE {
self.limiter_buf_index += FRAME_SIZE - next_frame_size;
if self.limiter_buf_index > self.limiter_buf.len() {
self.limiter_buf_index -= self.limiter_buf.len();
}
}
}
}
let pts = pts.into().map(|pts| pts + 100.mseconds() - 3.seconds());
Ok((outbuf, pts))
}
fn process_linear_frame(
&mut self,
imp: &AudioLoudNorm,
src: &[f64],
pts: impl Into<Option<gst::ClockTime>>,
) -> Result<(gst::Buffer, Option<gst::ClockTime>), gst::FlowError> {
gst::debug!(
CAT,
imp: imp,
"Applying linear gain adjustment of {}",
self.offset
);
let mut outbuf = gst::Buffer::with_size(src.len() * mem::size_of::<f64>())
.map_err(|_| gst::FlowError::Error)?;
{
let outbuf = outbuf.get_mut().unwrap();
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
let dst = dst
.as_mut_slice_of::<f64>()
.map_err(|_| gst::FlowError::Error)?;
for (o, i) in dst.iter_mut().zip(src.iter()) {
*o = *i * self.offset;
}
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
}
Ok((outbuf, pts.into()))
}
fn process(
&mut self,
imp: &AudioLoudNorm,
src: &[f64],
pts: impl Into<Option<gst::ClockTime>>,
) -> Result<(gst::Buffer, Option<gst::ClockTime>), gst::FlowError> {
self.r128_in
.add_frames_f64(src)
.map_err(|_| gst::FlowError::Error)?;
if self.frame_type == FrameType::First
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame
{
self.process_first_frame_is_last(imp)?;
}
match self.frame_type {
FrameType::First => self.process_first_frame(imp, src, pts),
FrameType::Inner => self.process_inner_frame(imp, src, pts),
FrameType::Final => self.process_final_frame(imp, src, pts),
FrameType::Linear => self.process_linear_frame(imp, src, pts),
}
}
fn true_peak_limiter_out(
&mut self,
imp: &AudioLoudNorm,
mut smp_cnt: usize,
nb_samples: usize,
) -> usize {
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
if let Some((peak_delta, peak_value)) = peak {
self.limiter_state = LimiterState::Attack;
self.env_cnt = 0;
self.sustain_cnt = None;
self.gain_reduction[0] = 1.;
self.gain_reduction[1] = self.target_tp / peak_value;
smp_cnt += LIMITER_LOOKAHEAD + peak_delta - LIMITER_ATTACK_WINDOW;
gst::debug!(
CAT,
imp: imp,
"Found peak {} at sample {}, going to attack state at sample {} (gain reduction {}-{})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[0],
self.gain_reduction[1]
);
} else {
smp_cnt = nb_samples;
}
smp_cnt
}
fn true_peak_limiter_attack(
&mut self,
imp: &AudioLoudNorm,
mut smp_cnt: usize,
nb_samples: usize,
) -> usize {
let channels = self.info.channels() as usize;
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
let mut new_peak_smp_cnt = None;
if let Some((peak_delta, _)) = peak {
new_peak_smp_cnt = Some(smp_cnt + peak_delta);
}
let mut index = self.limiter_buf_index + smp_cnt * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
while self.env_cnt < LIMITER_ATTACK_WINDOW && smp_cnt < nb_samples {
if let Some(new_peak_smp_cnt) = new_peak_smp_cnt {
if smp_cnt == new_peak_smp_cnt {
break;
}
}
let env = self.gain_reduction[0]
- (self.env_cnt as f64 / (LIMITER_ATTACK_WINDOW as f64 - 1.0)
* (self.gain_reduction[0] - self.gain_reduction[1]));
let samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for sample in samples {
*sample *= env;
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
smp_cnt += 1;
self.env_cnt += 1;
}
if let Some(new_peak_smp) = new_peak_smp_cnt {
assert!(smp_cnt < nb_samples);
if smp_cnt < new_peak_smp {
for _ in smp_cnt..new_peak_smp {
let samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for sample in samples {
*sample *= self.gain_reduction[1];
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
}
smp_cnt = new_peak_smp;
}
assert!(smp_cnt < nb_samples);
let (_, peak_value) = peak.unwrap();
let gain_reduction = self.target_tp / peak_value;
if gain_reduction < self.gain_reduction[1] {
let current_gain_reduction = self.gain_reduction[0]
- (self.env_cnt as f64 / (LIMITER_ATTACK_WINDOW as f64 - 1.0)
* (self.gain_reduction[0] - self.gain_reduction[1]));
let old_slope = -(self.gain_reduction[0] - self.gain_reduction[1]);
let new_slope = -(current_gain_reduction - gain_reduction);
if new_slope <= old_slope {
self.limiter_state = LimiterState::Attack;
self.gain_reduction[0] = current_gain_reduction;
self.gain_reduction[1] = gain_reduction;
self.env_cnt = 0;
self.sustain_cnt = None;
gst::debug!(
CAT,
imp: imp,
"Found new peak {} at sample {}, restarting attack state at sample {} (gain reduction {}-{})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[0],
self.gain_reduction[1],
);
} else {
let new_end = (gain_reduction - self.gain_reduction[0]) / old_slope;
let new_end = f64::max(new_end, 1.0);
let new_start = new_end - 1.0;
#[allow(clippy::assign_op_pattern)]
{
self.gain_reduction[0] = self.gain_reduction[0] + new_start * old_slope;
}
self.gain_reduction[1] = gain_reduction;
let cur_pos = (current_gain_reduction - self.gain_reduction[0]) / old_slope;
let cur_pos = f64::clamp(cur_pos, 0.0, 1.0);
self.env_cnt = ((LIMITER_ATTACK_WINDOW as f64 - 1.0) * cur_pos) as usize;
self.sustain_cnt = Some(self.env_cnt);
gst::debug!(
CAT,
imp: imp,
"Found new peak {} at sample {}, adjusting attack state at sample {} (gain reduction {}-{})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[0],
self.gain_reduction[1],
);
}
return smp_cnt;
} else {
gst::debug!(
CAT,
imp: imp,
"Found new low peak {} at sample {} in attack state at sample {}",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
);
if self.env_cnt < LIMITER_ATTACK_WINDOW {
self.sustain_cnt = Some(self.env_cnt);
}
}
}
if self.env_cnt == LIMITER_ATTACK_WINDOW && smp_cnt < nb_samples {
gst::debug!(
CAT,
imp: imp,
"Going to sustain state at sample {} (gain reduction {})",
smp_cnt,
self.gain_reduction[1]
);
self.limiter_state = LimiterState::Sustain;
}
smp_cnt
}
fn true_peak_limiter_sustain(
&mut self,
imp: &AudioLoudNorm,
mut smp_cnt: usize,
nb_samples: usize,
) -> usize {
let channels = self.info.channels() as usize;
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
if let Some(sustain_cnt) = peak.map(|(d, _v)| d).or(self.sustain_cnt) {
let mut index = self.limiter_buf_index + smp_cnt * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
let mut s = 0;
while s < sustain_cnt && smp_cnt < nb_samples {
let samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for sample in samples {
*sample *= self.gain_reduction[1];
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
smp_cnt += 1;
s += 1;
}
if let Some((_, peak_value)) = peak {
let gain_reduction = self.target_tp / peak_value;
if gain_reduction < self.gain_reduction[1] {
self.limiter_state = LimiterState::Attack;
self.env_cnt = 0;
self.sustain_cnt = None;
self.gain_reduction[0] = self.gain_reduction[1];
self.gain_reduction[1] = gain_reduction;
gst::debug!(
CAT,
imp: imp,
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[0],
self.gain_reduction[1],
);
} else {
gst::debug!(
CAT,
imp: imp,
"Found new peak {} at sample {}, going sustain further at sample {} (gain reduction {})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[1],
);
self.sustain_cnt = Some(LIMITER_LOOKAHEAD);
}
} else if let Some(ref mut sustain_cnt) = self.sustain_cnt {
*sustain_cnt -= s;
if *sustain_cnt == 0 {
self.sustain_cnt = None;
}
} else {
unreachable!();
}
} else {
self.limiter_state = LimiterState::Release;
self.gain_reduction[0] = self.gain_reduction[1];
self.gain_reduction[1] = 1.;
self.env_cnt = 0;
gst::debug!(
CAT,
imp: imp,
"Going to release state for sample {} at sample {} (gain reduction {}-1.0)",
smp_cnt + LIMITER_RELEASE_WINDOW,
smp_cnt,
self.gain_reduction[0]
);
}
smp_cnt
}
fn true_peak_limiter_release(
&mut self,
imp: &AudioLoudNorm,
mut smp_cnt: usize,
nb_samples: usize,
) -> usize {
let channels = self.info.channels() as usize;
let mut index = self.limiter_buf_index + smp_cnt * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
if let Some((peak_delta, peak_value)) = peak {
let gain_reduction = self.target_tp / peak_value;
let current_gain_reduction = self.gain_reduction[0]
- (self.env_cnt as f64 / (LIMITER_RELEASE_WINDOW as f64 - 1.0)
* (self.gain_reduction[1] - self.gain_reduction[0]));
if gain_reduction < current_gain_reduction {
assert!(smp_cnt + peak_delta < nb_samples);
for _ in 0..peak_delta {
let samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for sample in samples {
*sample *= self.gain_reduction[1];
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
smp_cnt += 1;
assert!(smp_cnt < nb_samples);
}
self.limiter_state = LimiterState::Attack;
self.env_cnt = 0;
self.sustain_cnt = None;
self.gain_reduction[0] = current_gain_reduction;
self.gain_reduction[1] = gain_reduction;
gst::debug!(
CAT,
imp: imp,
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
smp_cnt,
self.gain_reduction[0],
self.gain_reduction[1],
);
} else {
self.gain_reduction[1] = current_gain_reduction;
gst::debug!(
CAT,
imp: imp,
"Going from release to sustain state at sample {} because of low peak {} at sample {} (gain reduction {})",
smp_cnt,
peak_value,
smp_cnt + LIMITER_ATTACK_WINDOW,
self.gain_reduction[1]
);
self.limiter_state = LimiterState::Sustain;
}
return smp_cnt;
}
while self.env_cnt < LIMITER_RELEASE_WINDOW && smp_cnt < nb_samples {
let env = self.gain_reduction[0]
- (self.env_cnt as f64 / (LIMITER_RELEASE_WINDOW as f64 - 1.0)
* (self.gain_reduction[1] - self.gain_reduction[0]));
let samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for sample in samples {
*sample *= env;
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
smp_cnt += 1;
self.env_cnt += 1;
}
if smp_cnt < nb_samples {
self.limiter_state = LimiterState::Out;
gst::debug!(
CAT,
imp: imp,
"Leaving release state and going to out state at sample {}",
smp_cnt,
);
}
smp_cnt
}
fn true_peak_limiter_first_frame(&mut self, imp: &AudioLoudNorm) {
let channels = self.info.channels() as usize;
assert_eq!(self.limiter_buf_index, 0);
let mut max = 0.;
for sample in &self.limiter_buf[0..((LIMITER_LOOKAHEAD + 1) * channels)] {
if sample.abs() > max {
max = *sample;
}
}
for (o, i) in self
.prev_smp
.iter_mut()
.zip(self.limiter_buf[(LIMITER_LOOKAHEAD * channels)..].iter())
{
*o = i.abs();
}
if max > self.target_tp {
self.limiter_state = LimiterState::Sustain;
self.sustain_cnt = Some(LIMITER_LOOKAHEAD);
self.gain_reduction[1] = self.target_tp / max;
gst::debug!(
CAT,
imp: imp,
"Reducing gain for start of first frame by {} ({} > {}) and going to sustain state",
self.gain_reduction[1],
max,
self.target_tp
);
}
}
fn true_peak_limiter(&mut self, imp: &AudioLoudNorm, dst: &mut [f64]) {
let channels = self.info.channels() as usize;
let nb_samples = dst.len() / channels;
gst::debug!(CAT, imp: imp, "Running limiter for {} samples", nb_samples);
if self.frame_type == FrameType::First {
self.true_peak_limiter_first_frame(imp);
}
let mut smp_cnt = 0;
while smp_cnt < nb_samples {
match self.limiter_state {
LimiterState::Out => {
smp_cnt = self.true_peak_limiter_out(imp, smp_cnt, nb_samples);
}
LimiterState::Attack => {
smp_cnt = self.true_peak_limiter_attack(imp, smp_cnt, nb_samples);
}
LimiterState::Sustain => {
smp_cnt = self.true_peak_limiter_sustain(imp, smp_cnt, nb_samples);
}
LimiterState::Release => {
smp_cnt = self.true_peak_limiter_release(imp, smp_cnt, nb_samples);
}
}
}
let mut index = self.limiter_buf_index;
for dest_samples in dst.chunks_exact_mut(channels) {
let in_samples = unsafe {
self.limiter_buf
.get_unchecked_mut(index..(index + channels))
};
for (o, i) in dest_samples.iter_mut().zip(in_samples.iter()) {
*o = *i;
if o.abs() > self.target_tp {
*o = self.target_tp * o.signum();
}
}
index += channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
}
}
fn detect_peak(&mut self, offset: usize, samples: usize) -> Option<(usize, f64)> {
let channels = self.info.channels() as usize;
let mut index = self.limiter_buf_index + (offset + LIMITER_LOOKAHEAD) * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
for n in 0..samples {
let mut next_index = index + channels;
if next_index >= self.limiter_buf.len() {
next_index -= self.limiter_buf.len();
}
let (this, next) = unsafe {
(
self.limiter_buf.get_unchecked(index..(index + channels)),
self.limiter_buf
.get_unchecked(next_index..(next_index + channels)),
)
};
let mut detected = false;
for (c, (prev_smp, (this, next))) in self
.prev_smp
.iter_mut()
.zip(this.iter().zip(next.iter()))
.enumerate()
{
let this = this.abs();
let next = next.abs();
detected = false;
if (*prev_smp <= this) && (this >= next) && (this > self.target_tp) && (n > 0) {
detected = true;
for i in 2..12 {
let next = unsafe {
let mut next_index = index + c + i * channels;
if next_index >= self.limiter_buf.len() {
next_index -= self.limiter_buf.len();
}
self.limiter_buf.get_unchecked(next_index).abs()
};
if next > this {
detected = false;
break;
}
}
if detected {
break;
}
}
*prev_smp = this;
}
if detected {
let mut max_peak = 0.0;
for (c, (prev_smp, this)) in (self.prev_smp.iter_mut().zip(this.iter())).enumerate()
{
if c == 0 || this.abs() > max_peak {
max_peak = this.abs();
}
*prev_smp = this.abs();
}
return Some((n, max_peak));
}
index = next_index;
}
None
}
fn gaussian_filter(&self, index: usize) -> f64 {
let mut result = 0.;
let index = if index > 10 { index - 10 } else { index + 20 };
let delta = self.delta[index..].iter().chain(self.delta.iter());
for (weight, delta) in self.weights.iter().zip(delta) {
result += delta * weight;
}
result
}
}
impl AudioLoudNorm {
fn sink_chain(
&self,
_pad: &gst::Pad,
buffer: gst::Buffer,
) -> Result<gst::FlowSuccess, gst::FlowError> {
gst::log!(CAT, imp: self, "Handling buffer {:?}", buffer);
let mut state_guard = self.state.borrow_mut();
let state = match *state_guard {
None => {
gst::error!(CAT, imp: self, "Not negotiated yet");
return Err(gst::FlowError::NotNegotiated);
}
Some(ref mut state) => state,
};
let mut outbufs = vec![];
if buffer.flags().contains(gst::BufferFlags::DISCONT) {
gst::debug!(CAT, imp: self, "Draining on discontinuity");
match state.drain(self) {
Ok(outbuf) => {
outbufs.push(outbuf);
}
Err(gst::FlowError::Eos) => (),
Err(err) => return Err(err),
}
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
}
state.adapter.push(buffer);
outbufs.append(&mut state.drain_full_frames(self)?);
drop(state_guard);
for buffer in outbufs {
gst::log!(CAT, imp: self, "Outputting buffer {:?}", buffer);
self.srcpad.push(buffer)?;
}
Ok(gst::FlowSuccess::Ok)
}
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
use gst::EventView;
gst::log!(CAT, obj: pad, "Handling event {:?}", event);
match event.view() {
EventView::Caps(c) => {
let caps = c.caps();
gst::info!(CAT, obj: pad, "Got caps {:?}", caps);
let info = match gst_audio::AudioInfo::from_caps(caps) {
Ok(info) => info,
Err(_) => {
gst::error!(CAT, obj: pad, "Failed to parse caps");
return false;
}
};
let mut state = self.state.borrow_mut();
let mut outbuf = None;
if let Some(ref mut state) = &mut *state {
outbuf = match state.drain(self) {
Ok(outbuf) => Some(outbuf),
Err(gst::FlowError::Eos) => None,
Err(_) => return false,
};
}
*state = Some(State::new(&self.settings.lock().unwrap(), info));
drop(state);
if let Some(outbuf) = outbuf {
gst::log!(CAT, imp: self, "Outputting buffer {:?}", outbuf);
if let Err(err) = self.srcpad.push(outbuf) {
gst::error!(CAT, imp: self, "Failed to push drained data: {}", err);
return false;
}
}
}
EventView::Eos(_) | EventView::Segment(_) => {
let mut state = self.state.borrow_mut();
let mut outbuf = None;
if let Some(ref mut state) = &mut *state {
outbuf = match state.drain(self) {
Ok(outbuf) => Some(outbuf),
Err(gst::FlowError::Eos) => None,
Err(_) => return false,
};
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
}
drop(state);
if let Some(outbuf) = outbuf {
gst::log!(CAT, imp: self, "Outputting buffer {:?}", outbuf);
if let Err(err) = self.srcpad.push(outbuf) {
gst::error!(
CAT,
imp: self,
"Failed to push drained data on EOS: {}",
err
);
return false;
}
}
}
EventView::FlushStop(_) => {
let mut state = self.state.borrow_mut();
if let Some(info) = state.as_ref().map(|s| s.info.clone()) {
let settings = *self.settings.lock().unwrap();
*state = Some(State::new(&settings, info));
} else {
*state = None;
}
}
_ => (),
}
gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
#[allow(clippy::single_match)]
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
use gst::QueryViewMut;
gst::log!(CAT, obj: pad, "Handling query {:?}", query);
match query.view_mut() {
QueryViewMut::Latency(q) => {
let mut peer_query = gst::query::Latency::new();
if self.sinkpad.peer_query(&mut peer_query) {
let (live, min_latency, max_latency) = peer_query.result();
q.set(
live,
min_latency + 3.seconds(),
max_latency.opt_add(3.seconds()),
);
true
} else {
false
}
}
_ => gst::Pad::query_default(pad, Some(&*self.obj()), query),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for AudioLoudNorm {
const NAME: &'static str = "GstAudioLoudNorm";
type Type = super::AudioLoudNorm;
type ParentType = gst::Element;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
.chain_function(|pad, parent, buffer| {
Self::catch_panic_pad_function(
parent,
|| Err(gst::FlowError::Error),
|this| this.sink_chain(pad, buffer),
)
})
.event_function(|pad, parent, event| {
Self::catch_panic_pad_function(parent, || false, |this| this.sink_event(pad, event))
})
.flags(gst::PadFlags::PROXY_CAPS)
.build();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
.query_function(|pad, parent, query| {
Self::catch_panic_pad_function(parent, || false, |this| this.src_query(pad, query))
})
.flags(gst::PadFlags::PROXY_CAPS)
.build();
Self {
sinkpad,
srcpad,
settings: Mutex::new(Default::default()),
state: AtomicRefCell::new(None),
}
}
}
impl ObjectImpl for AudioLoudNorm {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecDouble::builder("loudness-target")
.nick("Loudness Target")
.blurb("Loudness target in LUFS")
.minimum(-70.0)
.maximum(-5.0)
.default_value(DEFAULT_LOUDNESS_TARGET)
.mutable_ready()
.build(),
glib::ParamSpecDouble::builder("loudness-range-target")
.nick("Loudness Range Target")
.blurb("Loudness range target in LU")
.minimum(1.0)
.maximum(20.0)
.default_value(DEFAULT_LOUDNESS_RANGE_TARGET)
.mutable_ready()
.build(),
glib::ParamSpecDouble::builder("max-true-peak")
.nick("Maximum True Peak")
.blurb("Maximum True Peak in dbTP")
.minimum(-9.0)
.maximum(0.0)
.default_value(DEFAULT_MAX_TRUE_PEAK)
.mutable_ready()
.build(),
glib::ParamSpecDouble::builder("offset")
.nick("Offset Gain")
.blurb("Offset Gain in LU")
.minimum(-99.0)
.maximum(99.0)
.default_value(DEFAULT_OFFSET)
.mutable_ready()
.build(),
]
});
PROPERTIES.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"loudness-target" => {
let mut settings = self.settings.lock().unwrap();
settings.loudness_target = value.get().expect("type checked upstream");
}
"loudness-range-target" => {
let mut settings = self.settings.lock().unwrap();
settings.loudness_range_target = value.get().expect("type checked upstream");
}
"max-true-peak" => {
let mut settings = self.settings.lock().unwrap();
settings.max_true_peak = value.get().expect("type checked upstream");
}
"offset" => {
let mut settings = self.settings.lock().unwrap();
settings.offset = value.get().expect("type checked upstream");
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"loudness-target" => {
let settings = self.settings.lock().unwrap();
settings.loudness_target.to_value()
}
"loudness-range-target" => {
let settings = self.settings.lock().unwrap();
settings.loudness_range_target.to_value()
}
"max-true-peak" => {
let settings = self.settings.lock().unwrap();
settings.max_true_peak.to_value()
}
"offset" => {
let settings = self.settings.lock().unwrap();
settings.offset.to_value()
}
_ => unimplemented!(),
}
}
}
impl GstObjectImpl for AudioLoudNorm {}
impl ElementImpl for AudioLoudNorm {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Audio loudness normalizer",
"Filter/Effect/Audio",
"Normalizes perceived loudness of an audio stream",
"Sebastian Dröge <sebastian@centricular.com>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let caps = gst_audio::AudioCapsBuilder::new_interleaved()
.format(gst_audio::AUDIO_FORMAT_F64)
.rate(192_000)
.build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps,
)
.unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
#[allow(clippy::single_match)]
fn change_state(
&self,
transition: gst::StateChange,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
let res = self.parent_change_state(transition);
match transition {
gst::StateChange::PausedToReady => {
*self.state.borrow_mut() = None;
}
_ => (),
}
res
}
}
fn init_gaussian_filter() -> [f64; 21] {
let mut weights = [0.0f64; 21];
let mut total_weight = 0.0f64;
let sigma = 3.5f64;
let offset = 21 / 2;
let c1 = 1.0 / (sigma * f64::sqrt(2.0 * std::f64::consts::PI));
let c2 = 2.0 * f64::powf(sigma, 2.0);
for (i, weight) in weights.iter_mut().enumerate() {
let x = i as f64 - offset as f64;
*weight = c1 * f64::exp(-(f64::powf(x, 2.0) / c2));
total_weight += *weight;
}
let adjust = 1.0 / total_weight;
for weight in weights.iter_mut() {
*weight *= adjust;
}
weights
}