gst-plugins-rs/audio/audiofx/src/audioloudnorm/imp.rs

1905 lines
70 KiB
Rust

// Copyright (C) 2019-2020 Sebastian Dröge <sebastian@centricular.com>
//
// Audio processing part of this file ported from ffmpeg/libavfilter/af_loudnorm.c
//
// Original C code
// Copyright (c) 2016 Kyle Swanson <k@ylo.ph>
// licensed under the LGPL-2.1+ and generously relicensed to MPL-2.0.
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
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 amount of sample we consume per iteration: for the first frame 3s, afterwards 100ms
current_samples_per_frame: usize,
// Settings during setup
offset: f64,
target_i: f64,
target_lra: f64,
target_tp: f64,
// Input ringbuffer for loudness analysis
// TODO: Convert to a proper ringbuffer
buf: Box<[f64]>,
// read index
buf_index: usize,
// write index (always 210ms from buf_index)
prev_buf_index: usize,
// Gaussian filter for gains
// TODO: These are actually constant. Once `for` is allowed
// in `const fn` we can make them proper constants.
weights: [f64; 21],
// TODO: Convert to a proper ringbuffer
delta: [f64; 30],
index: usize,
prev_delta: f64,
// Limiter
gain_reduction: [f64; 2],
// TODO: Convert to a proper ringbuffer
limiter_buf: Box<[f64]>,
// Read/write index, depending on context
limiter_buf_index: usize,
// Previous sample (potentially of the previous frame) used for detecting peaks in the limiter
prev_smp: Box<[f64]>,
limiter_state: LimiterState,
// During attack/release state, the position in the corresponding window
env_cnt: usize,
// Number of samples to sustain at the beginning of the sustain state, if any
sustain_cnt: Option<usize>,
frame_type: FrameType,
above_threshold: bool,
// Input loudness calculation
r128_in: ebur128::EbuR128,
// Actual output loudness calculation
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>>,
}
// Gain analysis parameters
const GAIN_LOOKAHEAD: usize = 3 * 192_000; // 3s
const FRAME_SIZE: usize = 19_200; // 100ms
// Limiter parameters
const LIMITER_ATTACK_WINDOW: usize = 1920; // 10ms
const LIMITER_RELEASE_WINDOW: usize = 19_200; // 100ms
const LIMITER_LOOKAHEAD: usize = 1920; // 10ms
impl State {
// Drains all full frames that are currently in the adapter
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)
}
// Drains everything
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 we already output something before then we go into final frame processing, otherwise
// we drain any data we still have by doing linear processing.
if self.current_samples_per_frame == FRAME_SIZE {
self.frame_type = FrameType::Final;
} else if src.is_empty() {
// Nothing to drain at all
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> {
// Calculated loudness in LUFS
let global = self
.r128_in
.loudness_global()
.map_err(|_| gst::FlowError::Error)?;
// Peak sample value for all changes
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
);
// Difference between targetted and calculated LUFS loudness as a linear scalefactor.
let offset = f64::powf(10., (self.target_i - global) / 20.);
// What the new peak would be after adjusting for the targetted loudness.
let offset_tp = true_peak * offset;
// If the new peak would be more quiet than targeted one, take it. Otherwise only go as
// high as the true peak allows.
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> {
// Fill our whole buffer here with the initial input, i.e. 3000ms of samples.
self.buf.copy_from_slice(src);
// Calculate the shortterm loudness in LUFS.
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
};
// Initialize with linear scale factor for reaching the target loudness.
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
);
// Fill the whole limiter_buf with the gain corrected first part of the buffered
// input, i.e. 210ms. 100ms for the current frame plus 100ms lookahead for the
// limiter with the next frame plus 10ms in addition because the limiter would
// look a few samples further when detecting a peak to make sure no higher values
// are following.
for (limiter_buf, sample) in self.limiter_buf.iter_mut().zip(self.buf.iter()) {
*limiter_buf = sample * self.prev_delta * self.offset;
}
// Read position of the buffer is now advanced.
self.buf_index = self.limiter_buf.len();
// Write position of the limiter_buf is at the beginning still. We consume
// the first 100ms of it below directly so that the next iteration will
// overwrite these 100ms directly.
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)?;
// This now consumes the first 100ms of limiter_buf for the output.
self.true_peak_limiter(imp, dst);
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
}
// From now on we consume 100ms input frames and output 100ms.
self.current_samples_per_frame = FRAME_SIZE;
self.frame_type = FrameType::Inner;
// PTS is the input PTS for the first frame, we output the first 100ms of the input
// buffer here
Ok((outbuf, pts.into()))
}
fn process_fill_inner_frame(&mut self, imp: &AudioLoudNorm, src: &[f64]) {
// Get gain for this and the next 100ms frame based the delta array
// and smoothened with a gaussian filter.
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
);
// Overwrite the first 100ms of the limiter_buf with the gain corrected 100ms of
// buf. This is correct because either above (for the first frame) or in the
// previous iteration here we already have output these 100ms.
//
// Also fill 100ms of buf with the 100ms of new input at the same time.
let channels = self.info.channels() as usize;
assert!(src.len() / channels <= FRAME_SIZE);
for (n, samples) in src.chunks_exact(channels).enumerate() {
// Safety: Index ranges are checked below and both slices from buf are
// guaranteed to be non-overlapping (210ms limiter_buf difference).
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);
// Linearly interpolate between the current and next gain for each sample.
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> {
// Calculate global, shortterm loudness and relative threshold in LUFS.
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 we were previously not above the threshold but are now above in the
// shortterm, slightly increase the scale factor. If the shortterm output was above
// the target then also consider this frame above 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 we're still below the threshold, continue using the previous delta. Otherwise
// calculate a new one.
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> {
// Fill in these 100ms and adjust its gain according to previous measurements, and
// at the same time copy 100ms over to the limiter_buf.
self.process_fill_inner_frame(imp, src);
// limiter_buf_index was 100ms advanced above, which brings us to exactly the
// position where we have to start consuming 100ms for the output now, and exactly
// the position where we have to start writing the next 100ms in the next
// iteration.
let mut outbuf = gst::Buffer::with_size(
self.current_samples_per_frame as usize * 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)?;
// This now consumes the next 100ms of limiter_buf for the output.
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)?;
// PTS is 2.9s seconds before the input PTS as we buffer 3s of samples and just
// outputted here the first 100ms of that.
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;
// Get gain for this and the next 100ms frame based the delta array
// and smoothened with a gaussian filter.
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 {
// Safety: Index ranges are checked below.
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)
};
// Linearly interpolate between the current and next gain for each sample.
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;
// First process any new/leftover data we get passed. This is the same
// as for inner frames. After this we will have done all gain adjustments
// and all samples we ever output are in buf or limiter_buf.
self.process_fill_inner_frame(imp, src);
// If we got passed less than 100ms in src then limiter_buf_index is now
// not yet at the correct read position! Adjust accordingly here so that all
// further reads come from the right position by copying over the next samples
// from buf.
if num_samples != FRAME_SIZE {
self.process_fill_final_frame(imp, num_samples, FRAME_SIZE);
}
// Now repeatadly run the limiter, output the output gain, update the gains, copy further
// data from the buf to limiter_buf until we have output everything.
//
// At this point we have to output 3s - (FRAME_SIZE - num_samples)
// buf.
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)];
// This now consumes the next frame_size samples of limiter_buf for the output.
// Note that on the very last call this will read up to 10ms of old limiter_buf
// data but as this was already processed it will not find any peak in there and
// just pass through.
//if frame_size < FRAME_SIZE {
// self.limiter_buf_index += FRAME_SIZE - num_samples;
//}
self.true_peak_limiter(imp, dst);
smp_cnt += frame_size;
if smp_cnt == out_num_samples {
break;
}
// Update the gain for the next iteration
self.r128_out
.add_frames_f64(dst)
.map_err(|_| gst::FlowError::Error)?;
self.process_update_gain_inner_frame(imp)?;
// And now copy over the next block of samples from buf to limiter_buf
let next_frame_size = std::cmp::min(out_num_samples - smp_cnt, FRAME_SIZE);
self.process_fill_final_frame(imp, 0, next_frame_size);
// Now for the very last frame we need to update the limiter buffer index by the
// amount of samples the last frame is short to reach the correct read position.
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();
}
}
}
}
// PTS is 2.9s seconds before the input PTS as we buffer 3s of samples and just
// outputted here the first 100ms of that.
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> {
// Apply a linear scale factor to the whole buffer
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)?;
}
// PTS is input PTS as we just pass through the data without latency.
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 we are at the end and had less than 3s of samples overall, do simple linear volume
// adjustment. frame_type should only ever be set to Final at the end if we ended up in
// Inner state before.
if self.frame_type == FrameType::First
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame as usize
{
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 {
// Default out state, check if we have a new peak to act on in the next frame
// and otherwise simply output all samples with the current gain adjustment.
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;
// Skip all samples that don't have to be adjusted because the peak is far
// enough in the future.
// Note: peak_delta=0 is LIMITER_LOOKAHEAD in the future and we have to start
// LIMITER_ATTACK_WINDOW before the peak position.
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 {
// Process all samples, no peak found
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;
// Attack state, we have a peak in the near future and need to apply gain
// reduction smoothly over the next milliseconds to not go over the threshold.
// Once env_cnt reaches attack window we're at the peak sample.
//
// As there might be another, higher peak right afterwards we still need to
// check for this and potentially update the gain reduction accordingly.
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
let mut new_peak_smp_cnt = None;
if let Some((peak_delta, _)) = peak {
// If smp_cnt == new_peak_smp we're exactly 10ms before the new, higher
// peak and need to increase the slope.
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 {
// Stop once we're exactly 10ms before the new higher peak so we can
// restart the attack state.
if let Some(new_peak_smp_cnt) = new_peak_smp_cnt {
if smp_cnt == new_peak_smp_cnt {
break;
}
}
// Linear interpolation between the start and target gain reduction
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]));
// Safety: Index checked below
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);
// Sustain until we are exactly 10ms before the new peak in case
// we finished the attack window above already.
if smp_cnt < new_peak_smp {
for _ in smp_cnt..new_peak_smp {
// Safety: Index checked below
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 the gain reduction is more than our current target gain reduction we
// need to change the attack state. If it less or the same we can simply
// contain the current attack state as we will end up at a low enough again
// before the new peak. We however have to remember to sustain at least
// that long.
if gain_reduction < self.gain_reduction[1] {
// If we need to change something we need to consider two different
// cases based on the slope of the gain reduction.
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]));
// Calculate the slopes. Note the minus!
let old_slope = -(self.gain_reduction[0] - self.gain_reduction[1]);
let new_slope = -(current_gain_reduction - gain_reduction);
if new_slope <= old_slope {
// If the slope from our current position to the new gain reduction at
// the new peak is higher (we need to reduce gain faster) then we
// restart the attack state at this point with the higher slope. We
// will then reach the new peak at the end of the attack window.
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 {
// If the slope is lower we can't simply reduce the slope as we would
// then have a lower gain reduction than needed at the previous peak.
// Instead of continue with the same slope but continue further than
// the old peak until we reach the required gain reduction for the new
// peak. Just like above we need to remember to sustain from the end of
// the attack window until the new peak.
// Calculate at which point we would reach the new gain reduction
// relative to 0.0 == attack window start, 1.0 attack window end.
let new_end = (gain_reduction - self.gain_reduction[0]) / old_slope;
let new_end = f64::max(new_end, 1.0);
// New start of the window, this will be in the past
let new_start = new_end - 1.0;
// Gain reduction at the new start. Note the plus as the slope is
// negative already here.
//
// Clippy warning ignored here because this is just incidentally the same as
// AssignAdd: we calculate a new adjusted gain reduction here, and override the
// previous one.
#[allow(clippy::assign_op_pattern)]
{
self.gain_reduction[0] = self.gain_reduction[0] + new_start * old_slope;
}
// At env_cnt == ATTACK_WINDOW we need the new gain reduction
self.gain_reduction[1] = gain_reduction;
// Calculate the current position in the attack window
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;
// Need to sustain in any case for this many samples to actually
// reach the new peak
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 {
// We're ending the attack state this much before the new peak so need
// to ensure that we at least sustain it for that long afterwards.
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 {
// If we reached the target gain reduction, go into sustain state.
gst::debug!(
CAT,
imp: imp,
"Going to sustain state at sample {} (gain reduction {})",
smp_cnt,
self.gain_reduction[1]
);
self.limiter_state = LimiterState::Sustain;
// Keep sustain_cnt as is from above
}
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;
// Sustain the previous gain reduction as long as a peak is found in the
// next frame, otherwise go over to smoothly release.
let peak = self.detect_peak(smp_cnt, nb_samples - smp_cnt);
// We might have to sustain for a few more samples regardless of any new peak
// we find in 10ms because of code above (first frame or ending the attack
// state).
// If another peak was found afterwards we can start working with that one: if
// it's higher than we go into attack state, if it's lower we sustain for now.
if let Some(sustain_cnt) = peak.map(|(d, _v)| d).or(self.sustain_cnt) {
// Apply the final gain reduction from the previous attack for the next
// samples until we're 1920 samples / 10ms before the peak and then either
// need to go into attack state if the peak was higher, or stay in sustain
// state and check for the next peak.
let mut index = self.limiter_buf_index + smp_cnt * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
// Sustain the current gain reduction until we're exactly 10ms before
// the new peak
let mut s = 0;
while s < sustain_cnt && smp_cnt < nb_samples {
// Safety: Index checked below
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 {
// If a higher peak than before is found in the next frame need to move
// into attack state again to reduce the gain smoothly further.
//
// Otherwise we stay in sustain mode and smp_cnt is now exactly 10ms before
// the new peak, i.e. the next call to detect_peak() would find the *next*
// 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],
);
// We need to sustain until the peak at least
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 {
// If no new peak is found, release smoothly over the next 100ms.
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;
// Smoothly release over the duration of 1 frame (100ms, 19200 samples).
let mut index = self.limiter_buf_index + smp_cnt * channels;
if index >= self.limiter_buf.len() {
index -= self.limiter_buf.len();
}
// There might be a new peak during these 100ms, which we will have to detect
// and in that case go into attack state again if the gain reduction is higher
// than the current gain reduction we have, or go into sustain mode if it is
// equal or lower. We don't stay in release mode if a peak is found.
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);
// Sustain the current gain reduction until we're exactly 10ms before
// the new peak
for _ in 0..peak_delta {
// Safety: Index checked below
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]));
// Safety: Index checked below
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 we're done with the release, go to out state
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;
}
}
// Initialize the previous sample for peak detection with the last sample we looked at
// above
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 {
// Pretend the first peak was at the last sample so that the sustain code can work
// as with normal peaks
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
);
// The sustain code below will already handle the gain reduction and checking for
// further peaks.
}
}
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);
// For the first frame we can't adjust the gain before it smoothly anymore so instead
// apply the gain reduction immediately if we get above the threshold and move to sustain
// state directly.
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);
}
}
}
// Copy over the samples into the output buffer, after going through the limiter above.
let mut index = self.limiter_buf_index;
for dest_samples in dst.chunks_exact_mut(channels) {
// Safety: Index checked below
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;
// Clamp to the maximum for rounding errors above
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();
}
}
}
// Checks if there is a peak above the threshold 10ms or 1920 samples after the current
// sample. Returns the peak delta and its value. The peak delta is relative to
// offset + LIMITER_LOOKAHEAD (10ms), i.e. a peak delta of 0 would be 10ms after the offset.
//
// peak delta 0 is never returned, i.e. it is safe to call this 10ms before a peak and it would
// then return the next peak.
fn detect_peak(&mut self, offset: usize, samples: usize) -> Option<(usize, f64)> {
let channels = self.info.channels() as usize;
// Check for a peak 1920 samples / 10ms in the future
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();
}
// Get the current sample for each channel and the next here
// Safety: Index checked above
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;
// Iterate over the previous sample for each channel, the current and the next, i.e.
// in each iteration we're looking at channel c for those 3 samples.
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;
// Check if the current sample is the highest peak
if (*prev_smp <= this) && (this >= next) && (this > self.target_tp) && (n > 0) {
detected = true;
// Check the 12 following samples, if one of them is higher then that would be
// the peak.
for i in 2..12 {
// Safety: Index checked right here
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;
}
}
// Remember as previous sample.
*prev_smp = this;
}
// If this was the highest peak then remember it as the previous sample (as we didn't
// just above here because of the break!) and return the peak index and value.
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 };
// Apply gaussian filter to the gain adjustments for smoothening them.
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),
}
// Need to reset the state now
*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(_) => {
// Resetting our whole state
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.instance()), 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.instance()), 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.instance();
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 => {
// Drop state
*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
}