audio_handler.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
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_handler.h"
6
7#include <math.h>
8
9#include "base/logging.h"
10#include "base/singleton.h"
11#include "chrome/browser/chromeos/pulse_audio_mixer.h"
12
13namespace chromeos {
14
15namespace {
16
17const double kSilenceDb = -200.0;
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
28}  // namespace
29
30// This class will set volume using PulseAudio to adjust volume and mute, and
31// handles the volume level logic.
32
33// TODO(davej): Serialize volume/mute for next startup?
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 kMinVolumeDb.
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 = 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 = 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
94  DVLOG(1) << "Setting Mute to " << do_mute;
95
96  mixer_->SetMute(do_mute);
97}
98
99void AudioHandler::OnMixerInitialized(bool success) {
100  connected_ = success;
101  DVLOG(1) << "OnMixerInitialized, success = " << success;
102}
103
104AudioHandler::AudioHandler()
105    : connected_(false),
106      reconnect_tries_(0) {
107  mixer_.reset(new PulseAudioMixer());
108  if (!mixer_->Init(NewCallback(this, &AudioHandler::OnMixerInitialized))) {
109    LOG(ERROR) << "Unable to connect to PulseAudio";
110  }
111}
112
113AudioHandler::~AudioHandler() {
114  mixer_.reset();
115};
116
117bool AudioHandler::VerifyMixerConnection() {
118  PulseAudioMixer::State mixer_state = mixer_->CheckState();
119  if (mixer_state == PulseAudioMixer::READY)
120    return true;
121  if (connected_) {
122    // Something happened and the mixer is no longer valid after having been
123    // initialized earlier.
124    connected_ = false;
125    LOG(ERROR) << "Lost connection to PulseAudio";
126  } else {
127    LOG(ERROR) << "Mixer not valid";
128  }
129
130  if ((mixer_state == PulseAudioMixer::INITIALIZING) ||
131      (mixer_state == PulseAudioMixer::SHUTTING_DOWN))
132    return false;
133
134  if (reconnect_tries_ < kMaxReconnectTries) {
135    reconnect_tries_++;
136    VLOG(1) << "Re-connecting to PulseAudio attempt " << reconnect_tries_ << "/"
137            << kMaxReconnectTries;
138    mixer_.reset(new PulseAudioMixer());
139    connected_ = mixer_->InitSync();
140    if (connected_) {
141      reconnect_tries_ = 0;
142      return true;
143    }
144    LOG(ERROR) << "Unable to re-connect to PulseAudio";
145  }
146  return false;
147}
148
149// VolumeDbToPercent() and PercentToVolumeDb() conversion functions allow us
150// complete control over how the 0 to 100% range is mapped to actual loudness.
151// Volume range is from kMinVolumeDb at just above 0% to kMaxVolumeDb at 100%
152// with a special case at 0% which maps to kSilenceDb.
153//
154// The mapping is confined to these two functions to make it easy to adjust and
155// have everything else just work.  The range is biased to give finer resolution
156// in the higher volumes if kVolumeBias is less than 1.0.
157
158// static
159double AudioHandler::VolumeDbToPercent(double volume_db) {
160  if (volume_db < kMinVolumeDb)
161    return 0;
162  return 100.0 * pow((volume_db - kMinVolumeDb) /
163      (kMaxVolumeDb - kMinVolumeDb), 1/kVolumeBias);
164}
165
166// static
167double AudioHandler::PercentToVolumeDb(double volume_percent) {
168  return pow(volume_percent / 100.0, kVolumeBias) *
169      (kMaxVolumeDb - kMinVolumeDb) + kMinVolumeDb;
170}
171
172// static
173AudioHandler* AudioHandler::GetInstance() {
174  return Singleton<AudioHandler>::get();
175}
176
177}  // namespace chromeos
178