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

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: holding lock during operations 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/chromeos/audio_mixer_pulse.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 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;
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) {
davejcool 2011/01/05 23:41:50 Calling DoGetvolumeDb instead of the public GetVol
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
davejcool 2011/01/05 23:41:50 Putting DoSet/DoGet here so it can be called when
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 snd_mixer_selem_id_alloca(&sid);
272 snd_mixer_selem_id_set_index(sid, 0);
273 snd_mixer_selem_id_set_name(sid, element_name);
274 snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
275 if (!elem) {
276 LOG(ERROR) << "ALSA unable to find simple control "
277 << snd_mixer_selem_id_get_name(sid);
278 }
279 return elem;
280 }
281
282 bool AudioMixerAlsa::GetElementVolume(snd_mixer_elem_t* elem,
283 double* current_vol) const {
284 alsa_long_t long_vol = 0;
285 snd_mixer_selem_get_playback_dB(elem,
286 static_cast<snd_mixer_selem_channel_id_t>(0),
287 &long_vol);
288 *current_vol = static_cast<double>(long_vol) / 100.0;
289
290 return true;
291 }
292
293 bool AudioMixerAlsa::SetElementVolume(snd_mixer_elem_t* elem,
294 double new_vol,
295 double* actual_vol,
296 double rounding_bias) {
297 alsa_long_t vol_lo = 0;
298 alsa_long_t vol_hi = 0;
299 snd_mixer_selem_get_playback_volume_range(elem, &vol_lo, &vol_hi);
300 alsa_long_t vol_range = vol_hi - vol_lo;
301 if (vol_range <= 0)
302 return false;
303
304 alsa_long_t db_lo_int = 0;
305 alsa_long_t db_hi_int = 0;
306 snd_mixer_selem_get_playback_dB_range(elem, &db_lo_int, &db_hi_int);
307 double db_lo = static_cast<double>(db_lo_int) / 100.0;
308 double db_hi = static_cast<double>(db_hi_int) / 100.0;
309 double db_step = static_cast<double>(db_hi - db_lo) / vol_range;
310 if (db_step <= 0.0)
311 return false;
312
313 if (new_vol < db_lo)
314 new_vol = db_lo;
315
316 alsa_long_t value = static_cast<alsa_long_t>(rounding_bias +
317 (new_vol - db_lo) / db_step) + vol_lo;
318 snd_mixer_selem_set_playback_volume_all(elem, value);
319
320 VLOG(1) << "Set volume " << snd_mixer_selem_get_name(elem)
321 << " to " << new_vol << " ==> " << (value - vol_lo) * db_step + db_lo
322 << " dB";
323
324 if (actual_vol) {
325 alsa_long_t volume;
326 snd_mixer_selem_get_playback_volume(
327 elem,
328 static_cast<snd_mixer_selem_channel_id_t>(0),
329 &volume);
330 *actual_vol = db_lo + (volume - vol_lo) * db_step;
331
332 VLOG(1) << "Actual volume " << snd_mixer_selem_get_name(elem)
333 << " now " << *actual_vol << " dB";
334 }
335 return true;
336 }
337
338 bool AudioMixerAlsa::GetElementMuted(snd_mixer_elem_t* elem) const {
339 int enabled;
340 snd_mixer_selem_get_playback_switch(
341 elem,
342 static_cast<snd_mixer_selem_channel_id_t>(0),
343 &enabled);
344 return (enabled) ? false : true;
345 }
346
347 void AudioMixerAlsa::SetElementMuted(snd_mixer_elem_t* elem, bool mute) {
348 int enabled = mute ? 0 : 1;
349 snd_mixer_selem_set_playback_switch_all(elem, enabled);
350
351 VLOG(1) << "Set playback switch " << snd_mixer_selem_get_name(elem)
352 << " to " << enabled;
353 }
354
355 } // namespace chromeos
356
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/audio_mixer_alsa.h ('k') | chrome/browser/chromeos/audio_mixer_pulse.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698