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_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