chromium/chromecast/media/cma/backend/volume_map.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/volume_map.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/values.h"
#include "chromecast/media/cma/backend/cast_audio_json.h"
#include "chromecast/public/volume_control.h"

namespace chromecast {
namespace media {

namespace {

constexpr char kKeyVolumeMap[] = "volume_map";
constexpr char kKeyLevel[] = "level";
constexpr char kKeyDb[] = "db";
constexpr float kMinDbFS = -120.0f;

VolumeMap& GetVolumeMap() {
  static base::NoDestructor<VolumeMap> volume_map;
  return *volume_map;
}

}  // namespace

VolumeMap::VolumeMap()
    : VolumeMap(std::make_unique<CastAudioJsonProviderImpl>()) {}

VolumeMap::VolumeMap(std::unique_ptr<CastAudioJsonProvider> config_provider)
    : config_provider_(std::move(config_provider)) {
  DCHECK(config_provider_);
  // base::Unretained is safe because VolumeMap outlives |config_provider_|.
  config_provider_->SetTuningChangedCallback(
      base::BindRepeating(&VolumeMap::LoadVolumeMap, base::Unretained(this)));
  LoadFromFile();
}

VolumeMap::~VolumeMap() = default;

void VolumeMap::LoadFromFile() {
  LoadVolumeMap(config_provider_->GetCastAudioConfig());
}

void VolumeMap::LoadVolumeMap(
    std::optional<base::Value::Dict> cast_audio_config) {
  if (!cast_audio_config) {
    LOG(WARNING) << "No cast audio config found; using default volume map.";
    UseDefaultVolumeMap();
    return;
  }

  const base::Value::List* volume_map_list =
      cast_audio_config->FindList(kKeyVolumeMap);
  if (!volume_map_list) {
    LOG(WARNING) << "No volume map found; using default volume map.";
    UseDefaultVolumeMap();
    return;
  }

  double prev_level = -1.0;
  std::vector<LevelToDb> new_map;

  for (const auto& value : *volume_map_list) {
    const base::Value::Dict& volume_map_entry = value.GetDict();

    std::optional<double> level = volume_map_entry.FindDouble(kKeyLevel);
    CHECK(level);
    CHECK_GE(*level, 0.0);
    CHECK_LE(*level, 1.0);
    CHECK_GT(*level, prev_level);
    prev_level = *level;

    std::optional<double> db = volume_map_entry.FindDouble(kKeyDb);
    CHECK(db);
    CHECK_LE(*db, 0.0);

    new_map.push_back({static_cast<float>(*level), static_cast<float>(*db)});
  }

  if (new_map.empty()) {
    LOG(FATAL) << "No entries in volume map.";
  }

  if (new_map[0].level > 0.0) {
    new_map.insert(new_map.begin(), {0.0, kMinDbFS});
  }

  if (new_map.rbegin()->level < 1.0) {
    new_map.push_back({1.0, 0.0});
  }
  base::AutoLock lock(lock_);
  volume_map_ = std::move(new_map);
}

float VolumeMap::VolumeToDbFS(float volume) {
  base::AutoLock lock(lock_);
  if (volume <= volume_map_[0].level) {
    return volume_map_[0].db;
  }
  for (size_t i = 1; i < volume_map_.size(); ++i) {
    if (volume < volume_map_[i].level) {
      const float x_range = volume_map_[i].level - volume_map_[i - 1].level;
      const float y_range = volume_map_[i].db - volume_map_[i - 1].db;
      const float x_pos = volume - volume_map_[i - 1].level;

      return volume_map_[i - 1].db + x_pos * y_range / x_range;
    }
  }
  return volume_map_[volume_map_.size() - 1].db;
}

float VolumeMap::DbFSToVolume(float db) {
  base::AutoLock lock(lock_);
  if (db <= volume_map_[0].db) {
    return volume_map_[0].level;
  }
  for (size_t i = 1; i < volume_map_.size(); ++i) {
    if (db < volume_map_[i].db) {
      const float x_range = volume_map_[i].db - volume_map_[i - 1].db;
      const float y_range = volume_map_[i].level - volume_map_[i - 1].level;
      const float x_pos = db - volume_map_[i - 1].db;

      return volume_map_[i - 1].level + x_pos * y_range / x_range;
    }
  }
  return volume_map_[volume_map_.size() - 1].level;
}

void VolumeMap::UseDefaultVolumeMap() {
  std::vector<LevelToDb> new_map = {{0.0f, kMinDbFS},
                                    {0.01f, -58.0f},
                                    {0.090909f, -48.0f},
                                    {0.818182f, -8.0f},
                                    {1.0f, 0.0f}};
  base::AutoLock lock(lock_);
  volume_map_ = std::move(new_map);
}

// static
float VolumeControl::VolumeToDbFS(float volume) {
  return GetVolumeMap().VolumeToDbFS(volume);
}

// static
float VolumeControl::DbFSToVolume(float db) {
  return GetVolumeMap().DbFSToVolume(db);
}

// static
void VolumeMap::Reload() {
  return GetVolumeMap().LoadFromFile();
}

}  // namespace media
}  // namespace chromecast