 Chromium Code Reviews
 Chromium Code Reviews Issue 5859003:
  Add ALSA support to volume keys  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/browser/chromeos
    
  
    Issue 5859003:
  Add ALSA support to volume keys  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/browser/chromeos| OLD | NEW | 
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/chromeos/audio_mixer_alsa.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #include "base/task.h" | |
| 9 | |
| 10 namespace chromeos { | |
| 11 | |
| 12 // Connect to the ALSA mixer using their simple element API. Init is performed | |
| 13 // asynchronously on the worker thread. | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
I think it'd be good to expand this to explain the
 | |
| 14 | |
| 15 // TODO(davej): Serialize volume/mute to preserve settings when restarting. | |
| 16 | |
| 17 typedef long alsa_long_t; // 'long' is required for ALSA API calls. | |
| 18 | |
| 19 namespace { | |
| 20 const char* kMasterVolume = {"Master"}; | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nits:
 - no need to have this indented
 - also do
 
davejcool
2010/12/23 03:09:02
:-)  Microsoft + 1990's "C" = {"Funny Braces"}
 | |
| 21 const char* kPCMVolume = {"PCM"}; | |
| 22 } // namespace | |
| 23 | |
| 24 AudioMixerAlsa::AudioMixerAlsa() | |
| 25 : min_volume_(-90), | |
| 
zhurunz
2010/12/22 22:21:26
Define a const for this.
 
scherkus (not reviewing)
2010/12/22 22:31:58
magic number alert!  do we have a constant for thi
 
davejcool
2010/12/23 03:09:02
We do now!
 | |
| 26 max_volume_(0), | |
| 27 save_volume_(0), | |
| 28 mixer_state_lock_(), | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: no need to list variables that have default c
 | |
