diff options
-rw-r--r-- | chrome/browser/automation/testing_automation_provider_chromeos.cc | 4 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_handler.cc | 204 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_handler.h | 58 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_mixer.h | 58 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_mixer_alsa.cc | 564 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_mixer_alsa.h | 144 | ||||
-rw-r--r-- | chrome/browser/chromeos/system_key_event_listener.cc | 28 | ||||
-rw-r--r-- | chrome/test/in_process_browser_test.cc | 4 |
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; |