OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 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_mixer_alsa.h" |
| 6 |
| 7 #include <alsa/asoundlib.h> |
| 8 |
| 9 #include "base/logging.h" |
| 10 #include "base/task.h" |
| 11 |
| 12 namespace chromeos { |
| 13 |
| 14 // Connect to the ALSA mixer using their simple element API. Init is performed |
| 15 // asynchronously on the worker thread. |
| 16 // |
| 17 // To get a wider range and finer control over volume levels, first the Master |
| 18 // level is set, then if the PCM element exists, the total level is refined by |
| 19 // adjusting that as well. If the PCM element has more volume steps, it allows |
| 20 // for finer granularity in the total volume. |
| 21 |
| 22 // TODO(davej): Serialize volume/mute to preserve settings when restarting. |
| 23 |
| 24 typedef long alsa_long_t; // 'long' is required for ALSA API calls. |
| 25 |
| 26 namespace { |
| 27 |
| 28 const char* kMasterVolume = "Master"; |
| 29 const char* kPCMVolume = "PCM"; |
| 30 const double kDefaultMinVolume = -90.0; |
| 31 const double kDefaultMaxVolume = 0.0; |
| 32 |
| 33 } // namespace |
| 34 |
| 35 AudioMixerAlsa::AudioMixerAlsa() |
| 36 : min_volume_(kDefaultMinVolume), |
| 37 max_volume_(kDefaultMaxVolume), |
| 38 save_volume_(0), |
| 39 mixer_state_(UNINITIALIZED), |
| 40 alsa_mixer_(NULL), |
| 41 elem_master_(NULL), |
| 42 elem_pcm_(NULL) { |
| 43 } |
| 44 |
| 45 AudioMixerAlsa::~AudioMixerAlsa() { |
| 46 FreeAlsaMixer(); |
| 47 if (thread_ != NULL) { |
| 48 thread_->Stop(); |
| 49 thread_.reset(); |
| 50 } |
| 51 } |
| 52 |
| 53 void AudioMixerAlsa::Init(InitDoneCallback* callback) { |
| 54 DCHECK(callback); |
| 55 if (!InitThread()) { |
| 56 callback->Run(false); |
| 57 delete callback; |
| 58 return; |
| 59 } |
| 60 |
| 61 // Post the task of starting up, which can block for 200-500ms, |
| 62 // so best not to do it on the caller's thread. |
| 63 thread_->message_loop()->PostTask(FROM_HERE, |
| 64 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); |
| 65 } |
| 66 |
| 67 bool AudioMixerAlsa::InitSync() { |
| 68 if (!InitThread()) |
| 69 return false; |
| 70 return InitializeAlsaMixer(); |
| 71 } |
| 72 |
| 73 double AudioMixerAlsa::GetVolumeDb() const { |
| 74 AutoLock lock(mixer_state_lock_); |
| 75 if (mixer_state_ != READY) |
| 76 return kSilenceDb; |
| 77 |
| 78 return DoGetVolumeDb_Locked(); |
| 79 } |
| 80 |
| 81 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { |
| 82 AutoLock lock(mixer_state_lock_); |
| 83 if (mixer_state_ != READY) |
| 84 return false; |
| 85 if (vol_min) |
| 86 *vol_min = min_volume_; |
| 87 if (vol_max) |
| 88 *vol_max = max_volume_; |
| 89 return true; |
| 90 } |
| 91 |
| 92 void AudioMixerAlsa::SetVolumeDb(double vol_db) { |
| 93 AutoLock lock(mixer_state_lock_); |
| 94 if (mixer_state_ != READY) |
| 95 return; |
| 96 DoSetVolumeDb_Locked(vol_db); |
| 97 } |
| 98 |
| 99 bool AudioMixerAlsa::IsMute() const { |
| 100 AutoLock lock(mixer_state_lock_); |
| 101 if (mixer_state_ != READY) |
| 102 return false; |
| 103 return GetElementMuted_Locked(elem_master_); |
| 104 } |
| 105 |
| 106 void AudioMixerAlsa::SetMute(bool mute) { |
| 107 AutoLock lock(mixer_state_lock_); |
| 108 if (mixer_state_ != READY) |
| 109 return; |
| 110 |
| 111 // Set volume to minimum on mute, since switching the element off does not |
| 112 // always mute as it should. |
| 113 |
| 114 // TODO(davej): Setting volume to minimum can be removed once switching the |
| 115 // element off can be guaranteed to work. |
| 116 |
| 117 bool old_value = GetElementMuted_Locked(elem_master_); |
| 118 |
| 119 if (old_value != mute) { |
| 120 if (mute) { |
| 121 save_volume_ = DoGetVolumeDb_Locked(); |
| 122 DoSetVolumeDb_Locked(min_volume_); |
| 123 } else { |
| 124 DoSetVolumeDb_Locked(save_volume_); |
| 125 } |
| 126 } |
| 127 |
| 128 SetElementMuted_Locked(elem_master_, mute); |
| 129 if (elem_pcm_) |
| 130 SetElementMuted_Locked(elem_pcm_, mute); |
| 131 } |
| 132 |
| 133 AudioMixer::State AudioMixerAlsa::GetState() const { |
| 134 AutoLock lock(mixer_state_lock_); |
| 135 // If we think it's ready, verify it is actually so. |
| 136 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) |
| 137 mixer_state_ = IN_ERROR; |
| 138 return mixer_state_; |
| 139 } |
| 140 |
| 141 //////////////////////////////////////////////////////////////////////////////// |
| 142 // Private functions follow |
| 143 |
| 144 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { |
| 145 bool success = InitializeAlsaMixer(); |
| 146 |
| 147 if (callback) { |
| 148 callback->Run(success); |
| 149 delete callback; |
| 150 } |
| 151 } |
| 152 |
| 153 bool AudioMixerAlsa::InitThread() { |
| 154 AutoLock lock(mixer_state_lock_); |
| 155 |
| 156 if (mixer_state_ != UNINITIALIZED) |
| 157 return false; |
| 158 |
| 159 if (thread_ == NULL) { |
| 160 thread_.reset(new base::Thread("AudioMixerAlsa")); |
| 161 if (!thread_->Start()) { |
| 162 thread_.reset(); |
| 163 return false; |
| 164 } |
| 165 } |
| 166 |
| 167 mixer_state_ = INITIALIZING; |
| 168 return true; |
| 169 } |
| 170 |
| 171 bool AudioMixerAlsa::InitializeAlsaMixer() { |
| 172 AutoLock lock(mixer_state_lock_); |
| 173 if (mixer_state_ != INITIALIZING) |
| 174 return false; |
| 175 |
| 176 int err; |
| 177 snd_mixer_t* handle = NULL; |
| 178 const char* card = "default"; |
| 179 |
| 180 if ((err = snd_mixer_open(&handle, 0)) < 0) { |
| 181 LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err); |
| 182 return false; |
| 183 } |
| 184 |
| 185 if ((err = snd_mixer_attach(handle, card)) < 0) { |
| 186 LOG(ERROR) << "ALSA Attach to card " << card << " failed: " |
| 187 << snd_strerror(err); |
| 188 snd_mixer_close(handle); |
| 189 return false; |
| 190 } |
| 191 |
| 192 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { |
| 193 LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err); |
| 194 snd_mixer_close(handle); |
| 195 return false; |
| 196 } |
| 197 |
| 198 if ((err = snd_mixer_load(handle)) < 0) { |
| 199 LOG(ERROR) << "ALSA mixer " << card << " load error: %s" |
| 200 << snd_strerror(err); |
| 201 snd_mixer_close(handle); |
| 202 return false; |
| 203 } |
| 204 |
| 205 VLOG(1) << "Opened ALSA mixer " << card << " OK"; |
| 206 |
| 207 elem_master_ = FindElementWithName_Locked(handle, kMasterVolume); |
| 208 if (elem_master_) { |
| 209 alsa_long_t long_lo, long_hi; |
| 210 snd_mixer_selem_get_playback_dB_range(elem_master_, &long_lo, &long_hi); |
| 211 min_volume_ = static_cast<double>(long_lo) / 100.0; |
| 212 max_volume_ = static_cast<double>(long_hi) / 100.0; |
| 213 } else { |
| 214 LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card; |
| 215 snd_mixer_close(handle); |
| 216 return false; |
| 217 } |
| 218 |
| 219 elem_pcm_ = FindElementWithName_Locked(handle, kPCMVolume); |
| 220 if (elem_pcm_) { |
| 221 alsa_long_t long_lo, long_hi; |
| 222 snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi); |
| 223 min_volume_ += static_cast<double>(long_lo) / 100.0; |
| 224 max_volume_ += static_cast<double>(long_hi) / 100.0; |
| 225 } |
| 226 |
| 227 VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to " |
| 228 << max_volume_ << " dB"; |
| 229 |
| 230 alsa_mixer_ = handle; |
| 231 mixer_state_ = READY; |
| 232 return true; |
| 233 } |
| 234 |
| 235 void AudioMixerAlsa::FreeAlsaMixer() { |
| 236 AutoLock lock(mixer_state_lock_); |
| 237 mixer_state_ = SHUTTING_DOWN; |
| 238 if (alsa_mixer_) { |
| 239 snd_mixer_close(alsa_mixer_); |
| 240 alsa_mixer_ = NULL; |
| 241 } |
| 242 } |
| 243 |
| 244 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { |
| 245 double vol_total = 0.0; |
| 246 GetElementVolume_Locked(elem_master_, &vol_total); |
| 247 |
| 248 double vol_pcm = 0.0; |
| 249 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) |
| 250 vol_total += vol_pcm; |
| 251 |
| 252 return vol_total; |
| 253 } |
| 254 |
| 255 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { |
| 256 double actual_vol = 0.0; |
| 257 |
| 258 // If a PCM volume slider exists, then first set the Master volume to the |
| 259 // nearest volume >= requested volume, then adjust PCM volume down to get |
| 260 // closer to the requested volume. |
| 261 |
| 262 if (elem_pcm_) { |
| 263 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); |
| 264 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); |
| 265 } else { |
| 266 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); |
| 267 } |
| 268 } |
| 269 |
| 270 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( |
| 271 snd_mixer_t* handle, |
| 272 const char* element_name) const { |
| 273 snd_mixer_selem_id_t* sid; |
| 274 |
| 275 // Using id_malloc/id_free API instead of id_alloca since the latter gives the |
| 276 // warning: the address of 'sid' will always evaluate as 'true' |
| 277 if (snd_mixer_selem_id_malloc(&sid)) |
| 278 return NULL; |
| 279 |
| 280 snd_mixer_selem_id_set_index(sid, 0); |
| 281 snd_mixer_selem_id_set_name(sid, element_name); |
| 282 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); |
| 283 if (!elem) { |
| 284 LOG(ERROR) << "ALSA unable to find simple control " |
| 285 << snd_mixer_selem_id_get_name(sid); |
| 286 } |
| 287 |
| 288 snd_mixer_selem_id_free(sid); |
| 289 return elem; |
| 290 } |
| 291 |
| 292 bool AudioMixerAlsa::GetElementVolume_Locked(snd_mixer_elem_t* elem, |
| 293 double* current_vol) const { |
| 294 alsa_long_t long_vol = 0; |
| 295 snd_mixer_selem_get_playback_dB(elem, |
| 296 static_cast<snd_mixer_selem_channel_id_t>(0), |
| 297 &long_vol); |
| 298 *current_vol = static_cast<double>(long_vol) / 100.0; |
| 299 |
| 300 return true; |
| 301 } |
| 302 |
| 303 bool AudioMixerAlsa::SetElementVolume_Locked(snd_mixer_elem_t* elem, |
| 304 double new_vol, |
| 305 double* actual_vol, |
| 306 double rounding_bias) { |
| 307 alsa_long_t vol_lo = 0; |
| 308 alsa_long_t vol_hi = 0; |
| 309 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi); |
| 310 alsa_long_t vol_range = vol_hi - vol_lo; |
| 311 if (vol_range <= 0) |
| 312 return false; |
| 313 |
| 314 alsa_long_t db_lo_int = 0; |
| 315 alsa_long_t db_hi_int = 0; |
| 316 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int); |
| 317 double db_lo = static_cast<double>(db_lo_int) / 100.0; |
| 318 double db_hi = static_cast<double>(db_hi_int) / 100.0; |
| 319 double db_step = static_cast<double>(db_hi - db_lo) / vol_range; |
| 320 if (db_step <= 0.0) |
| 321 return false; |
| 322 |
| 323 if (new_vol < db_lo) |
| 324 new_vol = db_lo; |
| 325 |
| 326 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias + |
| 327 (new_vol - db_lo) / db_step) + vol_lo; |
| 328 snd_mixer_selem_set_playback_volume_all(elem, value); |
| 329 |
| 330 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem) |
| 331 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo |
| 332 << " dB"; |
| 333 |
| 334 if (actual_vol) { |
| 335 alsa_long_t volume; |
| 336 snd_mixer_selem_get_playback_volume( |
| 337 elem, |
| 338 static_cast<snd_mixer_selem_channel_id_t>(0), |
| 339 &volume); |
| 340 *actual_vol = db_lo + (volume - vol_lo) * db_step; |
| 341 |
| 342 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem) |
| 343 << " now " << *actual_vol << " dB"; |
| 344 } |
| 345 return true; |
| 346 } |
| 347 |
| 348 bool AudioMixerAlsa::GetElementMuted_Locked(snd_mixer_elem_t* elem) const { |
| 349 int enabled; |
| 350 snd_mixer_selem_get_playback_switch( |
| 351 elem, |
| 352 static_cast<snd_mixer_selem_channel_id_t>(0), |
| 353 &enabled); |
| 354 return (enabled) ? false : true; |
| 355 } |
| 356 |
| 357 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { |
| 358 int enabled = mute ? 0 : 1; |
| 359 snd_mixer_selem_set_playback_switch_all(elem, enabled); |
| 360 |
| 361 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) |
| 362 << " to " << enabled; |
| 363 } |
| 364 |
| 365 } // namespace chromeos |
| 366 |
OLD | NEW |