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_mixer_alsa.h" 6 7 #include <cmath> 8 #include <unistd.h> 9 10 #include <alsa/asoundlib.h> 11 12 #include "base/logging.h" 13 #include "base/message_loop.h" 14 #include "base/task.h" 15 #include "base/threading/thread_restrictions.h" 16 #include "chrome/browser/browser_process.h" 17 #include "chrome/browser/prefs/pref_service.h" 18 #include "chrome/common/pref_names.h" 19 #include "content/browser/browser_thread.h" 20 21 namespace chromeos { 22 23 // Connect to the ALSA mixer using their simple element API. Init is performed 24 // asynchronously on the worker thread. 25 // 26 // To get a wider range and finer control over volume levels, first the Master 27 // level is set, then if the PCM element exists, the total level is refined by 28 // adjusting that as well. If the PCM element has more volume steps, it allows 29 // for finer granularity in the total volume. 30 31 typedef long alsa_long_t; // 'long' is required for ALSA API calls. 32 33 namespace { 34 35 const char kMasterVolume[] = "Master"; 36 const char kPCMVolume[] = "PCM"; 37 const double kDefaultMinVolume = -90.0; 38 const double kDefaultMaxVolume = 0.0; 39 const double kPrefVolumeInvalid = -999.0; 40 const int kPrefMuteOff = 0; 41 const int kPrefMuteOn = 1; 42 const int kPrefMuteInvalid = 2; 43 44 // Maximum number of times that we'll attempt to initialize the mixer. 45 // We'll fail until the ALSA modules have been loaded; see 46 // http://crosbug.com/13162. 47 const int kMaxInitAttempts = 20; 48 49 // Number of seconds that we'll sleep between each initialization attempt. 50 const int kInitRetrySleepSec = 1; 51 52 } // namespace 53 54 AudioMixerAlsa::AudioMixerAlsa() 55 : min_volume_(kDefaultMinVolume), 56 max_volume_(kDefaultMaxVolume), 57 save_volume_(0), 58 mixer_state_(UNINITIALIZED), 59 alsa_mixer_(NULL), 60 elem_master_(NULL), 61 elem_pcm_(NULL), 62 prefs_(NULL), 63 done_event_(true, false) { 64 } 65 66 AudioMixerAlsa::~AudioMixerAlsa() { 67 if (thread_ != NULL) { 68 { 69 base::AutoLock lock(mixer_state_lock_); 70 mixer_state_ = SHUTTING_DOWN; 71 thread_->message_loop()->PostTask(FROM_HERE, 72 NewRunnableMethod(this, &AudioMixerAlsa::FreeAlsaMixer)); 73 } 74 done_event_.Wait(); 75 76 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 77 // A ScopedAllowIO object is required to join the thread when calling Stop. 78 // The worker thread should be idle at this time. 79 // See http://crosbug.com/11110 for discussion. 80 base::ThreadRestrictions::ScopedAllowIO allow_io_for_thread_join; 81 thread_->message_loop()->AssertIdle(); 82 83 thread_->Stop(); 84 thread_.reset(); 85 } 86 } 87 88 void AudioMixerAlsa::Init(InitDoneCallback* callback) { 89 DCHECK(callback); 90 if (!InitThread()) { 91 callback->Run(false); 92 delete callback; 93 return; 94 } 95 InitPrefs(); 96 97 { 98 base::AutoLock lock(mixer_state_lock_); 99 if (mixer_state_ == SHUTTING_DOWN) 100 return; 101 102 // Post the task of starting up, which may block on the order of ms, 103 // so best not to do it on the caller's thread. 104 thread_->message_loop()->PostTask(FROM_HERE, 105 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); 106 } 107 } 108 109 bool AudioMixerAlsa::InitSync() { 110 if (!InitThread()) 111 return false; 112 InitPrefs(); 113 return InitializeAlsaMixer(); 114 } 115 116 double AudioMixerAlsa::GetVolumeDb() const { 117 base::AutoLock lock(mixer_state_lock_); 118 if (mixer_state_ != READY) 119 return kSilenceDb; 120 121 return DoGetVolumeDb_Locked(); 122 } 123 124 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { 125 base::AutoLock lock(mixer_state_lock_); 126 if (mixer_state_ != READY) 127 return false; 128 if (vol_min) 129 *vol_min = min_volume_; 130 if (vol_max) 131 *vol_max = max_volume_; 132 return true; 133 } 134 135 void AudioMixerAlsa::SetVolumeDb(double vol_db) { 136 base::AutoLock lock(mixer_state_lock_); 137 if (mixer_state_ != READY) 138 return; 139 140 if (vol_db < kSilenceDb || isnan(vol_db)) { 141 if (isnan(vol_db)) 142 LOG(WARNING) << "Got request to set volume to NaN"; 143 vol_db = kSilenceDb; 144 } 145 146 DoSetVolumeDb_Locked(vol_db); 147 prefs_->SetDouble(prefs::kAudioVolume, vol_db); 148 } 149 150 bool AudioMixerAlsa::IsMute() const { 151 base::AutoLock lock(mixer_state_lock_); 152 if (mixer_state_ != READY) 153 return false; 154 return GetElementMuted_Locked(elem_master_); 155 } 156 157 // To indicate the volume is not valid yet, a very low volume value is stored. 158 // We compare against a slightly higher value in case of rounding errors. 159 static bool PrefVolumeValid(double volume) { 160 return (volume > kPrefVolumeInvalid + 0.1); 161 } 162 163 void AudioMixerAlsa::SetMute(bool mute) { 164 base::AutoLock lock(mixer_state_lock_); 165 if (mixer_state_ != READY) 166 return; 167 168 // Set volume to minimum on mute, since switching the element off does not 169 // always mute as it should. 170 171 // TODO(davej): Remove save_volume_ and setting volume to minimum if 172 // switching the element off can be guaranteed to mute it. Currently mute 173 // is done by setting the volume to min_volume_. 174 175 bool old_value = GetElementMuted_Locked(elem_master_); 176 177 if (old_value != mute) { 178 if (mute) { 179 save_volume_ = DoGetVolumeDb_Locked(); 180 DoSetVolumeDb_Locked(min_volume_); 181 } else { 182 DoSetVolumeDb_Locked(save_volume_); 183 } 184 } 185 186 SetElementMuted_Locked(elem_master_, mute); 187 prefs_->SetInteger(prefs::kAudioMute, mute ? kPrefMuteOn : kPrefMuteOff); 188 } 189 190 AudioMixer::State AudioMixerAlsa::GetState() const { 191 base::AutoLock lock(mixer_state_lock_); 192 // If we think it's ready, verify it is actually so. 193 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) 194 mixer_state_ = IN_ERROR; 195 return mixer_state_; 196 } 197 198 // static 199 void AudioMixerAlsa::RegisterPrefs(PrefService* local_state) { 200 if (!local_state->FindPreference(prefs::kAudioVolume)) 201 local_state->RegisterDoublePref(prefs::kAudioVolume, kPrefVolumeInvalid); 202 if (!local_state->FindPreference(prefs::kAudioMute)) 203 local_state->RegisterIntegerPref(prefs::kAudioMute, kPrefMuteInvalid); 204 } 205 206 //////////////////////////////////////////////////////////////////////////////// 207 // Private functions follow 208 209 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { 210 bool success = false; 211 for (int num_attempts = 0; num_attempts < kMaxInitAttempts; ++num_attempts) { 212 success = InitializeAlsaMixer(); 213 if (success) { 214 break; 215 } else { 216 // If the destructor has reset the state, give up. 217 { 218 base::AutoLock lock(mixer_state_lock_); 219 if (mixer_state_ != INITIALIZING) 220 break; 221 } 222 sleep(kInitRetrySleepSec); 223 } 224 } 225 226 if (success) { 227 BrowserThread::PostTask( 228 BrowserThread::UI, FROM_HERE, 229 NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread)); 230 } 231 232 if (callback) { 233 callback->Run(success); 234 delete callback; 235 } 236 } 237 238 bool AudioMixerAlsa::InitThread() { 239 base::AutoLock lock(mixer_state_lock_); 240 241 if (mixer_state_ != UNINITIALIZED) 242 return false; 243 244 if (thread_ == NULL) { 245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 246 thread_.reset(new base::Thread("AudioMixerAlsa")); 247 if (!thread_->Start()) { 248 thread_.reset(); 249 return false; 250 } 251 } 252 253 mixer_state_ = INITIALIZING; 254 return true; 255 } 256 257 void AudioMixerAlsa::InitPrefs() { 258 prefs_ = g_browser_process->local_state(); 259 } 260 261 bool AudioMixerAlsa::InitializeAlsaMixer() { 262 // We can block; make sure that we're not on the UI thread. 263 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 264 265 base::AutoLock lock(mixer_state_lock_); 266 if (mixer_state_ != INITIALIZING) 267 return false; 268 269 int err; 270 snd_mixer_t* handle = NULL; 271 const char* card = "default"; 272 273 if ((err = snd_mixer_open(&handle, 0)) < 0) { 274 LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err); 275 return false; 276 } 277 278 if ((err = snd_mixer_attach(handle, card)) < 0) { 279 LOG(ERROR) << "ALSA Attach to card " << card << " failed: " 280 << snd_strerror(err); 281 snd_mixer_close(handle); 282 return false; 283 } 284 285 // Verify PCM can be opened, which also instantiates the PCM mixer element 286 // which is needed for finer volume control and for muting by setting to zero. 287 // If it fails, we can still try to use the mixer as best we can. 288 snd_pcm_t* pcm_out_handle; 289 if ((err = snd_pcm_open(&pcm_out_handle, 290 card, 291 SND_PCM_STREAM_PLAYBACK, 292 0)) >= 0) { 293 snd_pcm_close(pcm_out_handle); 294 } else { 295 LOG(WARNING) << "ALSA PCM open: " << snd_strerror(err); 296 } 297 298 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { 299 LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err); 300 snd_mixer_close(handle); 301 return false; 302 } 303 304 if ((err = snd_mixer_load(handle)) < 0) { 305 LOG(ERROR) << "ALSA mixer " << card << " load error: %s" 306 << snd_strerror(err); 307 snd_mixer_close(handle); 308 return false; 309 } 310 311 VLOG(1) << "Opened ALSA mixer " << card << " OK"; 312 313 elem_master_ = FindElementWithName_Locked(handle, kMasterVolume); 314 if (elem_master_) { 315 alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100); 316 alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100); 317 err = snd_mixer_selem_get_playback_dB_range( 318 elem_master_, &long_lo, &long_hi); 319 if (err != 0) { 320 LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed " 321 << "for master: " << snd_strerror(err); 322 snd_mixer_close(handle); 323 return false; 324 } 325 min_volume_ = static_cast<double>(long_lo) / 100.0; 326 max_volume_ = static_cast<double>(long_hi) / 100.0; 327 } else { 328 LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card; 329 snd_mixer_close(handle); 330 return false; 331 } 332 333 elem_pcm_ = FindElementWithName_Locked(handle, kPCMVolume); 334 if (elem_pcm_) { 335 alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100); 336 alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100); 337 err = snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi); 338 if (err != 0) { 339 LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed for PCM: " 340 << snd_strerror(err); 341 snd_mixer_close(handle); 342 return false; 343 } 344 min_volume_ += static_cast<double>(long_lo) / 100.0; 345 max_volume_ += static_cast<double>(long_hi) / 100.0; 346 } 347 348 VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to " 349 << max_volume_ << " dB"; 350 351 alsa_mixer_ = handle; 352 mixer_state_ = READY; 353 return true; 354 } 355 356 void AudioMixerAlsa::FreeAlsaMixer() { 357 if (alsa_mixer_) { 358 snd_mixer_close(alsa_mixer_); 359 alsa_mixer_ = NULL; 360 } 361 done_event_.Signal(); 362 } 363 364 void AudioMixerAlsa::DoSetVolumeMute(double pref_volume, int pref_mute) { 365 base::AutoLock lock(mixer_state_lock_); 366 if (mixer_state_ != READY) 367 return; 368 369 // If volume or mute are invalid, set them now to the current actual values. 370 if (!PrefVolumeValid(pref_volume)) 371 pref_volume = DoGetVolumeDb_Locked(); 372 bool mute = false; 373 if (pref_mute == kPrefMuteInvalid) 374 mute = GetElementMuted_Locked(elem_master_); 375 else 376 mute = (pref_mute == kPrefMuteOn) ? true : false; 377 378 VLOG(1) << "Setting volume to " << pref_volume << " and mute to " << mute; 379 380 if (mute) { 381 save_volume_ = pref_volume; 382 DoSetVolumeDb_Locked(min_volume_); 383 } else { 384 DoSetVolumeDb_Locked(pref_volume); 385 } 386 387 SetElementMuted_Locked(elem_master_, mute); 388 } 389 390 void AudioMixerAlsa::RestoreVolumeMuteOnUIThread() { 391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 392 // This happens during init, so set the volume off the UI thread. 393 int mute = prefs_->GetInteger(prefs::kAudioMute); 394 double vol = prefs_->GetDouble(prefs::kAudioVolume); 395 { 396 base::AutoLock lock(mixer_state_lock_); 397 if (mixer_state_ == SHUTTING_DOWN) 398 return; 399 thread_->message_loop()->PostTask(FROM_HERE, 400 NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute, vol, mute)); 401 } 402 } 403 404 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { 405 double vol_total = 0.0; 406 if (!GetElementVolume_Locked(elem_master_, &vol_total)) 407 return 0.0; 408 409 double vol_pcm = 0.0; 410 if (elem_pcm_ && GetElementVolume_Locked(elem_pcm_, &vol_pcm)) 411 vol_total += vol_pcm; 412 413 return vol_total; 414 } 415 416 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { 417 double actual_vol = 0.0; 418 419 // If a PCM volume slider exists, then first set the Master volume to the 420 // nearest volume >= requested volume, then adjust PCM volume down to get 421 // closer to the requested volume. 422 if (elem_pcm_) { 423 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); 424 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); 425 } else { 426 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); 427 } 428 } 429 430 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( 431 snd_mixer_t* handle, 432 const char* element_name) const { 433 snd_mixer_selem_id_t* sid; 434 435 // Using id_malloc/id_free API instead of id_alloca since the latter gives the 436 // warning: the address of 'sid' will always evaluate as 'true'. 437 if (snd_mixer_selem_id_malloc(&sid)) 438 return NULL; 439 440 snd_mixer_selem_id_set_index(sid, 0); 441 snd_mixer_selem_id_set_name(sid, element_name); 442 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); 443 if (!elem) { 444 LOG(ERROR) << "ALSA unable to find simple control " 445 << snd_mixer_selem_id_get_name(sid); 446 } 447 448 snd_mixer_selem_id_free(sid); 449 return elem; 450 } 451 452 bool AudioMixerAlsa::GetElementVolume_Locked(snd_mixer_elem_t* elem, 453 double* current_vol) const { 454 alsa_long_t long_vol = 0; 455 int alsa_result = snd_mixer_selem_get_playback_dB( 456 elem, static_cast<snd_mixer_selem_channel_id_t>(0), &long_vol); 457 if (alsa_result != 0) { 458 LOG(WARNING) << "snd_mixer_selem_get_playback_dB() failed: " 459 << snd_strerror(alsa_result); 460 return false; 461 } 462 463 *current_vol = static_cast<double>(long_vol) / 100.0; 464 return true; 465 } 466 467 bool AudioMixerAlsa::SetElementVolume_Locked(snd_mixer_elem_t* elem, 468 double new_vol, 469 double* actual_vol, 470 double rounding_bias) { 471 alsa_long_t vol_lo = 0; 472 alsa_long_t vol_hi = 0; 473 int alsa_result = 474 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi); 475 if (alsa_result != 0) { 476 LOG(WARNING) << "snd_mixer_selem_get_playback_volume_range() failed: " 477 << snd_strerror(alsa_result); 478 return false; 479 } 480 alsa_long_t vol_range = vol_hi - vol_lo; 481 if (vol_range <= 0) 482 return false; 483 484 alsa_long_t db_lo_int = 0; 485 alsa_long_t db_hi_int = 0; 486 alsa_result = 487 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int); 488 if (alsa_result != 0) { 489 LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed: " 490 << snd_strerror(alsa_result); 491 return false; 492 } 493 494 double db_lo = static_cast<double>(db_lo_int) / 100.0; 495 double db_hi = static_cast<double>(db_hi_int) / 100.0; 496 double db_step = static_cast<double>(db_hi - db_lo) / vol_range; 497 if (db_step <= 0.0) 498 return false; 499 500 if (new_vol < db_lo) 501 new_vol = db_lo; 502 503 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias + 504 (new_vol - db_lo) / db_step) + vol_lo; 505 alsa_result = snd_mixer_selem_set_playback_volume_all(elem, value); 506 if (alsa_result != 0) { 507 LOG(WARNING) << "snd_mixer_selem_set_playback_volume_all() failed: " 508 << snd_strerror(alsa_result); 509 return false; 510 } 511 512 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem) 513 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo 514 << " dB"; 515 516 if (actual_vol) { 517 alsa_long_t volume = vol_lo; 518 alsa_result = snd_mixer_selem_get_playback_volume( 519 elem, static_cast<snd_mixer_selem_channel_id_t>(0), &volume); 520 if (alsa_result != 0) { 521 LOG(WARNING) << "snd_mixer_selem_get_playback_volume() failed: " 522 << snd_strerror(alsa_result); 523 return false; 524 } 525 *actual_vol = db_lo + (volume - vol_lo) * db_step; 526 527 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem) 528 << " now " << *actual_vol << " dB"; 529 } 530 return true; 531 } 532 533 bool AudioMixerAlsa::GetElementMuted_Locked(snd_mixer_elem_t* elem) const { 534 int enabled = 0; 535 int alsa_result = snd_mixer_selem_get_playback_switch( 536 elem, static_cast<snd_mixer_selem_channel_id_t>(0), &enabled); 537 if (alsa_result != 0) { 538 LOG(WARNING) << "snd_mixer_selem_get_playback_switch() failed: " 539 << snd_strerror(alsa_result); 540 return false; 541 } 542 return (enabled) ? false : true; 543 } 544 545 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { 546 int enabled = mute ? 0 : 1; 547 int alsa_result = snd_mixer_selem_set_playback_switch_all(elem, enabled); 548 if (alsa_result != 0) { 549 LOG(WARNING) << "snd_mixer_selem_set_playback_switch_all() failed: " 550 << snd_strerror(alsa_result); 551 } else { 552 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) 553 << " to " << enabled; 554 } 555 } 556 557 } // namespace chromeos 558