blob: 0b3632766f149283dc149720feb5b68fc25270e7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/mixing_graph_impl.h"
#include "base/compiler_specific.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/loopback_audio_converter.h"
#include "services/audio/sync_mixing_graph_input.h"
namespace audio {
namespace {
std::unique_ptr<media::LoopbackAudioConverter> CreateConverter(
const media::AudioParameters& input_params,
const media::AudioParameters& output_params) {
return std::make_unique<media::LoopbackAudioConverter>(
input_params, output_params, /*disable_fifo=*/true);
}
// Clamps all samples to the interval [-1, 1].
void SanitizeOutput(media::AudioBus* bus) {
for (int channel = 0; channel < bus->channels(); ++channel) {
float* data = bus->channel(channel);
for (int frame = 0; frame < bus->frames(); frame++) {
float value = data[frame];
if (LIKELY(value * value <= 1.0f)) {
continue;
}
// The sample is out of range. Negative values are clamped to -1. Positive
// values and NaN are clamped to 1.
data[frame] = value < 0.0f ? -1.0f : 1.0f;
}
}
}
bool SameChannelSetup(const media::AudioParameters& a,
const media::AudioParameters& b) {
return a.channel_layout() == b.channel_layout() &&
a.channels() == b.channels();
}
} // namespace
// Counts how often mixing callback duration exceeded the given time limit and
// logs it as a UMA histogram.
class MixingGraphImpl::OvertimeLogger {
public:
// Logs once every 10s, assuming 10ms buffers.
constexpr static int kCallbacksPerLogPeriod = 1000;
explicit OvertimeLogger(base::TimeDelta timeout) : timeout_(timeout) {}
void Log(base::TimeTicks callback_start) {
++callback_count_;
if (base::TimeTicks::Now() - callback_start > timeout_)
overtime_count_++;
if (callback_count_ % kCallbacksPerLogPeriod)
return;
// Clipped to 100 to give more resolution to lower values.
base::UmaHistogramCounts100(
"Media.Audio.OutputDeviceMixer.OvertimeCount", overtime_count_);
overtime_count_ = 0;
}
private:
const base::TimeDelta timeout_;
int callback_count_ = 0;
int overtime_count_ = 0;
};
MixingGraphImpl::MixingGraphImpl(const media::AudioParameters& output_params,
OnMoreDataCallback on_more_data_cb,
OnErrorCallback on_error_cb)
: MixingGraphImpl(output_params,
on_more_data_cb,
on_error_cb,
base::BindRepeating(&CreateConverter)) {}
MixingGraphImpl::MixingGraphImpl(const media::AudioParameters& output_params,
OnMoreDataCallback on_more_data_cb,
OnErrorCallback on_error_cb,
CreateConverterCallback create_converter_cb)
: output_params_(output_params),
on_more_data_cb_(std::move(on_more_data_cb)),
on_error_cb_(std::move(on_error_cb)),
create_converter_cb_(std::move(create_converter_cb)),
overtime_logger_(
std::make_unique<OvertimeLogger>(output_params.GetBufferDuration())),
main_converter_(output_params, output_params, /*disable_fifo=*/true) {}
MixingGraphImpl::~MixingGraphImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK(main_converter_.empty());
DCHECK(converters_.empty());
}
std::unique_ptr<MixingGraph::Input> MixingGraphImpl::CreateInput(
const media::AudioParameters& params) {
return std::make_unique<SyncMixingGraphInput>(this, params);
}
media::LoopbackAudioConverter* MixingGraphImpl::FindOrAddConverter(
const media::AudioParameters& input_params,
const media::AudioParameters& output_params,
media::LoopbackAudioConverter* parent_converter) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
AudioConverterKey key(input_params);
auto converter = converters_.find(key);
if (converter == converters_.end()) {
// No existing suitable converter. Add a new converter to the graph.
std::pair<AudioConverters::iterator, bool> result =
converters_.insert(std::make_pair(
key, create_converter_cb_.Run(input_params, output_params)));
converter = result.first;
// Add the new converter as an input to its parent converter.
base::AutoLock scoped_lock(lock_);
if (parent_converter) {
parent_converter->AddInput(converter->second.get());
} else {
main_converter_.AddInput(converter->second.get());
}
}
return converter->second.get();
}
void MixingGraphImpl::AddInput(Input* input) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
const auto& input_params = input->GetParams();
DCHECK(input_params.format() ==
media::AudioParameters::AUDIO_PCM_LOW_LATENCY);
// Resampler input format is the same as output except sample rate.
media::AudioParameters resampler_input_params(output_params_);
resampler_input_params.set_sample_rate(input_params.sample_rate());
// Channel mixer input format is the same as resampler input except channel
// layout and channel count.
media::AudioParameters channel_mixer_input_params(
resampler_input_params.format(), input_params.channel_layout(),
resampler_input_params.sample_rate(),
resampler_input_params.frames_per_buffer());
if (channel_mixer_input_params.channel_layout() ==
media::CHANNEL_LAYOUT_DISCRETE)
channel_mixer_input_params.set_channels_for_discrete(
input_params.channels());
media::LoopbackAudioConverter* converter = nullptr;
// Check if resampling is needed.
if (resampler_input_params.sample_rate() != output_params_.sample_rate()) {
// Re-use or create a resampler.
converter =
FindOrAddConverter(resampler_input_params, output_params_, converter);
}
// Check if channel mixing is needed.
if (!SameChannelSetup(channel_mixer_input_params, resampler_input_params)) {
// Re-use or create a channel mixer.
converter = FindOrAddConverter(channel_mixer_input_params,
resampler_input_params, converter);
}
// Add the input to the mixing graph.
base::AutoLock scoped_lock(lock_);
if (converter) {
converter->AddInput(input);
} else {
main_converter_.AddInput(input);
}
}
void MixingGraphImpl::Remove(const AudioConverterKey& key,
media::AudioConverter::InputCallback* input) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (key == AudioConverterKey(output_params_)) {
base::AutoLock scoped_lock(lock_);
main_converter_.RemoveInput(input);
return;
}
auto converter = converters_.find(key);
DCHECK(converter != converters_.end());
media::LoopbackAudioConverter* parent = converter->second.get();
{
base::AutoLock scoped_lock(lock_);
parent->RemoveInput(input);
}
// Remove parent converter if empty.
if (parent->empty()) {
// With knowledge of the tree structure (resampling closer to the
// main converter than channel mixing) the key of the grandparent converter
// can be deduced. This key is used to find the grandparent and remove the
// reference to the empty parent converter.
AudioConverterKey next_key(key);
if (!key.SameChannelSetup(output_params_)) {
next_key.UpdateChannelSetup(output_params_);
} else {
// If the parent converter is not the main converter its key (and input
// parameters) should differ from the output parameters in sample rate,
// channel setup or both.
DCHECK_NE(key.sample_rate(), output_params_.sample_rate());
next_key.set_sample_rate(output_params_.sample_rate());
}
Remove(next_key, parent);
converters_.erase(converter);
}
}
void MixingGraphImpl::RemoveInput(Input* input) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
Remove(AudioConverterKey(input->GetParams()), input);
}
int MixingGraphImpl::OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped,
media::AudioBus* dest) {
const base::TimeTicks start_time(base::TimeTicks::Now());
TRACE_EVENT_BEGIN2(TRACE_DISABLED_BY_DEFAULT("audio"),
"MixingGraphImpl::OnMoreData", "delay", delay,
"delay_timestamp", delay_timestamp);
// The expected playout time is |delay_timestamp| + |delay|.
base::TimeDelta total_delay = delay_timestamp + delay - start_time;
if (total_delay < base::TimeDelta())
total_delay = base::TimeDelta();
uint32_t frames_delayed = media::AudioTimestampHelper::TimeToFrames(
total_delay, output_params_.sample_rate());
{
base::AutoLock scoped_lock(lock_);
main_converter_.ConvertWithDelay(frames_delayed, dest);
}
SanitizeOutput(dest);
on_more_data_cb_.Run(*dest, total_delay);
TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("audio"),
"MixingGraphImpl::OnMoreData", "total_delay", total_delay,
"frames_delayed", frames_delayed);
overtime_logger_->Log(start_time);
return dest->frames();
}
void MixingGraphImpl::OnError(ErrorType error) {
on_error_cb_.Run(error);
}
} // namespace audio