1// Copyright (c) 2011 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_handler.h"
6
7#include <math.h>
8
9#include "base/logging.h"
10#include "base/memory/singleton.h"
11#include "chrome/browser/chromeos/audio_mixer_alsa.h"
12#include "content/browser/browser_thread.h"
13
14namespace chromeos {
15
16namespace {
17
18const double kMinVolumeDb = -90.0;
19// Choosing 6.0dB here instead of 0dB to give user chance to amplify audio some
20// in case sounds or their setup is too quiet for them.
21const double kMaxVolumeDb = 6.0;
22// A value of less than one adjusts quieter volumes in larger steps (giving
23// finer resolution in the higher volumes).
24const double kVolumeBias = 0.5;
25// If a connection is lost, we try again this many times
26const int kMaxReconnectTries = 4;
27// A flag to disable mixer.
28bool g_disabled = false;
29
30}  // namespace
31
32// chromeos:  This class will set the volume using ALSA to adjust volume and
33// mute, and handle the volume level logic.
34
35double AudioHandler::GetVolumePercent() {
36  if (!VerifyMixerConnection())
37    return 0;
38
39  return VolumeDbToPercent(mixer_->GetVolumeDb());
40}
41
42// Set volume using our internal 0-100% range.  Notice 0% is a special case of
43// silence, so we set the mixer volume to kSilenceDb instead of min_volume_db_.
44void AudioHandler::SetVolumePercent(double volume_percent) {
45  if (!VerifyMixerConnection())
46    return;
47  DCHECK(volume_percent >= 0.0);
48
49  double vol_db;
50  if (volume_percent <= 0)
51    vol_db = AudioMixer::kSilenceDb;
52  else
53    vol_db = PercentToVolumeDb(volume_percent);
54
55  mixer_->SetVolumeDb(vol_db);
56}
57
58void AudioHandler::AdjustVolumeByPercent(double adjust_by_percent) {
59  if (!VerifyMixerConnection())
60    return;
61
62  DVLOG(1) << "Adjusting Volume by " << adjust_by_percent << " percent";
63
64  double volume = mixer_->GetVolumeDb();
65  double pct = VolumeDbToPercent(volume);
66
67  if (pct < 0)
68    pct = 0;
69  pct = pct + adjust_by_percent;
70  if (pct > 100.0)
71    pct = 100.0;
72
73  double new_volume;
74  if (pct <= 0.1)
75    new_volume = AudioMixer::kSilenceDb;
76  else
77    new_volume = PercentToVolumeDb(pct);
78
79  if (new_volume != volume)
80    mixer_->SetVolumeDb(new_volume);
81}
82
83bool AudioHandler::IsMute() {
84  if (!VerifyMixerConnection())
85    return false;
86
87  return mixer_->IsMute();
88}
89
90void AudioHandler::SetMute(bool do_mute) {
91  if (!VerifyMixerConnection())
92    return;
93  DVLOG(1) << "Setting Mute to " << do_mute;
94  mixer_->SetMute(do_mute);
95}
96
97void AudioHandler::Disconnect() {
98  mixer_.reset();
99}
100
101void AudioHandler::Disable() {
102  g_disabled = true;
103}
104
105bool AudioHandler::TryToConnect(bool async) {
106  if (mixer_type_ == MIXER_TYPE_ALSA) {
107    VLOG(1) << "Trying to connect to ALSA";
108    mixer_.reset(new AudioMixerAlsa());
109  } else {
110    VLOG(1) << "Cannot find valid volume mixer";
111    mixer_.reset();
112    return false;
113  }
114
115  if (async) {
116    mixer_->Init(NewCallback(this, &AudioHandler::OnMixerInitialized));
117  } else {
118    if (!mixer_->InitSync()) {
119      VLOG(1) << "Unable to reconnect to Mixer";
120      return false;
121    }
122  }
123  return true;
124}
125
126static void ClipVolume(double* min_volume, double* max_volume) {
127  if (*min_volume < kMinVolumeDb)
128    *min_volume = kMinVolumeDb;
129  if (*max_volume > kMaxVolumeDb)
130    *max_volume = kMaxVolumeDb;
131}
132
133void AudioHandler::OnMixerInitialized(bool success) {
134  connected_ = success;
135  DVLOG(1) << "OnMixerInitialized, success = " << success;
136
137  if (connected_) {
138    if (mixer_->GetVolumeLimits(&min_volume_db_, &max_volume_db_)) {
139      ClipVolume(&min_volume_db_, &max_volume_db_);
140    }
141    return;
142  }
143
144  VLOG(1) << "Unable to connect to mixer";
145  mixer_type_ = MIXER_TYPE_NONE;
146
147  // This frees the mixer on the UI thread
148  BrowserThread::PostTask(
149      BrowserThread::UI, FROM_HERE,
150      NewRunnableMethod(this, &AudioHandler::TryToConnect, true));
151}
152
153AudioHandler::AudioHandler()
154    : connected_(false),
155      reconnect_tries_(0),
156      max_volume_db_(kMaxVolumeDb),
157      min_volume_db_(kMinVolumeDb),
158      mixer_type_(g_disabled ? MIXER_TYPE_NONE : MIXER_TYPE_ALSA) {
159  // Start trying to connect to mixers asynchronously, starting with the current
160  // mixer_type_.  If the connection fails, another TryToConnect() for the next
161  // mixer will be posted at that time.
162  TryToConnect(true);
163}
164
165AudioHandler::~AudioHandler() {
166  Disconnect();
167};
168
169bool AudioHandler::VerifyMixerConnection() {
170  if (mixer_ == NULL)
171    return false;
172
173  AudioMixer::State mixer_state = mixer_->GetState();
174  if (mixer_state == AudioMixer::READY)
175    return true;
176  if (connected_) {
177    // Something happened and the mixer is no longer valid after having been
178    // initialized earlier.
179    connected_ = false;
180    LOG(ERROR) << "Lost connection to mixer";
181  } else {
182    LOG(ERROR) << "Mixer not valid";
183  }
184
185  if ((mixer_state == AudioMixer::INITIALIZING) ||
186      (mixer_state == AudioMixer::SHUTTING_DOWN))
187    return false;
188
189  if (reconnect_tries_ < kMaxReconnectTries) {
190    reconnect_tries_++;
191    VLOG(1) << "Re-connecting to mixer attempt " << reconnect_tries_ << "/"
192            << kMaxReconnectTries;
193
194    connected_ = TryToConnect(false);
195
196    if (connected_) {
197      reconnect_tries_ = 0;
198      return true;
199    }
200    LOG(ERROR) << "Unable to re-connect to mixer";
201  }
202  return false;
203}
204
205// VolumeDbToPercent() and PercentToVolumeDb() conversion functions allow us
206// complete control over how the 0 to 100% range is mapped to actual loudness.
207// Volume range is from min_volume_db_ at just above 0% to max_volume_db_
208// at 100% with a special case at 0% which maps to kSilenceDb.
209//
210// The mapping is confined to these two functions to make it easy to adjust and
211// have everything else just work.  The range is biased to give finer resolution
212// in the higher volumes if kVolumeBias is less than 1.0.
213
214// static
215double AudioHandler::VolumeDbToPercent(double volume_db) const {
216  if (volume_db < min_volume_db_)
217    return 0;
218  return 100.0 * pow((volume_db - min_volume_db_) /
219      (max_volume_db_ - min_volume_db_), 1/kVolumeBias);
220}
221
222// static
223double AudioHandler::PercentToVolumeDb(double volume_percent) const {
224  return pow(volume_percent / 100.0, kVolumeBias) *
225      (max_volume_db_ - min_volume_db_) + min_volume_db_;
226}
227
228// static
229AudioHandler* AudioHandler::GetInstance() {
230  return Singleton<AudioHandler>::get();
231}
232
233}  // namespace chromeos
234