blob: 2ab0cc996b48b9b710ed04a215b1ce0f5403f255 [file] [log] [blame]
// Copyright 2017 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 "chromecast/media/cma/backend/filter_group.h"
#include <algorithm>
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromecast/media/cma/backend/mixer_input.h"
#include "chromecast/media/cma/backend/post_processing_pipeline.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/vector_math.h"
namespace chromecast {
namespace media {
FilterGroup::FilterGroup(int num_channels,
const std::string& name,
std::unique_ptr<PostProcessingPipeline> pipeline)
: num_channels_(num_channels),
name_(name),
post_processing_pipeline_(std::move(pipeline)) {}
FilterGroup::~FilterGroup() = default;
void FilterGroup::AddMixedInput(FilterGroup* input) {
mixed_inputs_.push_back(input);
DCHECK_EQ(input->GetOutputChannelCount(), num_channels_);
}
void FilterGroup::AddStreamType(const std::string& stream_type) {
stream_types_.push_back(stream_type);
}
void FilterGroup::Initialize(int output_samples_per_second,
int output_frames_per_write) {
output_samples_per_second_ = output_samples_per_second;
output_frames_per_write_ = output_frames_per_write;
CHECK(post_processing_pipeline_->SetOutputSampleRate(
output_samples_per_second_));
input_samples_per_second_ = post_processing_pipeline_->GetInputSampleRate();
input_frames_per_write_ = output_frames_per_write *
input_samples_per_second_ /
output_samples_per_second_;
DCHECK_EQ(input_frames_per_write_ * output_samples_per_second_,
output_frames_per_write_ * input_samples_per_second_)
<< "Unable to produce stable buffer sizes for resampling rate "
<< input_samples_per_second_ << " : " << output_samples_per_second_;
for (FilterGroup* input : mixed_inputs_) {
input->Initialize(input_samples_per_second_, input_frames_per_write_);
}
post_processing_pipeline_->SetContentType(content_type_);
active_inputs_.clear();
ResizeBuffers();
// Run a buffer of 0's to initialize rendering delay.
delay_seconds_ = post_processing_pipeline_->ProcessFrames(
interleaved_.data(), input_frames_per_write_, last_volume_,
true /* is_silence */);
}
void FilterGroup::AddInput(MixerInput* input) {
active_inputs_.insert(input);
if (mixed_) {
AddTempBuffer(input->num_channels(), mixed_->frames());
}
}
void FilterGroup::RemoveInput(MixerInput* input) {
active_inputs_.erase(input);
}
float FilterGroup::MixAndFilter(
int num_output_frames,
MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay) {
DCHECK_NE(output_samples_per_second_, 0);
DCHECK_EQ(num_output_frames, output_frames_per_write_);
float volume = 0.0f;
AudioContentType content_type = static_cast<AudioContentType>(-1);
rendering_delay.delay_microseconds += GetRenderingDelayMicroseconds();
rendering_delay_to_output_ = rendering_delay;
// Recursively mix inputs.
for (auto* filter_group : mixed_inputs_) {
volume = std::max(volume, filter_group->MixAndFilter(
input_frames_per_write_, rendering_delay));
content_type = std::max(content_type, filter_group->content_type());
}
// |volume| can only be 0 if no |mixed_inputs_| have data.
// This is true because FilterGroup can only return 0 if:
// a) It has no data and its PostProcessorPipeline is not ringing.
// (early return, below) or
// b) The output volume is 0 and has NEVER been non-zero,
// since FilterGroup will use last_volume_ if volume is 0.
// In this case, there was never any data in the pipeline.
if (active_inputs_.empty() && volume == 0.0f &&
!post_processing_pipeline_->IsRinging()) {
if (frames_zeroed_ < num_output_frames) {
std::fill_n(GetOutputBuffer(),
num_output_frames * GetOutputChannelCount(), 0);
frames_zeroed_ = num_output_frames;
}
return 0.0f; // Output will be silence, no need to mix.
}
frames_zeroed_ = 0;
// Mix InputQueues
mixed_->ZeroFramesPartial(0, input_frames_per_write_);
for (MixerInput* input : active_inputs_) {
DCHECK_LT(input->num_channels(), static_cast<int>(temp_buffers_.size()));
DCHECK(temp_buffers_[input->num_channels()]);
::media::AudioBus* temp = temp_buffers_[input->num_channels()].get();
int filled =
input->FillAudioData(input_frames_per_write_, rendering_delay, temp);
if (filled > 0) {
int in_c = 0;
for (int out_c = 0; out_c < num_channels_; ++out_c) {
input->VolumeScaleAccumulate(temp->channel(in_c), filled,
mixed_->channel(out_c));
++in_c;
if (in_c >= input->num_channels()) {
in_c = 0;
}
}
volume = std::max(volume, input->InstantaneousVolume());
content_type = std::max(content_type, input->content_type());
}
}
mixed_->ToInterleaved<::media::FloatSampleTypeTraits<float>>(
input_frames_per_write_, interleaved_.data());
// Mix FilterGroups
for (FilterGroup* group : mixed_inputs_) {
if (group->last_volume() > 0.0f) {
::media::vector_math::FMAC(group->GetOutputBuffer(), 1.0f,
input_frames_per_write_ * num_channels_,
interleaved_.data());
}
}
if (playout_channel_selection_ != kChannelAll) {
// Duplicate selected channel to all channels.
float* data = interleaved_.data();
for (int f = 0; f < input_frames_per_write_; ++f) {
float selected = data[f * num_channels_ + playout_channel_selection_];
for (int c = 0; c < num_channels_; ++c)
data[f * num_channels_ + c] = selected;
}
}
// Allow paused streams to "ring out" at the last valid volume.
// If the stream volume is actually 0, this doesn't matter, since the
// data is 0's anyway.
bool is_silence = (volume == 0.0f);
if (!is_silence) {
last_volume_ = volume;
DCHECK_NE(-1, static_cast<int>(content_type))
<< "Got frames without content type.";
if (content_type != content_type_) {
content_type_ = content_type;
post_processing_pipeline_->SetContentType(content_type_);
}
}
delay_seconds_ = post_processing_pipeline_->ProcessFrames(
interleaved_.data(), input_frames_per_write_, last_volume_, is_silence);
return last_volume_;
}
float* FilterGroup::GetOutputBuffer() {
return post_processing_pipeline_->GetOutputBuffer();
}
int64_t FilterGroup::GetRenderingDelayMicroseconds() {
if (output_samples_per_second_ == 0) {
return 0;
}
return delay_seconds_ * base::Time::kMicrosecondsPerSecond;
}
MediaPipelineBackend::AudioDecoder::RenderingDelay
FilterGroup::GetRenderingDelayToOutput() {
return rendering_delay_to_output_;
}
int FilterGroup::GetOutputChannelCount() const {
return post_processing_pipeline_->NumOutputChannels();
}
void FilterGroup::ResizeBuffers() {
mixed_ = ::media::AudioBus::Create(num_channels_, input_frames_per_write_);
temp_buffers_.clear();
for (MixerInput* input : active_inputs_) {
AddTempBuffer(input->num_channels(), input_frames_per_write_);
}
interleaved_.assign(input_frames_per_write_ * num_channels_, 0.0f);
}
void FilterGroup::AddTempBuffer(int num_channels, int num_frames) {
if (static_cast<int>(temp_buffers_.size()) <= num_channels) {
temp_buffers_.resize(num_channels + 1);
}
if (!temp_buffers_[num_channels]) {
temp_buffers_[num_channels] =
::media::AudioBus::Create(num_channels, num_frames);
}
}
void FilterGroup::SetPostProcessorConfig(const std::string& name,
const std::string& config) {
post_processing_pipeline_->SetPostProcessorConfig(name, config);
}
void FilterGroup::UpdatePlayoutChannel(int playout_channel) {
if (playout_channel >= num_channels_) {
LOG(ERROR) << "only " << num_channels_ << " present, wanted channel #"
<< playout_channel;
return;
}
if (name_ == "linearize") {
// We only do playout channel selection in the "linearize" group.
playout_channel_selection_ = playout_channel;
}
post_processing_pipeline_->UpdatePlayoutChannel(playout_channel);
}
void FilterGroup::PrintTopology() const {
std::string filter_groups;
for (const FilterGroup* mixed_input : mixed_inputs_) {
mixed_input->PrintTopology();
filter_groups += "[GROUP]" + mixed_input->name() + ", ";
}
std::string input_groups;
for (const std::string& stream_type : stream_types_) {
input_groups += "[STREAM]" + stream_type + ", ";
}
// Trim trailing comma.
if (!filter_groups.empty()) {
filter_groups.resize(filter_groups.size() - 2);
}
if (!input_groups.empty()) {
input_groups.resize(input_groups.size() - 2);
}
std::string all_inputs;
if (filter_groups.empty()) {
all_inputs = input_groups;
} else if (input_groups.empty()) {
all_inputs = filter_groups;
} else {
all_inputs = input_groups + " + " + filter_groups;
}
LOG(INFO) << all_inputs << ": " << num_channels_ << "ch@"
<< input_samples_per_second_ << "hz -> [GROUP]" << name_ << " -> "
<< GetOutputChannelCount() << "ch@" << output_samples_per_second_
<< "hz";
}
} // namespace media
} // namespace chromecast