1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Use of this source code is governed by a BSD-style license that can be
372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// found in the LICENSE file.
472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/chromeos/audio_mixer_alsa.h"
672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
7dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <cmath>
8ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include <unistd.h>
9dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
1072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include <alsa/asoundlib.h>
1172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
1272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "base/logging.h"
1372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "base/message_loop.h"
1472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "base/task.h"
1572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "base/threading/thread_restrictions.h"
1672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/browser_process.h"
1772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/prefs/pref_service.h"
1872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/common/pref_names.h"
19dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "content/browser/browser_thread.h"
2072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
2172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsennamespace chromeos {
2272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
2372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Connect to the ALSA mixer using their simple element API.  Init is performed
2472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// asynchronously on the worker thread.
2572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen//
2672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// To get a wider range and finer control over volume levels, first the Master
2772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// level is set, then if the PCM element exists, the total level is refined by
2872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// adjusting that as well.  If the PCM element has more volume steps, it allows
2972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// for finer granularity in the total volume.
3072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
3172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsentypedef long alsa_long_t;  // 'long' is required for ALSA API calls.
3272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
3372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsennamespace {
3472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
3572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst char kMasterVolume[] = "Master";
3672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst char kPCMVolume[] = "PCM";
3772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst double kDefaultMinVolume = -90.0;
3872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst double kDefaultMaxVolume = 0.0;
3972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst double kPrefVolumeInvalid = -999.0;
4072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst int kPrefMuteOff = 0;
4172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst int kPrefMuteOn = 1;
4272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenconst int kPrefMuteInvalid = 2;
4372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
44ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Maximum number of times that we'll attempt to initialize the mixer.
45ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// We'll fail until the ALSA modules have been loaded; see
46ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// http://crosbug.com/13162.
47ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenconst int kMaxInitAttempts = 20;
48ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
49ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Number of seconds that we'll sleep between each initialization attempt.
50ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenconst int kInitRetrySleepSec = 1;
51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
5272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}  // namespace
5372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
5472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian MonsenAudioMixerAlsa::AudioMixerAlsa()
5572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    : min_volume_(kDefaultMinVolume),
5672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      max_volume_(kDefaultMaxVolume),
5772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      save_volume_(0),
5872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      mixer_state_(UNINITIALIZED),
5972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      alsa_mixer_(NULL),
6072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      elem_master_(NULL),
6172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      elem_pcm_(NULL),
6272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      prefs_(NULL),
6372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      done_event_(true, false) {
6472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
6572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
6672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian MonsenAudioMixerAlsa::~AudioMixerAlsa() {
6772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (thread_ != NULL) {
6872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    {
6972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      base::AutoLock lock(mixer_state_lock_);
7072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      mixer_state_ = SHUTTING_DOWN;
7172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      thread_->message_loop()->PostTask(FROM_HERE,
7272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          NewRunnableMethod(this, &AudioMixerAlsa::FreeAlsaMixer));
7372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    }
7472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    done_event_.Wait();
7572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
7672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
7772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    // A ScopedAllowIO object is required to join the thread when calling Stop.
7872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    // The worker thread should be idle at this time.
7972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    // See http://crosbug.com/11110 for discussion.
8072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    base::ThreadRestrictions::ScopedAllowIO allow_io_for_thread_join;
8172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_->message_loop()->AssertIdle();
8272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
8372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_->Stop();
8472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_.reset();
8572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
8672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
8772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
8872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::Init(InitDoneCallback* callback) {
8972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  DCHECK(callback);
9072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!InitThread()) {
9172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    callback->Run(false);
9272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    delete callback;
9372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return;
9472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
9572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  InitPrefs();
9672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
9772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  {
9872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    base::AutoLock lock(mixer_state_lock_);
9972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if (mixer_state_ == SHUTTING_DOWN)
10072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return;
10172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
10272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    // Post the task of starting up, which may block on the order of ms,
10372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    // so best not to do it on the caller's thread.
10472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_->message_loop()->PostTask(FROM_HERE,
10572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback));
10672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
10772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
10872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
10972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::InitSync() {
11072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!InitThread())
11172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
11272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  InitPrefs();
11372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return InitializeAlsaMixer();
11472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
11572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
11672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsendouble AudioMixerAlsa::GetVolumeDb() const {
11772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
11872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
11972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return kSilenceDb;
12072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
12172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return DoGetVolumeDb_Locked();
12272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
12372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
12472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) {
12572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
12672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
12772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
12872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (vol_min)
12972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    *vol_min = min_volume_;
13072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (vol_max)
13172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    *vol_max = max_volume_;
13272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return true;
13372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
13472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
13572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::SetVolumeDb(double vol_db) {
13672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
13772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
13872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return;
139dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
140dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (vol_db < kSilenceDb || isnan(vol_db)) {
141dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (isnan(vol_db))
142dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      LOG(WARNING) << "Got request to set volume to NaN";
14372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    vol_db = kSilenceDb;
144dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
145dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
14672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  DoSetVolumeDb_Locked(vol_db);
14772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  prefs_->SetDouble(prefs::kAudioVolume, vol_db);
14872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
14972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
15072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::IsMute() const {
15172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
15272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
15372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
15472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return GetElementMuted_Locked(elem_master_);
15572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
15672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
15772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// To indicate the volume is not valid yet, a very low volume value is stored.
15872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// We compare against a slightly higher value in case of rounding errors.
15972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenstatic bool PrefVolumeValid(double volume) {
16072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return (volume > kPrefVolumeInvalid + 0.1);
16172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
16272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
16372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::SetMute(bool mute) {
16472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
16572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
16672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return;
16772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
16872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // Set volume to minimum on mute, since switching the element off does not
16972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // always mute as it should.
17072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
17172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // TODO(davej): Remove save_volume_ and setting volume to minimum if
17272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // switching the element off can be guaranteed to mute it.  Currently mute
17372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // is done by setting the volume to min_volume_.
17472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
17572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  bool old_value = GetElementMuted_Locked(elem_master_);
17672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
17772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (old_value != mute) {
17872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if (mute) {
17972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      save_volume_ = DoGetVolumeDb_Locked();
18072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      DoSetVolumeDb_Locked(min_volume_);
18172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    } else {
18272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      DoSetVolumeDb_Locked(save_volume_);
18372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    }
18472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
18572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
18672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  SetElementMuted_Locked(elem_master_, mute);
18772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  prefs_->SetInteger(prefs::kAudioMute, mute ? kPrefMuteOn : kPrefMuteOff);
18872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
18972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
19072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian MonsenAudioMixer::State AudioMixerAlsa::GetState() const {
19172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
19272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // If we think it's ready, verify it is actually so.
19372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if ((mixer_state_ == READY) && (alsa_mixer_ == NULL))
19472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    mixer_state_ = IN_ERROR;
19572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return mixer_state_;
19672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
19772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
19872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// static
19972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::RegisterPrefs(PrefService* local_state) {
20072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!local_state->FindPreference(prefs::kAudioVolume))
20172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    local_state->RegisterDoublePref(prefs::kAudioVolume, kPrefVolumeInvalid);
20272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!local_state->FindPreference(prefs::kAudioMute))
20372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    local_state->RegisterIntegerPref(prefs::kAudioMute, kPrefMuteInvalid);
20472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
20572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
20672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen////////////////////////////////////////////////////////////////////////////////
20772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Private functions follow
20872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
20972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::DoInit(InitDoneCallback* callback) {
210ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  bool success = false;
211ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  for (int num_attempts = 0; num_attempts < kMaxInitAttempts; ++num_attempts) {
212ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    success = InitializeAlsaMixer();
213ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (success) {
214ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      break;
215ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    } else {
216ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // If the destructor has reset the state, give up.
217ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      {
218ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        base::AutoLock lock(mixer_state_lock_);
219ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        if (mixer_state_ != INITIALIZING)
220ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          break;
221ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
222ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      sleep(kInitRetrySleepSec);
223ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
224ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
22572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
22672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (success) {
22772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    BrowserThread::PostTask(
22872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        BrowserThread::UI, FROM_HERE,
22972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread));
23072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
23172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
23272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (callback) {
23372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    callback->Run(success);
23472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    delete callback;
23572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
23672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
23772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
23872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::InitThread() {
23972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
24072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
24172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != UNINITIALIZED)
24272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
24372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
24472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (thread_ == NULL) {
24572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
24672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_.reset(new base::Thread("AudioMixerAlsa"));
24772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if (!thread_->Start()) {
24872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      thread_.reset();
24972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return false;
25072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    }
25172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
25272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
25372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  mixer_state_ = INITIALIZING;
25472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return true;
25572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
25672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
25772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::InitPrefs() {
25872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  prefs_ = g_browser_process->local_state();
25972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
26072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
26172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::InitializeAlsaMixer() {
262ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // We can block; make sure that we're not on the UI thread.
263ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
264ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
26572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
26672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != INITIALIZING)
26772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
26872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
26972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  int err;
27072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_t* handle = NULL;
27172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  const char* card = "default";
27272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
27372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if ((err = snd_mixer_open(&handle, 0)) < 0) {
27472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err);
27572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
27672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
27772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
27872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if ((err = snd_mixer_attach(handle, card)) < 0) {
27972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "ALSA Attach to card " << card << " failed: "
28072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen               << snd_strerror(err);
28172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_close(handle);
28272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
28372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
28472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
285dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  // Verify PCM can be opened, which also instantiates the PCM mixer element
286dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  // which is needed for finer volume control and for muting by setting to zero.
287dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  // If it fails, we can still try to use the mixer as best we can.
288dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  snd_pcm_t* pcm_out_handle;
289dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if ((err = snd_pcm_open(&pcm_out_handle,
290dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                          card,
291dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                          SND_PCM_STREAM_PLAYBACK,
292dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                          0)) >= 0) {
293dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    snd_pcm_close(pcm_out_handle);
294dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  } else {
295dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "ALSA PCM open: " << snd_strerror(err);
296dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
297dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
29872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
29972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err);
30072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_close(handle);
30172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
30272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
30372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
30472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if ((err = snd_mixer_load(handle)) < 0) {
30572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "ALSA mixer " << card << " load error: %s"
30672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen               << snd_strerror(err);
30772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_close(handle);
30872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
30972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
31072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
31172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  VLOG(1) << "Opened ALSA mixer " << card << " OK";
31272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
31372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  elem_master_ = FindElementWithName_Locked(handle, kMasterVolume);
31472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (elem_master_) {
31572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100);
31672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100);
317dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    err = snd_mixer_selem_get_playback_dB_range(
318dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        elem_master_, &long_lo, &long_hi);
319dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (err != 0) {
320dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed "
321dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                   << "for master: " << snd_strerror(err);
322dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      snd_mixer_close(handle);
323dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return false;
324dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    }
32572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    min_volume_ = static_cast<double>(long_lo) / 100.0;
32672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    max_volume_ = static_cast<double>(long_hi) / 100.0;
32772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  } else {
32872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card;
32972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_close(handle);
33072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
33172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
33272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
33372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  elem_pcm_ = FindElementWithName_Locked(handle, kPCMVolume);
33472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (elem_pcm_) {
33572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100);
33672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100);
337dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    err = snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi);
338dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (err != 0) {
339dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed for PCM: "
340dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                   << snd_strerror(err);
341dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      snd_mixer_close(handle);
342dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return false;
343dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    }
34472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    min_volume_ += static_cast<double>(long_lo) / 100.0;
34572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    max_volume_ += static_cast<double>(long_hi) / 100.0;
34672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
34772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
34872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to "
34972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          << max_volume_ << " dB";
35072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
35172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_mixer_ = handle;
35272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  mixer_state_ = READY;
35372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return true;
35472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
35572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
35672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::FreeAlsaMixer() {
35772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (alsa_mixer_) {
35872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_close(alsa_mixer_);
35972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_mixer_ = NULL;
36072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
36172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  done_event_.Signal();
36272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
36372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
36472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::DoSetVolumeMute(double pref_volume, int pref_mute) {
36572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  base::AutoLock lock(mixer_state_lock_);
36672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mixer_state_ != READY)
36772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return;
36872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
36972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // If volume or mute are invalid, set them now to the current actual values.
37072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!PrefVolumeValid(pref_volume))
37172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    pref_volume = DoGetVolumeDb_Locked();
372dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  bool mute = false;
37372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (pref_mute == kPrefMuteInvalid)
37472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    mute = GetElementMuted_Locked(elem_master_);
37572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  else
37672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    mute = (pref_mute == kPrefMuteOn) ? true : false;
37772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
37872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  VLOG(1) << "Setting volume to " << pref_volume << " and mute to " << mute;
37972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
38072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (mute) {
38172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    save_volume_ = pref_volume;
38272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    DoSetVolumeDb_Locked(min_volume_);
38372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  } else {
38472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    DoSetVolumeDb_Locked(pref_volume);
38572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
38672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
38772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  SetElementMuted_Locked(elem_master_, mute);
38872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
38972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
39072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::RestoreVolumeMuteOnUIThread() {
39172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
39272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // This happens during init, so set the volume off the UI thread.
39372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  int mute = prefs_->GetInteger(prefs::kAudioMute);
39472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double vol = prefs_->GetDouble(prefs::kAudioVolume);
39572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  {
39672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    base::AutoLock lock(mixer_state_lock_);
39772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if (mixer_state_ == SHUTTING_DOWN)
39872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return;
39972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    thread_->message_loop()->PostTask(FROM_HERE,
40072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute, vol, mute));
40172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
40272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
40372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
40472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsendouble AudioMixerAlsa::DoGetVolumeDb_Locked() const {
40572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double vol_total = 0.0;
406dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (!GetElementVolume_Locked(elem_master_, &vol_total))
407dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return 0.0;
40872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
40972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double vol_pcm = 0.0;
410dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (elem_pcm_ && GetElementVolume_Locked(elem_pcm_, &vol_pcm))
41172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    vol_total += vol_pcm;
41272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
41372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return vol_total;
41472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
41572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
41672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) {
41772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double actual_vol = 0.0;
41872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
41972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // If a PCM volume slider exists, then first set the Master volume to the
42072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // nearest volume >= requested volume, then adjust PCM volume down to get
42172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // closer to the requested volume.
42272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (elem_pcm_) {
42372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f);
42472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f);
42572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  } else {
42672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f);
42772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
42872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
42972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
43072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsensnd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked(
43172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    snd_mixer_t* handle,
43272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const char* element_name) const {
43372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_selem_id_t* sid;
43472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
43572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // Using id_malloc/id_free API instead of id_alloca since the latter gives the
43672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  // warning: the address of 'sid' will always evaluate as 'true'.
43772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (snd_mixer_selem_id_malloc(&sid))
43872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return NULL;
43972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
44072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_selem_id_set_index(sid, 0);
44172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_selem_id_set_name(sid, element_name);
44272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
44372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!elem) {
44472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    LOG(ERROR) << "ALSA unable to find simple control "
44572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen               << snd_mixer_selem_id_get_name(sid);
44672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
44772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
44872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  snd_mixer_selem_id_free(sid);
44972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return elem;
45072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
45172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
45272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::GetElementVolume_Locked(snd_mixer_elem_t* elem,
45372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                             double* current_vol) const {
45472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t long_vol = 0;
455dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  int alsa_result = snd_mixer_selem_get_playback_dB(
456dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      elem, static_cast<snd_mixer_selem_channel_id_t>(0), &long_vol);
457dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
458dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_get_playback_dB() failed: "
459dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
460dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return false;
461dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
46272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
463dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  *current_vol = static_cast<double>(long_vol) / 100.0;
46472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return true;
46572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
46672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
46772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::SetElementVolume_Locked(snd_mixer_elem_t* elem,
46872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                             double new_vol,
46972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                             double* actual_vol,
47072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                             double rounding_bias) {
47172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t vol_lo = 0;
47272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t vol_hi = 0;
473dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  int alsa_result =
474dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi);
475dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
476dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_get_playback_volume_range() failed: "
477dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
478dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return false;
479dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
48072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t vol_range = vol_hi - vol_lo;
48172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (vol_range <= 0)
48272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
48372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
48472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t db_lo_int = 0;
48572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t db_hi_int = 0;
486dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  alsa_result =
487dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int);
488dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
489dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed: "
490dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
491dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return false;
492dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
493dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
49472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double db_lo = static_cast<double>(db_lo_int) / 100.0;
49572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double db_hi = static_cast<double>(db_hi_int) / 100.0;
49672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  double db_step = static_cast<double>(db_hi - db_lo) / vol_range;
49772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (db_step <= 0.0)
49872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return false;
49972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
50072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (new_vol < db_lo)
50172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    new_vol = db_lo;
50272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
50372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  alsa_long_t value = static_cast<alsa_long_t>(rounding_bias +
50472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      (new_vol - db_lo) / db_step) + vol_lo;
505dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  alsa_result = snd_mixer_selem_set_playback_volume_all(elem, value);
506dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
507dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_set_playback_volume_all() failed: "
508dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
509dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return false;
510dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
51172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
51272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem)
51372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo
51472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          << " dB";
51572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
51672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (actual_vol) {
51772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    alsa_long_t volume = vol_lo;
518dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    alsa_result = snd_mixer_selem_get_playback_volume(
519dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        elem, static_cast<snd_mixer_selem_channel_id_t>(0), &volume);
520dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (alsa_result != 0) {
521dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      LOG(WARNING) << "snd_mixer_selem_get_playback_volume() failed: "
522dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                   << snd_strerror(alsa_result);
523dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return false;
524dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    }
52572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    *actual_vol = db_lo + (volume - vol_lo) * db_step;
52672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
52772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem)
52872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen            << " now " << *actual_vol << " dB";
52972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  }
53072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return true;
53172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
53272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
53372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenbool AudioMixerAlsa::GetElementMuted_Locked(snd_mixer_elem_t* elem) const {
53472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  int enabled = 0;
535dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  int alsa_result = snd_mixer_selem_get_playback_switch(
536dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      elem, static_cast<snd_mixer_selem_channel_id_t>(0), &enabled);
537dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
538dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_get_playback_switch() failed: "
539dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
540dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return false;
541dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
54272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return (enabled) ? false : true;
54372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
54472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
54572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenvoid AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) {
54672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  int enabled = mute ? 0 : 1;
547dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  int alsa_result = snd_mixer_selem_set_playback_switch_all(elem, enabled);
548dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  if (alsa_result != 0) {
549dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    LOG(WARNING) << "snd_mixer_selem_set_playback_switch_all() failed: "
550dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                 << snd_strerror(alsa_result);
551dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  } else {
552dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
553dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen            << " to " << enabled;
554dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  }
55572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}
55672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
55772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen}  // namespace chromeos
558