blob: 3a0659d6140a17b82465092aa11748f13f224055 [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/interleaved_channel_mixer.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::GroupInput::GroupInput(
FilterGroup* group,
std::unique_ptr<InterleavedChannelMixer> channel_mixer)
: group(group), channel_mixer(std::move(channel_mixer)) {}
FilterGroup::GroupInput::GroupInput(GroupInput&& other) = default;
FilterGroup::GroupInput::~GroupInput() = default;
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) {
// Channel mixers are created in Initialize().
mixed_inputs_.emplace_back(input, std::unique_ptr<InterleavedChannelMixer>());
DCHECK_EQ(input->GetOutputChannelCount(), num_channels_);
}
void FilterGroup::AddStreamType(const std::string& stream_type) {
stream_types_.push_back(stream_type);
}
void FilterGroup::Initialize(const AudioPostProcessor2::Config& output_config) {
output_config_ = output_config;
CHECK(post_processing_pipeline_->SetOutputConfig(output_config_));
input_samples_per_second_ = post_processing_pipeline_->GetInputSampleRate();
input_frames_per_write_ = output_config_.output_frames_per_write *
input_samples_per_second_ /
output_config_.output_sample_rate;
DCHECK_EQ(input_frames_per_write_ * output_config_.output_sample_rate,
output_config_.output_frames_per_write * input_samples_per_second_)
<< "Unable to produce stable buffer sizes for resampling rate "
<< input_samples_per_second_ << " : "
<< output_config_.output_sample_rate;
AudioPostProcessor2::Config input_config = output_config;
input_config.output_sample_rate = input_samples_per_second_;
input_config.output_frames_per_write = input_frames_per_write_;
for (auto& input : mixed_inputs_) {
input.group->Initialize(input_config);
input.channel_mixer = std::make_unique<InterleavedChannelMixer>(
::media::GuessChannelLayout(input.group->GetOutputChannelCount()),
::media::GuessChannelLayout(num_channels_), input_frames_per_write_);
}
post_processing_pipeline_->SetContentType(content_type_);
active_inputs_.clear();
ResizeBuffers();
// Run a buffer of 0's to initialize rendering delay.
std::fill_n(interleaved_.data(), interleaved_.size(), 0.0f);
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);
}
void FilterGroup::RemoveInput(MixerInput* input) {
active_inputs_.erase(input);
}
float FilterGroup::MixAndFilter(
int num_output_frames,
MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay) {
DCHECK_NE(output_config_.output_sample_rate, 0);
DCHECK_EQ(num_output_frames, output_config_.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 (const auto& filter_group : mixed_inputs_) {
volume = std::max(volume, filter_group.group->MixAndFilter(
input_frames_per_write_, rendering_delay));
content_type = std::max(content_type, filter_group.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_) {
::media::AudioBus* temp = temp_buffer_.get();
int filled =
input->FillAudioData(input_frames_per_write_, rendering_delay, temp);
if (filled > 0) {
for (int c = 0; c < num_channels_; ++c) {
input->VolumeScaleAccumulate(temp->channel(c), filled,
mixed_->channel(c));
}
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 (const auto& input : mixed_inputs_) {
if (input.group->last_volume() > 0.0f) {
float* buffer = input.channel_mixer->Transform(
input.group->GetOutputBuffer(), input_frames_per_write_);
for (int i = 0; i < input_frames_per_write_ * num_channels_; ++i) {
interleaved_[i] += buffer[i];
}
}
}
// 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_config_.output_sample_rate == 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_);
mixed_->Zero();
temp_buffer_ =
::media::AudioBus::Create(num_channels_, input_frames_per_write_);
temp_buffer_->Zero();
interleaved_.assign(input_frames_per_write_ * num_channels_, 0.0f);
}
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;
}
post_processing_pipeline_->UpdatePlayoutChannel(playout_channel);
}
bool FilterGroup::IsRinging() const {
return post_processing_pipeline_->IsRinging();
}
void FilterGroup::PrintTopology() const {
std::string filter_groups;
for (const auto& mixed_input : mixed_inputs_) {
mixed_input.group->PrintTopology();
filter_groups += "[GROUP]" + mixed_input.group->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_config_.output_sample_rate << "hz";
}
} // namespace media
} // namespace chromecast