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 <alsa/asoundlib.h> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/task.h" | |
11 | |
12 namespace chromeos { | |
13 | |
14 // Connect to the ALSA mixer using their simple element API. Init is performed | |
15 // asynchronously on the worker thread. | |
16 // | |
17 // 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 | |
19 // adjusting that as well. If the PCM element has more volume steps, it allows | |
20 // for finer granularity in the total volume. | |
21 | |
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. | |
25 | |
26 namespace { | |
27 | |
28 const char* kMasterVolume = "Master"; | |
29 const char* kPCMVolume = "PCM"; | |
30 const double kDefaultMinVolume = -90.0; | |
31 const double kDefaultMaxVolume = 0.0; | |
32 | |
33 } // namespace | |
34 | |
35 AudioMixerAlsa::AudioMixerAlsa() | |
36 : min_volume_(kDefaultMinVolume), | |
37 max_volume_(kDefaultMaxVolume), | |
38 save_volume_(0), | |
39 mixer_state_(UNINITIALIZED), | |
40 alsa_mixer_(NULL), | |
41 elem_master_(NULL), | |
42 elem_pcm_(NULL) { | |
43 } | |
44 | |
45 AudioMixerAlsa::~AudioMixerAlsa() { | |
46 FreeAlsaMixer(); | |
47 if (thread_ != NULL) { | |
48 thread_->Stop(); | |
49 thread_.reset(); | |
50 } | |
51 } | |
52 | |
53 bool AudioMixerAlsa::Init(InitDoneCallback* callback) { | |
54 if (!InitThread()) | |
55 return false; | |
scherkus (not reviewing)
2011/01/11 00:24:12
what happens to the callback?
davejcool
2011/01/11 02:52:54
Deleting callback if not used now.
| |
56 | |
57 // Post the task of starting up, which can block for 200-500ms, | |
58 // so best not to do it on the caller's thread. | |
59 thread_->message_loop()->PostTask(FROM_HERE, | |
60 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback)); | |
61 return true; | |
62 } | |
63 | |
64 bool AudioMixerAlsa::InitSync() { | |
65 if (!InitThread()) | |
66 return false; | |
67 return InitializeAlsaMixer(); | |
68 } | |
69 | |
70 double AudioMixerAlsa::GetVolumeDb() const { | |
71 AutoLock lock(mixer_state_lock_); | |
72 if (mixer_state_ != READY) | |
73 return kSilenceDb; | |
74 | |
75 return DoGetVolumeDb(); | |
76 } | |
77 | |
78 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) { | |
79 AutoLock lock(mixer_state_lock_); | |
80 if (mixer_state_ != READY) | |
81 return false; | |
82 if (vol_min) | |
83 *vol_min = min_volume_; | |
84 if (vol_max) | |
85 *vol_max = max_volume_; | |
86 return true; | |
87 } | |
88 | |
89 void AudioMixerAlsa::SetVolumeDb(double vol_db) { | |
90 AutoLock lock(mixer_state_lock_); | |
91 if (mixer_state_ != READY) | |
92 return; | |
93 DoSetVolumeDb(vol_db); | |
94 } | |
95 | |
96 bool AudioMixerAlsa::IsMute() const { | |
97 AutoLock lock(mixer_state_lock_); | |
98 if (mixer_state_ != READY) | |
99 return false; | |
100 return GetElementMuted(elem_master_); | |
101 } | |
102 | |
103 void AudioMixerAlsa::SetMute(bool mute) { | |
104 AutoLock lock(mixer_state_lock_); | |
105 if (mixer_state_ != READY) | |
106 return; | |
107 | |
108 // Set volume to minimum on mute, since switching the element off does not | |
109 // always mute as it should. | |
110 | |
111 // TODO(davej): Setting volume to minimum can be removed once switching the | |
112 // element off can be guaranteed to work. | |
113 | |
114 bool old_value = GetElementMuted(elem_master_); | |
115 | |
116 if (old_value != mute) { | |
117 if (mute) { | |
118 save_volume_ = DoGetVolumeDb(); | |
119 DoSetVolumeDb(min_volume_); | |
120 } else { | |
121 DoSetVolumeDb(save_volume_); | |
122 } | |
123 } | |
124 | |
125 SetElementMuted(elem_master_, mute); | |
126 if (elem_pcm_) | |
127 SetElementMuted(elem_pcm_, mute); | |
128 } | |
129 | |
130 AudioMixer::State AudioMixerAlsa::CheckState() const { | |
131 AutoLock lock(mixer_state_lock_); | |
132 // If we think it's ready, verify it is actually so. | |
133 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL)) | |
134 mixer_state_ = IN_ERROR; | |
135 return mixer_state_; | |
136 } | |
137 | |
138 //////////////////////////////////////////////////////////////////////////////// | |
139 // Private functions follow | |
140 | |
141 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) { | |
142 bool success = InitializeAlsaMixer(); | |
143 | |
144 if (callback) { | |
145 callback->Run(success); | |
146 delete callback; | |
147 } | |
148 } | |
149 | |
150 bool AudioMixerAlsa::InitThread() { | |
151 AutoLock lock(mixer_state_lock_); | |
152 | |
153 if (mixer_state_ != UNINITIALIZED) | |
154 return false; | |
155 | |
156 if (thread_ == NULL) { | |
157 thread_.reset(new base::Thread("AudioMixerAlsa")); | |
158 if (!thread_->Start()) { | |
159 thread_.reset(); | |
160 return false; | |
161 } | |
162 } | |
163 | |
164 mixer_state_ = INITIALIZING; | |
165 return true; | |
166 } | |
167 | |
168 bool AudioMixerAlsa::InitializeAlsaMixer() { | |
169 AutoLock lock(mixer_state_lock_); | |
170 if (mixer_state_ != INITIALIZING) | |
171 return false; | |
172 | |
173 int err; | |
174 snd_mixer_t* handle = NULL; | |
175 const char* card = "default"; | |
176 | |
177 if ((err = snd_mixer_open(&handle, 0)) < 0) { | |
178 LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err); | |
179 return false; | |
180 } | |
181 | |
182 if ((err = snd_mixer_attach(handle, card)) < 0) { | |
183 LOG(ERROR) << "ALSA Attach to card " << card << " failed: " | |
184 << snd_strerror(err); | |
185 snd_mixer_close(handle); | |
186 return false; | |
187 } | |
188 | |
189 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { | |
190 LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err); | |
191 snd_mixer_close(handle); | |
192 return false; | |
193 } | |
194 | |
195 if ((err = snd_mixer_load(handle)) < 0) { | |
196 LOG(ERROR) << "ALSA mixer " << card << " load error: %s" | |
197 << snd_strerror(err); | |
198 snd_mixer_close(handle); | |
199 return false; | |
200 } | |
201 | |
202 VLOG(1) << "Opened ALSA mixer " << card << " OK"; | |
203 | |
204 elem_master_ = FindElementWithName(handle, kMasterVolume); | |
205 if (elem_master_) { | |
206 alsa_long_t long_lo, long_hi; | |
207 snd_mixer_selem_get_playback_dB_range(elem_master_, &long_lo, &long_hi); | |
208 min_volume_ = static_cast<double>(long_lo) / 100.0; | |
209 max_volume_ = static_cast<double>(long_hi) / 100.0; | |
210 } else { | |
211 LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card; | |
212 snd_mixer_close(handle); | |
213 return false; | |
214 } | |
215 | |
216 elem_pcm_ = FindElementWithName(handle, kPCMVolume); | |
217 if (elem_pcm_) { | |
218 alsa_long_t long_lo, long_hi; | |
219 snd_mixer_selem_get_playback_dB_range(elem_pcm_, &long_lo, &long_hi); | |
220 min_volume_ += static_cast<double>(long_lo) / 100.0; | |
221 max_volume_ += static_cast<double>(long_hi) / 100.0; | |
222 } | |
223 | |
224 VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to " | |
225 << max_volume_ << " dB"; | |
226 | |
227 alsa_mixer_ = handle; | |
228 mixer_state_ = READY; | |
229 return true; | |
230 } | |
231 | |
232 void AudioMixerAlsa::FreeAlsaMixer() { | |
233 AutoLock lock(mixer_state_lock_); | |
234 mixer_state_ = SHUTTING_DOWN; | |
235 if (alsa_mixer_) { | |
236 snd_mixer_close(alsa_mixer_); | |
237 alsa_mixer_ = NULL; | |
238 } | |
239 } | |
240 | |
241 double AudioMixerAlsa::DoGetVolumeDb() const { | |
242 double vol_total = 0.0; | |
243 GetElementVolume(elem_master_, &vol_total); | |
244 | |
245 double vol_pcm = 0.0; | |
246 if (elem_pcm_ && (GetElementVolume(elem_pcm_, &vol_pcm))) | |
247 vol_total += vol_pcm; | |
248 | |
249 return vol_total; | |
250 } | |
251 | |
252 void AudioMixerAlsa::DoSetVolumeDb(double vol_db) { | |
253 double actual_vol = 0.0; | |
254 | |
255 // If a PCM volume slider exists, then first set the Master volume to the | |
256 // nearest volume >= requested volume, then adjust PCM volume down to get | |
257 // closer to the requested volume. | |
258 | |
259 if (elem_pcm_) { | |
260 SetElementVolume(elem_master_, vol_db, &actual_vol, 0.9999f); | |
261 SetElementVolume(elem_pcm_, vol_db - actual_vol, NULL, 0.5f); | |
262 } else { | |
263 SetElementVolume(elem_master_, vol_db, NULL, 0.5f); | |
264 } | |
265 } | |
266 | |
267 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName( | |
268 snd_mixer_t* handle, | |
269 const char* element_name) const { | |
270 snd_mixer_selem_id_t* sid; | |
271 | |
272 // Using id_malloc/id_free API instead of id_alloca since the latter gives the | |
273 // warning: the address of 'sid' will always evaluate as 'true' | |
274 if (snd_mixer_selem_id_malloc(&sid)) | |
275 return NULL; | |
276 | |
davejcool
2011/01/06 23:19:12
Using the ALSA macro snd_mixer_selem_id_alloca(&si
| |
277 snd_mixer_selem_id_set_index(sid, 0); | |
278 snd_mixer_selem_id_set_name(sid, element_name); | |
279 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); | |
280 if (!elem) { | |
281 LOG(ERROR) << "ALSA unable to find simple control " | |
282 << snd_mixer_selem_id_get_name(sid); | |
283 } | |
284 | |
285 snd_mixer_selem_id_free(sid); | |
286 return elem; | |
287 } | |
288 | |
289 bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* elem, | |
290 double* current_vol) const { | |
291 alsa_long_t long_vol = 0; | |
292 snd_mixer_selem_get_playback_dB(elem, | |
293 static_cast<snd_mixer_selem_channel_id_t>(0), | |
294 &long_vol); | |
295 *current_vol = static_cast<double>(long_vol) / 100.0; | |
296 | |
297 return true; | |
298 } | |
299 | |
300 bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* elem, | |
301 double new_vol, | |
302 double* actual_vol, | |
303 double rounding_bias) { | |
304 alsa_long_t vol_lo = 0; | |
305 alsa_long_t vol_hi = 0; | |
306 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi); | |
307 alsa_long_t vol_range = vol_hi - vol_lo; | |
308 if (vol_range <= 0) | |
309 return false; | |
310 | |
311 alsa_long_t db_lo_int = 0; | |
312 alsa_long_t db_hi_int = 0; | |
313 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int); | |
314 double db_lo = static_cast<double>(db_lo_int) / 100.0; | |
315 double db_hi = static_cast<double>(db_hi_int) / 100.0; | |
316 double db_step = static_cast<double>(db_hi - db_lo) / vol_range; | |
317 if (db_step <= 0.0) | |
318 return false; | |
319 | |
320 if (new_vol < db_lo) | |
321 new_vol = db_lo; | |
322 | |
323 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias + | |
324 (new_vol - db_lo) / db_step) + vol_lo; | |
325 snd_mixer_selem_set_playback_volume_all(elem, value); | |
326 | |
327 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem) | |
328 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo | |
329 << " dB"; | |
330 | |
331 if (actual_vol) { | |
332 alsa_long_t volume; | |
333 snd_mixer_selem_get_playback_volume( | |
334 elem, | |
335 static_cast<snd_mixer_selem_channel_id_t>(0), | |
336 &volume); | |
337 *actual_vol = db_lo + (volume - vol_lo) * db_step; | |
338 | |
339 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem) | |
340 << " now " << *actual_vol << " dB"; | |
341 } | |
342 return true; | |
343 } | |
344 | |
345 bool AudioMixerAlsa::GetElementMuted(snd_mixer_elem_t* elem) const { | |
346 int enabled; | |
347 snd_mixer_selem_get_playback_switch( | |
348 elem, | |
349 static_cast<snd_mixer_selem_channel_id_t>(0), | |
350 &enabled); | |
351 return (enabled) ? false : true; | |
352 } | |
353 | |
354 void AudioMixerAlsa::SetElementMuted(snd_mixer_elem_t* elem, bool mute) { | |
355 int enabled = mute ? 0 : 1; | |
356 snd_mixer_selem_set_playback_switch_all(elem, enabled); | |
357 | |
358 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem) | |
359 << " to " << enabled; | |
360 } | |
361 | |
362 } // namespace chromeos | |
363 | |
OLD | NEW |