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
21namespace 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
31typedef long alsa_long_t;  // 'long' is required for ALSA API calls.
32
33namespace {
34
35const char kMasterVolume[] = "Master";
36const char kPCMVolume[] = "PCM";
37const double kDefaultMinVolume = -90.0;
38const double kDefaultMaxVolume = 0.0;
39const double kPrefVolumeInvalid = -999.0;
40const int kPrefMuteOff = 0;
41const int kPrefMuteOn = 1;
42const 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.
47const int kMaxInitAttempts = 20;
48
49// Number of seconds that we'll sleep between each initialization attempt.
50const int kInitRetrySleepSec = 1;
51
52}  // namespace
53
54AudioMixerAlsa::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
66AudioMixerAlsa::~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
88void 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
109bool AudioMixerAlsa::InitSync() {
110  if (!InitThread())
111    return false;
112  InitPrefs();
113  return InitializeAlsaMixer();
114}
115
116double 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
124bool 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
135void 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
150bool 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.
159static bool PrefVolumeValid(double volume) {
160  return (volume > kPrefVolumeInvalid + 0.1);
161}
162
163void 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
190AudioMixer::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
199void 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
209void 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
238bool 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
257void AudioMixerAlsa::InitPrefs() {
258  prefs_ = g_browser_process->local_state();
259}
260
261bool 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
356void AudioMixerAlsa::FreeAlsaMixer() {
357  if (alsa_mixer_) {
358    snd_mixer_close(alsa_mixer_);
359    alsa_mixer_ = NULL;
360  }
361  done_event_.Signal();
362}
363
364void 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
390void 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
404double 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
416void 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
430snd_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
452bool 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
467bool 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
533bool 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
545void 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