Home | History | Annotate | Download | only in chromeos
      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