Index: chromecast/media/cma/backend/alsa/volume_control.cc |
diff --git a/chromecast/media/cma/backend/alsa/volume_control.cc b/chromecast/media/cma/backend/alsa/volume_control.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..090e13c051e35265452caa5d4dff19e83879af20 |
--- /dev/null |
+++ b/chromecast/media/cma/backend/alsa/volume_control.cc |
@@ -0,0 +1,294 @@ |
+// 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 <map> |
+#include <memory> |
+#include <string> |
+#include <utility> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/files/file_path.h" |
+#include "base/files/file_util.h" |
+#include "base/lazy_instance.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/synchronization/lock.h" |
+#include "base/threading/thread.h" |
+#include "base/values.h" |
+#include "chromecast/base/init_command_line_shlib.h" |
+#include "chromecast/base/serializers.h" |
+#include "chromecast/media/cma/backend/alsa/stream_mixer_alsa.h" |
+ |
+namespace chromecast { |
+namespace media { |
+ |
+namespace { |
+ |
+const float kDefaultMediaDbFS = -25.0f; |
+const float kDefaultAlarmDbFS = -20.0f; |
+const float kDefaultCommunicationDbFS = -25.0f; |
+ |
+const float kMinDbFS = -120.0f; |
+ |
+const char kKeyMediaDbFS[] = "dbfs.media"; |
+const char kKeyAlarmDbFS[] = "dbfs.alarm"; |
+const char kKeyCommunicationDbFS[] = "dbfs.communication"; |
+ |
+struct LevelToDb { |
+ float level; |
+ float db; |
+}; |
+ |
+const LevelToDb kVolumeMap[] = {{0.0f, kMinDbFS}, |
+ {0.01f, -58.0f}, |
+ {0.090909f, -48.0f}, |
+ {0.818182f, -8.0f}, |
+ {1.0f, 0.0f}}; |
+ |
+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: |
+ VolumeControlInternal() : thread_("VolumeControl") { |
+ 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); |
+ } |
+ } |
+ } |
+ |
+ for (auto type : types) { |
+ CHECK(stored_values_.GetDouble(ContentTypeToDbFSKey(type), &volume)); |
+ volumes_[type] = VolumeControl::DbFSToVolume(volume); |
+ StreamMixerAlsa::Get()->SetVolume(type, DbFsToScale(volume)); |
+ |
+ // Note that mute state is not persisted across reboots. |
+ muted_[type] = false; |
+ } |
+ |
+ thread_.Start(); |
+ } |
+ |
+ 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(AudioContentType type, float level) { |
+ level = std::max(0.0f, std::min(level, 1.0f)); |
+ thread_.task_runner()->PostTask( |
+ FROM_HERE, base::Bind(&VolumeControlInternal::SetVolumeOnThread, |
+ base::Unretained(this), type, level)); |
+ } |
+ |
+ bool IsMuted(AudioContentType type) { |
+ base::AutoLock lock(volume_lock_); |
+ return muted_[type]; |
+ } |
+ |
+ void SetMuted(AudioContentType type, bool muted) { |
+ thread_.task_runner()->PostTask( |
+ FROM_HERE, base::Bind(&VolumeControlInternal::SetMutedOnThread, |
+ base::Unretained(this), type, muted)); |
+ } |
+ |
+ void SetOutputLimit(AudioContentType type, float limit) { |
+ limit = std::max(0.0f, std::min(limit, 1.0f)); |
+ StreamMixerAlsa::Get()->SetOutputLimit( |
+ type, DbFsToScale(VolumeControl::VolumeToDbFS(limit))); |
+ } |
+ |
+ private: |
+ void SetVolumeOnThread(AudioContentType type, float level) { |
+ DCHECK(thread_.task_runner()->BelongsToCurrentThread()); |
+ { |
+ base::AutoLock lock(volume_lock_); |
+ if (level == volumes_[type]) { |
+ return; |
+ } |
+ volumes_[type] = level; |
+ } |
+ |
+ float dbfs = VolumeControl::VolumeToDbFS(level); |
+ StreamMixerAlsa::Get()->SetVolume(type, DbFsToScale(dbfs)); |
+ |
+ { |
+ base::AutoLock lock(observer_lock_); |
+ for (VolumeObserver* observer : volume_observers_) { |
+ observer->OnVolumeChange(type, level); |
+ } |
+ } |
+ |
+ stored_values_.SetDouble(ContentTypeToDbFSKey(type), dbfs); |
+ SerializeJsonToFile(storage_path_, stored_values_); |
+ } |
+ |
+ void SetMutedOnThread(AudioContentType type, bool muted) { |
+ { |
+ base::AutoLock lock(volume_lock_); |
+ if (muted == muted_[type]) { |
+ return; |
+ } |
+ muted_[type] = muted; |
+ } |
+ |
+ StreamMixerAlsa::Get()->SetMuted(type, muted); |
+ |
+ { |
+ base::AutoLock lock(observer_lock_); |
+ for (VolumeObserver* observer : volume_observers_) { |
+ observer->OnMuteChange(type, muted); |
+ } |
+ } |
+ } |
+ |
+ base::FilePath storage_path_; |
+ base::DictionaryValue stored_values_; |
+ |
+ base::Lock volume_lock_; |
+ std::map<AudioContentType, float> volumes_; |
+ std::map<AudioContentType, bool> muted_; |
+ |
+ base::Lock observer_lock_; |
+ std::vector<VolumeObserver*> volume_observers_; |
+ |
+ base::Thread thread_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(VolumeControlInternal); |
+}; |
+ |
+base::LazyInstance<VolumeControlInternal> g_volume_control = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+} // namespace |
+ |
+// static |
+void VolumeControl::Initialize(const std::vector<std::string>& argv) { |
+ chromecast::InitCommandLineShlib(argv); |
+} |
+ |
+// static |
+void VolumeControl::Finalize() { |
+ // Nothing to do. |
+} |
+ |
+// static |
+void VolumeControl::AddVolumeObserver(VolumeObserver* observer) { |
+ g_volume_control.Get().AddVolumeObserver(observer); |
+} |
+ |
+// static |
+void VolumeControl::RemoveVolumeObserver(VolumeObserver* observer) { |
+ g_volume_control.Get().RemoveVolumeObserver(observer); |
+} |
+ |
+// static |
+float VolumeControl::GetVolume(AudioContentType type) { |
+ return g_volume_control.Get().GetVolume(type); |
+} |
+ |
+// static |
+void VolumeControl::SetVolume(AudioContentType type, float level) { |
+ g_volume_control.Get().SetVolume(type, level); |
+} |
+ |
+// static |
+bool VolumeControl::IsMuted(AudioContentType type) { |
+ return g_volume_control.Get().IsMuted(type); |
+} |
+ |
+// static |
+void VolumeControl::SetMuted(AudioContentType type, bool muted) { |
+ g_volume_control.Get().SetMuted(type, muted); |
+} |
+ |
+// static |
+void VolumeControl::SetOutputLimit(AudioContentType type, float limit) { |
+ g_volume_control.Get().SetOutputLimit(type, limit); |
+} |
+ |
+// static |
+float VolumeControl::VolumeToDbFS(float volume) { |
+ if (volume <= kVolumeMap[0].level) { |
+ return kVolumeMap[0].db; |
+ } |
+ for (size_t i = 1; i < arraysize(kVolumeMap); ++i) { |
+ if (volume < kVolumeMap[i].level) { |
+ const float x_diff = kVolumeMap[i].level - kVolumeMap[i - 1].level; |
+ const float y_diff = kVolumeMap[i].db - kVolumeMap[i - 1].db; |
+ |
+ return kVolumeMap[i - 1].db + |
+ (volume - kVolumeMap[i - 1].level) * y_diff / x_diff; |
+ } |
+ } |
+ return kVolumeMap[arraysize(kVolumeMap) - 1].db; |
+} |
+ |
+// static |
+float VolumeControl::DbFSToVolume(float db) { |
+ if (db <= kVolumeMap[0].db) { |
+ return kVolumeMap[0].level; |
+ } |
+ for (size_t i = 1; i < arraysize(kVolumeMap); ++i) { |
+ if (db < kVolumeMap[i].db) { |
+ const float x_diff = kVolumeMap[i].db - kVolumeMap[i - 1].db; |
+ const float y_diff = kVolumeMap[i].level - kVolumeMap[i - 1].level; |
+ |
+ return kVolumeMap[i - 1].level + |
+ (db - kVolumeMap[i - 1].db) * y_diff / x_diff; |
+ } |
+ } |
+ return kVolumeMap[arraysize(kVolumeMap) - 1].level; |
+} |
+ |
+} // namespace media |
+} // namespace chromecast |