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