summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/automation/testing_automation_provider_chromeos.cc4
-rw-r--r--chrome/browser/chromeos/audio_handler.cc204
-rw-r--r--chrome/browser/chromeos/audio_handler.h58
-rw-r--r--chrome/browser/chromeos/audio_mixer.h58
-rw-r--r--chrome/browser/chromeos/audio_mixer_alsa.cc564
-rw-r--r--chrome/browser/chromeos/audio_mixer_alsa.h144
-rw-r--r--chrome/browser/chromeos/system_key_event_listener.cc28
-rw-r--r--chrome/test/in_process_browser_test.cc4
8 files changed, 694 insertions, 370 deletions
diff --git a/chrome/browser/automation/testing_automation_provider_chromeos.cc b/chrome/browser/automation/testing_automation_provider_chromeos.cc
index f6c888e..d6d69ef 100644
--- a/chrome/browser/automation/testing_automation_provider_chromeos.cc
+++ b/chrome/browser/automation/testing_automation_provider_chromeos.cc
@@ -970,7 +970,7 @@ void TestingAutomationProvider::GetVolumeInfo(DictionaryValue* args,
scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance();
return_value->SetDouble("volume", audio_handler->GetVolumePercent());
- return_value->SetBoolean("is_mute", audio_handler->IsMuted());
+ return_value->SetBoolean("is_mute", audio_handler->IsMute());
AutomationJSONReply(this, reply_message).SendSuccess(return_value.get());
}
@@ -998,6 +998,6 @@ void TestingAutomationProvider::SetMute(DictionaryValue* args,
}
chromeos::AudioHandler* audio_handler = chromeos::AudioHandler::GetInstance();
- audio_handler->SetMuted(mute);
+ audio_handler->SetMute(mute);
reply.SendSuccess(NULL);
}
diff --git a/chrome/browser/chromeos/audio_handler.cc b/chrome/browser/chromeos/audio_handler.cc
index 890f2ee..30fbcf7 100644
--- a/chrome/browser/chromeos/audio_handler.cc
+++ b/chrome/browser/chromeos/audio_handler.cc
@@ -4,89 +4,225 @@
#include "chrome/browser/chromeos/audio_handler.h"
-#include <algorithm>
-#include <cmath>
+#include <math.h>
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "chrome/browser/chromeos/audio_mixer_alsa.h"
#include "content/browser/browser_thread.h"
-using std::max;
-using std::min;
-
namespace chromeos {
namespace {
+const double kMinVolumeDb = -90.0;
+// Choosing 6.0dB here instead of 0dB to give user chance to amplify audio some
+// in case sounds or their setup is too quiet for them.
+const double kMaxVolumeDb = 6.0;
// A value of less than one adjusts quieter volumes in larger steps (giving
// finer resolution in the higher volumes).
const double kVolumeBias = 0.5;
+// If a connection is lost, we try again this many times
+const int kMaxReconnectTries = 4;
+// A flag to disable mixer.
+bool g_disabled = false;
} // namespace
-bool AudioHandler::IsInitialized() {
- return mixer_->IsInitialized();
-}
+// chromeos: This class will set the volume using ALSA to adjust volume and
+// mute, and handle the volume level logic.
double AudioHandler::GetVolumePercent() {
+ if (!VerifyMixerConnection())
+ return 0;
+
return VolumeDbToPercent(mixer_->GetVolumeDb());
}
+// Set volume using our internal 0-100% range. Notice 0% is a special case of
+// silence, so we set the mixer volume to kSilenceDb instead of min_volume_db_.
void AudioHandler::SetVolumePercent(double volume_percent) {
- volume_percent = min(max(volume_percent, 0.0), 100.0);
- mixer_->SetVolumeDb(PercentToVolumeDb(volume_percent));
+ if (!VerifyMixerConnection())
+ return;
+ DCHECK_GE(volume_percent, 0.0);
+
+ double vol_db;
+ if (volume_percent <= 0)
+ vol_db = AudioMixer::kSilenceDb;
+ else
+ vol_db = PercentToVolumeDb(volume_percent);
+
+ mixer_->SetVolumeDb(vol_db);
}
void AudioHandler::AdjustVolumeByPercent(double adjust_by_percent) {
- const double old_volume_db = mixer_->GetVolumeDb();
- const double old_percent = VolumeDbToPercent(old_volume_db);
- SetVolumePercent(old_percent + adjust_by_percent);
+ if (!VerifyMixerConnection())
+ return;
+
+ DVLOG(1) << "Adjusting Volume by " << adjust_by_percent << " percent";
+
+ double volume = mixer_->GetVolumeDb();
+ double pct = VolumeDbToPercent(volume);
+
+ if (pct < 0)
+ pct = 0;
+ pct = pct + adjust_by_percent;
+ if (pct > 100.0)
+ pct = 100.0;
+
+ double new_volume;
+ if (pct <= 0.1)
+ new_volume = AudioMixer::kSilenceDb;
+ else
+ new_volume = PercentToVolumeDb(pct);
+
+ if (new_volume != volume)
+ mixer_->SetVolumeDb(new_volume);
+}
+
+bool AudioHandler::IsMute() {
+ if (!VerifyMixerConnection())
+ return false;
+
+ return mixer_->IsMute();
+}
+
+void AudioHandler::SetMute(bool do_mute) {
+ if (!VerifyMixerConnection())
+ return;
+ DVLOG(1) << "Setting Mute to " << do_mute;
+ mixer_->SetMute(do_mute);
}
-bool AudioHandler::IsMuted() {
- return mixer_->IsMuted();
+void AudioHandler::Disconnect() {
+ mixer_.reset();
}
-void AudioHandler::SetMuted(bool mute) {
- mixer_->SetMuted(mute);
+void AudioHandler::Disable() {
+ g_disabled = true;
+}
+
+bool AudioHandler::TryToConnect(bool async) {
+ if (mixer_type_ == MIXER_TYPE_ALSA) {
+ VLOG(1) << "Trying to connect to ALSA";
+ mixer_.reset(new AudioMixerAlsa());
+ } else {
+ VLOG(1) << "Cannot find valid volume mixer";
+ mixer_.reset();
+ return false;
+ }
+
+ if (async) {
+ mixer_->Init(NewCallback(this, &AudioHandler::OnMixerInitialized));
+ } else {
+ if (!mixer_->InitSync()) {
+ VLOG(1) << "Unable to reconnect to Mixer";
+ return false;
+ }
+ }
+ return true;
+}
+
+static void ClipVolume(double* min_volume, double* max_volume) {
+ if (*min_volume < kMinVolumeDb)
+ *min_volume = kMinVolumeDb;
+ if (*max_volume > kMaxVolumeDb)
+ *max_volume = kMaxVolumeDb;
+}
+
+void AudioHandler::OnMixerInitialized(bool success) {
+ connected_ = success;
+ DVLOG(1) << "OnMixerInitialized, success = " << success;
+
+ if (connected_) {
+ if (mixer_->GetVolumeLimits(&min_volume_db_, &max_volume_db_)) {
+ ClipVolume(&min_volume_db_, &max_volume_db_);
+ }
+ return;
+ }
+
+ VLOG(1) << "Unable to connect to mixer";
+ mixer_type_ = MIXER_TYPE_NONE;
+
+ // This frees the mixer on the UI thread
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this, &AudioHandler::TryToConnect, true));
}
AudioHandler::AudioHandler()
- : mixer_(new AudioMixerAlsa()) {
- mixer_->Init();
+ : connected_(false),
+ reconnect_tries_(0),
+ max_volume_db_(kMaxVolumeDb),
+ min_volume_db_(kMinVolumeDb),
+ mixer_type_(g_disabled ? MIXER_TYPE_NONE : MIXER_TYPE_ALSA) {
+ // Start trying to connect to mixers asynchronously, starting with the current
+ // mixer_type_. If the connection fails, another TryToConnect() for the next
+ // mixer will be posted at that time.
+ TryToConnect(true);
}
AudioHandler::~AudioHandler() {
- mixer_.reset();
+ Disconnect();
};
+bool AudioHandler::VerifyMixerConnection() {
+ if (mixer_ == NULL)
+ return false;
+
+ AudioMixer::State mixer_state = mixer_->GetState();
+ if (mixer_state == AudioMixer::READY)
+ return true;
+ if (connected_) {
+ // Something happened and the mixer is no longer valid after having been
+ // initialized earlier.
+ connected_ = false;
+ LOG(ERROR) << "Lost connection to mixer";
+ } else {
+ LOG(ERROR) << "Mixer not valid";
+ }
+
+ if ((mixer_state == AudioMixer::INITIALIZING) ||
+ (mixer_state == AudioMixer::SHUTTING_DOWN))
+ return false;
+
+ if (reconnect_tries_ < kMaxReconnectTries) {
+ reconnect_tries_++;
+ VLOG(1) << "Re-connecting to mixer attempt " << reconnect_tries_ << "/"
+ << kMaxReconnectTries;
+
+ connected_ = TryToConnect(false);
+
+ if (connected_) {
+ reconnect_tries_ = 0;
+ return true;
+ }
+ LOG(ERROR) << "Unable to re-connect to mixer";
+ }
+ return false;
+}
+
// VolumeDbToPercent() and PercentToVolumeDb() conversion functions allow us
// complete control over how the 0 to 100% range is mapped to actual loudness.
+// Volume range is from min_volume_db_ at just above 0% to max_volume_db_
+// at 100% with a special case at 0% which maps to kSilenceDb.
//
// The mapping is confined to these two functions to make it easy to adjust and
// have everything else just work. The range is biased to give finer resolution
// in the higher volumes if kVolumeBias is less than 1.0.
+// static
double AudioHandler::VolumeDbToPercent(double volume_db) const {
- double min_volume_db, max_volume_db;
- mixer_->GetVolumeLimits(&min_volume_db, &max_volume_db);
-
- if (volume_db < min_volume_db)
- return 0.0;
- // TODO(derat): Choose a better mapping between percent and decibels. The
- // bottom twenty-five percent or so is useless on a CR-48's internal speakers;
- // it's all inaudible.
- return 100.0 * pow((volume_db - min_volume_db) /
- (max_volume_db - min_volume_db), 1/kVolumeBias);
+ if (volume_db < min_volume_db_)
+ return 0;
+ return 100.0 * pow((volume_db - min_volume_db_) /
+ (max_volume_db_ - min_volume_db_), 1/kVolumeBias);
}
+// static
double AudioHandler::PercentToVolumeDb(double volume_percent) const {
- double min_volume_db, max_volume_db;
- mixer_->GetVolumeLimits(&min_volume_db, &max_volume_db);
-
return pow(volume_percent / 100.0, kVolumeBias) *
- (max_volume_db - min_volume_db) + min_volume_db;
+ (max_volume_db_ - min_volume_db_) + min_volume_db_;
}
// static
diff --git a/chrome/browser/chromeos/audio_handler.h b/chrome/browser/chromeos/audio_handler.h
index 0b286d5..a6b5c8b 100644
--- a/chrome/browser/chromeos/audio_handler.h
+++ b/chrome/browser/chromeos/audio_handler.h
@@ -21,35 +21,54 @@ class AudioHandler {
public:
static AudioHandler* GetInstance();
- // Is the mixer initialized?
- // TODO(derat): All of the volume-percent methods will produce "interesting"
- // results before the mixer is initialized, since the driver's volume range
- // isn't known at that point. This could be avoided if AudioMixer objects
- // instead took percentages and did their own conversions to decibels.
- bool IsInitialized();
-
- // Gets volume level in our internal 0-100% range, 0 being pure silence.
+ // Get volume level in our internal 0-100% range, 0 being pure silence.
+ // Returns default of 0 on error. This function will block until the volume
+ // is retrieved or fails. Blocking call.
double GetVolumePercent();
- // Sets volume level from 0-100%.
+ // Set volume level from 0-100%. Volumes above 100% are OK and boost volume,
+ // although clipping will occur more at higher volumes. Volume gets quieter
+ // as the percentage gets lower, and then switches to silence at 0%.
void SetVolumePercent(double volume_percent);
- // Adjusts volume up (positive percentage) or down (negative percentage).
+ // Adjust volume up (positive percentage) or down (negative percentage),
+ // capping at 100%. GetVolumePercent() will be accurate after this
+ // blocking call.
void AdjustVolumeByPercent(double adjust_by_percent);
- // Is the volume currently muted?
- bool IsMuted();
+ // Just returns true if mute, false if not or an error occurred.
+ // Blocking call.
+ bool IsMute();
- // Mutes or unmutes all audio.
- void SetMuted(bool do_mute);
+ // Mutes all audio. Non-blocking call.
+ void SetMute(bool do_mute);
+
+ // Disconnects from mixer. Called during shutdown.
+ void Disconnect();
private:
+ enum MixerType {
+ MIXER_TYPE_ALSA = 0,
+ MIXER_TYPE_NONE,
+ };
+
// Defines the delete on exit Singleton traits we like. Best to have this
// and constructor/destructor private as recommended for Singletons.
friend struct DefaultSingletonTraits<AudioHandler>;
+ friend class ::InProcessBrowserTest;
+ // Disable audio in browser tests. This is a workaround for the bug
+ // crosbug.com/17058. Remove this once it's fixed.
+ static void Disable();
+
+ // Connect to the current mixer_type_.
+ bool TryToConnect(bool async);
+
+ void OnMixerInitialized(bool success);
+
AudioHandler();
virtual ~AudioHandler();
+ bool VerifyMixerConnection();
// Conversion between our internal scaling (0-100%) and decibels.
double VolumeDbToPercent(double volume_db) const;
@@ -57,6 +76,17 @@ class AudioHandler {
scoped_ptr<AudioMixer> mixer_;
+ bool connected_;
+ int reconnect_tries_;
+
+ // The min and max volume in decibels, limited to the maximum range of the
+ // audio system being used.
+ double max_volume_db_;
+ double min_volume_db_;
+
+ // Which mixer is being used, ALSA or none.
+ MixerType mixer_type_;
+
DISALLOW_COPY_AND_ASSIGN(AudioHandler);
};
diff --git a/chrome/browser/chromeos/audio_mixer.h b/chrome/browser/chromeos/audio_mixer.h
index 3187ed5..f936c7c 100644
--- a/chrome/browser/chromeos/audio_mixer.h
+++ b/chrome/browser/chromeos/audio_mixer.h
@@ -13,38 +13,49 @@ namespace chromeos {
class AudioMixer {
public:
+ // Approximation of pure silence expressed in decibels.
+ static const double kSilenceDb;
+
+ enum State {
+ UNINITIALIZED = 0,
+ INITIALIZING,
+ READY,
+ SHUTTING_DOWN,
+ IN_ERROR,
+ };
+
AudioMixer() {}
virtual ~AudioMixer() {}
- // Initializes the connection to the device. This must be called on the UI
- // thread; blocking tasks may take place in the background. IsInitialized()
- // may be called to check if initialization is done.
- virtual void Init() = 0;
+ // Non-Blocking call. Connect to Mixer, find a default device, then call the
+ // callback when complete with success code.
+ typedef Callback1<bool>::Type InitDoneCallback;
+ virtual void Init(InitDoneCallback* callback) = 0;
- // Returns true if device initialization is complete.
- virtual bool IsInitialized() = 0;
+ // Call may block. Mixer will be connected before returning, unless error.
+ virtual bool InitSync() = 0;
- // Returns the actual volume range available, according to the mixer.
- // Values will be incorrect if called before initialization is complete.
- virtual void GetVolumeLimits(double* min_volume_db,
- double* max_volume_db) = 0;
+ // Call may block. Returns a default of kSilenceDb on error.
+ virtual double GetVolumeDb() const = 0;
- // Gets the most-recently-set volume, in decibels.
- virtual double GetVolumeDb() = 0;
+ // Non-Blocking call. Returns the actual volume limits possible according
+ // to the mixer. Limits are left unchanged on error
+ virtual bool GetVolumeLimits(double* vol_min, double* vol_max) = 0;
- // Sets the volume, in decibels.
- // If initialization is still in progress, the value will be applied once
- // initialization is complete.
- virtual void SetVolumeDb(double volume_db) = 0;
+ // Non-blocking call.
+ virtual void SetVolumeDb(double vol_db) = 0;
- // Gets the most-recently set mute state of the default device (true means
- // muted).
- virtual bool IsMuted() = 0;
+ // Call may block. Gets the mute state of the default device (true == mute).
+ // Returns a default of false on error.
+ virtual bool IsMute() const = 0;
- // Sets the mute state of the default device.
- // If initialization is still in progress, the value will be applied once
- // initialization is complete.
- virtual void SetMuted(bool mute) = 0;
+ // Non-Blocking call.
+ virtual void SetMute(bool mute) = 0;
+
+ // Returns READY if we have a valid working connection to the Mixer.
+ // This can return IN_ERROR if we lose the connection, even after an original
+ // successful init. Non-blocking call.
+ virtual State GetState() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(AudioMixer);
@@ -53,3 +64,4 @@ class AudioMixer {
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_AUDIO_MIXER_H_
+
diff --git a/chrome/browser/chromeos/audio_mixer_alsa.cc b/chrome/browser/chromeos/audio_mixer_alsa.cc
index 8e2df38..191bb58 100644
--- a/chrome/browser/chromeos/audio_mixer_alsa.cc
+++ b/chrome/browser/chromeos/audio_mixer_alsa.cc
@@ -4,186 +4,286 @@
#include "chrome/browser/chromeos/audio_mixer_alsa.h"
-#include <unistd.h>
-
-#include <algorithm>
#include <cmath>
+#include <unistd.h>
#include <alsa/asoundlib.h>
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/task.h"
-#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/pref_names.h"
#include "content/browser/browser_thread.h"
-typedef long alsa_long_t; // 'long' is required for ALSA API calls.
-
-using std::max;
-using std::min;
-
namespace chromeos {
-namespace {
+// 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.
-// Name of the ALSA card to which we connect.
-const char kCardName[] = "default";
-
-// Mixer element names.
-const char kMasterElementName[] = "Master";
-const char kPCMElementName[] = "PCM";
-
-// Default minimum and maximum volume (before we've loaded the actual range from
-// ALSA), in decibels.
-const double kDefaultMinVolumeDb = -90.0;
-const double kDefaultMaxVolumeDb = 0.0;
+typedef long alsa_long_t; // 'long' is required for ALSA API calls.
-// Default value assigned to the pref when it's first created, in decibels.
-const double kDefaultVolumeDb = -10.0;
+namespace {
-// Values used for muted preference.
+const char kMasterVolume[] = "Master";
+const char kPCMVolume[] = "PCM";
+const double kDefaultMinVolume = -90.0;
+const double kDefaultMaxVolume = 0.0;
+const double kPrefVolumeInvalid = -999.0;
const int kPrefMuteOff = 0;
const int kPrefMuteOn = 1;
+const int kPrefMuteInvalid = 2;
+
+// Maximum number of times that we'll attempt to initialize the mixer.
+// We'll fail until the ALSA modules have been loaded; see
+// http://crosbug.com/13162.
+const int kMaxInitAttempts = 20;
// Number of seconds that we'll sleep between each initialization attempt.
const int kInitRetrySleepSec = 1;
} // namespace
+// static
+const double AudioMixer::kSilenceDb = -200.0;
+
AudioMixerAlsa::AudioMixerAlsa()
- : min_volume_db_(kDefaultMinVolumeDb),
- max_volume_db_(kDefaultMaxVolumeDb),
- volume_db_(kDefaultVolumeDb),
- is_muted_(false),
- apply_is_pending_(true),
+ : min_volume_(kDefaultMinVolume),
+ max_volume_(kDefaultMaxVolume),
+ save_volume_(0),
+ mixer_state_(UNINITIALIZED),
alsa_mixer_(NULL),
- element_master_(NULL),
- element_pcm_(NULL),
+ elem_master_(NULL),
+ elem_pcm_(NULL),
prefs_(NULL),
- disconnected_event_(true, false) {
+ done_event_(true, false) {
}
AudioMixerAlsa::~AudioMixerAlsa() {
- if (!thread_.get())
- return;
- DCHECK(MessageLoop::current() != thread_->message_loop());
+ if (thread_ != NULL) {
+ {
+ base::AutoLock lock(mixer_state_lock_);
+ mixer_state_ = SHUTTING_DOWN;
+ thread_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioMixerAlsa::FreeAlsaMixer));
+ }
+ done_event_.Wait();
- thread_->message_loop()->PostTask(
- FROM_HERE, NewRunnableMethod(this, &AudioMixerAlsa::Disconnect));
- disconnected_event_.Wait();
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // A ScopedAllowIO object is required to join the thread when calling Stop.
+ // The worker thread should be idle at this time.
+ // See http://crosbug.com/11110 for discussion.
+ base::ThreadRestrictions::ScopedAllowIO allow_io_for_thread_join;
+ thread_->message_loop()->AssertIdle();
- base::ThreadRestrictions::ScopedAllowIO allow_io_for_thread_join;
- thread_->Stop();
- thread_.reset();
+ thread_->Stop();
+ thread_.reset();
+ }
}
-void AudioMixerAlsa::Init() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- prefs_ = g_browser_process->local_state();
- volume_db_ = prefs_->GetDouble(prefs::kAudioVolume);
- is_muted_ = prefs_->GetInteger(prefs::kAudioMute);
+void AudioMixerAlsa::Init(InitDoneCallback* callback) {
+ DCHECK(callback);
+ if (!InitThread()) {
+ callback->Run(false);
+ delete callback;
+ return;
+ }
+ InitPrefs();
+
+ {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ == SHUTTING_DOWN)
+ return;
- thread_.reset(new base::Thread("AudioMixerAlsa"));
- CHECK(thread_->Start());
- thread_->message_loop()->PostTask(
- FROM_HERE, NewRunnableMethod(this, &AudioMixerAlsa::Connect));
+ // Post the task of starting up, which may block on the order of ms,
+ // so best not to do it on the caller's thread.
+ thread_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback));
+ }
}
-bool AudioMixerAlsa::IsInitialized() {
- base::AutoLock lock(lock_);
- return alsa_mixer_ != NULL;
+bool AudioMixerAlsa::InitSync() {
+ if (!InitThread())
+ return false;
+ InitPrefs();
+ return InitializeAlsaMixer();
}
-void AudioMixerAlsa::GetVolumeLimits(double* min_volume_db,
- double* max_volume_db) {
- base::AutoLock lock(lock_);
- if (min_volume_db)
- *min_volume_db = min_volume_db_;
- if (max_volume_db)
- *max_volume_db = max_volume_db_;
+double AudioMixerAlsa::GetVolumeDb() const {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return kSilenceDb;
+
+ return DoGetVolumeDb_Locked();
}
-double AudioMixerAlsa::GetVolumeDb() {
- base::AutoLock lock(lock_);
- return volume_db_;
+bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) {
+ base::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 volume_db) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+void AudioMixerAlsa::SetVolumeDb(double vol_db) {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return;
- base::AutoLock lock(lock_);
- if (isnan(volume_db)) {
- LOG(WARNING) << "Got request to set volume to NaN";
- volume_db = min_volume_db_;
- } else {
- volume_db = min(max(volume_db, min_volume_db_), max_volume_db_);
+ if (vol_db < kSilenceDb || isnan(vol_db)) {
+ if (isnan(vol_db))
+ LOG(WARNING) << "Got request to set volume to NaN";
+ vol_db = kSilenceDb;
}
- prefs_->SetDouble(prefs::kAudioVolume, volume_db);
- volume_db_ = volume_db;
- if (!apply_is_pending_)
- thread_->message_loop()->PostTask(FROM_HERE,
- NewRunnableMethod(this, &AudioMixerAlsa::ApplyState));
+ DoSetVolumeDb_Locked(vol_db);
+ prefs_->SetDouble(prefs::kAudioVolume, vol_db);
}
-bool AudioMixerAlsa::IsMuted() {
- base::AutoLock lock(lock_);
- return is_muted_;
+bool AudioMixerAlsa::IsMute() const {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
+ return false;
+ return GetElementMuted_Locked(elem_master_);
}
-void AudioMixerAlsa::SetMuted(bool muted) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- prefs_->SetInteger(prefs::kAudioMute, muted ? kPrefMuteOn : kPrefMuteOff);
- base::AutoLock lock(lock_);
- is_muted_ = muted;
- if (!apply_is_pending_)
- thread_->message_loop()->PostTask(FROM_HERE,
- NewRunnableMethod(this, &AudioMixerAlsa::ApplyState));
+// To indicate the volume is not valid yet, a very low volume value is stored.
+// We compare against a slightly higher value in case of rounding errors.
+static bool PrefVolumeValid(double volume) {
+ return (volume > kPrefVolumeInvalid + 0.1);
+}
+
+void AudioMixerAlsa::SetMute(bool mute) {
+ base::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): Remove save_volume_ and setting volume to minimum if
+ // switching the element off can be guaranteed to mute it. Currently mute
+ // is done by setting the volume to min_volume_.
+
+ bool old_value = GetElementMuted_Locked(elem_master_);
+
+ if (old_value != mute) {
+ if (mute) {
+ save_volume_ = DoGetVolumeDb_Locked();
+ DoSetVolumeDb_Locked(min_volume_);
+ } else {
+ DoSetVolumeDb_Locked(save_volume_);
+ }
+ }
+
+ SetElementMuted_Locked(elem_master_, mute);
+ prefs_->SetInteger(prefs::kAudioMute, mute ? kPrefMuteOn : kPrefMuteOff);
+}
+
+AudioMixer::State AudioMixerAlsa::GetState() const {
+ base::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_;
}
// static
void AudioMixerAlsa::RegisterPrefs(PrefService* local_state) {
- // TODO(derat): Store audio volume percent instead of decibels.
if (!local_state->FindPreference(prefs::kAudioVolume))
local_state->RegisterDoublePref(prefs::kAudioVolume,
- kDefaultVolumeDb,
+ kPrefVolumeInvalid,
PrefService::UNSYNCABLE_PREF);
if (!local_state->FindPreference(prefs::kAudioMute))
local_state->RegisterIntegerPref(prefs::kAudioMute,
- kPrefMuteOff,
+ kPrefMuteInvalid,
PrefService::UNSYNCABLE_PREF);
}
-void AudioMixerAlsa::Connect() {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- DCHECK(!alsa_mixer_);
+////////////////////////////////////////////////////////////////////////////////
+// Private functions follow
+
+void AudioMixerAlsa::DoInit(InitDoneCallback* callback) {
+ bool success = false;
+ for (int num_attempts = 0; num_attempts < kMaxInitAttempts; ++num_attempts) {
+ success = InitializeAlsaMixer();
+ if (success) {
+ break;
+ } else {
+ // If the destructor has reset the state, give up.
+ {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != INITIALIZING)
+ break;
+ }
+ sleep(kInitRetrySleepSec);
+ }
+ }
+
+ if (success) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread));
+ }
+
+ if (callback) {
+ callback->Run(success);
+ delete callback;
+ }
+}
+
+bool AudioMixerAlsa::InitThread() {
+ base::AutoLock lock(mixer_state_lock_);
- if (disconnected_event_.IsSignaled())
- return;
+ if (mixer_state_ != UNINITIALIZED)
+ return false;
- if (!ConnectInternal()) {
- thread_->message_loop()->PostDelayedTask(FROM_HERE,
- NewRunnableMethod(this, &AudioMixerAlsa::Connect),
- kInitRetrySleepSec * 1000);
+ if (thread_ == NULL) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ thread_.reset(new base::Thread("AudioMixerAlsa"));
+ if (!thread_->Start()) {
+ thread_.reset();
+ return false;
+ }
}
+
+ mixer_state_ = INITIALIZING;
+ return true;
}
-bool AudioMixerAlsa::ConnectInternal() {
- DCHECK(MessageLoop::current() == thread_->message_loop());
+void AudioMixerAlsa::InitPrefs() {
+ prefs_ = g_browser_process->local_state();
+}
+
+bool AudioMixerAlsa::InitializeAlsaMixer() {
+ // We can block; make sure that we're not on the UI thread.
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::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 open error: " << snd_strerror(err);
+ LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err);
return false;
}
- if ((err = snd_mixer_attach(handle, kCardName)) < 0) {
- LOG(ERROR) << "ALSA Attach to card " << kCardName << " failed: "
+ 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;
@@ -194,7 +294,7 @@ bool AudioMixerAlsa::ConnectInternal() {
// If it fails, we can still try to use the mixer as best we can.
snd_pcm_t* pcm_out_handle;
if ((err = snd_pcm_open(&pcm_out_handle,
- kCardName,
+ card,
SND_PCM_STREAM_PLAYBACK,
0)) >= 0) {
snd_pcm_close(pcm_out_handle);
@@ -209,113 +309,135 @@ bool AudioMixerAlsa::ConnectInternal() {
}
if ((err = snd_mixer_load(handle)) < 0) {
- LOG(ERROR) << "ALSA mixer " << kCardName << " load error: %s"
+ LOG(ERROR) << "ALSA mixer " << card << " load error: %s"
<< snd_strerror(err);
snd_mixer_close(handle);
return false;
}
- VLOG(1) << "Opened ALSA mixer " << kCardName << " OK";
+ VLOG(1) << "Opened ALSA mixer " << card << " OK";
- double min_volume_db = kDefaultMinVolumeDb;
- double max_volume_db = kDefaultMaxVolumeDb;
-
- element_master_ = FindElementWithName(handle, kMasterElementName);
- if (element_master_) {
- alsa_long_t long_low = static_cast<alsa_long_t>(kDefaultMinVolumeDb * 100);
- alsa_long_t long_high = static_cast<alsa_long_t>(kDefaultMaxVolumeDb * 100);
+ elem_master_ = FindElementWithName_Locked(handle, kMasterVolume);
+ if (elem_master_) {
+ alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100);
+ alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100);
err = snd_mixer_selem_get_playback_dB_range(
- element_master_, &long_low, &long_high);
+ elem_master_, &long_lo, &long_hi);
if (err != 0) {
LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed "
- << "for " << kMasterElementName << ": " << snd_strerror(err);
+ << "for master: " << snd_strerror(err);
snd_mixer_close(handle);
return false;
}
- min_volume_db = static_cast<double>(long_low) / 100.0;
- max_volume_db = static_cast<double>(long_high) / 100.0;
+ min_volume_ = static_cast<double>(long_lo) / 100.0;
+ max_volume_ = static_cast<double>(long_hi) / 100.0;
} else {
- LOG(ERROR) << "Cannot find " << kMasterElementName
- << " ALSA mixer element on " << kCardName;
+ LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card;
snd_mixer_close(handle);
return false;
}
- element_pcm_ = FindElementWithName(handle, kPCMElementName);
- if (element_pcm_) {
- alsa_long_t long_low = static_cast<alsa_long_t>(kDefaultMinVolumeDb * 100);
- alsa_long_t long_high = static_cast<alsa_long_t>(kDefaultMaxVolumeDb * 100);
- err = snd_mixer_selem_get_playback_dB_range(
- element_pcm_, &long_low, &long_high);
+ elem_pcm_ = FindElementWithName_Locked(handle, kPCMVolume);
+ if (elem_pcm_) {
+ alsa_long_t long_lo = static_cast<alsa_long_t>(kDefaultMinVolume * 100);
+ alsa_long_t long_hi = static_cast<alsa_long_t>(kDefaultMaxVolume * 100);
+ err = snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi);
if (err != 0) {
- LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed for "
- << kPCMElementName << ": " << snd_strerror(err);
+ LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed for PCM: "
+ << snd_strerror(err);
snd_mixer_close(handle);
return false;
}
- min_volume_db += static_cast<double>(long_low) / 100.0;
- max_volume_db += static_cast<double>(long_high) / 100.0;
+ 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 << " dB to "
- << max_volume_db << " dB";
- {
- base::AutoLock lock(lock_);
- alsa_mixer_ = handle;
- min_volume_db_ = min_volume_db;
- max_volume_db_ = max_volume_db;
- volume_db_ = min(max(volume_db_, min_volume_db_), max_volume_db_);
- }
+ VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to "
+ << max_volume_ << " dB";
- ApplyState();
+ alsa_mixer_ = handle;
+ mixer_state_ = READY;
return true;
}
-void AudioMixerAlsa::Disconnect() {
- DCHECK(MessageLoop::current() == thread_->message_loop());
+void AudioMixerAlsa::FreeAlsaMixer() {
if (alsa_mixer_) {
snd_mixer_close(alsa_mixer_);
alsa_mixer_ = NULL;
}
- disconnected_event_.Signal();
+ done_event_.Signal();
}
-void AudioMixerAlsa::ApplyState() {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- if (!alsa_mixer_)
+void AudioMixerAlsa::DoSetVolumeMute(double pref_volume, int pref_mute) {
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ != READY)
return;
- bool should_mute = false;
- double new_volume_db = 0;
+ // If volume or mute are invalid, set them now to the current actual values.
+ if (!PrefVolumeValid(pref_volume))
+ pref_volume = DoGetVolumeDb_Locked();
+ bool mute = false;
+ if (pref_mute == kPrefMuteInvalid)
+ mute = GetElementMuted_Locked(elem_master_);
+ else
+ mute = (pref_mute == kPrefMuteOn) ? true : false;
+
+ VLOG(1) << "Setting volume to " << pref_volume << " and mute to " << mute;
+
+ if (mute) {
+ save_volume_ = pref_volume;
+ DoSetVolumeDb_Locked(min_volume_);
+ } else {
+ DoSetVolumeDb_Locked(pref_volume);
+ }
+
+ SetElementMuted_Locked(elem_master_, mute);
+}
+
+void AudioMixerAlsa::RestoreVolumeMuteOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // This happens during init, so set the volume off the UI thread.
+ int mute = prefs_->GetInteger(prefs::kAudioMute);
+ double vol = prefs_->GetDouble(prefs::kAudioVolume);
{
- base::AutoLock lock(lock_);
- should_mute = is_muted_;
- new_volume_db = should_mute ? min_volume_db_ : volume_db_;
- apply_is_pending_ = false;
+ base::AutoLock lock(mixer_state_lock_);
+ if (mixer_state_ == SHUTTING_DOWN)
+ return;
+ thread_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute, vol, mute));
}
+}
+
+double AudioMixerAlsa::DoGetVolumeDb_Locked() const {
+ double vol_total = 0.0;
+ if (!GetElementVolume_Locked(elem_master_, &vol_total))
+ return 0.0;
+
+ double vol_pcm = 0.0;
+ if (elem_pcm_ && GetElementVolume_Locked(elem_pcm_, &vol_pcm))
+ vol_total += vol_pcm;
+
+ return vol_total;
+}
- if (element_pcm_) {
- // 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.
- SetElementVolume(element_master_, new_volume_db, 0.9999f);
-
- double pcm_volume_db = 0.0;
- double master_volume_db = 0.0;
- if (GetElementVolume(element_master_, &master_volume_db))
- pcm_volume_db = new_volume_db - master_volume_db;
- SetElementVolume(element_pcm_, pcm_volume_db, 0.5f);
+void AudioMixerAlsa::DoSetVolumeDb_Locked(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_Locked(elem_master_, vol_db, &actual_vol, 0.9999f);
+ SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f);
} else {
- SetElementVolume(element_master_, new_volume_db, 0.5f);
+ SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f);
}
-
- SetElementMuted(element_master_, should_mute);
}
-snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName(
- snd_mixer_t* handle, const char* element_name) const {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- snd_mixer_selem_id_t* sid = NULL;
+snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked(
+ 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'.
@@ -324,92 +446,118 @@ snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName(
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, element_name);
- snd_mixer_elem_t* element = snd_mixer_find_selem(handle, sid);
- if (!element) {
+ 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 element;
+ return elem;
}
-bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* element,
- double* current_volume_db) {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- alsa_long_t long_volume = 0;
+bool AudioMixerAlsa::GetElementVolume_Locked(snd_mixer_elem_t* elem,
+ double* current_vol) const {
+ alsa_long_t long_vol = 0;
int alsa_result = snd_mixer_selem_get_playback_dB(
- element, static_cast<snd_mixer_selem_channel_id_t>(0), &long_volume);
+ elem, static_cast<snd_mixer_selem_channel_id_t>(0), &long_vol);
if (alsa_result != 0) {
LOG(WARNING) << "snd_mixer_selem_get_playback_dB() failed: "
<< snd_strerror(alsa_result);
return false;
}
- *current_volume_db = static_cast<double>(long_volume) / 100.0;
+
+ *current_vol = static_cast<double>(long_vol) / 100.0;
return true;
}
-bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* element,
- double new_volume_db,
- double rounding_bias) {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- alsa_long_t volume_low = 0;
- alsa_long_t volume_high = 0;
- int alsa_result = snd_mixer_selem_get_playback_volume_range(
- element, &volume_low, &volume_high);
+bool AudioMixerAlsa::SetElementVolume_Locked(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;
+ int alsa_result =
+ snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi);
if (alsa_result != 0) {
LOG(WARNING) << "snd_mixer_selem_get_playback_volume_range() failed: "
<< snd_strerror(alsa_result);
return false;
}
- alsa_long_t volume_range = volume_high - volume_low;
- if (volume_range <= 0)
+ alsa_long_t vol_range = vol_hi - vol_lo;
+ if (vol_range <= 0)
return false;
- alsa_long_t db_low_int = 0;
- alsa_long_t db_high_int = 0;
+ alsa_long_t db_lo_int = 0;
+ alsa_long_t db_hi_int = 0;
alsa_result =
- snd_mixer_selem_get_playback_dB_range(element, &db_low_int, &db_high_int);
+ snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int);
if (alsa_result != 0) {
LOG(WARNING) << "snd_mixer_selem_get_playback_dB_range() failed: "
<< snd_strerror(alsa_result);
return false;
}
- double db_low = static_cast<double>(db_low_int) / 100.0;
- double db_high = static_cast<double>(db_high_int) / 100.0;
- double db_step = static_cast<double>(db_high - db_low) / volume_range;
+ 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_volume_db < db_low)
- new_volume_db = db_low;
+ if (new_vol < db_lo)
+ new_vol = db_lo;
- alsa_long_t value = static_cast<alsa_long_t>(
- rounding_bias + (new_volume_db - db_low) / db_step) + volume_low;
- alsa_result = snd_mixer_selem_set_playback_volume_all(element, value);
+ alsa_long_t value = static_cast<alsa_long_t>(rounding_bias +
+ (new_vol - db_lo) / db_step) + vol_lo;
+ alsa_result = snd_mixer_selem_set_playback_volume_all(elem, value);
if (alsa_result != 0) {
LOG(WARNING) << "snd_mixer_selem_set_playback_volume_all() failed: "
<< snd_strerror(alsa_result);
return false;
}
- VLOG(1) << "Set volume " << snd_mixer_selem_get_name(element)
- << " to " << new_volume_db << " ==> "
- << (value - volume_low) * db_step + db_low << " dB";
+ 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 = vol_lo;
+ alsa_result = snd_mixer_selem_get_playback_volume(
+ elem, static_cast<snd_mixer_selem_channel_id_t>(0), &volume);
+ if (alsa_result != 0) {
+ LOG(WARNING) << "snd_mixer_selem_get_playback_volume() failed: "
+ << snd_strerror(alsa_result);
+ return false;
+ }
+ *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;
}
-void AudioMixerAlsa::SetElementMuted(snd_mixer_elem_t* element, bool mute) {
- DCHECK(MessageLoop::current() == thread_->message_loop());
- int alsa_result = snd_mixer_selem_set_playback_switch_all(element, !mute);
+bool AudioMixerAlsa::GetElementMuted_Locked(snd_mixer_elem_t* elem) const {
+ int enabled = 0;
+ int alsa_result = snd_mixer_selem_get_playback_switch(
+ elem, static_cast<snd_mixer_selem_channel_id_t>(0), &enabled);
+ if (alsa_result != 0) {
+ LOG(WARNING) << "snd_mixer_selem_get_playback_switch() failed: "
+ << snd_strerror(alsa_result);
+ return false;
+ }
+ return (enabled) ? false : true;
+}
+
+void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) {
+ int enabled = mute ? 0 : 1;
+ int alsa_result = snd_mixer_selem_set_playback_switch_all(elem, enabled);
if (alsa_result != 0) {
LOG(WARNING) << "snd_mixer_selem_set_playback_switch_all() failed: "
<< snd_strerror(alsa_result);
} else {
- VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(element)
- << " to " << mute;
+ VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
+ << " to " << enabled;
}
}
diff --git a/chrome/browser/chromeos/audio_mixer_alsa.h b/chrome/browser/chromeos/audio_mixer_alsa.h
index bd24e0d..4fc6274 100644
--- a/chrome/browser/chromeos/audio_mixer_alsa.h
+++ b/chrome/browser/chromeos/audio_mixer_alsa.h
@@ -21,93 +21,87 @@ struct _snd_mixer;
namespace chromeos {
-// Simple wrapper around ALSA's mixer functions.
-// Interaction with ALSA takes place on a background thread.
class AudioMixerAlsa : public AudioMixer {
public:
AudioMixerAlsa();
virtual ~AudioMixerAlsa();
- // AudioMixer implementation.
- virtual void Init();
- virtual bool IsInitialized();
- virtual void GetVolumeLimits(double* min_volume_db, double* max_volume_db);
- virtual double GetVolumeDb();
- virtual void SetVolumeDb(double volume_db);
- virtual bool IsMuted();
- virtual void SetMuted(bool muted);
-
- // Registers volume and mute preferences.
- // TODO(derat): Move prefs into AudioHandler.
+ // Implementation of AudioMixer
+ virtual void Init(InitDoneCallback* callback);
+ virtual bool InitSync();
+ virtual double GetVolumeDb() const;
+ virtual bool GetVolumeLimits(double* vol_min, double* vol_max);
+ virtual void SetVolumeDb(double vol_db);
+ virtual bool IsMute() const;
+ virtual void SetMute(bool mute);
+ virtual State GetState() const;
+
+ // Registers volume and mute in preferences
static void RegisterPrefs(PrefService* local_state);
private:
- // Tries to connect to ALSA. On failure, posts a delayed Connect() task to
- // try again.
- void Connect();
-
- // Helper method called by Connect(). On success, initializes
- // |min_volume_db_|, |max_volume_db_|, |alsa_mixer_|, and |element_*| and
- // returns true.
- bool ConnectInternal();
-
- // Disconnects from ALSA if currently connected and signals
- // |disconnected_event_|.
- void Disconnect();
-
- // Updates |alsa_mixer_| for current values of |volume_db_| and |is_muted_|.
- // No-op if not connected.
- void ApplyState();
-
- // Finds the element named |element_name|. Returns NULL on failure.
- _snd_mixer_elem* FindElementWithName(_snd_mixer* handle,
- const char* element_name) const;
-
- // Queries |element|'s current volume, copying it to |new_volume_db|.
- // Returns true on success.
- bool GetElementVolume(_snd_mixer_elem* element, double* current_volume_db);
-
- // Sets |element|'s volume. Since volume is done in steps, we may not get the
- // exact volume asked for. |rounding_bias| is added in before rounding to the
- // nearest volume step (use 0.5 to round to nearest).
- bool SetElementVolume(_snd_mixer_elem* element,
- double new_volume_db,
- double rounding_bias);
-
- // Mutes or unmutes |element|.
- void SetElementMuted(_snd_mixer_elem* element, bool mute);
-
- // Guards |min_volume_db_|, |max_volume_db_|, |volume_db_|, |is_muted_|, and
- // |apply_is_pending_|.
- base::Lock lock_;
-
- // Volume range limits are computed once in ConnectInternal().
- double min_volume_db_;
- double max_volume_db_;
-
- // Most recently requested volume, in decibels. This variable is updated
- // immediately by GetVolumeDb(); the actual mixer volume is updated later on
- // |thread_| by ApplyState().
- double volume_db_;
-
- // Most recently requested muting state.
- bool is_muted_;
-
- // Is there already a pending call to ApplyState() scheduled on |thread_|?
- bool apply_is_pending_;
-
- // Cached context for use in ALSA calls. NULL if not connected.
+ // Called to do initialization in background from worker thread.
+ void DoInit(InitDoneCallback* callback);
+
+ // Helper functions to get our message loop thread and prefs initialized.
+ bool InitThread();
+ void InitPrefs();
+
+ // Try to connect to the ALSA mixer through their simple controls interface,
+ // and cache mixer handle and mixer elements we'll be using.
+ bool InitializeAlsaMixer();
+ void FreeAlsaMixer();
+ void DoSetVolumeMute(double pref_volume, int pref_mute);
+
+ // Access to PrefMember variables must be done on UI thread.
+ void RestoreVolumeMuteOnUIThread();
+
+ // All these internal volume commands must be called with the lock held.
+ double DoGetVolumeDb_Locked() const;
+ void DoSetVolumeDb_Locked(double vol_db);
+
+ _snd_mixer_elem* FindElementWithName_Locked(_snd_mixer* handle,
+ const char* element_name) const;
+
+ bool GetElementVolume_Locked(_snd_mixer_elem* elem,
+ double* current_vol) const;
+
+ // Since volume is done in steps, we may not get the exact volume asked for,
+ // so actual_vol will contain the true volume that was set. This information
+ // can be used to further refine the volume by adjust a different mixer
+ // element. The rounding_bias is added in before rounding to the nearest
+ // volume step (use 0.5 to round to nearest).
+ bool SetElementVolume_Locked(_snd_mixer_elem* elem,
+ double new_vol,
+ double* actual_vol,
+ double rounding_bias);
+
+ // In ALSA, the mixer element's 'switch' is turned off to mute.
+ // GetElementMuted_Locked() returns false on failure.
+ bool GetElementMuted_Locked(_snd_mixer_elem* elem) const;
+ void SetElementMuted_Locked(_snd_mixer_elem* elem, bool mute);
+
+ // Volume range limits are computed once during InitializeAlsaMixer.
+ double min_volume_;
+ double max_volume_;
+
+ // Muting is done by setting volume to minimum, so we must save the original.
+ // This is the only state information kept in this object. In some cases,
+ // ALSA can report it has a volume switch and we can turn it off, but it has
+ // no effect.
+ double save_volume_;
+
+ mutable base::Lock mixer_state_lock_;
+ mutable State mixer_state_;
+
+ // Cached contexts for use in ALSA calls.
_snd_mixer* alsa_mixer_;
- _snd_mixer_elem* element_master_;
- _snd_mixer_elem* element_pcm_;
+ _snd_mixer_elem* elem_master_;
+ _snd_mixer_elem* elem_pcm_;
PrefService* prefs_;
+ base::WaitableEvent done_event_;
- // Signalled after Disconnect() finishes (which is itself invoked by the
- // d'tor).
- base::WaitableEvent disconnected_event_;
-
- // Background thread used for interacting with ALSA.
scoped_ptr<base::Thread> thread_;
DISALLOW_COPY_AND_ASSIGN(AudioMixerAlsa);
diff --git a/chrome/browser/chromeos/system_key_event_listener.cc b/chrome/browser/chromeos/system_key_event_listener.cc
index 09049f6..4625697 100644
--- a/chrome/browser/chromeos/system_key_event_listener.cc
+++ b/chrome/browser/chromeos/system_key_event_listener.cc
@@ -97,6 +97,7 @@ void SystemKeyEventListener::Stop() {
#else
gdk_window_remove_filter(NULL, GdkEventFilter, this);
#endif
+ audio_handler_->Disconnect();
stopped_ = true;
}
@@ -109,6 +110,7 @@ void SystemKeyEventListener::RemoveCapsLockObserver(
caps_lock_observers_.RemoveObserver(observer);
}
+
void SystemKeyEventListener::ProcessWmMessage(const WmIpc::Message& message,
GdkWindow* window) {
if (message.type() != WM_IPC_MESSAGE_CHROME_NOTIFY_SYSKEY_PRESSED)
@@ -162,22 +164,24 @@ void SystemKeyEventListener::GrabKey(int32 key, uint32 mask) {
True, GrabModeAsync, GrabModeAsync);
}
-void SystemKeyEventListener::OnVolumeMute() {
- if (!audio_handler_->IsInitialized())
- return;
+// TODO(davej): Move the ShowBubble() calls in to AudioHandler so that this
+// function returns faster without blocking on GetVolumePercent(), and still
+// guarantees that the volume displayed will be that after the adjustment.
+// TODO(davej): The IsMute() check can also be made non-blocking by changing to
+// an AdjustVolumeByPercentOrUnmute() function which can do the steps off of
+// this thread when ShowBubble() is moved in to AudioHandler.
+
+void SystemKeyEventListener::OnVolumeMute() {
// Always muting (and not toggling) as per final decision on
// http://crosbug.com/3751
- audio_handler_->SetMuted(true);
+ audio_handler_->SetMute(true);
VolumeBubble::GetInstance()->ShowBubble(0);
BrightnessBubble::GetInstance()->HideBubble();
}
void SystemKeyEventListener::OnVolumeDown() {
- if (!audio_handler_->IsInitialized())
- return;
-
- if (audio_handler_->IsMuted()) {
+ if (audio_handler_->IsMute()) {
VolumeBubble::GetInstance()->ShowBubble(0);
} else {
audio_handler_->AdjustVolumeByPercent(-kStepPercentage);
@@ -188,14 +192,10 @@ void SystemKeyEventListener::OnVolumeDown() {
}
void SystemKeyEventListener::OnVolumeUp() {
- if (!audio_handler_->IsInitialized())
- return;
-
- if (audio_handler_->IsMuted())
- audio_handler_->SetMuted(false);
+ if (audio_handler_->IsMute())
+ audio_handler_->SetMute(false);
else
audio_handler_->AdjustVolumeByPercent(kStepPercentage);
-
VolumeBubble::GetInstance()->ShowBubble(
audio_handler_->GetVolumePercent());
BrightnessBubble::GetInstance()->HideBubble();
diff --git a/chrome/test/in_process_browser_test.cc b/chrome/test/in_process_browser_test.cc
index 8575d1f..195ed7e 100644
--- a/chrome/test/in_process_browser_test.cc
+++ b/chrome/test/in_process_browser_test.cc
@@ -129,6 +129,10 @@ void InProcessBrowserTest::SetUp() {
// Make sure that the log directory exists.
FilePath log_dir = logging::GetSessionLogFile(*command_line).DirName();
file_util::CreateDirectory(log_dir);
+
+ // Disable audio mixer as it can cause hang.
+ // see http://crosbug.com/17058.
+ chromeos::AudioHandler::Disable();
#endif // defined(OS_CHROMEOS)
SandboxInitWrapper sandbox_wrapper;