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

// Copyright 2017 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/post_processing_pipeline_impl.h"

#include <algorithm>
#include <cmath>
#include <string>

#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/scoped_native_library.h"
#include "base/values.h"
#include "chromecast/public/media/audio_post_processor2_shlib.h"
#include "chromecast/public/volume_control.h"

namespace chromecast {
namespace media {

namespace {

// Used for AudioPostProcessor(1)
const char kJsonKeyProcessor[] = "processor";

// Used for AudioPostProcessor2
const char kJsonKeyLib[] = "lib";

const char kJsonKeyName[] = "name";
const char kJsonKeyConfig[] = "config";

}  // namespace

PostProcessingPipelineFactoryImpl::PostProcessingPipelineFactoryImpl() =
    default;
PostProcessingPipelineFactoryImpl::~PostProcessingPipelineFactoryImpl() =
    default;

std::unique_ptr<PostProcessingPipeline>
PostProcessingPipelineFactoryImpl::CreatePipeline(
    const std::string& name,
    const base::Value* filter_description_list,
    int num_channels) {
  return std::make_unique<PostProcessingPipelineImpl>(
      name, filter_description_list, num_channels);
}

PostProcessingPipelineImpl::PostProcessingPipelineImpl(
    const std::string& name,
    const base::Value* filter_description_list,
    int channels)
    : name_(name), num_output_channels_(channels) {
  if (!filter_description_list) {
    return;  // Warning logged.
  }

  LOG(INFO) << "Create pipeline for " << channels << " input channels";
  for (const base::Value& processor_description_value :
       filter_description_list->GetList()) {
    DCHECK(processor_description_value.is_dict());
    const base::Value::Dict& processor_description_dict =
        processor_description_value.GetDict();

    std::string processor_name;
    const std::string* name_val =
        processor_description_dict.FindString(kJsonKeyName);
    if (name_val) {
      processor_name = *name_val;
    }

    if (!processor_name.empty()) {
      std::vector<PostProcessorInfo>::iterator it =
          find_if(processors_.begin(), processors_.end(),
                  [&processor_name](PostProcessorInfo& p) {
                    return p.name == processor_name;
                  });
      LOG_IF(DFATAL, it != processors_.end())
          << "Duplicate postprocessor name " << processor_name;
    }

    std::string library_path;

    // Keys for AudioPostProcessor2:
    const std::string* library_val =
        processor_description_dict.FindString(kJsonKeyLib);
    if (library_val) {
      library_path = *library_val;
    } else {
      // Keys for AudioPostProcessor
      // TODO(bshaya): Remove when AudioPostProcessor support is removed.
      library_val = processor_description_dict.FindString(kJsonKeyProcessor);
      DCHECK(library_val) << "Post processor description is missing key "
                          << kJsonKeyLib;
      library_path = *library_val;
    }

    std::string processor_config_string;
    const base::Value* processor_config_val =
        processor_description_dict.Find(kJsonKeyConfig);
    if (processor_config_val) {
      DCHECK(processor_config_val->is_dict() ||
             processor_config_val->is_string());
      base::JSONWriter::Write(*processor_config_val, &processor_config_string);
    }

    LOG(INFO) << "Creating '" << processor_name << "', an instance of "
              << library_path << ", for " << channels << " channels";

    processors_.emplace_back(PostProcessorInfo{
        factory_.CreatePostProcessor(library_path, processor_config_string,
                                     channels),
        1 /* output_frames_per_input_frame */, processor_name});
    channels = processors_.back().ptr->GetStatus().output_channels;
  }
  num_output_channels_ = channels;
  LOG(INFO) << "Created pipeline with " << channels << " output channels";
}

PostProcessingPipelineImpl::~PostProcessingPipelineImpl() = default;

void PostProcessingPipelineImpl::ProcessFrames(float* data,
                                               int num_input_frames,
                                               float current_multiplier,
                                               float target_multiplier,
                                               bool is_silence) {
  DCHECK_GT(input_sample_rate_, 0);
  DCHECK(data);
  if (processors_.size() > 0) {
    DCHECK_EQ(processors_[0].input_frames_per_write, num_input_frames);
  }

  output_buffer_ = data;

  if (is_silence) {
    if (!IsRinging()) {
      // If the input sample rate differs from the output sample rate, then the
      // input data will be the incorrect size without any resampling. Output a
      // zeroed buffer of correct size.
      if (input_sample_rate_ != output_sample_rate_) {
        // We cannot guarantee that the consumer of the output buffer will not
        // mutate it, so set it back to zero.
        std::fill_n(silence_buffer_.data(), silence_buffer_.size(), 0.0);
        output_buffer_ = silence_buffer_.data();
      }
      return;
    }
    silence_frames_processed_ += num_input_frames;
  } else {
    silence_frames_processed_ = 0;
  }

  UpdateCastVolume(current_multiplier, target_multiplier);
  AudioPostProcessor2::Metadata metadata = {current_dbfs_, target_dbfs_,
                                            cast_volume_};

  delay_s_ = 0;
  for (auto& processor : processors_) {
    processor.ptr->ProcessFrames(output_buffer_,
                                 processor.input_frames_per_write, &metadata);
    const auto& status = processor.ptr->GetStatus();
    delay_s_ += static_cast<double>(status.rendering_delay_frames) /
                status.input_sample_rate;
    output_buffer_ = status.output_buffer;
  }
}

int PostProcessingPipelineImpl::NumOutputChannels() const {
  return num_output_channels_;
}

float* PostProcessingPipelineImpl::GetOutputBuffer() {
  DCHECK(output_buffer_);
  return output_buffer_;
}

bool PostProcessingPipelineImpl::SetOutputConfig(
    const AudioPostProcessor2::Config& output_config) {
  output_sample_rate_ = output_config.output_sample_rate;
  AudioPostProcessor2::Config processor_config = output_config;

  // Each Processor's output rate must be the following processor's input rate.
  for (int i = static_cast<int>(processors_.size()) - 1; i >= 0; --i) {
    if (!processors_[i].ptr->SetConfig(processor_config)) {
      return false;
    }
    int input_sample_rate = processors_[i].ptr->GetStatus().input_sample_rate;
    DCHECK_GT(input_sample_rate, 0)
        << processors_[i].name << " did not set its sample rate";
    processors_[i].input_frames_per_write =
        processor_config.output_frames_per_write * input_sample_rate /
        processor_config.output_sample_rate;
    processor_config.output_sample_rate = input_sample_rate;
    processor_config.output_frames_per_write =
        processors_[i].input_frames_per_write;
  }

  input_sample_rate_ = processor_config.output_sample_rate;
  ringing_time_in_frames_ = GetRingingTimeInFrames();
  silence_frames_processed_ = 0;

  if (input_sample_rate_ != output_sample_rate_) {
    size_t silence_size = num_output_channels_ *
                          processors_[0].input_frames_per_write *
                          output_sample_rate_ / input_sample_rate_;
    silence_buffer_.resize(silence_size);
  }

  delay_s_ = 0;
  for (auto& processor : processors_) {
    const auto& status = processor.ptr->GetStatus();
    delay_s_ += static_cast<double>(status.rendering_delay_frames) /
                status.input_sample_rate;
  }

  return true;
}

int PostProcessingPipelineImpl::GetInputSampleRate() const {
  return input_sample_rate_;
}

bool PostProcessingPipelineImpl::IsRinging() {
  return ringing_time_in_frames_ < 0 ||
         silence_frames_processed_ < ringing_time_in_frames_;
}

int PostProcessingPipelineImpl::GetRingingTimeInFrames() {
  int memory_frames = 0;
  for (auto& processor : processors_) {
    int ringing_time = processor.ptr->GetStatus().ringing_time_frames;
    if (ringing_time < 0) {
      return -1;
    }
    memory_frames += ringing_time;
  }
  return memory_frames;
}

void PostProcessingPipelineImpl::UpdateCastVolume(float multiplier,
                                                  float target) {
  DCHECK_GE(multiplier, 0.0);

  if (multiplier != current_multiplier_) {
    current_multiplier_ = multiplier;
    current_dbfs_ =
        (multiplier == 0.0f ? -200.0f : std::log10(multiplier) * 20);
    DCHECK(chromecast::media::VolumeControl::DbFSToVolume);
    cast_volume_ =
        chromecast::media::VolumeControl::DbFSToVolume(current_dbfs_);
  }

  if (target != target_multiplier_) {
    target_multiplier_ = target;
    target_dbfs_ = (target == 0.0f ? -200.0f : std::log10(target) * 20);
  }
}

// Send string |config| to postprocessor |name|.
void PostProcessingPipelineImpl::SetPostProcessorConfig(
    const std::string& name,
    const std::string& config) {
  DCHECK(!name.empty());
  std::vector<PostProcessorInfo>::iterator it =
      find_if(processors_.begin(), processors_.end(),
              [&name](PostProcessorInfo& p) { return p.name == name; });
  if (it != processors_.end()) {
    it->ptr->UpdateParameters(config);
    DVLOG(2) << "Config string: " << config
             << " was delivered to postprocessor " << name;
  }
}

// Set content type.
void PostProcessingPipelineImpl::SetContentType(AudioContentType content_type) {
  for (auto& processor : processors_) {
    processor.ptr->SetContentType(content_type);
  }
}

void PostProcessingPipelineImpl::UpdatePlayoutChannel(int channel) {
  for (auto& processor : processors_) {
    processor.ptr->SetPlayoutChannel(channel);
  }
}

double PostProcessingPipelineImpl::GetDelaySeconds() {
  return delay_s_;
}

}  // namespace media
}  // namespace chromecast