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

Side by Side Diff: chrome/browser/chromeos/audio_mixer_alsa.cc

Issue 6118006: Save/Restore ALSA volume/mute (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/browser/chromeos
Patch Set: Fixed mute not set case Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/chromeos/audio_mixer_alsa.h ('k') | chrome/browser/prefs/browser_prefs.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 kPrefVolumeInvalid = -999.0;
35 const int kPrefMuteOff = 0;
36 const int kPrefMuteOn = 1;
37 const int kPrefMuteInvalid = 2;
32 38
33 } // namespace 39 } // namespace
34 40
35 AudioMixerAlsa::AudioMixerAlsa() 41 AudioMixerAlsa::AudioMixerAlsa()
36 : min_volume_(kDefaultMinVolume), 42 : min_volume_(kDefaultMinVolume),
37 max_volume_(kDefaultMaxVolume), 43 max_volume_(kDefaultMaxVolume),
38 save_volume_(0), 44 save_volume_(0),
39 mixer_state_(UNINITIALIZED), 45 mixer_state_(UNINITIALIZED),
40 alsa_mixer_(NULL), 46 alsa_mixer_(NULL),
41 elem_master_(NULL), 47 elem_master_(NULL),
42 elem_pcm_(NULL) { 48 elem_pcm_(NULL) {
43 } 49 }
44 50
45 AudioMixerAlsa::~AudioMixerAlsa() { 51 AudioMixerAlsa::~AudioMixerAlsa() {
46 FreeAlsaMixer(); 52 FreeAlsaMixer();
47 if (thread_ != NULL) { 53 if (thread_ != NULL) {
48 thread_->Stop(); 54 thread_->Stop();
49 thread_.reset(); 55 thread_.reset();
50 } 56 }
51 } 57 }
52 58
53 void AudioMixerAlsa::Init(InitDoneCallback* callback) { 59 void AudioMixerAlsa::Init(InitDoneCallback* callback) {
54 DCHECK(callback); 60 DCHECK(callback);
55 if (!InitThread()) { 61 if (!InitThread()) {
56 callback->Run(false); 62 callback->Run(false);
57 delete callback; 63 delete callback;
58 return; 64 return;
59 } 65 }
66 InitPrefs();
60 67
61 // Post the task of starting up, which can block for 200-500ms, 68 // 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. 69 // so best not to do it on the caller's thread.
63 thread_->message_loop()->PostTask(FROM_HERE, 70 thread_->message_loop()->PostTask(FROM_HERE,
64 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); 71 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback));
65 } 72 }
66 73
67 bool AudioMixerAlsa::InitSync() { 74 bool AudioMixerAlsa::InitSync() {
68 if (!InitThread()) 75 if (!InitThread())
69 return false; 76 return false;
77 InitPrefs();
70 return InitializeAlsaMixer(); 78 return InitializeAlsaMixer();
71 } 79 }
72 80
73 double AudioMixerAlsa::GetVolumeDb() const { 81 double AudioMixerAlsa::GetVolumeDb() const {
74 AutoLock lock(mixer_state_lock_); 82 AutoLock lock(mixer_state_lock_);
75 if (mixer_state_ != READY) 83 if (mixer_state_ != READY)
76 return kSilenceDb; 84 return kSilenceDb;
77 85
78 return DoGetVolumeDb_Locked(); 86 return DoGetVolumeDb_Locked();
79 } 87 }
80 88
81 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { 89 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) {
82 AutoLock lock(mixer_state_lock_); 90 AutoLock lock(mixer_state_lock_);
83 if (mixer_state_ != READY) 91 if (mixer_state_ != READY)
84 return false; 92 return false;
85 if (vol_min) 93 if (vol_min)
86 *vol_min = min_volume_; 94 *vol_min = min_volume_;
87 if (vol_max) 95 if (vol_max)
88 *vol_max = max_volume_; 96 *vol_max = max_volume_;
89 return true; 97 return true;
90 } 98 }
91 99
92 void AudioMixerAlsa::SetVolumeDb(double vol_db) { 100 void AudioMixerAlsa::SetVolumeDb(double vol_db) {
93 AutoLock lock(mixer_state_lock_); 101 AutoLock lock(mixer_state_lock_);
94 if (mixer_state_ != READY) 102 if (mixer_state_ != READY)
95 return; 103 return;
104 if (vol_db < kSilenceDb)
105 vol_db = kSilenceDb;
96 DoSetVolumeDb_Locked(vol_db); 106 DoSetVolumeDb_Locked(vol_db);
107 volume_pref_.SetValue(vol_db);
97 } 108 }
98 109
99 bool AudioMixerAlsa::IsMute() const { 110 bool AudioMixerAlsa::IsMute() const {
100 AutoLock lock(mixer_state_lock_); 111 AutoLock lock(mixer_state_lock_);
101 if (mixer_state_ != READY) 112 if (mixer_state_ != READY)
102 return false; 113 return false;
103 return GetElementMuted_Locked(elem_master_); 114 return GetElementMuted_Locked(elem_master_);
104 } 115 }
105 116
117 // To indicate the volume is not valid yet, a very low volume value is stored.
118 // We compare against a slightly higher value in case of rounding errors.
119 static bool PrefVolumeValid(double volume) {
120 return (volume > kPrefVolumeInvalid + 0.1);
121 }
122
106 void AudioMixerAlsa::SetMute(bool mute) { 123 void AudioMixerAlsa::SetMute(bool mute) {
107 AutoLock lock(mixer_state_lock_); 124 AutoLock lock(mixer_state_lock_);
108 if (mixer_state_ != READY) 125 if (mixer_state_ != READY)
109 return; 126 return;
110 127
111 // Set volume to minimum on mute, since switching the element off does not 128 // Set volume to minimum on mute, since switching the element off does not
112 // always mute as it should. 129 // always mute as it should.
113 130
114 // TODO(davej): Setting volume to minimum can be removed once switching the 131 // TODO(davej): Remove save_volume_ and setting volume to minimum if
115 // element off can be guaranteed to work. 132 // switching the element off can be guaranteed to mute it. Currently mute
133 // is done by setting the volume to min_volume_.
116 134
117 bool old_value = GetElementMuted_Locked(elem_master_); 135 bool old_value = GetElementMuted_Locked(elem_master_);
118 136
119 if (old_value != mute) { 137 if (old_value != mute) {
120 if (mute) { 138 if (mute) {
121 save_volume_ = DoGetVolumeDb_Locked(); 139 save_volume_ = DoGetVolumeDb_Locked();
122 DoSetVolumeDb_Locked(min_volume_); 140 DoSetVolumeDb_Locked(min_volume_);
123 } else { 141 } else {
124 DoSetVolumeDb_Locked(save_volume_); 142 DoSetVolumeDb_Locked(save_volume_);
125 } 143 }
126 } 144 }
127 145
128 SetElementMuted_Locked(elem_master_, mute); 146 SetElementMuted_Locked(elem_master_, mute);
129 if (elem_pcm_) 147 if (elem_pcm_)
130 SetElementMuted_Locked(elem_pcm_, mute); 148 SetElementMuted_Locked(elem_pcm_, mute);
149 mute_pref_.SetValue(mute ? kPrefMuteOn : kPrefMuteOff);
131 } 150 }
132 151
133 AudioMixer::State AudioMixerAlsa::GetState() const { 152 AudioMixer::State AudioMixerAlsa::GetState() const {
134 AutoLock lock(mixer_state_lock_); 153 AutoLock lock(mixer_state_lock_);
135 // If we think it's ready, verify it is actually so. 154 // If we think it's ready, verify it is actually so.
136 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) 155 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL))
137 mixer_state_ = IN_ERROR; 156 mixer_state_ = IN_ERROR;
138 return mixer_state_; 157 return mixer_state_;
139 } 158 }
140 159
160 // static
161 void AudioMixerAlsa::RegisterPrefs(PrefService* local_state) {
162 if (!local_state->FindPreference(prefs::kAudioVolume))
163 local_state->RegisterRealPref(prefs::kAudioVolume, kPrefVolumeInvalid);
164 if (!local_state->FindPreference(prefs::kAudioMute))
165 local_state->RegisterIntegerPref(prefs::kAudioMute, kPrefMuteInvalid);
166 }
167
141 //////////////////////////////////////////////////////////////////////////////// 168 ////////////////////////////////////////////////////////////////////////////////
142 // Private functions follow 169 // Private functions follow
143 170
144 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { 171 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) {
145 bool success = InitializeAlsaMixer(); 172 bool success = InitializeAlsaMixer();
146 173
174 if (success) {
175 BrowserThread::PostTask(
176 BrowserThread::UI, FROM_HERE,
177 NewRunnableMethod(this, &AudioMixerAlsa::RestoreVolumeMuteOnUIThread));
178 }
179
147 if (callback) { 180 if (callback) {
148 callback->Run(success); 181 callback->Run(success);
149 delete callback; 182 delete callback;
150 } 183 }
151 } 184 }
152 185
153 bool AudioMixerAlsa::InitThread() { 186 bool AudioMixerAlsa::InitThread() {
154 AutoLock lock(mixer_state_lock_); 187 AutoLock lock(mixer_state_lock_);
155 188
156 if (mixer_state_ != UNINITIALIZED) 189 if (mixer_state_ != UNINITIALIZED)
157 return false; 190 return false;
158 191
159 if (thread_ == NULL) { 192 if (thread_ == NULL) {
160 thread_.reset(new base::Thread("AudioMixerAlsa")); 193 thread_.reset(new base::Thread("AudioMixerAlsa"));
161 if (!thread_->Start()) { 194 if (!thread_->Start()) {
162 thread_.reset(); 195 thread_.reset();
163 return false; 196 return false;
164 } 197 }
165 } 198 }
166 199
167 mixer_state_ = INITIALIZING; 200 mixer_state_ = INITIALIZING;
168 return true; 201 return true;
169 } 202 }
170 203
204 void AudioMixerAlsa::InitPrefs() {
205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 PrefService* prefs = g_browser_process->local_state();
207 volume_pref_.Init(prefs::kAudioVolume, prefs, NULL);
208 mute_pref_.Init(prefs::kAudioMute, prefs, NULL);
209 }
210
171 bool AudioMixerAlsa::InitializeAlsaMixer() { 211 bool AudioMixerAlsa::InitializeAlsaMixer() {
172 AutoLock lock(mixer_state_lock_); 212 AutoLock lock(mixer_state_lock_);
173 if (mixer_state_ != INITIALIZING) 213 if (mixer_state_ != INITIALIZING)
174 return false; 214 return false;
175 215
176 int err; 216 int err;
177 snd_mixer_t* handle = NULL; 217 snd_mixer_t* handle = NULL;
178 const char* card = "default"; 218 const char* card = "default";
179 219
180 if ((err = snd_mixer_open(&handle, 0)) < 0) { 220 if ((err = snd_mixer_open(&handle, 0)) < 0) {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 274
235 void AudioMixerAlsa::FreeAlsaMixer() { 275 void AudioMixerAlsa::FreeAlsaMixer() {
236 AutoLock lock(mixer_state_lock_); 276 AutoLock lock(mixer_state_lock_);
237 mixer_state_ = SHUTTING_DOWN; 277 mixer_state_ = SHUTTING_DOWN;
238 if (alsa_mixer_) { 278 if (alsa_mixer_) {
239 snd_mixer_close(alsa_mixer_); 279 snd_mixer_close(alsa_mixer_);
240 alsa_mixer_ = NULL; 280 alsa_mixer_ = NULL;
241 } 281 }
242 } 282 }
243 283
284 void AudioMixerAlsa::DoSetVolumeMute(double pref_volume, int pref_mute) {
285 AutoLock lock(mixer_state_lock_);
286 if (mixer_state_ != READY)
287 return;
288
289 // If volume or mute are invalid, set them now to the current actual values.
290 if (!PrefVolumeValid(pref_volume))
291 pref_volume = DoGetVolumeDb_Locked();
292 bool mute;
293 if (pref_mute == kPrefMuteInvalid)
294 mute = GetElementMuted_Locked(elem_master_);
295 else
296 mute = (pref_mute == kPrefMuteOn) ? true : false;
297
298 VLOG(1) << "Setting volume to " << pref_volume << " and mute to " << mute;
299
300 if (mute) {
301 save_volume_ = pref_volume;
302 DoSetVolumeDb_Locked(min_volume_);
303 } else if (pref_mute == kPrefMuteOff) {
304 DoSetVolumeDb_Locked(pref_volume);
305 }
306
307 SetElementMuted_Locked(elem_master_, mute);
308 if (elem_pcm_)
309 SetElementMuted_Locked(elem_pcm_, mute);
310 }
311
312 void AudioMixerAlsa::RestoreVolumeMuteOnUIThread() {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314 // This happens during init, so set the volume off the UI thread.
315 thread_->message_loop()->PostTask(FROM_HERE,
316 NewRunnableMethod(this, &AudioMixerAlsa::DoSetVolumeMute,
317 volume_pref_.GetValue(), mute_pref_.GetValue()));
318 }
319
244 double AudioMixerAlsa::DoGetVolumeDb_Locked() const { 320 double AudioMixerAlsa::DoGetVolumeDb_Locked() const {
245 double vol_total = 0.0; 321 double vol_total = 0.0;
246 GetElementVolume_Locked(elem_master_, &vol_total); 322 GetElementVolume_Locked(elem_master_, &vol_total);
247 323
248 double vol_pcm = 0.0; 324 double vol_pcm = 0.0;
249 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm))) 325 if (elem_pcm_ && (GetElementVolume_Locked(elem_pcm_, &vol_pcm)))
250 vol_total += vol_pcm; 326 vol_total += vol_pcm;
251 327
252 return vol_total; 328 return vol_total;
253 } 329 }
254 330
255 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) { 331 void AudioMixerAlsa::DoSetVolumeDb_Locked(double vol_db) {
256 double actual_vol = 0.0; 332 double actual_vol = 0.0;
257 333
258 // If a PCM volume slider exists, then first set the Master volume to the 334 // 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 335 // nearest volume >= requested volume, then adjust PCM volume down to get
260 // closer to the requested volume. 336 // closer to the requested volume.
261
262 if (elem_pcm_) { 337 if (elem_pcm_) {
263 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f); 338 SetElementVolume_Locked(elem_master_, vol_db, &actual_vol, 0.9999f);
264 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); 339 SetElementVolume_Locked(elem_pcm_, vol_db - actual_vol, NULL, 0.5f);
265 } else { 340 } else {
266 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f); 341 SetElementVolume_Locked(elem_master_, vol_db, NULL, 0.5f);
267 } 342 }
268 } 343 }
269 344
270 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked( 345 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName_Locked(
271 snd_mixer_t* handle, 346 snd_mixer_t* handle,
272 const char* element_name) const { 347 const char* element_name) const {
273 snd_mixer_selem_id_t* sid; 348 snd_mixer_selem_id_t* sid;
274 349
275 // Using id_malloc/id_free API instead of id_alloca since the latter gives the 350 // 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' 351 // warning: the address of 'sid' will always evaluate as 'true'.
277 if (snd_mixer_selem_id_malloc(&sid)) 352 if (snd_mixer_selem_id_malloc(&sid))
278 return NULL; 353 return NULL;
279 354
280 snd_mixer_selem_id_set_index(sid, 0); 355 snd_mixer_selem_id_set_index(sid, 0);
281 snd_mixer_selem_id_set_name(sid, element_name); 356 snd_mixer_selem_id_set_name(sid, element_name);
282 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); 357 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
283 if (!elem) { 358 if (!elem) {
284 LOG(ERROR) << "ALSA unable to find simple control " 359 LOG(ERROR) << "ALSA unable to find simple control "
285 << snd_mixer_selem_id_get_name(sid); 360 << snd_mixer_selem_id_get_name(sid);
286 } 361 }
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) { 432 void AudioMixerAlsa::SetElementMuted_Locked(snd_mixer_elem_t* elem, bool mute) {
358 int enabled = mute ? 0 : 1; 433 int enabled = mute ? 0 : 1;
359 snd_mixer_selem_set_playback_switch_all(elem, enabled); 434 snd_mixer_selem_set_playback_switch_all(elem, enabled);
360 435
361 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) 436 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
362 << " to " << enabled; 437 << " to " << enabled;
363 } 438 }
364 439
365 } // namespace chromeos 440 } // namespace chromeos
366 441
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/audio_mixer_alsa.h ('k') | chrome/browser/prefs/browser_prefs.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698