1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
media / audio / agc_audio_stream.h [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_AUDIO_AGC_AUDIO_STREAM_H_
#define MEDIA_AUDIO_AGC_AUDIO_STREAM_H_
#include <atomic>
#include "base/logging.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "media/audio/audio_io.h"
// The template based AgcAudioStream implements platform-independent parts
// of the AudioInterface interface. Supported interfaces to pass as
// AudioInterface are AudioIntputStream and AudioOutputStream. Each platform-
// dependent implementation should derive from this class.
//
// Usage example (on Windows):
//
// class WASAPIAudioInputStream : public AgcAudioStream<AudioInputStream> {
// public:
// WASAPIAudioInputStream();
// ...
// };
//
// Call flow example:
//
// 1) User creates AgcAudioStream<AudioInputStream>
// 2) User calls AudioInputStream::SetAutomaticGainControl(true) =>
// AGC usage is now initialized but not yet started.
// 3) User calls AudioInputStream::Start() => implementation calls
// AgcAudioStream<AudioInputStream>::StartAgc() which detects that AGC
// is enabled and then starts the periodic AGC timer.
// 4) Microphone volume samples are now taken and included in all
// AudioInputCallback::OnData() callbacks.
// 5) User calls AudioInputStream::Stop() => implementation calls
// AgcAudioStream<AudioInputStream>::StopAgc() which stops the timer.
//
// Note that, calling AudioInputStream::SetAutomaticGainControl(false) while
// AGC measurements are active will not have an effect until StopAgc(),
// StartAgc() are called again since SetAutomaticGainControl() only sets a
// a state.
//
// Calling SetAutomaticGainControl(true) enables the AGC and StartAgc() starts
// a periodic timer which calls QueryAndStoreNewMicrophoneVolume()
// approximately once every second. QueryAndStoreNewMicrophoneVolume() asks
// the actual microphone about its current volume level. This value is
// normalized and stored so it can be read by GetAgcVolume() when the real-time
// audio thread needs the value. The main idea behind this scheme is to avoid
// accessing the audio hardware from the real-time audio thread and to ensure
// that we don't take new microphone-level samples too often (~1 Hz is a
// suitable compromise). The timer will be active until StopAgc() is called.
//
// This class should be created and destroyed on the audio manager thread and
// a thread checker is added to ensure that this is the case (uses DCHECK).
// All methods except GetAgcVolume() should be called on the creating thread
// as well to ensure that thread safety is maintained. It will also guarantee
// that the periodic timer runs on the audio manager thread.
// |normalized_volume_|, which is updated by QueryAndStoreNewMicrophoneVolume()
// and read in GetAgcVolume(), is atomic to ensure that it can be accessed from
// any real-time audio thread that needs it to update the its AGC volume.
namespace media {
template <typename AudioInterface>
class MEDIA_EXPORT AgcAudioStream : public AudioInterface {
public:
// Time between two successive timer events.
static constexpr base::TimeDelta kIntervalBetweenVolumeUpdates =
base::Milliseconds(1000);
AgcAudioStream()
: agc_is_enabled_(false), max_volume_(0.0), normalized_volume_(0.0) {
}
AgcAudioStream(const AgcAudioStream&) = delete;
AgcAudioStream& operator=(const AgcAudioStream&) = delete;
virtual ~AgcAudioStream() {
DCHECK(thread_checker_.CalledOnValidThread());
}
protected:
// Starts the periodic timer which periodically checks and updates the
// current microphone volume level.
// The timer is only started if AGC mode is first enabled using the
// SetAutomaticGainControl() method.
void StartAgc() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!agc_is_enabled_ || timer_.IsRunning())
return;
max_volume_ = static_cast<AudioInterface*>(this)->GetMaxVolume();
if (max_volume_ <= 0) {
DLOG(WARNING) << "Failed to get max volume from hardware. Won't provide "
<< "normalized volume.";
return;
}
// Query and cache the volume to avoid sending 0 as volume to AGC at the
// beginning of the audio stream, otherwise AGC will try to raise the
// volume from 0.
QueryAndStoreNewMicrophoneVolume();
timer_.Start(FROM_HERE, kIntervalBetweenVolumeUpdates, this,
&AgcAudioStream::QueryAndStoreNewMicrophoneVolume);
}
// Stops the periodic timer which periodically checks and updates the
// current microphone volume level.
void StopAgc() {
DCHECK(thread_checker_.CalledOnValidThread());
if (timer_.IsRunning())
timer_.Stop();
}
// Stores a new microphone volume level by checking the audio input device.
// Called on the audio manager thread.
void UpdateAgcVolume() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!timer_.IsRunning())
return;
// We take new volume samples once every second when the AGC is enabled.
// To ensure that a new setting has an immediate effect, the new volume
// setting is cached here. It will ensure that the next OnData() callback
// will contain a new valid volume level. If this approach was not taken,
// we could report invalid volume levels to the client for a time period
// of up to one second.
QueryAndStoreNewMicrophoneVolume();
}
// Gets the latest stored volume level if AGC is enabled.
// Called at each capture callback on a real-time capture thread (platform
// dependent).
void GetAgcVolume(double* normalized_volume) {
*normalized_volume = normalized_volume_.load(std::memory_order_relaxed);
}
// Gets the current automatic gain control state.
bool GetAutomaticGainControl() override {
DCHECK(thread_checker_.CalledOnValidThread());
return agc_is_enabled_;
}
private:
// Sets the automatic gain control (AGC) to on or off. When AGC is enabled,
// the microphone volume is queried periodically and the volume level can
// be read in each AudioInputCallback::OnData() callback and fed to the
// render-side AGC. User must call StartAgc() as well to start measuring
// the microphone level.
bool SetAutomaticGainControl(bool enabled) override {
DVLOG(1) << "SetAutomaticGainControl(enabled=" << enabled << ")";
DCHECK(thread_checker_.CalledOnValidThread());
agc_is_enabled_ = enabled;
return true;
}
// Takes a new microphone volume sample and stores it in |normalized_volume_|.
// Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux.
// This method is called periodically when AGC is enabled and always on the
// audio manager thread. We use it to read the current microphone level and
// to store it so it can be read by the main capture thread. By using this
// approach, we can avoid accessing audio hardware from a real-time audio
// thread and it leads to a more stable capture performance.
void QueryAndStoreNewMicrophoneVolume() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_GT(max_volume_, 0.0);
// Retrieve the current volume level by asking the audio hardware.
// Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux.
double normalized_volume =
static_cast<AudioInterface*>(this)->GetVolume() / max_volume_;
normalized_volume_.store(normalized_volume, std::memory_order_relaxed);
}
// Ensures that this class is created and destroyed on the same thread.
base::ThreadChecker thread_checker_;
// Repeating timer which cancels itself when it goes out of scope.
// Used to check the microphone volume periodically.
base::RepeatingTimer timer_;
// True when automatic gain control is enabled, false otherwise.
bool agc_is_enabled_;
// Stores the maximum volume which is used for normalization to a volume
// range of [0.0, 1.0].
double max_volume_;
// Contains last result of internal call to GetVolume(). We save resources
// by not querying the capture volume for each callback. The range is
// normalized to [0.0, 1.0].
std::atomic<double> normalized_volume_;
};
} // namespace media
#endif // MEDIA_AUDIO_AGC_AUDIO_STREAM_H_