chromium/chromecast/media/cma/backend/mixer/mixer_pipeline.cc

// Copyright 2018 The Chromium Authors
// 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/mixer/mixer_pipeline.h"

#include <algorithm>
#include <utility>

#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "chromecast/media/base/audio_device_ids.h"
#include "chromecast/media/cma/backend/mixer/filter_group.h"
#include "chromecast/media/cma/backend/mixer/post_processing_pipeline_impl.h"
#include "chromecast/media/cma/backend/mixer/post_processing_pipeline_parser.h"
#include "chromecast/public/media/audio_post_processor2_shlib.h"
#include "media/audio/audio_device_description.h"

namespace chromecast {
namespace media {

namespace {

bool IsOutputDeviceId(const std::string& device) {
  return device == ::media::AudioDeviceDescription::kDefaultDeviceId ||
         device == ::media::AudioDeviceDescription::kCommunicationsDeviceId ||
         device == kLocalAudioDeviceId || device == kAlarmAudioDeviceId ||
         device == kNoDelayDeviceId || device == kLowLatencyDeviceId ||
         device == kPlatformAudioDeviceId /* e.g. bluetooth and aux */ ||
         device == kTtsAudioDeviceId || device == kBypassAudioDeviceId;
}

}  // namespace

// static
std::unique_ptr<MixerPipeline> MixerPipeline::CreateMixerPipeline(
    PostProcessingPipelineParser* config,
    PostProcessingPipelineFactory* factory,
    int expected_input_channels) {
  std::unique_ptr<MixerPipeline> mixer_pipeline(new MixerPipeline());
  if (mixer_pipeline->BuildPipeline(config, factory, expected_input_channels)) {
    return mixer_pipeline;
  }
  return nullptr;
}

MixerPipeline::MixerPipeline() = default;
MixerPipeline::~MixerPipeline() = default;

bool MixerPipeline::BuildPipeline(PostProcessingPipelineParser* config,
                                  PostProcessingPipelineFactory* factory,
                                  int expected_input_channels) {
  DCHECK(config);
  DCHECK(factory);
  int mix_group_input_channels = -1;

  // Create "stream" processor groups:
  for (auto& stream_pipeline : config->GetStreamPipelines()) {
    const base::Value::List& device_ids =
        stream_pipeline.stream_types->GetList();
    int input_channels = (stream_pipeline.num_input_channels.has_value()
                              ? stream_pipeline.num_input_channels.value()
                              : expected_input_channels);
    DCHECK(!device_ids.empty());
    DCHECK(device_ids[0].is_string());
    const std::string& name = device_ids[0].GetString();
    LOG(INFO) << input_channels << " input channels to '" << name << "' group";

    filter_groups_.push_back(std::make_unique<FilterGroup>(
        input_channels, name, stream_pipeline.prerender_pipeline.Clone(),
        &stream_pipeline.pipeline, factory, stream_pipeline.volume_limits));

    if (!SetGroupDeviceIds(stream_pipeline.stream_types,
                           filter_groups_.back().get())) {
      return false;
    }

    mix_group_input_channels =
        std::max(mix_group_input_channels,
                 filter_groups_.back()->GetOutputChannelCount());
  }

  if (mix_group_input_channels == -1) {
    mix_group_input_channels = expected_input_channels;
  }

  // Create "mix" processor group:
  const auto mix_pipeline = config->GetMixPipeline();
  if (mix_pipeline.num_input_channels.has_value()) {
    mix_group_input_channels = mix_pipeline.num_input_channels.value();
  }
  LOG(INFO) << mix_group_input_channels << " input channels to 'mix' group";
  auto mix_filter = std::make_unique<FilterGroup>(
      mix_group_input_channels, "mix", mix_pipeline.prerender_pipeline.Clone(),
      &mix_pipeline.pipeline, factory, mix_pipeline.volume_limits);
  for (std::unique_ptr<FilterGroup>& group : filter_groups_) {
    mix_filter->AddMixedInput(group.get());
  }
  if (!SetGroupDeviceIds(mix_pipeline.stream_types, mix_filter.get())) {
    return false;
  }
  loopback_output_group_ = mix_filter.get();
  filter_groups_.push_back(std::move(mix_filter));

  // Create "linearize" processor group:
  const auto linearize_pipeline = config->GetLinearizePipeline();
  int linearize_group_input_channels =
      loopback_output_group_->GetOutputChannelCount();
  if (linearize_pipeline.num_input_channels.has_value()) {
    linearize_group_input_channels =
        linearize_pipeline.num_input_channels.value();
  }
  LOG(INFO) << linearize_group_input_channels
            << " input channels to 'linearize' group";
  filter_groups_.push_back(std::make_unique<FilterGroup>(
      linearize_group_input_channels, "linearize",
      linearize_pipeline.prerender_pipeline.Clone(),
      &linearize_pipeline.pipeline, factory, linearize_pipeline.volume_limits));
  output_group_ = filter_groups_.back().get();
  output_group_->AddMixedInput(loopback_output_group_);
  if (!SetGroupDeviceIds(linearize_pipeline.stream_types, output_group_)) {
    return false;
  }

  // If no default group is provided, use the "mix" group.
  if (stream_sinks_.find(::media::AudioDeviceDescription::kDefaultDeviceId) ==
      stream_sinks_.end()) {
    stream_sinks_[::media::AudioDeviceDescription::kDefaultDeviceId] =
        loopback_output_group_;
  }

  return true;
}

bool MixerPipeline::SetGroupDeviceIds(const base::Value* ids,
                                      FilterGroup* filter_group) {
  if (!ids) {
    return true;
  }
  DCHECK(filter_group);
  DCHECK(ids->is_list());

  for (const base::Value& stream_type_val : ids->GetList()) {
    DCHECK(stream_type_val.is_string());
    const std::string& stream_type = stream_type_val.GetString();
    if (!IsOutputDeviceId(stream_type)) {
      LOG(ERROR) << stream_type
                 << " is not a stream type. Stream types are listed "
                 << "in chromecast/media/base/audio_device_ids.cc and "
                 << "media/audio/audio_device_description.cc";
      return false;
    }
    if (stream_sinks_.find(stream_type) != stream_sinks_.end()) {
      LOG(ERROR) << "Multiple instances of stream type '" << stream_type
                 << "' in cast_audio.json";
      return false;
    }
    stream_sinks_[stream_type] = filter_group;
    filter_group->AddStreamType(stream_type);
  }
  return true;
}

void MixerPipeline::Initialize(int output_samples_per_second,
                               int frames_per_write) {
  // The output group will recursively set the sample rate of all other
  // FilterGroups.
  AudioPostProcessor2::Config config;
  config.output_sample_rate = output_samples_per_second;
  config.system_output_sample_rate = output_samples_per_second;
  config.output_frames_per_write = frames_per_write;
  output_group_->Initialize(config);
  output_group_->PrintTopology();
}

FilterGroup* MixerPipeline::GetInputGroup(const std::string& device_id) {
  auto got = stream_sinks_.find(device_id);
  if (got != stream_sinks_.end()) {
    return got->second;
  }

  return stream_sinks_[::media::AudioDeviceDescription::kDefaultDeviceId];
}

void MixerPipeline::MixAndFilter(
    int frames_per_write,
    MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay) {
  output_group_->MixAndFilter(frames_per_write, rendering_delay);
}

float* MixerPipeline::GetLoopbackOutput() {
  return loopback_output_group_->GetOutputBuffer();
}

float* MixerPipeline::GetOutput() {
  return output_group_->GetOutputBuffer();
}

int MixerPipeline::GetLoopbackChannelCount() const {
  return loopback_output_group_->GetOutputChannelCount();
}

int MixerPipeline::GetOutputChannelCount() const {
  return output_group_->GetOutputChannelCount();
}

int64_t MixerPipeline::GetPostLoopbackRenderingDelayMicroseconds() const {
  return output_group_->GetRenderingDelayMicroseconds();
}

void MixerPipeline::SetPostProcessorConfig(const std::string& name,
                                           const std::string& config) {
  for (auto&& filter_group : filter_groups_) {
    filter_group->SetPostProcessorConfig(name, config);
  }
}

bool MixerPipeline::IsRinging() const {
  for (auto& filter_group : filter_groups_) {
    if (filter_group->IsRinging()) {
      return true;
    }
  }
  return false;
}

}  // namespace media
}  // namespace chromecast