• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetVolumePercent()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_.
SetVolumePercent(double volume_percent)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 
AdjustVolumeByPercent(double adjust_by_percent)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 
IsMute()83 bool AudioHandler::IsMute() {
84   if (!VerifyMixerConnection())
85     return false;
86 
87   return mixer_->IsMute();
88 }
89 
SetMute(bool do_mute)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 
Disconnect()97 void AudioHandler::Disconnect() {
98   mixer_.reset();
99 }
100 
Disable()101 void AudioHandler::Disable() {
102   g_disabled = true;
103 }
104 
TryToConnect(bool async)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 
ClipVolume(double * min_volume,double * max_volume)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 
OnMixerInitialized(bool success)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 
AudioHandler()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 
~AudioHandler()165 AudioHandler::~AudioHandler() {
166   Disconnect();
167 };
168 
VerifyMixerConnection()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
VolumeDbToPercent(double volume_db) const215 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
PercentToVolumeDb(double volume_percent) const223 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
GetInstance()229 AudioHandler* AudioHandler::GetInstance() {
230   return Singleton<AudioHandler>::get();
231 }
232 
233 }  // namespace chromeos
234