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
AudioMixerAlsa()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
~AudioMixerAlsa()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
Init(InitDoneCallback * callback)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
InitSync()109 bool AudioMixerAlsa::InitSync() {
110 if (!InitThread())
111 return false;
112 InitPrefs();
113 return InitializeAlsaMixer();
114 }
115
GetVolumeDb() const116 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
GetVolumeLimits(double * vol_min,double * vol_max)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
SetVolumeDb(double vol_db)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
IsMute() const150 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.
PrefVolumeValid(double volume)159 static bool PrefVolumeValid(double volume) {
160 return (volume > kPrefVolumeInvalid + 0.1);
161 }
162
SetMute(bool mute)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
GetState() const190 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
RegisterPrefs(PrefService * local_state)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
DoInit(InitDoneCallback * callback)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
InitThread()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
InitPrefs()257 void AudioMixerAlsa::InitPrefs() {
258 prefs_ = g_browser_process->local_state();
259 }
260
InitializeAlsaMixer()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
FreeAlsaMixer()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
DoSetVolumeMute(double pref_volume,int pref_mute)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
RestoreVolumeMuteOnUIThread()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
DoGetVolumeDb_Locked() const404 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
DoSetVolumeDb_Locked(double vol_db)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
FindElementWithName_Locked(snd_mixer_t * handle,const char * element_name) const430 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
GetElementVolume_Locked(snd_mixer_elem_t * elem,double * current_vol) const452 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
SetElementVolume_Locked(snd_mixer_elem_t * elem,double new_vol,double * actual_vol,double rounding_bias)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
GetElementMuted_Locked(snd_mixer_elem_t * elem) const533 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
SetElementMuted_Locked(snd_mixer_elem_t * elem,bool mute)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