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 14namespace chromeos { 15 16namespace { 17 18const 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. 21const 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). 24const double kVolumeBias = 0.5; 25// If a connection is lost, we try again this many times 26const int kMaxReconnectTries = 4; 27// A flag to disable mixer. 28bool 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 35double 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_. 44void 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 58void 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 83bool AudioHandler::IsMute() { 84 if (!VerifyMixerConnection()) 85 return false; 86 87 return mixer_->IsMute(); 88} 89 90void 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 97void AudioHandler::Disconnect() { 98 mixer_.reset(); 99} 100 101void AudioHandler::Disable() { 102 g_disabled = true; 103} 104 105bool 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 126static 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 133void 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 153AudioHandler::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 165AudioHandler::~AudioHandler() { 166 Disconnect(); 167}; 168 169bool 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 215double 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 223double 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 229AudioHandler* AudioHandler::GetInstance() { 230 return Singleton<AudioHandler>::get(); 231} 232 233} // namespace chromeos 234