OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/audio_mixer_alsa.h" | 5 #include "chrome/browser/chromeos/audio_mixer_alsa.h" |
6 | 6 |
7 #include <alsa/asoundlib.h> | 7 #include <alsa/asoundlib.h> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/task.h" | 10 #include "base/task.h" |
| 11 #include "chrome/browser/browser_process.h" |
| 12 #include "chrome/browser/browser_thread.h" |
| 13 #include "chrome/browser/prefs/pref_service.h" |
| 14 #include "chrome/common/pref_names.h" |
11 | 15 |
12 namespace chromeos { | 16 namespace chromeos { |
13 | 17 |
14 // Connect to the ALSA mixer using their simple element API. Init is performed | 18 // Connect to the ALSA mixer using their simple element API. Init is performed |
15 // asynchronously on the worker thread. | 19 // asynchronously on the worker thread. |
16 // | 20 // |
17 // To get a wider range and finer control over volume levels, first the Master | 21 // To get a wider range and finer control over volume levels, first the Master |
18 // level is set, then if the PCM element exists, the total level is refined by | 22 // level is set, then if the PCM element exists, the total level is refined by |
19 // adjusting that as well. If the PCM element has more volume steps, it allows | 23 // adjusting that as well. If the PCM element has more volume steps, it allows |
20 // for finer granularity in the total volume. | 24 // for finer granularity in the total volume. |
21 | 25 |
22 // TODO(davej): Serialize volume/mute to preserve settings when restarting. | |
23 | |
24 typedef long alsa_long_t; // 'long' is required for ALSA API calls. | 26 typedef long alsa_long_t; // 'long' is required for ALSA API calls. |
25 | 27 |
26 namespace { | 28 namespace { |
27 | 29 |
28 const char* kMasterVolume = "Master"; | 30 const char* kMasterVolume = "Master"; |
29 const char* kPCMVolume = "PCM"; | 31 const char* kPCMVolume = "PCM"; |
30 const double kDefaultMinVolume = -90.0; | 32 const double kDefaultMinVolume = -90.0; |
31 const double kDefaultMaxVolume = 0.0; | 33 const double kDefaultMaxVolume = 0.0; |
| 34 const double kPrefVolumeInvalid = -999.0; |
| 35 const int kPrefMuteOff = 0; |
| 36 const int kPrefMuteOn = 1; |
| 37 const int kPrefMuteInvalid = 2; |
32 | 38 |
33 } // namespace | 39 } // namespace |
34 | 40 |
35 AudioMixerAlsa::AudioMixerAlsa() | 41 AudioMixerAlsa::AudioMixerAlsa() |
36 : min_volume_(kDefaultMinVolume), | 42 : min_volume_(kDefaultMinVolume), |
37 max_volume_(kDefaultMaxVolume), | 43 max_volume_(kDefaultMaxVolume), |
38 save_volume_(0), | 44 save_volume_(0), |
39 mixer_state_(UNINITIALIZED), | 45 mixer_state_(UNINITIALIZED), |
40 alsa_mixer_(NULL), | 46 alsa_mixer_(NULL), |
41 elem_master_(NULL), | 47 elem_master_(NULL), |
42 elem_pcm_(NULL) { | 48 elem_pcm_(NULL) { |
43 } | 49 } |
44 | 50 |
45 AudioMixerAlsa::~AudioMixerAlsa() { | 51 AudioMixerAlsa::~AudioMixerAlsa() { |
46 FreeAlsaMixer(); | 52 FreeAlsaMixer(); |
47 if (thread_ != NULL) { | 53 if (thread_ != NULL) { |
48 thread_->Stop(); | 54 thread_->Stop(); |
49 thread_.reset(); | 55 thread_.reset(); |
50 } | 56 } |
51 } | 57 } |
52 | 58 |
53 void AudioMixerAlsa::Init(InitDoneCallback* callback) { | 59 void AudioMixerAlsa::Init(InitDoneCallback* callback) { |
54 DCHECK(callback); | 60 DCHECK(callback); |
55 if (!InitThread()) { | 61 if (!InitThread()) { |
56 callback->Run(false); | 62 callback->Run(false); |
57 delete callback; | 63 delete callback; |
58 return; | 64 return; |
59 } | 65 } |
| 66 InitPrefs(); |
60 | 67 |
61 // Post the task of starting up, which can block for 200-500ms, | 68 // Post the task of starting up, which may block on the order of ms, |
62 // so best not to do it on the caller's thread. | 69 // so best not to do it on the caller's thread. |
63 thread_->message_loop()->PostTask(FROM_HERE, | 70 thread_->message_loop()->PostTask(FROM_HERE, |
64 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); | 71 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); |
65 } | 72 } |
66 | 73 |
67 bool AudioMixerAlsa::InitSync() { | 74 bool AudioMixerAlsa::InitSync() { |
68 if (!InitThread()) | 75 if (!InitThread()) |
69 return false; | 76 return false; |
| 77 InitPrefs(); |
70 return InitializeAlsaMixer(); | 78 return InitializeAlsaMixer(); |
71 } | 79 } |
72 | 80 |
73 double AudioMixerAlsa::GetVolumeDb() const { | 81 double AudioMixerAlsa::GetVolumeDb() const { |
74 AutoLock lock(mixer_state_lock_); | 82 AutoLock lock(mixer_state_lock_); |
75 if (mixer_state_ != READY) | 83 if (mixer_state_ != READY) |
76 return kSilenceDb; | 84 return kSilenceDb; |
77 | 85 |
78 return DoGetVolumeDb_Locked(); | 86 return DoGetVolumeDb_Locked(); |
79 } | 87 } |
80 | 88 |
81 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { | 89 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { |
82 AutoLock lock(mixer_state_lock_); | 90 AutoLock lock(mixer_state_lock_); |
83 if (mixer_state_ != READY) | 91 if (mixer_state_ != READY) |
84 return false; | 92 return false; |
85 if (vol_min) | 93 if (vol_min) |
86 *vol_min = min_volume_; | 94 *vol_min = min_volume_; |
87 if (vol_max) | 95 if (vol_max) |
88 *vol_max = max_volume_; | 96 *vol_max = max_volume_; |
89 return true; | 97 return true; |
90 } | 98 } |
91 | 99 |
92 void AudioMixerAlsa::SetVolumeDb(double vol_db) { | 100 void AudioMixerAlsa::SetVolumeDb(double vol_db) { |
93 AutoLock lock(mixer_state_lock_); | 101 AutoLock lock(mixer_state_lock_); |
94 if (mixer_state_ != READY) | 102 if (mixer_state_ != READY) |
95 return; | 103 return; |
| 104 if (vol_db < kSilenceDb) |
| 105 vol_db = kSilenceDb; |
96 DoSetVolumeDb_Locked(vol_db); | 106 DoSetVolumeDb_Locked(vol_db); |
| 107 volume_pref_.SetValue(vol_db); |
97 } | 108 } |
98 | 109 |
99 bool AudioMixerAlsa::IsMute() const { | 110 bool AudioMixerAlsa::IsMute() const { |
100 AutoLock lock(mixer_state_lock_); | 111 AutoLock lock(mixer_state_lock_); |
101 if (mixer_state_ != READY) | 112 if (mixer_state_ != READY) |
102 return false; | 113 return false; |
103 return GetElementMuted_Locked(elem_master_); | 114 return GetElementMuted_Locked(elem_master_); |
104 } | 115 } |
105 | 116 |
| 117 // To indicate the volume is not valid yet, a very low volume value is stored. |
| 118 // We compare against a slightly higher value in case of rounding errors. |
| 119 static bool PrefVolumeValid(double volume) { |
| 120 return (volume > kPrefVolumeInvalid + 0.1); |
| 121 } |
| 122 |
106 void AudioMixerAlsa::SetMute(bool mute) { | 123 void AudioMixerAlsa::SetMute(bool mute) { |
107 AutoLock lock(mixer_state_lock_); | 124 AutoLock lock(mixer_state_lock_); |
108 if (mixer_state_ != READY) | 125 if (mixer_state_ != READY) |
109 return; | 126 return; |
110 | 127 |
111 // Set volume to minimum on mute, since switching the element off does not | 128 // Set volume to minimum on mute, since switching the element off does not |
112 // always mute as it should. | 129 // always mute as it should. |
113 | 130 |
114 // TODO(davej): Setting volume to minimum can be removed once switching the | 131 // TODO(davej): Remove save_volume_ and setting volume to minimum if |
115 // element off can be guaranteed to work. | 132 // switching the element off can be guaranteed to mute it. Currently mute |
| 133 // is done by setting the volume to min_volume_. |
116 | 134 |
117 bool old_value = GetElementMuted_Locked(elem_master_); | 135 bool old_value = GetElementMuted_Locked(elem_master_); |
118 | 136 |
119 if (old_value != mute) { | 137 if (old_value != mute) { |
120 if (mute) { | 138 if (mute) { |
121 save_volume_ = DoGetVolumeDb_Locked(); | 139 save_volume_ = DoGetVolumeDb_Locked(); |
122 DoSetVolumeDb_Locked(min_volume_); | 140 DoSetVolumeDb_Locked(min_volume_); |
123 } else { | 141 } else { |
124 DoSetVolumeDb_Locked(save_volume_); | 142 DoSetVolumeDb_Locked(save_volume_); |
125 } | 143 } |
126 } | 144 } |
127 | 145 |
128 SetElementMuted_Locked(elem_master_, mute); | 146 SetElementMuted_Locked(elem_master_, mute); |
129 if (elem_pcm_) | 147 if (elem_pcm_) |
130 SetElementMuted_Locked(elem_pcm_, mute); | 148 SetElementMuted_Locked(elem_pcm_, mute); |
| 149 mute_pref_.SetValue(mute ? kPrefMuteOn : kPrefMuteOff); |
131 } | 150 } |
132 | 151 |
133 AudioMixer::State AudioMixerAlsa::GetState() const { | 152 AudioMixer::State AudioMixerAlsa::GetState() const { |
134 AutoLock lock(mixer_state_lock_); | 153 AutoLock lock(mixer_state_lock_); |
135 // If we think it's ready, verify it is actually so. | 154 // If we think it's ready, verify it is actually so. |
136 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) | 155 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) |
137 mixer_state_ = IN_ERROR; | 156 mixer_state_ = IN_ERROR; |
138 return mixer_state_; | 157 return mixer_state_; |
139 } | 158 } |
140 | 159 |
| 160 // static |
| 161 void AudioMixerAlsa::RegisterPrefs(PrefService* local_state) { |
| 162 if (!local_state->FindPreference(prefs::kAudioVolume)) |
| 163 local_state->RegisterRealPref(prefs::kAudioVolume, kPrefVolumeInvalid); |
| 164 if (!local_state->FindPreference(prefs::kAudioMute)) |
| 165 local_state->RegisterIntegerPref(prefs::kAudioMute, kPrefMuteInvalid); |
| 166 } |
| 167 |
141 //////////////////////////////////////////////////////////////////////////////// | 168 //////////////////////////////////////////////////////////////////////////////// |
142 // Private functions follow | 169 // Private functions follow |
143 | 170 |
144 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { | 171 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { |
145 bool success = InitializeAlsaMixer(); | 172 bool success = InitializeAlsaMixer(); |
146 | 173 |
| 174 if (success) { |
| 175 BrowserThread::PostTask( |
| 176 BrowserThread::UI, FROM_HERE, |
| 177 NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread)); |
| 178 } |
| 179 |
147 if (callback) { | 180 if (callback) { |
148 callback->Run(success); | 181 callback->Run(success); |
149 delete callback; | 182 delete callback; |
150 } | 183 } |
151 } | 184 } |
152 | 185 |
153 bool AudioMixerAlsa::InitThread() { | 186 bool AudioMixerAlsa::InitThread() { |
154 AutoLock lock(mixer_state_lock_); | 187 AutoLock lock(mixer_state_lock_); |
155 | 188 |
156 if (mixer_state_ != UNINITIALIZED) | 189 if (mixer_state_ != UNINITIALIZED) |
157 return false; | 190 return false; |
158 | 191 |
159 if (thread_ == NULL) { | 192 if (thread_ == NULL) { |
160 thread_.reset(new base::Thread("AudioMixerAlsa")); | 193 thread_.reset(new base::Thread("AudioMixerAlsa")); |
161 if (!thread_->Start()) { | 194 if (!thread_->Start()) { |
162 thread_.reset(); | 195 thread_.reset(); |
163 return false; | 196 return false; |
164 } | 197 } |
165 } | 198 } |
166 | 199 |
167 mixer_state_ = INITIALIZING; | 200 mixer_state_ = INITIALIZING; |
168 return true; | 201 return true; |
169 } | 202 } |
170 | 203 |
| 204 void AudioMixerAlsa::InitPrefs() { |
| 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 206 PrefService* prefs = g_browser_process->local_state(); |
| 207 volume_pref_.Init(prefs::kAudioVolume, prefs, NULL); |
| 208 mute_pref_.Init(prefs::kAudioMute, prefs, NULL); |
| 209 } |
| 210 |
171 bool AudioMixerAlsa::InitializeAlsaMixer() { | 211 bool AudioMixerAlsa::InitializeAlsaMixer() { |
172 AutoLock lock(mixer_state_lock_); | 212 AutoLock lock(mixer_state_lock_); |
173 if (mixer_state_ != INITIALIZING) | 213 if (mixer_state_ != INITIALIZING) |
174 return false; | 214 return false; |
175 | 215 |
176 int err; | 216 int err; |
177 snd_mixer_t* handle = NULL; | 217 snd_mixer_t* handle = NULL; |
178 const char* card = "default"; | 218 const char* card = "default"; |
179 | 219 |
180 if ((err = snd_mixer_open(&handle, 0)) < 0) { | 220 if ((err = snd_mixer_open(&handle, 0)) < 0) { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
234 | 274 |
235 void AudioMixerAlsa::FreeAlsaMixer() { | 275 void AudioMixerAlsa::FreeAlsaMixer() { |
236 AutoLock lock(mixer_state_lock_); | 276 AutoLock lock(mixer_state_lock_); |
237 mixer_state_ = SHUTTING_DOWN; | 277 mixer_state_ = SHUTTING_DOWN; |
238 if (alsa_mixer_) { | 278 if (alsa_mixer_) { |
239 snd_mixer_close(alsa_mixer_); | 279 snd_mixer_close(alsa_mixer_); |
240 alsa_mixer_ = NULL; | 280 alsa_mixer_ = NULL; |
241 } | 281 } |
242 } | 282 } |
243 | 283 |
| 284 void AudioMixerAlsa::DoSetVolumeMute(double pref_volume, int pref_mute) { |
| 285 AutoLock lock(mixer_state_lock_); |
| 286 if (mixer_state_ != READY) |
| 287 return; |
| 288 |
| 289 // If volume or mute are invalid, set them now to the current actual values. |
| 290 if (!PrefVolumeValid(pref_volume)) |
| 291 pref_volume = DoGetVolumeDb_Locked(); |
| 292 bool mute; |
| 293 if (pref_mute == kPrefMuteInvalid) |
| 294 mute = GetElementMuted_Locked(elem_master_); |
| 295 else |
| 296 mute = (pref_mute == kPrefMuteOn) ? true : false; |
| 297 |
| 298 VLOG(1) << "Setting volume to " << pref_volume << " and mute to " << mute; |
| 299 |
| 300 if (mute) { |
| 301 save_volume_ = pref_volume; |
| 302 DoSetVolumeDb_Locked(min_volume_); |
| 303 } else if (pref_mute == kPrefMuteOff) { |
| 304 DoSetVolumeDb_Locked(pref_volume); |
| 305 } |
| 306 |
| 307 SetElementMuted_Locked(elem_master_, mute); |
| 308 if (elem_pcm_) |
| 309 SetElementMuted_Locked(elem_pcm_, mute); |
| 310 } |
| 311 |
| 312 void AudioMixerAlsa::RestoreVolumeMuteOnUIThread() { |
| 313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 314 // This happens during init, so set the volume off the UI thread. |
| 315 thread_->message_loop()->PostTask(FROM_HERE, |
| 316 NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute, |
| 317 volume_pref_.GetValue(), mute_pref_.GetValue())); |
| 318 } |
| 319 |
244 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { | 320 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { |
245 double vol_total = 0.0; | 321 double vol_total = 0.0; |
246 GetElementVolume_Locked(elem_master_, &vol_total); | 322 GetElementVolume_Locked(elem_master_, &vol_total); |
247 | 323 |
248 double vol_pcm = 0.0; | 324 double vol_pcm = 0.0; |
249 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) | 325 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) |
250 vol_total += vol_pcm; | 326 vol_total += vol_pcm; |
251 | 327 |
252 return vol_total; | 328 return vol_total; |
253 } | 329 } |
254 | 330 |
255 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { | 331 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { |
256 double actual_vol = 0.0; | 332 double actual_vol = 0.0; |
257 | 333 |
258 // If a PCM volume slider exists, then first set the Master volume to the | 334 // If a PCM volume slider exists, then first set the Master volume to the |
259 // nearest volume >= requested volume, then adjust PCM volume down to get | 335 // nearest volume >= requested volume, then adjust PCM volume down to get |
260 // closer to the requested volume. | 336 // closer to the requested volume. |
261 | |
262 if (elem_pcm_) { | 337 if (elem_pcm_) { |
263 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); | 338 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); |
264 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); | 339 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); |
265 } else { | 340 } else { |
266 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); | 341 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); |
267 } | 342 } |
268 } | 343 } |
269 | 344 |
270 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( | 345 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( |
271 snd_mixer_t* handle, | 346 snd_mixer_t* handle, |
272 const char* element_name) const { | 347 const char* element_name) const { |
273 snd_mixer_selem_id_t* sid; | 348 snd_mixer_selem_id_t* sid; |
274 | 349 |
275 // Using id_malloc/id_free API instead of id_alloca since the latter gives the | 350 // Using id_malloc/id_free API instead of id_alloca since the latter gives the |
276 // warning: the address of 'sid' will always evaluate as 'true' | 351 // warning: the address of 'sid' will always evaluate as 'true'. |
277 if (snd_mixer_selem_id_malloc(&sid)) | 352 if (snd_mixer_selem_id_malloc(&sid)) |
278 return NULL; | 353 return NULL; |
279 | 354 |
280 snd_mixer_selem_id_set_index(sid, 0); | 355 snd_mixer_selem_id_set_index(sid, 0); |
281 snd_mixer_selem_id_set_name(sid, element_name); | 356 snd_mixer_selem_id_set_name(sid, element_name); |
282 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); | 357 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); |
283 if (!elem) { | 358 if (!elem) { |
284 LOG(ERROR) << "ALSA unable to find simple control " | 359 LOG(ERROR) << "ALSA unable to find simple control " |
285 << snd_mixer_selem_id_get_name(sid); | 360 << snd_mixer_selem_id_get_name(sid); |
286 } | 361 } |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
357 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { | 432 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { |
358 int enabled = mute ? 0 : 1; | 433 int enabled = mute ? 0 : 1; |
359 snd_mixer_selem_set_playback_switch_all(elem, enabled); | 434 snd_mixer_selem_set_playback_switch_all(elem, enabled); |
360 | 435 |
361 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) | 436 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) |
362 << " to " << enabled; | 437 << " to " << enabled; |
363 } | 438 } |
364 | 439 |
365 } // namespace chromeos | 440 } // namespace chromeos |
366 | 441 |
OLD | NEW |