blob: d318984629836ec8b0a50399eac799c71413f336 [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/public/volume_control.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_pump_type.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "chromecast/base/serializers.h"
#include "chromecast/media/cma/backend/audio_buildflags.h"
#include "chromecast/media/cma/backend/cast_audio_json.h"
#include "chromecast/media/cma/backend/mixer/stream_mixer.h"
#include "chromecast/media/cma/backend/system_volume_control.h"
#include "chromecast/media/cma/backend/volume_map.h"
namespace chromecast {
namespace media {
namespace {
constexpr float kDefaultMediaDbFS = -25.0f;
constexpr float kDefaultAlarmDbFS = -20.0f;
constexpr float kDefaultCommunicationDbFS = -25.0f;
constexpr float kMinDbFS = -120.0f;
constexpr char kKeyMediaDbFS[] = "dbfs.media";
constexpr char kKeyAlarmDbFS[] = "dbfs.alarm";
constexpr char kKeyCommunicationDbFS[] = "dbfs.communication";
constexpr char kKeyDefaultVolume[] = "default_volume";
float DbFsToScale(float db) {
if (db <= kMinDbFS) {
return 0.0f;
}
return std::pow(10, db / 20);
}
std::string ContentTypeToDbFSKey(AudioContentType type) {
switch (type) {
case AudioContentType::kAlarm:
return kKeyAlarmDbFS;
case AudioContentType::kCommunication:
return kKeyCommunicationDbFS;
default:
return kKeyMediaDbFS;
}
}
class VolumeControlInternal : public SystemVolumeControl::Delegate {
public:
VolumeControlInternal()
: thread_("VolumeControl"),
initialize_complete_event_(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {
// Load volume map to check that the config file is correct.
VolumeControl::VolumeToDbFS(0.0f);
stored_values_.SetDouble(kKeyMediaDbFS, kDefaultMediaDbFS);
stored_values_.SetDouble(kKeyAlarmDbFS, kDefaultAlarmDbFS);
stored_values_.SetDouble(kKeyCommunicationDbFS, kDefaultCommunicationDbFS);
auto types = {AudioContentType::kMedia, AudioContentType::kAlarm,
AudioContentType::kCommunication};
double volume;
storage_path_ = base::GetHomeDir().Append("saved_volumes");
auto old_stored_data = DeserializeJsonFromFile(storage_path_);
base::DictionaryValue* old_stored_dict;
if (old_stored_data && old_stored_data->GetAsDictionary(&old_stored_dict)) {
for (auto type : types) {
if (old_stored_dict->GetDouble(ContentTypeToDbFSKey(type), &volume)) {
stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume);
}
}
} else {
// If saved_volumes does not exist, use per device default if it exists.
auto cast_audio_config =
DeserializeJsonFromFile(CastAudioJson::GetFilePath());
const base::DictionaryValue* cast_audio_dict;
if (cast_audio_config &&
cast_audio_config->GetAsDictionary(&cast_audio_dict)) {
const base::DictionaryValue* default_volume_dict;
if (cast_audio_dict && cast_audio_dict->GetDictionary(
kKeyDefaultVolume, &default_volume_dict)) {
for (auto type : types) {
if (default_volume_dict->GetDouble(ContentTypeToDbFSKey(type),
&volume)) {
stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume);
LOG(INFO) << "Setting default volume for "
<< ContentTypeToDbFSKey(type) << " to " << volume;
}
}
}
}
}
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
thread_.StartWithOptions(options);
thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VolumeControlInternal::InitializeOnThread,
base::Unretained(this)));
initialize_complete_event_.Wait();
}
~VolumeControlInternal() override = default;
void AddVolumeObserver(VolumeObserver* observer) {
base::AutoLock lock(observer_lock_);
volume_observers_.push_back(observer);
}
void RemoveVolumeObserver(VolumeObserver* observer) {
base::AutoLock lock(observer_lock_);
volume_observers_.erase(std::remove(volume_observers_.begin(),
volume_observers_.end(), observer),
volume_observers_.end());
}
float GetVolume(AudioContentType type) {
base::AutoLock lock(volume_lock_);
return volumes_[type];
}
void SetVolume(VolumeChangeSource source,
AudioContentType type,
float level) {
if (type == AudioContentType::kOther) {
NOTREACHED() << "Can't set volume for content type kOther";
return;
}
level = base::ClampToRange(level, 0.0f, 1.0f);
thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VolumeControlInternal::SetVolumeOnThread,
base::Unretained(this), source, type, level,
false /* from_system */));
}
void SetVolumeMultiplier(AudioContentType type, float multiplier) {
if (type == AudioContentType::kOther) {
NOTREACHED() << "Can't set volume multiplier for content type kOther";
return;
}
if (BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
LOG(INFO) << "Ignore global volume multiplier since volume is externally "
<< "controlled";
return;
}
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VolumeControlInternal::SetVolumeMultiplierOnThread,
base::Unretained(this), type, multiplier));
}
bool IsMuted(AudioContentType type) {
base::AutoLock lock(volume_lock_);
return muted_[type];
}
void SetMuted(VolumeChangeSource source, AudioContentType type, bool muted) {
if (type == AudioContentType::kOther) {
NOTREACHED() << "Can't set mute state for content type kOther";
return;
}
thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VolumeControlInternal::SetMutedOnThread,
base::Unretained(this), source, type, muted,
false /* from_system */));
}
void SetOutputLimit(AudioContentType type, float limit) {
if (type == AudioContentType::kOther) {
NOTREACHED() << "Can't set output limit for content type kOther";
return;
}
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VolumeControlInternal::SetOutputLimitOnThread,
base::Unretained(this), type, limit));
}
void SetPowerSaveMode(bool power_save_on) {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VolumeControlInternal::SetPowerSaveModeOnThread,
base::Unretained(this), power_save_on));
}
private:
void InitializeOnThread() {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
system_volume_control_ = SystemVolumeControl::Create(this);
double dbfs;
for (auto type : {AudioContentType::kMedia, AudioContentType::kAlarm,
AudioContentType::kCommunication}) {
CHECK(stored_values_.GetDouble(ContentTypeToDbFSKey(type), &dbfs));
volumes_[type] = VolumeControl::DbFSToVolume(dbfs);
volume_multipliers_[type] = 1.0f;
if (BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
// If ALSA owns volume, our internal mixer should not apply any scaling
// multiplier.
StreamMixer::Get()->SetVolume(type, 1.0f);
} else {
StreamMixer::Get()->SetVolume(type, DbFsToScale(dbfs));
}
// Note that mute state is not persisted across reboots.
muted_[type] = false;
}
if (BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
// If ALSA owns the volume, then read the current volume and mute state
// from the ALSA mixer element(s).
volumes_[AudioContentType::kMedia] = system_volume_control_->GetVolume();
muted_[AudioContentType::kMedia] = system_volume_control_->IsMuted();
} else {
// Otherwise, make sure the ALSA mixer element correctly reflects the
// current volume state.
system_volume_control_->SetVolume(volumes_[AudioContentType::kMedia]);
system_volume_control_->SetMuted(false);
}
volumes_[AudioContentType::kOther] = 1.0;
muted_[AudioContentType::kOther] = false;
initialize_complete_event_.Signal();
}
void SetVolumeOnThread(VolumeChangeSource source,
AudioContentType type,
float level,
bool from_system) {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
DCHECK(type != AudioContentType::kOther);
DCHECK(!from_system || type == AudioContentType::kMedia);
DCHECK(volume_multipliers_.find(type) != volume_multipliers_.end());
{
base::AutoLock lock(volume_lock_);
if (from_system && system_volume_control_->GetRoundtripVolume(
volumes_[AudioContentType::kMedia]) == level) {
return;
}
if (level == volumes_[type]) {
return;
}
volumes_[type] = level;
}
float dbfs = VolumeControl::VolumeToDbFS(level);
if (!BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
StreamMixer::Get()->SetVolume(
type, DbFsToScale(dbfs) * volume_multipliers_[type]);
}
if (!from_system && type == AudioContentType::kMedia) {
system_volume_control_->SetVolume(level);
}
{
base::AutoLock lock(observer_lock_);
for (VolumeObserver* observer : volume_observers_) {
observer->OnVolumeChange(source, type, level);
}
}
stored_values_.SetDouble(ContentTypeToDbFSKey(type), dbfs);
SerializeJsonToFile(storage_path_, stored_values_);
}
void SetVolumeMultiplierOnThread(AudioContentType type, float multiplier) {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
DCHECK(type != AudioContentType::kOther);
DCHECK(!BUILDFLAG(SYSTEM_OWNS_VOLUME));
volume_multipliers_[type] = multiplier;
float scale =
DbFsToScale(VolumeControl::VolumeToDbFS(volumes_[type])) * multiplier;
StreamMixer::Get()->SetVolume(type, scale);
}
void SetMutedOnThread(VolumeChangeSource source,
AudioContentType type,
bool muted,
bool from_system) {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
DCHECK(type != AudioContentType::kOther);
{
base::AutoLock lock(volume_lock_);
if (muted == muted_[type]) {
return;
}
muted_[type] = muted;
}
if (!BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
StreamMixer::Get()->SetMuted(type, muted);
}
if (!from_system && type == AudioContentType::kMedia) {
system_volume_control_->SetMuted(muted);
}
{
base::AutoLock lock(observer_lock_);
for (VolumeObserver* observer : volume_observers_) {
observer->OnMuteChange(source, type, muted);
}
}
}
void SetOutputLimitOnThread(AudioContentType type, float limit) {
if (type == AudioContentType::kOther) {
NOTREACHED() << "Can't set output limit for content type kOther";
return;
}
if (BUILDFLAG(SYSTEM_OWNS_VOLUME)) {
return;
}
limit = base::ClampToRange(limit, 0.0f, 1.0f);
StreamMixer::Get()->SetOutputLimit(
type, DbFsToScale(VolumeControl::VolumeToDbFS(limit)));
if (type == AudioContentType::kMedia) {
system_volume_control_->SetLimit(limit);
}
}
void SetPowerSaveModeOnThread(bool power_save_on) {
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
system_volume_control_->SetPowerSave(power_save_on);
}
// SystemVolumeControl::Delegate implementation:
void OnSystemVolumeOrMuteChange(float new_volume, bool new_mute) override {
LOG(INFO) << "System volume/mute change, new volume = " << new_volume
<< ", new mute = " << new_mute;
DCHECK(thread_.task_runner()->BelongsToCurrentThread());
SetVolumeOnThread(VolumeChangeSource::kUser, AudioContentType::kMedia,
new_volume, true /* from_system */);
SetMutedOnThread(VolumeChangeSource::kUser, AudioContentType::kMedia,
new_mute, true /* from_system */);
}
base::FilePath storage_path_;
base::DictionaryValue stored_values_;
base::Lock volume_lock_;
base::flat_map<AudioContentType, float> volumes_;
base::flat_map<AudioContentType, float> volume_multipliers_;
base::flat_map<AudioContentType, bool> muted_;
base::Lock observer_lock_;
std::vector<VolumeObserver*> volume_observers_;
base::Thread thread_;
base::WaitableEvent initialize_complete_event_;
std::unique_ptr<SystemVolumeControl> system_volume_control_;
DISALLOW_COPY_AND_ASSIGN(VolumeControlInternal);
};
VolumeControlInternal& GetVolumeControl() {
static base::NoDestructor<VolumeControlInternal> g_volume_control;
return *g_volume_control;
}
} // namespace
// static
void VolumeControl::Initialize(const std::vector<std::string>& argv) {
GetVolumeControl();
}
// static
void VolumeControl::Finalize() {
// Nothing to do.
}
// static
void VolumeControl::AddVolumeObserver(VolumeObserver* observer) {
GetVolumeControl().AddVolumeObserver(observer);
}
// static
void VolumeControl::RemoveVolumeObserver(VolumeObserver* observer) {
GetVolumeControl().RemoveVolumeObserver(observer);
}
// static
float VolumeControl::GetVolume(AudioContentType type) {
return GetVolumeControl().GetVolume(type);
}
// static
void VolumeControl::SetVolume(VolumeChangeSource source,
AudioContentType type,
float level) {
GetVolumeControl().SetVolume(source, type, level);
}
// static
void VolumeControl::SetVolumeMultiplier(AudioContentType type,
float multiplier) {
GetVolumeControl().SetVolumeMultiplier(type, multiplier);
}
// static
bool VolumeControl::IsMuted(AudioContentType type) {
return GetVolumeControl().IsMuted(type);
}
// static
void VolumeControl::SetMuted(VolumeChangeSource source,
AudioContentType type,
bool muted) {
GetVolumeControl().SetMuted(source, type, muted);
}
// static
void VolumeControl::SetOutputLimit(AudioContentType type, float limit) {
GetVolumeControl().SetOutputLimit(type, limit);
}
// static
void VolumeControl::SetPowerSaveMode(bool power_save_on) {
GetVolumeControl().SetPowerSaveMode(power_save_on);
}
} // namespace media
} // namespace chromecast