#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 "media/base/vector_math.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, false);
}
void SanitizeOutput(media::AudioBus* bus) {
for (auto channel : bus->AllChannels()) {
media::vector_math::FCLAMP(channel, channel);
}
}
bool SameChannelSetup(const media::AudioParameters& a,
const media::AudioParameters& b) {
return a.channel_layout() == b.channel_layout() &&
a.channels() == b.channels();
}
}
class MixingGraphImpl::OvertimeLogger {
public:
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;
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, false) {}
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()) {
std::pair<AudioConverters::iterator, bool> result =
converters_.insert(std::make_pair(
key, create_converter_cb_.Run(input_params, output_params)));
converter = result.first;
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);
media::AudioParameters resampler_input_params(output_params_);
resampler_input_params.set_sample_rate(input_params.sample_rate());
resampler_input_params.set_frames_per_buffer(
input_params.frames_per_buffer());
media::AudioParameters channel_mixer_input_params(
resampler_input_params.format(), input_params.channel_layout_config(),
resampler_input_params.sample_rate(),
resampler_input_params.frames_per_buffer());
media::LoopbackAudioConverter* converter = nullptr;
if (resampler_input_params.sample_rate() != output_params_.sample_rate()) {
converter =
FindOrAddConverter(resampler_input_params, output_params_, converter);
}
if (!SameChannelSetup(channel_mixer_input_params, resampler_input_params)) {
converter = FindOrAddConverter(channel_mixer_input_params,
resampler_input_params, converter);
}
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);
CHECK(converter != converters_.end());
media::LoopbackAudioConverter* parent = converter->second.get();
{
base::AutoLock scoped_lock(lock_);
parent->RemoveInput(input);
}
if (parent->empty()) {
AudioConverterKey next_key(key);
if (!key.SameChannelSetup(output_params_)) {
next_key.UpdateChannelSetup(output_params_);
} else {
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,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) {
const base::TimeTicks start_time(base::TimeTicks::Now());
uint32_t frames_delayed = media::AudioTimestampHelper::TimeToFrames(
delay, output_params_.sample_rate());
TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("audio"), "MixingGraphImpl::OnMoreData",
"playout_delay (ms)", delay.InMillisecondsF(),
"delay_timestamp (ms)",
(delay_timestamp - base::TimeTicks()).InMillisecondsF(),
"delay (frames)", frames_delayed);
{
base::AutoLock scoped_lock(lock_);
main_converter_.ConvertWithInfo(frames_delayed, glitch_info, dest);
}
SanitizeOutput(dest);
on_more_data_cb_.Run(*dest, delay);
overtime_logger_->Log(start_time);
return dest->frames();
}
void MixingGraphImpl::OnError(ErrorType error) {
on_error_cb_.Run(error);
}
}