Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(785)

Unified Diff: chrome/browser/chromeos/audio_mixer_alsa.cc

Issue 5859003: Add ALSA support to volume keys (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/browser/chromeos
Patch Set: using snd_mixer_selem_id_malloc Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/chromeos/audio_mixer_alsa.cc
diff --git a/chrome/browser/chromeos/audio_mixer_alsa.cc b/chrome/browser/chromeos/audio_mixer_alsa.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f3e49070f7907c1dc844f2a65698f1b9f4563b97
--- /dev/null
+++ b/chrome/browser/chromeos/audio_mixer_alsa.cc
@@ -0,0 +1,363 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/audio_mixer_alsa.h"
+
+#include <alsa/asoundlib.h>
+
+#include "base/logging.h"
+#include "base/task.h"
+
+namespace chromeos {
+
+// Connect to the ALSA mixer using their simple element API. Init is performed
+// asynchronously on the worker thread.
+//
+// To get a wider range and finer control over volume levels, first the Master
+// level is set, then if the PCM element exists, the total level is refined by
+// adjusting that as well. If the PCM element has more volume steps, it allows
+// for finer granularity in the total volume.
+
+// TODO(davej): Serialize volume/mute to preserve settings when restarting.
+
+typedef long alsa_long_t; // 'long' is required for ALSA API calls.
+
+namespace {
+
+const char* kMasterVolume = "Master";
+const char* kPCMVolume = "PCM";
+const double kDefaultMinVolume = -90.0;
+const double kDefaultMaxVolume = 0.0;
+
+} // namespace
+
+AudioMixerAlsa::AudioMixerAlsa()
+ : min_volume_(kDefaultMinVolume),
+ max_volume_(kDefaultMaxVolume),
+ save_volume_(0),
+ mixer_state_(UNINITIALIZED),
+ alsa_mixer_(NULL),
+ elem_master_(NULL),
+ elem_pcm_(NULL) {
+}
+
+AudioMixerAlsa::~AudioMixerAlsa() {
+ FreeAlsaMixer();
+ if (thread_ != NULL) {
+ thread_->Stop();
+ thread_.reset();
+ }
+}
+
+bool AudioMixerAlsa::Init(InitDoneCallback* callback) {
+ if (!InitThread())
+ return false;
scherkus (not reviewing) 2011/01/11 00:24:12 what happens to the callback?
davejcool 2011/01/11 02:52:54 Deleting callback if not used now.
+
+ // Post the task of starting up, which can block for 200-500ms,
+ // so best not to do it on the caller's thread.
+ thread_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback));
+ return true;
+}
+
+bool AudioMixerAlsa::InitSync() {
+ if (!InitThread())
+ return false;
+ return InitializeAlsaMixer();
+}
+
+double AudioMixerAlsa::GetVolumeDb() const {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return kSilenceDb;
+
+ return DoGetVolumeDb();
+}
+
+bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return false;
+ if (vol_min)
+ *vol_min = min_volume_;
+ if (vol_max)
+ *vol_max = max_volume_;
+ return true;
+}
+
+void AudioMixerAlsa::SetVolumeDb(double vol_db) {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return;
+ DoSetVolumeDb(vol_db);
+}
+
+bool AudioMixerAlsa::IsMute() const {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return false;
+ return GetElementMuted(elem_master_);
+}
+
+void AudioMixerAlsa::SetMute(bool mute) {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return;
+
+ // Set volume to minimum on mute, since switching the element off does not
+ // always mute as it should.
+
+ // TODO(davej): Setting volume to minimum can be removed once switching the
+ // element off can be guaranteed to work.
+
+ bool old_value = GetElementMuted(elem_master_);
+
+ if (old_value != mute) {
+ if (mute) {
+ save_volume_ = DoGetVolumeDb();
+ DoSetVolumeDb(min_volume_);
+ } else {
+ DoSetVolumeDb(save_volume_);
+ }
+ }
+
+ SetElementMuted(elem_master_, mute);
+ if (elem_pcm_)
+ SetElementMuted(elem_pcm_, mute);
+}
+
+AudioMixer::State AudioMixerAlsa::CheckState() const {
+ AutoLock lock(mixer_state_lock_);
+ // If we think it's ready, verify it is actually so.
+ if ((mixer_state_ == READY) && (alsa_mixer_ == NULL))
+ mixer_state_ = IN_ERROR;
+ return mixer_state_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private functions follow
+
+void AudioMixerAlsa::DoInit(InitDoneCallback* callback) {
+ bool success = InitializeAlsaMixer();
+
+ if (callback) {
+ callback->Run(success);
+ delete callback;
+ }
+}
+
+bool AudioMixerAlsa::InitThread() {
+ AutoLock lock(mixer_state_lock_);
+
+ if (mixer_state_ != UNINITIALIZED)
+ return false;
+
+ if (thread_ == NULL) {
+ thread_.reset(new base::Thread("AudioMixerAlsa"));
+ if (!thread_->Start()) {
+ thread_.reset();
+ return false;
+ }
+ }
+
+ mixer_state_ = INITIALIZING;
+ return true;
+}
+
+bool AudioMixerAlsa::InitializeAlsaMixer() {
+ AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != INITIALIZING)
+ return false;
+
+ int err;
+ snd_mixer_t* handle = NULL;
+ const char* card = "default";
+
+ if ((err = snd_mixer_open(&handle, 0)) < 0) {
+ LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err);
+ return false;
+ }
+
+ if ((err = snd_mixer_attach(handle, card)) < 0) {
+ LOG(ERROR) << "ALSA Attach to card " << card << " failed: "
+ << snd_strerror(err);
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
+ LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err);
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ if ((err = snd_mixer_load(handle)) < 0) {
+ LOG(ERROR) << "ALSA mixer " << card << " load error: %s"
+ << snd_strerror(err);
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ VLOG(1) << "Opened ALSA mixer " << card << " OK";
+
+ elem_master_ = FindElementWithName(handle, kMasterVolume);
+ if (elem_master_) {
+ alsa_long_t long_lo, long_hi;
+ snd_mixer_selem_get_playback_dB_range(elem_master_, &long_lo, &long_hi);
+ min_volume_ = static_cast<double>(long_lo) / 100.0;
+ max_volume_ = static_cast<double>(long_hi) / 100.0;
+ } else {
+ LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card;
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ elem_pcm_ = FindElementWithName(handle, kPCMVolume);
+ if (elem_pcm_) {
+ alsa_long_t long_lo, long_hi;
+ snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi);
+ min_volume_ += static_cast<double>(long_lo) / 100.0;
+ max_volume_ += static_cast<double>(long_hi) / 100.0;
+ }
+
+ VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to "
+ << max_volume_ << " dB";
+
+ alsa_mixer_ = handle;
+ mixer_state_ = READY;
+ return true;
+}
+
+void AudioMixerAlsa::FreeAlsaMixer() {
+ AutoLock lock(mixer_state_lock_);
+ mixer_state_ = SHUTTING_DOWN;
+ if (alsa_mixer_) {
+ snd_mixer_close(alsa_mixer_);
+ alsa_mixer_ = NULL;
+ }
+}
+
+double AudioMixerAlsa::DoGetVolumeDb() const {
+ double vol_total = 0.0;
+ GetElementVolume(elem_master_, &vol_total);
+
+ double vol_pcm = 0.0;
+ if (elem_pcm_ && (GetElementVolume(elem_pcm_, &vol_pcm)))
+ vol_total += vol_pcm;
+
+ return vol_total;
+}
+
+void AudioMixerAlsa::DoSetVolumeDb(double vol_db) {
+ double actual_vol = 0.0;
+
+ // If a PCM volume slider exists, then first set the Master volume to the
+ // nearest volume >= requested volume, then adjust PCM volume down to get
+ // closer to the requested volume.
+
+ if (elem_pcm_) {
+ SetElementVolume(elem_master_, vol_db, &actual_vol, 0.9999f);
+ SetElementVolume(elem_pcm_, vol_db - actual_vol, NULL, 0.5f);
+ } else {
+ SetElementVolume(elem_master_, vol_db, NULL, 0.5f);
+ }
+}
+
+snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName(
+ snd_mixer_t* handle,
+ const char* element_name) const {
+ snd_mixer_selem_id_t* sid;
+
+ // Using id_malloc/id_free API instead of id_alloca since the latter gives the
+ // warning: the address of 'sid' will always evaluate as 'true'
+ if (snd_mixer_selem_id_malloc(&sid))
+ return NULL;
+
davejcool 2011/01/06 23:19:12 Using the ALSA macro snd_mixer_selem_id_alloca(&si
+ snd_mixer_selem_id_set_index(sid, 0);
+ snd_mixer_selem_id_set_name(sid, element_name);
+ snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
+ if (!elem) {
+ LOG(ERROR) << "ALSA unable to find simple control "
+ << snd_mixer_selem_id_get_name(sid);
+ }
+
+ snd_mixer_selem_id_free(sid);
+ return elem;
+}
+
+bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* elem,
+ double* current_vol) const {
+ alsa_long_t long_vol = 0;
+ snd_mixer_selem_get_playback_dB(elem,
+ static_cast<snd_mixer_selem_channel_id_t>(0),
+ &long_vol);
+ *current_vol = static_cast<double>(long_vol) / 100.0;
+
+ return true;
+}
+
+bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* elem,
+ double new_vol,
+ double* actual_vol,
+ double rounding_bias) {
+ alsa_long_t vol_lo = 0;
+ alsa_long_t vol_hi = 0;
+ snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi);
+ alsa_long_t vol_range = vol_hi - vol_lo;
+ if (vol_range <= 0)
+ return false;
+
+ alsa_long_t db_lo_int = 0;
+ alsa_long_t db_hi_int = 0;
+ snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int);
+ double db_lo = static_cast<double>(db_lo_int) / 100.0;
+ double db_hi = static_cast<double>(db_hi_int) / 100.0;
+ double db_step = static_cast<double>(db_hi - db_lo) / vol_range;
+ if (db_step <= 0.0)
+ return false;
+
+ if (new_vol < db_lo)
+ new_vol = db_lo;
+
+ alsa_long_t value = static_cast<alsa_long_t>(rounding_bias +
+ (new_vol - db_lo) / db_step) + vol_lo;
+ snd_mixer_selem_set_playback_volume_all(elem, value);
+
+ VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem)
+ << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo
+ << " dB";
+
+ if (actual_vol) {
+ alsa_long_t volume;
+ snd_mixer_selem_get_playback_volume(
+ elem,
+ static_cast<snd_mixer_selem_channel_id_t>(0),
+ &volume);
+ *actual_vol = db_lo + (volume - vol_lo) * db_step;
+
+ VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem)
+ << " now " << *actual_vol << " dB";
+ }
+ return true;
+}
+
+bool AudioMixerAlsa::GetElementMuted(snd_mixer_elem_t* elem) const {
+ int enabled;
+ snd_mixer_selem_get_playback_switch(
+ elem,
+ static_cast<snd_mixer_selem_channel_id_t>(0),
+ &enabled);
+ return (enabled) ? false : true;
+}
+
+void AudioMixerAlsa::SetElementMuted(snd_mixer_elem_t* elem, bool mute) {
+ int enabled = mute ? 0 : 1;
+ snd_mixer_selem_set_playback_switch_all(elem, enabled);
+
+ VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
+ << " to " << enabled;
+}
+
+} // namespace chromeos
+

Powered by Google App Engine
This is Rietveld 408576698