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

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