#include "media/base/audio_limiter.h"
#include <algorithm>
#include "base/containers/span_reader.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/audio_timestamp_helper.h"
namespace media {
constexpr base::TimeDelta kAttackTime = base::Milliseconds(5);
constexpr base::TimeDelta kReleaseTime = base::Milliseconds(50);
constexpr double kThreshold = 1.0;
AudioLimiter::PendingOutput::PendingOutput(OutputChannels channels,
OutputFilledCB filled_callback)
: on_filled_callback(std::move(filled_callback)),
channels(std::move(channels)) {}
AudioLimiter::PendingOutput::~PendingOutput() = default;
AudioLimiter::PendingOutput::PendingOutput(PendingOutput&&) = default;
AudioLimiter::AudioLimiter(int sample_rate, int channels)
: channels_(channels),
attack_frames_(
AudioTimestampHelper::TimeToFrames(kAttackTime, sample_rate)),
release_frames_(
AudioTimestampHelper::TimeToFrames(kReleaseTime, sample_rate)),
attack_constant_(attack_frames_ ? std::pow(0.1, 1.0 / attack_frames_)
: 0.0),
release_constant_(release_frames_ ? std::pow(0.1, 1.0 / release_frames_)
: 0.0),
moving_max_(attack_frames_),
initial_output_delay_in_frames_(attack_frames_) {
CHECK(sample_rate);
CHECK(channels_);
}
AudioLimiter::~AudioLimiter() = default;
void AudioLimiter::LimitPeaks(const AudioBus& input,
const OutputChannels& output,
OutputFilledCB on_output_filled_cb) {
LimitPeaksPartial(input, input.frames(), output,
std::move(on_output_filled_cb));
}
void AudioLimiter::LimitPeaksPartial(const AudioBus& input,
int num_frames,
const OutputChannels& output,
OutputFilledCB on_output_filled_cb) {
CHECK(!was_flushed_);
CHECK_GT(num_frames, 0);
CHECK_LE(num_frames, input.frames());
CHECK_EQ(input.channels(), channels_);
CHECK_EQ(static_cast<size_t>(input.channels()), output.size());
for (int ch = 0; ch < channels_; ++ch) {
CHECK_EQ(num_frames * sizeof(float), output[ch].size_bytes());
}
outputs_.emplace_back(std::move(output), std::move(on_output_filled_cb));
FeedInput(input, num_frames);
}
void AudioLimiter::Flush() {
CHECK(!was_flushed_);
auto silence = AudioBus::Create(channels_, attack_frames_);
silence->Zero();
FeedInput(*silence, attack_frames_);
CHECK(outputs_.empty());
was_flushed_ = true;
}
void AudioLimiter::FeedInput(const AudioBus& input, int num_frames) {
CHECK_EQ(input.channels(), channels_);
const uint32_t frame_size = channels_;
std::vector<float> interleaved_input;
interleaved_input.resize(num_frames * frame_size);
input.ToInterleaved<Float32SampleTypeTraitsNoClip>(num_frames,
interleaved_input.data());
std::ranges::for_each(interleaved_input, [](float& sample) {
if (std::isnan(sample) || std::isinf(sample)) {
sample = 0.0f;
}
});
delayed_interleaved_input_.reserve(delayed_interleaved_input_.size() +
interleaved_input.size());
std::ranges::copy(interleaved_input,
std::back_inserter(delayed_interleaved_input_));
base::SpanReader<float> input_reader(interleaved_input);
while (input_reader.remaining()) {
auto frame_data = input_reader.Read(frame_size);
float max_sample_for_frame = kThreshold;
for (float sample : *frame_data) {
max_sample_for_frame = std::max(std::abs(sample), max_sample_for_frame);
}
moving_max_.AddSample(max_sample_for_frame);
UpdateGain(moving_max_.Max());
WriteLimitedFrameToOutput();
}
CHECK_LE(delayed_interleaved_input_.size(),
static_cast<size_t>(channels_ * attack_frames_));
}
void AudioLimiter::UpdateGain(float current_maximum) {
double gain = 1.0;
if (current_maximum > kThreshold) {
gain = kThreshold / current_maximum;
}
if (gain < smoothed_gain_) {
constexpr double kOneTenth = 1.0 / 10.0;
constexpr double kTenNinths = 10.0 / 9.0;
const double target_gain = kTenNinths * (gain - kOneTenth * smoothed_gain_);
target_gain_ = std::min(target_gain_, target_gain);
} else {
target_gain_ = gain;
}
if (target_gain_ < smoothed_gain_) {
smoothed_gain_ =
attack_constant_ * (smoothed_gain_ - target_gain_) + target_gain_;
smoothed_gain_ = std::max(smoothed_gain_, gain);
} else {
smoothed_gain_ =
release_constant_ * (smoothed_gain_ - target_gain_) + target_gain_;
}
CHECK_LE(smoothed_gain_, 1.0);
}
void AudioLimiter::WriteLimitedFrameToOutput() {
if (initial_output_delay_in_frames_ > 0) {
--initial_output_delay_in_frames_;
return;
}
CHECK(!outputs_.empty());
CHECK_GT(delayed_interleaved_input_.size(),
static_cast<size_t>(channels_ * attack_frames_));
OutputChannels& output_channels = outputs_.front().channels;
CHECK(!output_channels.empty());
CHECK(!output_channels[0].empty());
const auto copy_float_to_channel = [](float src, base::span<uint8_t>& ch) {
auto [dest, remainder] = ch.split_at<sizeof(float)>();
dest.copy_from(base::byte_span_from_ref(base::allow_nonunique_obj, src));
ch = remainder;
};
if (smoothed_gain_ < 1.0) {
for (int ch = 0; ch < channels_; ++ch) {
const float adjusted_sample = static_cast<float>(
static_cast<double>(delayed_interleaved_input_.front()) *
smoothed_gain_);
copy_float_to_channel(adjusted_sample, output_channels[ch]);
delayed_interleaved_input_.pop_front();
}
} else {
for (int ch = 0; ch < channels_; ++ch) {
copy_float_to_channel(delayed_interleaved_input_.front(),
output_channels[ch]);
delayed_interleaved_input_.pop_front();
}
}
if (outputs_.front().channels[0].empty()) {
std::move(outputs_.front().on_filled_callback).Run();
outputs_.pop_front();
}
}
}