1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/audio_handler.h" 6 7 #include <math.h> 8 9 #include "base/logging.h" 10 #include "base/memory/singleton.h" 11 #include "chrome/browser/chromeos/audio_mixer_alsa.h" 12 #include "content/browser/browser_thread.h" 13 14 namespace chromeos { 15 16 namespace { 17 18 const double kMinVolumeDb = -90.0; 19 // Choosing 6.0dB here instead of 0dB to give user chance to amplify audio some 20 // in case sounds or their setup is too quiet for them. 21 const double kMaxVolumeDb = 6.0; 22 // A value of less than one adjusts quieter volumes in larger steps (giving 23 // finer resolution in the higher volumes). 24 const double kVolumeBias = 0.5; 25 // If a connection is lost, we try again this many times 26 const int kMaxReconnectTries = 4; 27 // A flag to disable mixer. 28 bool g_disabled = false; 29 30 } // namespace 31 32 // chromeos: This class will set the volume using ALSA to adjust volume and 33 // mute, and handle the volume level logic. 34 35 double AudioHandler::GetVolumePercent() { 36 if (!VerifyMixerConnection()) 37 return 0; 38 39 return VolumeDbToPercent(mixer_->GetVolumeDb()); 40 } 41 42 // Set volume using our internal 0-100% range. Notice 0% is a special case of 43 // silence, so we set the mixer volume to kSilenceDb instead of min_volume_db_. 44 void AudioHandler::SetVolumePercent(double volume_percent) { 45 if (!VerifyMixerConnection()) 46 return; 47 DCHECK(volume_percent >= 0.0); 48 49 double vol_db; 50 if (volume_percent <= 0) 51 vol_db = AudioMixer::kSilenceDb; 52 else 53 vol_db = PercentToVolumeDb(volume_percent); 54 55 mixer_->SetVolumeDb(vol_db); 56 } 57 58 void AudioHandler::AdjustVolumeByPercent(double adjust_by_percent) { 59 if (!VerifyMixerConnection()) 60 return; 61 62 DVLOG(1) << "Adjusting Volume by " << adjust_by_percent << " percent"; 63 64 double volume = mixer_->GetVolumeDb(); 65 double pct = VolumeDbToPercent(volume); 66 67 if (pct < 0) 68 pct = 0; 69 pct = pct + adjust_by_percent; 70 if (pct > 100.0) 71 pct = 100.0; 72 73 double new_volume; 74 if (pct <= 0.1) 75 new_volume = AudioMixer::kSilenceDb; 76 else 77 new_volume = PercentToVolumeDb(pct); 78 79 if (new_volume != volume) 80 mixer_->SetVolumeDb(new_volume); 81 } 82 83 bool AudioHandler::IsMute() { 84 if (!VerifyMixerConnection()) 85 return false; 86 87 return mixer_->IsMute(); 88 } 89 90 void AudioHandler::SetMute(bool do_mute) { 91 if (!VerifyMixerConnection()) 92 return; 93 DVLOG(1) << "Setting Mute to " << do_mute; 94 mixer_->SetMute(do_mute); 95 } 96 97 void AudioHandler::Disconnect() { 98 mixer_.reset(); 99 } 100 101 void AudioHandler::Disable() { 102 g_disabled = true; 103 } 104 105 bool AudioHandler::TryToConnect(bool async) { 106 if (mixer_type_ == MIXER_TYPE_ALSA) { 107 VLOG(1) << "Trying to connect to ALSA"; 108 mixer_.reset(new AudioMixerAlsa()); 109 } else { 110 VLOG(1) << "Cannot find valid volume mixer"; 111 mixer_.reset(); 112 return false; 113 } 114 115 if (async) { 116 mixer_->Init(NewCallback(this, &AudioHandler::OnMixerInitialized)); 117 } else { 118 if (!mixer_->InitSync()) { 119 VLOG(1) << "Unable to reconnect to Mixer"; 120 return false; 121 } 122 } 123 return true; 124 } 125 126 static void ClipVolume(double* min_volume, double* max_volume) { 127 if (*min_volume < kMinVolumeDb) 128 *min_volume = kMinVolumeDb; 129 if (*max_volume > kMaxVolumeDb) 130 *max_volume = kMaxVolumeDb; 131 } 132 133 void AudioHandler::OnMixerInitialized(bool success) { 134 connected_ = success; 135 DVLOG(1) << "OnMixerInitialized, success = " << success; 136 137 if (connected_) { 138 if (mixer_->GetVolumeLimits(&min_volume_db_, &max_volume_db_)) { 139 ClipVolume(&min_volume_db_, &max_volume_db_); 140 } 141 return; 142 } 143 144 VLOG(1) << "Unable to connect to mixer"; 145 mixer_type_ = MIXER_TYPE_NONE; 146 147 // This frees the mixer on the UI thread 148 BrowserThread::PostTask( 149 BrowserThread::UI, FROM_HERE, 150 NewRunnableMethod(this, &AudioHandler::TryToConnect, true)); 151 } 152 153 AudioHandler::AudioHandler() 154 : connected_(false), 155 reconnect_tries_(0), 156 max_volume_db_(kMaxVolumeDb), 157 min_volume_db_(kMinVolumeDb), 158 mixer_type_(g_disabled ? MIXER_TYPE_NONE : MIXER_TYPE_ALSA) { 159 // Start trying to connect to mixers asynchronously, starting with the current 160 // mixer_type_. If the connection fails, another TryToConnect() for the next 161 // mixer will be posted at that time. 162 TryToConnect(true); 163 } 164 165 AudioHandler::~AudioHandler() { 166 Disconnect(); 167 }; 168 169 bool AudioHandler::VerifyMixerConnection() { 170 if (mixer_ == NULL) 171 return false; 172 173 AudioMixer::State mixer_state = mixer_->GetState(); 174 if (mixer_state == AudioMixer::READY) 175 return true; 176 if (connected_) { 177 // Something happened and the mixer is no longer valid after having been 178 // initialized earlier. 179 connected_ = false; 180 LOG(ERROR) << "Lost connection to mixer"; 181 } else { 182 LOG(ERROR) << "Mixer not valid"; 183 } 184 185 if ((mixer_state == AudioMixer::INITIALIZING) || 186 (mixer_state == AudioMixer::SHUTTING_DOWN)) 187 return false; 188 189 if (reconnect_tries_ < kMaxReconnectTries) { 190 reconnect_tries_++; 191 VLOG(1) << "Re-connecting to mixer attempt " << reconnect_tries_ << "/" 192 << kMaxReconnectTries; 193 194 connected_ = TryToConnect(false); 195 196 if (connected_) { 197 reconnect_tries_ = 0; 198 return true; 199 } 200 LOG(ERROR) << "Unable to re-connect to mixer"; 201 } 202 return false; 203 } 204 205 // VolumeDbToPercent() and PercentToVolumeDb() conversion functions allow us 206 // complete control over how the 0 to 100% range is mapped to actual loudness. 207 // Volume range is from min_volume_db_ at just above 0% to max_volume_db_ 208 // at 100% with a special case at 0% which maps to kSilenceDb. 209 // 210 // The mapping is confined to these two functions to make it easy to adjust and 211 // have everything else just work. The range is biased to give finer resolution 212 // in the higher volumes if kVolumeBias is less than 1.0. 213 214 // static 215 double AudioHandler::VolumeDbToPercent(double volume_db) const { 216 if (volume_db < min_volume_db_) 217 return 0; 218 return 100.0 * pow((volume_db - min_volume_db_) / 219 (max_volume_db_ - min_volume_db_), 1/kVolumeBias); 220 } 221 222 // static 223 double AudioHandler::PercentToVolumeDb(double volume_percent) const { 224 return pow(volume_percent / 100.0, kVolumeBias) * 225 (max_volume_db_ - min_volume_db_) + min_volume_db_; 226 } 227 228 // static 229 AudioHandler* AudioHandler::GetInstance() { 230 return Singleton<AudioHandler>::get(); 231 } 232 233 } // namespace chromeos 234