| 29 mixer_state_(UNINITIALIZED), | |
| 30 alsa_mixer_(NULL), | |
| 31 elem_master_(NULL), | |
| 32 elem_pcm_(NULL), | |
| 33 thread_(NULL) { | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: no need to list variables that have default c
 | |
| 34 } | |
| 35 | |
| 36 AudioMixerAlsa::~AudioMixerAlsa() { | |
| 37 { | |
| 38 AutoLock lock(mixer_state_lock_); | |
| 39 mixer_state_ = SHUTTING_DOWN; | |
| 40 FreeAlsaMixer(); | |
| 41 } | |
| 42 | |
| 43 if (thread_ != NULL) { | |
| 44 thread_->Stop(); | |
| 45 thread_.reset(); | |
| 46 } | |
| 47 | |
| 48 { | |
| 49 AutoLock lock(mixer_state_lock_); | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: over indented
do we need to lock + update st
 | |
| 50 mixer_state_ = UNINITIALIZED; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 bool AudioMixerAlsa::Init(InitDoneCallback* callback) { | |
| 55 if (!InitThread()) | |
| 56 return false; | |
| 57 | |
| 58 // Post the task of starting up, which can block for 200-500ms, | |
| 59 // so best not to do it on the caller's thread. | |
| 60 thread_->message_loop()->PostTask(FROM_HERE, | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
we only seem to use this thread for doing initiali
 
davejcool
2010/12/23 03:09:02
I just wanted the init to happen away from everyth
 | |
| 61 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: indent by extra 2 spaces
 | |
| 62 return true; | |
| 63 } | |
| 64 | |
| 65 bool AudioMixerAlsa::InitSync() { | |
| 66 if (!InitThread()) | |
| 67 return false; | |
| 68 return InitializeAlsaMixer(); | |
| 69 } | |
| 70 | |
| 71 double AudioMixerAlsa::GetVolumeDb() const { | |
| 72 if (mixer_state_ != READY) | |
| 73 return kSilenceDb; | |
| 74 | |
| 75 float vol_total = 0; | |
| 76 GetElementVolume(elem_master_, &vol_total); | |
| 77 | |
| 78 float vol_pcm = 0; | |
| 79 if (elem_pcm_ && (GetElementVolume(elem_pcm_, &vol_pcm))) | |
| 80 vol_total += vol_pcm; | |
| 81 | |
| 82 return vol_total; | |
| 83 } | |
| 84 | |
| 85 void AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { | |
| 86 if (!MixerReady()) | |
| 87 return; | |
| 88 if (vol_min) | |
| 89 *vol_min = min_volume_; | |
| 90 if (vol_max) | |
| 91 *vol_max = max_volume_; | |
| 92 } | |
| 93 | |
| 94 void AudioMixerAlsa::SetVolumeDb(double vol_db) { | |
| 95 if (!MixerReady()) | |
| 96 return; | |
| 97 float actual_vol = 0; | |
| 98 | |
| 99 // If a PCM volume slider exists, then first set the Master volume to the | |
| 100 // nearest volume >= requested volume, then adjust PCM volume down to get | |
| 101 // closer to the requested volume. This allows for going to a quieter volume | |
| 102 // than the Master alone would allow, as well as give a finer resolution | |
| 103 // to the overall volume. | |
| 104 | |
| 105 if (elem_pcm_) { | |
| 106 SetElementVolume(elem_master_, vol_db, &actual_vol, 0.9999f); | |
| 107 SetElementVolume(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); | |
| 108 } else { | |
| 109 SetElementVolume(elem_master_, vol_db, NULL, 0.5f); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 bool AudioMixerAlsa::IsMute() const { | |
| 114 if (!MixerReady()) | |
| 115 return false; | |
| 116 int switched = 0; | |
| 117 GetElementSwitched(elem_master_, &switched); | |
| 118 return (switched) ? false : true; | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
instead of having callees do the int<->bool conver
 
davejcool
2010/12/23 03:09:02
Yup, removing the 'switched' language from Get/Set
 | |
| 119 } | |
| 120 | |
| 121 void AudioMixerAlsa::SetMute(bool mute) { | |
| 122 if (!MixerReady()) | |
| 123 return; | |
| 124 int new_value = mute ? 0 : 1; | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
ditto
 | |
| 125 | |
| 126 // Set volume to minimum on mute, since switching the element off does not | |
| 127 // always mute as it should. | |
| 128 | |
| 129 // TODO(davej): Setting volume to minimum can be removed once switching the | |
| 130 // element off can be guaranteed to work. | |
| 131 | |
| 132 int old_value; | |
| 133 GetElementSwitched(elem_master_, &old_value); | |
| 134 | |
| 135 if (old_value != new_value) { | |
| 136 if (!new_value) { | |
| 137 save_volume_ = GetVolumeDb(); | |
| 138 SetVolumeDb(min_volume_); | |
| 139 } else { | |
| 140 SetVolumeDb(save_volume_); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 SetElementSwitched(elem_master_, new_value); | |
| 145 if (elem_pcm_) | |
| 146 SetElementSwitched(elem_pcm_, new_value); | |
| 147 } | |
| 148 | |
| 149 AudioMixerBase::State AudioMixerAlsa::CheckState() const { | |
| 150 AutoLock lock(mixer_state_lock_); | |
| 151 // If we think it's ready, verify it is actually so. | |
| 152 if ((mixer_state_ == READY) && | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: condition can fit on one line
 | |
| 153 (alsa_mixer_ == NULL)) | |
| 
zhurunz
2010/12/22 22:21:26
can be in one line.
 | |
| 154 mixer_state_ = IN_ERROR; | |
| 155 return mixer_state_; | |
| 156 } | |
| 157 | |
| 158 //////////////////////////////////////////////////////////////////////////////// | |
| 159 // Private functions follow | |
| 160 | |
| 161 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { | |
| 162 bool success = InitializeAlsaMixer(); | |
| 163 | |
| 164 if (callback) { | |
| 165 callback->Run(success); | |
| 166 delete callback; | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 bool AudioMixerAlsa::InitThread() { | |
| 171 AutoLock lock(mixer_state_lock_); | |
| 172 | |
| 173 if (mixer_state_ != UNINITIALIZED) | |
| 174 return false; | |
| 175 | |
| 176 if (thread_ == NULL) { | |
| 177 thread_.reset(new base::Thread("AudioMixerAlsa")); | |
| 178 if (!thread_->Start()) { | |
| 179 thread_.reset(); | |
| 180 return false; | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 mixer_state_ = INITIALIZING; | |
| 185 return true; | |
| 186 } | |
| 187 | |
| 188 bool AudioMixerAlsa::InitializeAlsaMixer() { | |
| 189 AutoLock lock(mixer_state_lock_); | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
sanity check: did you intend to lock for this enti
 
davejcool
2010/12/23 03:09:02
In this case I did intend this.  The initializatio
 | |
| 190 if (mixer_state_ != INITIALIZING) | |
| 191 return false; | |
| 192 | |
| 193 // ALSA hardware devices start at 0 | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: end comment w/ period
 | |
| 194 int device_id_ = -1; | |
| 195 if ((snd_card_next(&device_id_) < 0) || (device_id_ < 0)) { | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
so this selects the first ALSA device found on the
 
davejcool
2010/12/23 03:09:02
Oops... this was left over from earlier testing.
 | |
| 196 LOG(ERROR) << "No ALSA Soundcards Found"; | |
| 197 return false; | |
| 198 } | |
| 199 | |
| 200 int err; | |
| 201 snd_mixer_t* handle = NULL; | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
you may want to consider defining a scoped_ptr_mal
 
davejcool
2010/12/23 03:09:02
Thanks, I'll have learn more about that one.
 | |
| 202 char card[16]; | |
| 203 | |
| 204 snprintf(card, sizeof(card), "hw:%i", device_id_); | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: use base/stringprintf.h, std::string and c_st
 | |
| 205 | |
| 206 if ((err = snd_mixer_open(&handle, 0)) < 0) { | |
| 207 LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err); | |
| 208 return NULL; | |
| 209 } | |
| 
zhurunz
2010/12/22 22:21:26
return false.
Same below.
 | |
| 210 | |
| 211 if ((err = snd_mixer_attach(handle, card)) < 0) { | |
| 212 LOG(ERROR) << "ALSA Attach to card " << card << " failed: " | |
| 213 << snd_strerror(err); | |
| 214 snd_mixer_close(handle); | |
| 215 return NULL; | |
| 216 } | |
| 217 | |
| 218 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { | |
| 219 LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err); | |
| 220 snd_mixer_close(handle); | |
| 221 return NULL; | |
| 222 } | |
| 223 | |
| 224 err = snd_mixer_load(handle); | |
| 225 if (err < 0) { | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: for consistency might as well put snd_mixer_l
 | |
| 226 LOG(ERROR) << "ALSA mixer " << card << " load error: %s" | |
| 227 << snd_strerror(err); | |
| 228 snd_mixer_close(handle); | |
| 229 return NULL; | |
| 230 } | |
| 231 | |
| 232 VLOG(1) << "Opened ALSA mixer " << card << " OK"; | |
| 233 | |
| 234 elem_master_ = FindElementWithName(handle, kMasterVolume); | |
| 235 if (elem_master_) { | |
| 236 alsa_long_t long_lo, long_hi; | |
| 237 snd_mixer_selem_get_playback_dB_range(elem_master_, &long_lo, &long_hi); | |
| 238 min_volume_ = static_cast<double>(long_lo) / 100.0; | |
| 239 max_volume_ = static_cast<double>(long_hi) / 100.0; | |
| 240 } | |
| 241 | |
| 242 elem_pcm_ = FindElementWithName(handle, kPCMVolume); | |
| 243 if (elem_pcm_) { | |
| 244 alsa_long_t long_lo, long_hi; | |
| 245 snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi); | |
| 246 min_volume_ += static_cast<double>(long_lo) / 100.0; | |
| 247 max_volume_ += static_cast<double>(long_hi) / 100.0; | |
| 248 } | |
| 249 | |
| 250 VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to " | |
| 251 << max_volume_ << " dB"; | |
| 252 | |
| 253 alsa_mixer_ = handle; | |
| 254 mixer_state_ = READY; | |
| 255 return true; | |
| 256 } | |
| 257 | |
| 258 void AudioMixerAlsa::FreeAlsaMixer() { | |
| 259 if (alsa_mixer_) { | |
| 260 snd_mixer_close(alsa_mixer_); | |
| 261 alsa_mixer_ = NULL; | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName(snd_mixer_t* handle, | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: these two arguments should be dropped to next
 | |
| 266 const char* element_name) const { | |
| 267 snd_mixer_selem_id_t* sid; | |
| 268 snd_mixer_selem_id_alloca(&sid); | |
| 269 snd_mixer_selem_id_set_index(sid, 0); | |
| 270 snd_mixer_selem_id_set_name(sid, element_name); | |
| 271 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); | |
| 272 if (!elem) { | |
| 273 LOG(ERROR) << "ALSA unable to find simple control " | |
| 274 << snd_mixer_selem_id_get_name(sid); | |
| 275 } | |
| 276 return elem; | |
| 277 } | |
| 278 | |
| 279 bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* elem, | |
| 280 float* current_vol) const { | |
| 281 alsa_long_t long_vol; | |
| 282 snd_mixer_selem_get_playback_dB(elem, (snd_mixer_selem_channel_id_t)0, | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
no c-style casts
 | |
| 283 &long_vol); | |
| 284 *current_vol = static_cast<float>(long_vol) / 100.0f; | |
| 285 | |
| 286 return true; | |
| 287 } | |
| 288 | |
| 289 bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* elem, | |
| 290 float new_vol, | |
| 291 float* actual_vol, | |
| 292 float rounding_bias) { | |
| 293 alsa_long_t vol_lo, vol_hi; | |
| 294 alsa_long_t db_lo_int, db_hi_int; | |
| 
zhurunz
2010/12/22 22:21:26
Better initialize them just in case those snd_xxx
 | |
| 295 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi); | |
| 296 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int); | |
| 297 float db_lo = static_cast<float>(db_lo_int) / 100.0f; | |
| 298 float db_hi = static_cast<float>(db_hi_int) / 100.0f; | |
| 299 float db_step = static_cast<float>(db_hi - db_lo) / (vol_hi - vol_lo); | |
| 300 | |
| 
zhurunz
2010/12/22 22:21:26
check vol_hi != vol_lo
 | |
| 301 if (new_vol < db_lo) | |
| 302 new_vol = db_lo; | |
| 303 | |
| 304 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias + | |
| 305 (new_vol - db_lo) / db_step) + vol_lo; | |
| 306 snd_mixer_selem_set_playback_volume_all(elem, value); | |
| 307 | |
| 308 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem) | |
| 309 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo | |
| 310 << " dB"; | |
| 311 | |
| 312 if (actual_vol) { | |
| 313 alsa_long_t volume; | |
| 314 snd_mixer_selem_get_playback_volume(elem, (snd_mixer_selem_channel_id_t)0, | |
| 315 &volume); | |
| 316 *actual_vol = db_lo + (volume - vol_lo) * db_step; | |
| 317 | |
| 318 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem) | |
| 319 << " now " << *actual_vol << " dB"; | |
| 320 } | |
| 321 return true; | |
| 322 } | |
| 323 | |
| 324 bool AudioMixerAlsa::GetElementSwitched(snd_mixer_elem_t* elem, | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
this function always return true... maybe it shoul
 | |
| 325 int * switched) const { | |
| 
zhurunz
2010/12/22 22:21:26
space after *
 | |
| 326 snd_mixer_selem_get_playback_switch(elem, (snd_mixer_selem_channel_id_t)0, | |
| 
zhurunz
2010/12/22 22:21:26
use static_cast to be consistent.
 | |
| 327 switched); | |
| 328 return true; | |
| 329 } | |
| 330 | |
| 331 bool AudioMixerAlsa::SetElementSwitched(snd_mixer_elem_t* elem, | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
this function always return true.. perhaps it shou
 
davejcool
2010/12/23 03:09:02
Ahh, of course!  In a previous incarnation the fun
 | |
| 332 int enabled) { | |
| 333 snd_mixer_selem_set_playback_switch_all(elem, enabled); | |
| 334 | |
| 335 VLOG(1) << "Set playback switch" << snd_mixer_selem_get_name(elem) | |
| 
zhurunz
2010/12/22 22:21:26
space here after ' switch'?
 | |
| 336 << " to " << enabled; | |
| 337 return true; | |
| 338 } | |
| 339 | |
| 340 bool AudioMixerAlsa::MixerReady() const { | |
| 341 AutoLock lock(mixer_state_lock_); | |
| 
scherkus (not reviewing)
2010/12/22 22:31:58
nit: over indented
 | |
| 342 return (mixer_state_ == READY); | |
| 343 } | |
| 344 | |
| 345 } // namespace chromeos | |
| 346 | |
| OLD | NEW |