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

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

Issue 5859003: Add ALSA support to volume keys (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/browser/chromeos
Patch Set: fix nits 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
OLDNEW
(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 {
47 AutoLock lock(mixer_state_lock_);
48 mixer_state_ = SHUTTING_DOWN;
49 FreeAlsaMixer();
50 }
51
52 if (thread_ != NULL) {
53 thread_->Stop();
54 thread_.reset();
55 }
56 }
57
58 bool AudioMixerAlsa::Init(InitDoneCallback* callback) {
59 if (!InitThread())
60 return false;
61
62 // Post the task of starting up, which can block for 200-500ms,
63 // so best not to do it on the caller's thread.
64 thread_->message_loop()->PostTask(FROM_HERE,
65 NewRunnableMethod(this, &AudioMixerAlsa::DoInit, callback));
66 return true;
67 }
68
69 bool AudioMixerAlsa::InitSync() {
70 if (!InitThread())
71 return false;
72 return InitializeAlsaMixer();
73 }
74
75 double AudioMixerAlsa::GetVolumeDb() const {
76 if (mixer_state_ != READY)
zhurunz 2011/01/05 18:44:44 Do we need a lock here? Or just call MixerReady()
77 return kSilenceDb;
78
79 double vol_total = 0;
zhurunz 2011/01/05 18:44:44 "0.0" is better
80 GetElementVolume(elem_master_, &vol_total);
81
82 double vol_pcm = 0;
83 if (elem_pcm_ && (GetElementVolume(elem_pcm_, &vol_pcm)))
84 vol_total += vol_pcm;
85
86 return vol_total;
87 }
88
89 bool AudioMixerAlsa::GetVolumeLimits(double* vol_min, double* vol_max) {
90 if (!MixerReady())
91 return false;
92 if (vol_min)
93 *vol_min = min_volume_;
94 if (vol_max)
95 *vol_max = max_volume_;
96 return true;
97 }
98
99 void AudioMixerAlsa::SetVolumeDb(double vol_db) {
100 if (!MixerReady())
101 return;
102 double actual_vol = 0;
103
104 // If a PCM volume slider exists, then first set the Master volume to the
105 // nearest volume >= requested volume, then adjust PCM volume down to get
106 // closer to the requested volume.
107
108 if (elem_pcm_) {
109 SetElementVolume(elem_master_, vol_db, &actual_vol, 0.9999f);
110 SetElementVolume(elem_pcm_, vol_db - actual_vol, NULL, 0.5f);
111 } else {
112 SetElementVolume(elem_master_, vol_db, NULL, 0.5f);
113 }
114 }
115
116 bool AudioMixerAlsa::IsMute() const {
117 if (!MixerReady())
118 return false;
119 return GetElementMuted(elem_master_);
120 }
121
122 void AudioMixerAlsa::SetMute(bool mute) {
123 if (!MixerReady())
124 return;
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 bool old_value = GetElementMuted(elem_master_);
133
134 if (old_value != mute) {
135 if (mute) {
136 save_volume_ = GetVolumeDb();
137 SetVolumeDb(min_volume_);
138 } else {
139 SetVolumeDb(save_volume_);
140 }
141 }
142
143 SetElementMuted(elem_master_, mute);
144 if (elem_pcm_)
145 SetElementMuted(elem_pcm_, mute);
146 }
147
148 AudioMixer::State AudioMixerAlsa::CheckState() const {
149 AutoLock lock(mixer_state_lock_);
150 // If we think it's ready, verify it is actually so.
151 if ((mixer_state_ == READY) && (alsa_mixer_ == NULL))
152 mixer_state_ = IN_ERROR;
153 return mixer_state_;
154 }
155
156 ////////////////////////////////////////////////////////////////////////////////
157 // Private functions follow
158
159 void AudioMixerAlsa::DoInit(InitDoneCallback* callback) {
160 bool success = InitializeAlsaMixer();
161
162 if (callback) {
163 callback->Run(success);
164 delete callback;
165 }
166 }
167
168 bool AudioMixerAlsa::InitThread() {
169 AutoLock lock(mixer_state_lock_);
170
171 if (mixer_state_ != UNINITIALIZED)
172 return false;
173
174 if (thread_ == NULL) {
175 thread_.reset(new base::Thread("AudioMixerAlsa"));
176 if (!thread_->Start()) {
177 thread_.reset();
178 return false;
179 }
180 }
181
182 mixer_state_ = INITIALIZING;
183 return true;
184 }
185
186 bool AudioMixerAlsa::InitializeAlsaMixer() {
187 AutoLock lock(mixer_state_lock_);
188 if (mixer_state_ != INITIALIZING)
189 return false;
190
191 int err;
192 snd_mixer_t* handle = NULL;
193 const char* card = "default";
194
195 if ((err = snd_mixer_open(&handle, 0)) < 0) {
196 LOG(ERROR) << "ALSA mixer " << card << " open error: " << snd_strerror(err);
197 return false;
198 }
199
200 if ((err = snd_mixer_attach(handle, card)) < 0) {
201 LOG(ERROR) << "ALSA Attach to card " << card << " failed: "
202 << snd_strerror(err);
203 snd_mixer_close(handle);
204 return false;
205 }
206
207 if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
208 LOG(ERROR) << "ALSA mixer register error: " << snd_strerror(err);
209 snd_mixer_close(handle);
210 return false;
211 }
212
213 if ((err = snd_mixer_load(handle)) < 0) {
214 LOG(ERROR) << "ALSA mixer " << card << " load error: %s"
215 << snd_strerror(err);
216 snd_mixer_close(handle);
217 return false;
218 }
219
220 VLOG(1) << "Opened ALSA mixer " << card << " OK";
221
222 elem_master_ = FindElementWithName(handle, kMasterVolume);
223 if (elem_master_) {
224 alsa_long_t long_lo, long_hi;
225 snd_mixer_selem_get_playback_dB_range(elem_master_, &long_lo, &long_hi);
226 min_volume_ = static_cast<double>(long_lo) / 100.0;
227 max_volume_ = static_cast<double>(long_hi) / 100.0;
228 } else {
229 LOG(ERROR) << "Cannot find 'Master' ALSA mixer element on " << card;
230 snd_mixer_close(handle);
231 return false;
232 }
233
234 elem_pcm_ = FindElementWithName(handle, kPCMVolume);
235 if (elem_pcm_) {
236 alsa_long_t long_lo, long_hi;
237 snd_mixer_selem_get_playback_dB_range(elem_pcm_, &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 VLOG(1) << "ALSA volume range is " << min_volume_ << " dB to "
243 << max_volume_ << " dB";
244
245 alsa_mixer_ = handle;
246 mixer_state_ = READY;
247 return true;
248 }
249
250 void AudioMixerAlsa::FreeAlsaMixer() {
251 if (alsa_mixer_) {
252 snd_mixer_close(alsa_mixer_);
253 alsa_mixer_ = NULL;
254 }
255 }
256
257 snd_mixer_elem_t* AudioMixerAlsa::FindElementWithName(
258 snd_mixer_t* handle,
259 const char* element_name) const {
260 snd_mixer_selem_id_t* sid;
261 snd_mixer_selem_id_alloca(&sid);
262 snd_mixer_selem_id_set_index(sid, 0);
263 snd_mixer_selem_id_set_name(sid, element_name);
264 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
265 if (!elem) {
266 LOG(ERROR) << "ALSA unable to find simple control "
267 << snd_mixer_selem_id_get_name(sid);
268 }
269 return elem;
270 }
271
272 bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* elem,
273 double* current_vol) const {
274 alsa_long_t long_vol;
zhurunz 2011/01/05 18:44:44 = 0; In case the API failed.
275 snd_mixer_selem_get_playback_dB(elem,
276 static_cast<snd_mixer_selem_channel_id_t>(0),
277 &long_vol);
278 *current_vol = static_cast<double>(long_vol) / 100.0;
279
280 return true;
281 }
282
283 bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* elem,
284 double new_vol,
285 double* actual_vol,
286 double rounding_bias) {
287 alsa_long_t vol_lo = 0;
288 alsa_long_t vol_hi = 0;
289 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi);
290 alsa_long_t vol_range = vol_hi - vol_lo;
291 if (vol_range <= 0)
292 return false;
293
294 alsa_long_t db_lo_int = 0;
295 alsa_long_t db_hi_int = 0;
296 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int);
297 double db_lo = static_cast<double>(db_lo_int) / 100.0;
298 double db_hi = static_cast<double>(db_hi_int) / 100.0;
299 double db_step = static_cast<double>(db_hi - db_lo) / vol_range;
300 if (db_step <= 0.0)
301 return false;
302
303 if (new_vol < db_lo)
304 new_vol = db_lo;
305
306 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias +
307 (new_vol - db_lo) / db_step) + vol_lo;
308 snd_mixer_selem_set_playback_volume_all(elem, value);
309
310 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem)
311 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo
312 << " dB";
313
314 if (actual_vol) {
315 alsa_long_t volume;
316 snd_mixer_selem_get_playback_volume(
317 elem,
318 static_cast<snd_mixer_selem_channel_id_t>(0),
319 &volume);
320 *actual_vol = db_lo + (volume - vol_lo) * db_step;
321
322 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem)
323 << " now " << *actual_vol << " dB";
324 }
325 return true;
326 }
327
328 bool AudioMixerAlsa::GetElementMuted(snd_mixer_elem_t* elem) const {
329 int enabled;
330 snd_mixer_selem_get_playback_switch(
331 elem,
332 static_cast<snd_mixer_selem_channel_id_t>(0),
333 &enabled);
334 return (enabled) ? false : true;
335 }
336
337 void AudioMixerAlsa::SetElementMuted(snd_mixer_elem_t* elem, bool mute) {
338 int enabled = mute ? 0 : 1;
339 snd_mixer_selem_set_playback_switch_all(elem, enabled);
340
341 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
342 << " to " << enabled;
343 }
344
345 bool AudioMixerAlsa::MixerReady() const {
346 AutoLock lock(mixer_state_lock_);
347 return (mixer_state_ == READY);
348 }
349
350 } // namespace chromeos
351
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698