| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chromecast/public/volume_control.h" | 5 #include "chromecast/public/volume_control.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <cmath> | 8 #include <cmath> |
| 9 #include <map> | 9 #include <map> |
| 10 #include <memory> | 10 #include <memory> |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 #include "base/memory/ptr_util.h" | 23 #include "base/memory/ptr_util.h" |
| 24 #include "base/message_loop/message_loop.h" | 24 #include "base/message_loop/message_loop.h" |
| 25 #include "base/synchronization/lock.h" | 25 #include "base/synchronization/lock.h" |
| 26 #include "base/synchronization/waitable_event.h" | 26 #include "base/synchronization/waitable_event.h" |
| 27 #include "base/threading/thread.h" | 27 #include "base/threading/thread.h" |
| 28 #include "base/values.h" | 28 #include "base/values.h" |
| 29 #include "chromecast/base/init_command_line_shlib.h" | 29 #include "chromecast/base/init_command_line_shlib.h" |
| 30 #include "chromecast/base/serializers.h" | 30 #include "chromecast/base/serializers.h" |
| 31 #include "chromecast/media/cma/backend/alsa/alsa_features.h" | 31 #include "chromecast/media/cma/backend/alsa/alsa_features.h" |
| 32 #include "chromecast/media/cma/backend/alsa/alsa_volume_control.h" | 32 #include "chromecast/media/cma/backend/alsa/alsa_volume_control.h" |
| 33 #include "chromecast/media/cma/backend/alsa/cast_audio_json.h" |
| 33 #include "chromecast/media/cma/backend/alsa/post_processing_pipeline_parser.h" | 34 #include "chromecast/media/cma/backend/alsa/post_processing_pipeline_parser.h" |
| 34 #include "chromecast/media/cma/backend/alsa/stream_mixer_alsa.h" | 35 #include "chromecast/media/cma/backend/alsa/stream_mixer_alsa.h" |
| 36 #include "chromecast/media/cma/backend/alsa/volume_map.h" |
| 35 | 37 |
| 36 namespace chromecast { | 38 namespace chromecast { |
| 37 namespace media { | 39 namespace media { |
| 38 | 40 |
| 39 namespace { | 41 namespace { |
| 40 | 42 |
| 41 constexpr float kDefaultMediaDbFS = -25.0f; | 43 constexpr float kDefaultMediaDbFS = -25.0f; |
| 42 constexpr float kDefaultAlarmDbFS = -20.0f; | 44 constexpr float kDefaultAlarmDbFS = -20.0f; |
| 43 constexpr float kDefaultCommunicationDbFS = -25.0f; | 45 constexpr float kDefaultCommunicationDbFS = -25.0f; |
| 44 | 46 |
| 45 constexpr float kMinDbFS = -120.0f; | 47 constexpr float kMinDbFS = -120.0f; |
| 46 | 48 |
| 47 constexpr char kKeyMediaDbFS[] = "dbfs.media"; | 49 constexpr char kKeyMediaDbFS[] = "dbfs.media"; |
| 48 constexpr char kKeyAlarmDbFS[] = "dbfs.alarm"; | 50 constexpr char kKeyAlarmDbFS[] = "dbfs.alarm"; |
| 49 constexpr char kKeyCommunicationDbFS[] = "dbfs.communication"; | 51 constexpr char kKeyCommunicationDbFS[] = "dbfs.communication"; |
| 50 constexpr char kKeyVolumeMap[] = "volume_map"; | |
| 51 constexpr char kKeyLevel[] = "level"; | |
| 52 constexpr char kKeyDb[] = "db"; | |
| 53 constexpr char kKeyDefaultVolume[] = "default_volume"; | 52 constexpr char kKeyDefaultVolume[] = "default_volume"; |
| 54 | 53 |
| 55 struct LevelToDb { | |
| 56 float level; | |
| 57 float db; | |
| 58 }; | |
| 59 | |
| 60 const LevelToDb kDefaultVolumeMap[] = {{0.0f, kMinDbFS}, | |
| 61 {0.01f, -58.0f}, | |
| 62 {0.090909f, -48.0f}, | |
| 63 {0.818182f, -8.0f}, | |
| 64 {1.0f, 0.0f}}; | |
| 65 | |
| 66 float DbFsToScale(float db) { | 54 float DbFsToScale(float db) { |
| 67 if (db <= kMinDbFS) { | 55 if (db <= kMinDbFS) { |
| 68 return 0.0f; | 56 return 0.0f; |
| 69 } | 57 } |
| 70 return std::pow(10, db / 20); | 58 return std::pow(10, db / 20); |
| 71 } | 59 } |
| 72 | 60 |
| 73 std::string ContentTypeToDbFSKey(AudioContentType type) { | 61 std::string ContentTypeToDbFSKey(AudioContentType type) { |
| 74 switch (type) { | 62 switch (type) { |
| 75 case AudioContentType::kAlarm: | 63 case AudioContentType::kAlarm: |
| 76 return kKeyAlarmDbFS; | 64 return kKeyAlarmDbFS; |
| 77 case AudioContentType::kCommunication: | 65 case AudioContentType::kCommunication: |
| 78 return kKeyCommunicationDbFS; | 66 return kKeyCommunicationDbFS; |
| 79 default: | 67 default: |
| 80 return kKeyMediaDbFS; | 68 return kKeyMediaDbFS; |
| 81 } | 69 } |
| 82 } | 70 } |
| 83 | 71 |
| 84 class VolumeMap { | |
| 85 public: | |
| 86 VolumeMap() { | |
| 87 auto cast_audio_config = DeserializeJsonFromFile( | |
| 88 base::FilePath(PostProcessingPipelineParser::GetFilePath())); | |
| 89 const base::DictionaryValue* cast_audio_dict; | |
| 90 if (!cast_audio_config || | |
| 91 !cast_audio_config->GetAsDictionary(&cast_audio_dict)) { | |
| 92 LOG(WARNING) << "No cast audio config found; using default volume map."; | |
| 93 volume_map_.insert(volume_map_.end(), kDefaultVolumeMap, | |
| 94 kDefaultVolumeMap + arraysize(kDefaultVolumeMap)); | |
| 95 return; | |
| 96 } | |
| 97 | |
| 98 const base::ListValue* volume_map_list; | |
| 99 if (!cast_audio_dict->GetList(kKeyVolumeMap, &volume_map_list)) { | |
| 100 LOG(WARNING) << "No volume map found; using default volume map."; | |
| 101 volume_map_.insert(volume_map_.end(), kDefaultVolumeMap, | |
| 102 kDefaultVolumeMap + arraysize(kDefaultVolumeMap)); | |
| 103 return; | |
| 104 } | |
| 105 | |
| 106 double prev_level = -1.0; | |
| 107 for (size_t i = 0; i < volume_map_list->GetSize(); ++i) { | |
| 108 const base::DictionaryValue* volume_map_entry; | |
| 109 CHECK(volume_map_list->GetDictionary(i, &volume_map_entry)); | |
| 110 | |
| 111 double level; | |
| 112 CHECK(volume_map_entry->GetDouble(kKeyLevel, &level)); | |
| 113 CHECK_GE(level, 0.0); | |
| 114 CHECK_LE(level, 1.0); | |
| 115 CHECK_GT(level, prev_level); | |
| 116 prev_level = level; | |
| 117 | |
| 118 double db; | |
| 119 CHECK(volume_map_entry->GetDouble(kKeyDb, &db)); | |
| 120 CHECK_LE(db, 0.0); | |
| 121 if (level == 1.0) { | |
| 122 CHECK_EQ(db, 0.0); | |
| 123 } | |
| 124 | |
| 125 volume_map_.push_back({level, db}); | |
| 126 } | |
| 127 | |
| 128 if (volume_map_.empty()) { | |
| 129 LOG(FATAL) << "No entries in volume map."; | |
| 130 return; | |
| 131 } | |
| 132 | |
| 133 if (volume_map_[0].level > 0.0) { | |
| 134 volume_map_.insert(volume_map_.begin(), {0.0, kMinDbFS}); | |
| 135 } | |
| 136 | |
| 137 if (volume_map_.rbegin()->level < 1.0) { | |
| 138 volume_map_.push_back({1.0, 0.0}); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 float VolumeToDbFS(float volume) { | |
| 143 if (volume <= volume_map_[0].level) { | |
| 144 return volume_map_[0].db; | |
| 145 } | |
| 146 for (size_t i = 1; i < volume_map_.size(); ++i) { | |
| 147 if (volume < volume_map_[i].level) { | |
| 148 const float x_range = volume_map_[i].level - volume_map_[i - 1].level; | |
| 149 const float y_range = volume_map_[i].db - volume_map_[i - 1].db; | |
| 150 const float x_pos = volume - volume_map_[i - 1].level; | |
| 151 | |
| 152 return volume_map_[i - 1].db + x_pos * y_range / x_range; | |
| 153 } | |
| 154 } | |
| 155 return volume_map_[volume_map_.size() - 1].db; | |
| 156 } | |
| 157 | |
| 158 // static | |
| 159 float DbFSToVolume(float db) { | |
| 160 if (db <= volume_map_[0].db) { | |
| 161 return volume_map_[0].level; | |
| 162 } | |
| 163 for (size_t i = 1; i < volume_map_.size(); ++i) { | |
| 164 if (db < volume_map_[i].db) { | |
| 165 const float x_range = volume_map_[i].db - volume_map_[i - 1].db; | |
| 166 const float y_range = volume_map_[i].level - volume_map_[i - 1].level; | |
| 167 const float x_pos = db - volume_map_[i - 1].db; | |
| 168 | |
| 169 return volume_map_[i - 1].level + x_pos * y_range / x_range; | |
| 170 } | |
| 171 } | |
| 172 return volume_map_[volume_map_.size() - 1].level; | |
| 173 } | |
| 174 | |
| 175 private: | |
| 176 std::vector<LevelToDb> volume_map_; | |
| 177 | |
| 178 DISALLOW_COPY_AND_ASSIGN(VolumeMap); | |
| 179 }; | |
| 180 | |
| 181 base::LazyInstance<VolumeMap>::Leaky g_volume_map = LAZY_INSTANCE_INITIALIZER; | 72 base::LazyInstance<VolumeMap>::Leaky g_volume_map = LAZY_INSTANCE_INITIALIZER; |
| 182 | 73 |
| 183 class VolumeControlInternal : public AlsaVolumeControl::Delegate { | 74 class VolumeControlInternal : public AlsaVolumeControl::Delegate { |
| 184 public: | 75 public: |
| 185 VolumeControlInternal() | 76 VolumeControlInternal() |
| 186 : thread_("VolumeControl"), | 77 : thread_("VolumeControl"), |
| 187 initialize_complete_event_( | 78 initialize_complete_event_( |
| 188 base::WaitableEvent::ResetPolicy::MANUAL, | 79 base::WaitableEvent::ResetPolicy::MANUAL, |
| 189 base::WaitableEvent::InitialState::NOT_SIGNALED) { | 80 base::WaitableEvent::InitialState::NOT_SIGNALED) { |
| 190 // Load volume map to check that the config file is correct. | 81 // Load volume map to check that the config file is correct. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 202 auto old_stored_data = DeserializeJsonFromFile(storage_path_); | 93 auto old_stored_data = DeserializeJsonFromFile(storage_path_); |
| 203 base::DictionaryValue* old_stored_dict; | 94 base::DictionaryValue* old_stored_dict; |
| 204 if (old_stored_data && old_stored_data->GetAsDictionary(&old_stored_dict)) { | 95 if (old_stored_data && old_stored_data->GetAsDictionary(&old_stored_dict)) { |
| 205 for (auto type : types) { | 96 for (auto type : types) { |
| 206 if (old_stored_dict->GetDouble(ContentTypeToDbFSKey(type), &volume)) { | 97 if (old_stored_dict->GetDouble(ContentTypeToDbFSKey(type), &volume)) { |
| 207 stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume); | 98 stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume); |
| 208 } | 99 } |
| 209 } | 100 } |
| 210 } else { | 101 } else { |
| 211 // If saved_volumes does not exist, use per device default if it exists. | 102 // If saved_volumes does not exist, use per device default if it exists. |
| 212 auto cast_audio_config = DeserializeJsonFromFile( | 103 auto cast_audio_config = |
| 213 base::FilePath(PostProcessingPipelineParser::GetFilePath())); | 104 DeserializeJsonFromFile(base::FilePath(kCastAudioJsonFilePath)); |
| 214 const base::DictionaryValue* cast_audio_dict; | 105 const base::DictionaryValue* cast_audio_dict; |
| 215 if (cast_audio_config && | 106 if (cast_audio_config && |
| 216 cast_audio_config->GetAsDictionary(&cast_audio_dict)) { | 107 cast_audio_config->GetAsDictionary(&cast_audio_dict)) { |
| 217 const base::DictionaryValue* default_volume_dict; | 108 const base::DictionaryValue* default_volume_dict; |
| 218 if (cast_audio_dict && cast_audio_dict->GetDictionary( | 109 if (cast_audio_dict && cast_audio_dict->GetDictionary( |
| 219 kKeyDefaultVolume, &default_volume_dict)) { | 110 kKeyDefaultVolume, &default_volume_dict)) { |
| 220 for (auto type : types) { | 111 for (auto type : types) { |
| 221 if (default_volume_dict->GetDouble(ContentTypeToDbFSKey(type), | 112 if (default_volume_dict->GetDouble(ContentTypeToDbFSKey(type), |
| 222 &volume)) { | 113 &volume)) { |
| 223 stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume); | 114 stored_values_.SetDouble(ContentTypeToDbFSKey(type), volume); |
| (...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 466 return g_volume_map.Get().VolumeToDbFS(volume); | 357 return g_volume_map.Get().VolumeToDbFS(volume); |
| 467 } | 358 } |
| 468 | 359 |
| 469 // static | 360 // static |
| 470 float VolumeControl::DbFSToVolume(float db) { | 361 float VolumeControl::DbFSToVolume(float db) { |
| 471 return g_volume_map.Get().DbFSToVolume(db); | 362 return g_volume_map.Get().DbFSToVolume(db); |
| 472 } | 363 } |
| 473 | 364 |
| 474 } // namespace media | 365 } // namespace media |
| 475 } // namespace chromecast | 366 } // namespace chromecast |
| OLD | NEW |