Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/audio_mixer_alsa.h" | 5 #include "chrome/browser/chromeos/audio_mixer_alsa.h" |
| 6 | 6 |
| 7 #include <alsa/asoundlib.h> | 7 #include <alsa/asoundlib.h> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/task.h" | 10 #include "base/task.h" |
| 11 #include "chrome/browser/browser_process.h" | |
| 12 #include "chrome/browser/browser_thread.h" | |
| 13 #include "chrome/browser/prefs/pref_service.h" | |
| 14 #include "chrome/common/pref_names.h" | |
| 11 | 15 |
| 12 namespace chromeos { | 16 namespace chromeos { |
| 13 | 17 |
| 14 // Connect to the ALSA mixer using their simple element API. Init is performed | 18 // Connect to the ALSA mixer using their simple element API. Init is performed |
| 15 // asynchronously on the worker thread. | 19 // asynchronously on the worker thread. |
| 16 // | 20 // |
| 17 // To get a wider range and finer control over volume levels, first the Master | 21 // To get a wider range and finer control over volume levels, first the Master |
| 18 // level is set, then if the PCM element exists, the total level is refined by | 22 // level is set, then if the PCM element exists, the total level is refined by |
| 19 // adjusting that as well. If the PCM element has more volume steps, it allows | 23 // adjusting that as well. If the PCM element has more volume steps, it allows |
| 20 // for finer granularity in the total volume. | 24 // for finer granularity in the total volume. |
| 21 | 25 |
| 22 // TODO(davej): Serialize volume/mute to preserve settings when restarting. | |
| 23 | |
| 24 typedef long alsa_long_t; // 'long' is required for ALSA API calls. | 26 typedef long alsa_long_t; // 'long' is required for ALSA API calls. |
| 25 | 27 |
| 26 namespace { | 28 namespace { |
| 27 | 29 |
| 28 const char* kMasterVolume = "Master"; | 30 const char* kMasterVolume = "Master"; |
| 29 const char* kPCMVolume = "PCM"; | 31 const char* kPCMVolume = "PCM"; |
| 30 const double kDefaultMinVolume = -90.0; | 32 const double kDefaultMinVolume = -90.0; |
| 31 const double kDefaultMaxVolume = 0.0; | 33 const double kDefaultMaxVolume = 0.0; |
| 34 const double kPrefVolumeNotSet = AudioMixer::kSilenceDb - 100.0; | |
| 32 | 35 |
| 33 } // namespace | 36 } // namespace |
| 34 | 37 |
| 35 AudioMixerAlsa::AudioMixerAlsa() | 38 AudioMixerAlsa::AudioMixerAlsa() |
| 36 : min_volume_(kDefaultMinVolume), | 39 : min_volume_(kDefaultMinVolume), |
| 37 max_volume_(kDefaultMaxVolume), | 40 max_volume_(kDefaultMaxVolume), |
| 38 save_volume_(0), | 41 save_volume_(0), |
| 39 mixer_state_(UNINITIALIZED), | 42 mixer_state_(UNINITIALIZED), |
| 40 alsa_mixer_(NULL), | 43 alsa_mixer_(NULL), |
| 41 elem_master_(NULL), | 44 elem_master_(NULL), |
| 42 elem_pcm_(NULL) { | 45 elem_pcm_(NULL) { |
| 43 } | 46 } |
| 44 | 47 |
| 45 AudioMixerAlsa::~AudioMixerAlsa() { | 48 AudioMixerAlsa::~AudioMixerAlsa() { |
| 46 FreeAlsaMixer(); | 49 FreeAlsaMixer(); |
| 47 if (thread_ != NULL) { | 50 if (thread_ != NULL) { |
| 48 thread_->Stop(); | 51 thread_->Stop(); |
| 49 thread_.reset(); | 52 thread_.reset(); |
| 50 } | 53 } |
| 51 } | 54 } |
| 52 | 55 |
| 53 void AudioMixerAlsa::Init(InitDoneCallback* callback) { | 56 void AudioMixerAlsa::Init(InitDoneCallback* callback) { |
| 54 DCHECK(callback); | 57 DCHECK(callback); |
| 55 if (!InitThread()) { | 58 if (!InitThread()) { |
| 56 callback->Run(false); | 59 callback->Run(false); |
| 57 delete callback; | 60 delete callback; |
| 58 return; | 61 return; |
| 59 } | 62 } |
| 63 InitPrefs(); | |
| 60 | 64 |
| 61 // Post the task of starting up, which can block for 200-500ms, | 65 // Post the task of starting up, which may block on the order of ms, |
| 62 // so best not to do it on the caller's thread. | 66 // so best not to do it on the caller's thread. |
| 63 thread_->message_loop()->PostTask(FROM_HERE, | 67 thread_->message_loop()->PostTask(FROM_HERE, |
| 64 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); | 68 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); |
| 65 } | 69 } |
| 66 | 70 |
| 67 bool AudioMixerAlsa::InitSync() { | 71 bool AudioMixerAlsa::InitSync() { |
| 68 if (!InitThread()) | 72 if (!InitThread()) |
| 69 return false; | 73 return false; |
| 74 InitPrefs(); | |
| 70 return InitializeAlsaMixer(); | 75 return InitializeAlsaMixer(); |
| 71 } | 76 } |
| 72 | 77 |
| 73 double AudioMixerAlsa::GetVolumeDb() const { | 78 double AudioMixerAlsa::GetVolumeDb() const { |
| 74 AutoLock lock(mixer_state_lock_); | 79 AutoLock lock(mixer_state_lock_); |
| 75 if (mixer_state_ != READY) | 80 if (mixer_state_ != READY) |
| 76 return kSilenceDb; | 81 return kSilenceDb; |
| 77 | 82 |
| 78 return DoGetVolumeDb_Locked(); | 83 return DoGetVolumeDb_Locked(); |
| 79 } | 84 } |
| 80 | 85 |
| 81 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { | 86 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { |
| 82 AutoLock lock(mixer_state_lock_); | 87 AutoLock lock(mixer_state_lock_); |
| 83 if (mixer_state_ != READY) | 88 if (mixer_state_ != READY) |
| 84 return false; | 89 return false; |
| 85 if (vol_min) | 90 if (vol_min) |
| 86 *vol_min = min_volume_; | 91 *vol_min = min_volume_; |
| 87 if (vol_max) | 92 if (vol_max) |
| 88 *vol_max = max_volume_; | 93 *vol_max = max_volume_; |
| 89 return true; | 94 return true; |
| 90 } | 95 } |
| 91 | 96 |
| 92 void AudioMixerAlsa::SetVolumeDb(double vol_db) { | 97 void AudioMixerAlsa::SetVolumeDb(double vol_db) { |
| 93 AutoLock lock(mixer_state_lock_); | 98 AutoLock lock(mixer_state_lock_); |
| 94 if (mixer_state_ != READY) | 99 if (mixer_state_ != READY) |
| 95 return; | 100 return; |
| 101 if (vol_db < kSilenceDb) | |
| 102 vol_db = kSilenceDb; | |
| 96 DoSetVolumeDb_Locked(vol_db); | 103 DoSetVolumeDb_Locked(vol_db); |
| 104 volume_pref_.SetValue(vol_db); | |
| 97 } | 105 } |
| 98 | 106 |
| 99 bool AudioMixerAlsa::IsMute() const { | 107 bool AudioMixerAlsa::IsMute() const { |
| 100 AutoLock lock(mixer_state_lock_); | 108 AutoLock lock(mixer_state_lock_); |
| 101 if (mixer_state_ != READY) | 109 if (mixer_state_ != READY) |
| 102 return false; | 110 return false; |
| 103 return GetElementMuted_Locked(elem_master_); | 111 return GetElementMuted_Locked(elem_master_); |
| 104 } | 112 } |
| 105 | 113 |
| 106 void AudioMixerAlsa::SetMute(bool mute) { | 114 void AudioMixerAlsa::SetMute(bool mute) { |
| 107 AutoLock lock(mixer_state_lock_); | 115 AutoLock lock(mixer_state_lock_); |
| 108 if (mixer_state_ != READY) | 116 if (mixer_state_ != READY) |
| 109 return; | 117 return; |
| 110 | 118 |
| 111 // Set volume to minimum on mute, since switching the element off does not | 119 // Set volume to minimum on mute, since switching the element off does not |
| 112 // always mute as it should. | 120 // always mute as it should. |
| 113 | 121 |
| 114 // TODO(davej): Setting volume to minimum can be removed once switching the | 122 // TODO(davej): Remove save_volume_ and setting volume to minimum if |
| 115 // element off can be guaranteed to work. | 123 // switching the element off can be guaranteed to mute it. |
| 116 | 124 |
| 117 bool old_value = GetElementMuted_Locked(elem_master_); | 125 bool old_value = GetElementMuted_Locked(elem_master_); |
| 118 | 126 |
| 119 if (old_value != mute) { | 127 if (old_value != mute) { |
| 120 if (mute) { | 128 if (mute) { |
| 121 save_volume_ = DoGetVolumeDb_Locked(); | 129 save_volume_ = DoGetVolumeDb_Locked(); |
| 122 DoSetVolumeDb_Locked(min_volume_); | 130 DoSetVolumeDb_Locked(min_volume_); |
| 123 } else { | 131 } else { |
| 124 DoSetVolumeDb_Locked(save_volume_); | 132 DoSetVolumeDb_Locked(save_volume_); |
| 125 } | 133 } |
| 126 } | 134 } |
| 127 | 135 |
| 128 SetElementMuted_Locked(elem_master_, mute); | 136 SetElementMuted_Locked(elem_master_, mute); |
| 129 if (elem_pcm_) | 137 if (elem_pcm_) |
| 130 SetElementMuted_Locked(elem_pcm_, mute); | 138 SetElementMuted_Locked(elem_pcm_, mute); |
| 139 mute_pref_.SetValue(mute); | |
| 131 } | 140 } |
| 132 | 141 |
| 133 AudioMixer::State AudioMixerAlsa::GetState() const { | 142 AudioMixer::State AudioMixerAlsa::GetState() const { |
| 134 AutoLock lock(mixer_state_lock_); | 143 AutoLock lock(mixer_state_lock_); |
| 135 // If we think it's ready, verify it is actually so. | 144 // If we think it's ready, verify it is actually so. |
| 136 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) | 145 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) |
| 137 mixer_state_ = IN_ERROR; | 146 mixer_state_ = IN_ERROR; |
| 138 return mixer_state_; | 147 return mixer_state_; |
| 139 } | 148 } |
| 140 | 149 |
| 150 // static | |
| 151 void AudioMixerAlsa::RegisterPrefs(PrefService* local_state) { | |
| 152 if (!local_state->FindPreference(prefs::kAudioVolume)) { | |
|
scherkus (not reviewing)
2011/01/14 23:38:50
nit: what about checking for kAudioMute?
davejcool
2011/01/15 03:49:07
Hmmmm... This unraveled an issue with the case of
| |
| 153 local_state->RegisterRealPref(prefs::kAudioVolume, kPrefVolumeNotSet); | |
| 154 local_state->RegisterBooleanPref(prefs::kAudioMute, false); | |
| 155 } | |
| 156 } | |
| 157 | |
| 141 //////////////////////////////////////////////////////////////////////////////// | 158 //////////////////////////////////////////////////////////////////////////////// |
| 142 // Private functions follow | 159 // Private functions follow |
| 143 | 160 |
| 144 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { | 161 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { |
| 145 bool success = InitializeAlsaMixer(); | 162 bool success = InitializeAlsaMixer(); |
| 146 | 163 |
| 164 if (success) { | |
| 165 BrowserThread::PostTask( | |
| 166 BrowserThread::UI, FROM_HERE, | |
| 167 NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread)); | |
| 168 } | |
| 169 | |
| 147 if (callback) { | 170 if (callback) { |
| 148 callback->Run(success); | 171 callback->Run(success); |
| 149 delete callback; | 172 delete callback; |
| 150 } | 173 } |
| 151 } | 174 } |
| 152 | 175 |
| 153 bool AudioMixerAlsa::InitThread() { | 176 bool AudioMixerAlsa::InitThread() { |
| 154 AutoLock lock(mixer_state_lock_); | 177 AutoLock lock(mixer_state_lock_); |
| 155 | 178 |
| 156 if (mixer_state_ != UNINITIALIZED) | 179 if (mixer_state_ != UNINITIALIZED) |
| 157 return false; | 180 return false; |
| 158 | 181 |
| 159 if (thread_ == NULL) { | 182 if (thread_ == NULL) { |
| 160 thread_.reset(new base::Thread("AudioMixerAlsa")); | 183 thread_.reset(new base::Thread("AudioMixerAlsa")); |
| 161 if (!thread_->Start()) { | 184 if (!thread_->Start()) { |
| 162 thread_.reset(); | 185 thread_.reset(); |
| 163 return false; | 186 return false; |
| 164 } | 187 } |
| 165 } | 188 } |
| 166 | 189 |
| 167 mixer_state_ = INITIALIZING; | 190 mixer_state_ = INITIALIZING; |
| 168 return true; | 191 return true; |
| 169 } | 192 } |
| 170 | 193 |
| 194 void AudioMixerAlsa::InitPrefs() { | |
| 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 196 PrefService* prefs = g_browser_process->local_state(); | |
| 197 volume_pref_.Init(prefs::kAudioVolume, prefs, NULL); | |
| 198 mute_pref_.Init(prefs::kAudioMute, prefs, NULL); | |
| 199 } | |
| 200 | |
| 171 bool AudioMixerAlsa::InitializeAlsaMixer() { | 201 bool AudioMixerAlsa::InitializeAlsaMixer() { |
| 172 AutoLock lock(mixer_state_lock_); | 202 AutoLock lock(mixer_state_lock_); |
| 173 if (mixer_state_ != INITIALIZING) | 203 if (mixer_state_ != INITIALIZING) |
| 174 return false; | 204 return false; |
| 175 | 205 |
| 176 int err; | 206 int err; |
| 177 snd_mixer_t* handle = NULL; | 207 snd_mixer_t* handle = NULL; |
| 178 const char* card = "default"; | 208 const char* card = "default"; |
| 179 | 209 |
| 180 if ((err = snd_mixer_open(&handle, 0)) < 0) { | 210 if ((err = snd_mixer_open(&handle, 0)) < 0) { |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 234 | 264 |
| 235 void AudioMixerAlsa::FreeAlsaMixer() { | 265 void AudioMixerAlsa::FreeAlsaMixer() { |
| 236 AutoLock lock(mixer_state_lock_); | 266 AutoLock lock(mixer_state_lock_); |
| 237 mixer_state_ = SHUTTING_DOWN; | 267 mixer_state_ = SHUTTING_DOWN; |
| 238 if (alsa_mixer_) { | 268 if (alsa_mixer_) { |
| 239 snd_mixer_close(alsa_mixer_); | 269 snd_mixer_close(alsa_mixer_); |
| 240 alsa_mixer_ = NULL; | 270 alsa_mixer_ = NULL; |
| 241 } | 271 } |
| 242 } | 272 } |
| 243 | 273 |
| 274 void AudioMixerAlsa::DoSetVolumeMute(double volume, bool mute) { | |
| 275 AutoLock lock(mixer_state_lock_); | |
| 276 if (mixer_state_ != READY) | |
| 277 return; | |
| 278 | |
| 279 VLOG(1) << "Setting volume to " << volume << "and mute to " << mute; | |
| 280 if (mute) { | |
| 281 save_volume_ = volume; | |
| 282 DoSetVolumeDb_Locked(min_volume_); | |
| 283 } else { | |
| 284 DoSetVolumeDb_Locked(volume); | |
| 285 } | |
| 286 | |
| 287 SetElementMuted_Locked(elem_master_, mute); | |
| 288 if (elem_pcm_) | |
| 289 SetElementMuted_Locked(elem_pcm_, mute); | |
| 290 } | |
| 291 | |
| 292 void AudioMixerAlsa::RestoreVolumeMuteOnUIThread() { | |
| 293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 294 if (volume_pref_.GetValue() < kSilenceDb) | |
| 295 return; | |
| 296 | |
| 297 // This happens during init, so set the volume off the UI thread. | |
| 298 thread_->message_loop()->PostTask(FROM_HERE, | |
| 299 NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute, | |
| 300 volume_pref_.GetValue(), mute_pref_.GetValue())); | |
| 301 } | |
| 302 | |
| 244 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { | 303 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { |
| 245 double vol_total = 0.0; | 304 double vol_total = 0.0; |
| 246 GetElementVolume_Locked(elem_master_, &vol_total); | 305 GetElementVolume_Locked(elem_master_, &vol_total); |
| 247 | 306 |
| 248 double vol_pcm = 0.0; | 307 double vol_pcm = 0.0; |
| 249 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) | 308 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) |
| 250 vol_total += vol_pcm; | 309 vol_total += vol_pcm; |
| 251 | 310 |
| 252 return vol_total; | 311 return vol_total; |
| 253 } | 312 } |
| 254 | 313 |
| 255 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { | 314 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { |
| 256 double actual_vol = 0.0; | 315 double actual_vol = 0.0; |
| 257 | 316 |
| 258 // If a PCM volume slider exists, then first set the Master volume to the | 317 // If a PCM volume slider exists, then first set the Master volume to the |
| 259 // nearest volume >= requested volume, then adjust PCM volume down to get | 318 // nearest volume >= requested volume, then adjust PCM volume down to get |
| 260 // closer to the requested volume. | 319 // closer to the requested volume. |
| 261 | |
| 262 if (elem_pcm_) { | 320 if (elem_pcm_) { |
| 263 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); | 321 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); |
| 264 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); | 322 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); |
| 265 } else { | 323 } else { |
| 266 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); | 324 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); |
| 267 } | 325 } |
| 268 } | 326 } |
| 269 | 327 |
| 270 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( | 328 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( |
| 271 snd_mixer_t* handle, | 329 snd_mixer_t* handle, |
| 272 const char* element_name) const { | 330 const char* element_name) const { |
| 273 snd_mixer_selem_id_t* sid; | 331 snd_mixer_selem_id_t* sid; |
| 274 | 332 |
| 275 // Using id_malloc/id_free API instead of id_alloca since the latter gives the | 333 // Using id_malloc/id_free API instead of id_alloca since the latter gives the |
| 276 // warning: the address of 'sid' will always evaluate as 'true' | 334 // warning: the address of 'sid' will always evaluate as 'true'. |
| 277 if (snd_mixer_selem_id_malloc(&sid)) | 335 if (snd_mixer_selem_id_malloc(&sid)) |
| 278 return NULL; | 336 return NULL; |
| 279 | 337 |
| 280 snd_mixer_selem_id_set_index(sid, 0); | 338 snd_mixer_selem_id_set_index(sid, 0); |
| 281 snd_mixer_selem_id_set_name(sid, element_name); | 339 snd_mixer_selem_id_set_name(sid, element_name); |
| 282 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); | 340 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); |
| 283 if (!elem) { | 341 if (!elem) { |
| 284 LOG(ERROR) << "ALSA unable to find simple control " | 342 LOG(ERROR) << "ALSA unable to find simple control " |
| 285 << snd_mixer_selem_id_get_name(sid); | 343 << snd_mixer_selem_id_get_name(sid); |
| 286 } | 344 } |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 357 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { | 415 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { |
| 358 int enabled = mute ? 0 : 1; | 416 int enabled = mute ? 0 : 1; |
| 359 snd_mixer_selem_set_playback_switch_all(elem, enabled); | 417 snd_mixer_selem_set_playback_switch_all(elem, enabled); |
| 360 | 418 |
| 361 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) | 419 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) |
| 362 << " to " << enabled; | 420 << " to " << enabled; |
| 363 } | 421 } |
| 364 | 422 |
| 365 } // namespace chromeos | 423 } // namespace chromeos |
| 366 | 424 |
| OLD | NEW |