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
|
// 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_
|