1 // Copyright 2013 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.
5 #include "content/renderer/media/media_stream_audio_processor_options.h"
7 #include "base/files/file_path.h"
8 #include "base/files/file_util.h"
9 #include "base/logging.h"
10 #include "base/metrics/field_trial.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "content/common/media/media_stream_options.h"
16 #include "content/renderer/media/media_stream_constraints_util.h"
17 #include "content/renderer/media/media_stream_source.h"
18 #include "content/renderer/media/rtc_media_constraints.h"
19 #include "media/audio/audio_parameters.h"
20 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
21 #include "third_party/webrtc/modules/audio_processing/typing_detection.h"
25 const char MediaAudioConstraints::kEchoCancellation
[] = "echoCancellation";
26 const char MediaAudioConstraints::kGoogEchoCancellation
[] =
27 "googEchoCancellation";
28 const char MediaAudioConstraints::kGoogExperimentalEchoCancellation
[] =
29 "googEchoCancellation2";
30 const char MediaAudioConstraints::kGoogAutoGainControl
[] =
31 "googAutoGainControl";
32 const char MediaAudioConstraints::kGoogExperimentalAutoGainControl
[] =
33 "googAutoGainControl2";
34 const char MediaAudioConstraints::kGoogNoiseSuppression
[] =
35 "googNoiseSuppression";
36 const char MediaAudioConstraints::kGoogExperimentalNoiseSuppression
[] =
37 "googNoiseSuppression2";
38 const char MediaAudioConstraints::kGoogBeamforming
[] = "googBeamforming";
39 const char MediaAudioConstraints::kGoogHighpassFilter
[] = "googHighpassFilter";
40 const char MediaAudioConstraints::kGoogTypingNoiseDetection
[] =
41 "googTypingNoiseDetection";
42 const char MediaAudioConstraints::kGoogAudioMirroring
[] = "googAudioMirroring";
46 // Constant constraint keys which enables default audio constraints on
47 // mediastreams with audio.
51 } const kDefaultAudioConstraints
[] = {
52 { MediaAudioConstraints::kEchoCancellation
, true },
53 { MediaAudioConstraints::kGoogEchoCancellation
, true },
54 #if defined(OS_ANDROID) || defined(OS_IOS)
55 { MediaAudioConstraints::kGoogExperimentalEchoCancellation
, false },
57 // Enable the extended filter mode AEC on all non-mobile platforms.
58 { MediaAudioConstraints::kGoogExperimentalEchoCancellation
, true },
60 { MediaAudioConstraints::kGoogAutoGainControl
, true },
61 { MediaAudioConstraints::kGoogExperimentalAutoGainControl
, true },
62 { MediaAudioConstraints::kGoogNoiseSuppression
, true },
63 { MediaAudioConstraints::kGoogHighpassFilter
, true },
64 { MediaAudioConstraints::kGoogTypingNoiseDetection
, true },
65 { MediaAudioConstraints::kGoogExperimentalNoiseSuppression
, false },
66 { MediaAudioConstraints::kGoogBeamforming
, false },
68 { kMediaStreamAudioDucking
, true },
70 { kMediaStreamAudioDucking
, false },
72 { kMediaStreamAudioHotword
, false },
75 bool IsAudioProcessingConstraint(const std::string
& key
) {
76 // |kMediaStreamAudioDucking| does not require audio processing.
77 return key
!= kMediaStreamAudioDucking
;
80 // Used to log echo quality based on delay estimates.
81 enum DelayBasedEchoQuality
{
82 DELAY_BASED_ECHO_QUALITY_GOOD
= 0,
83 DELAY_BASED_ECHO_QUALITY_SPURIOUS
,
84 DELAY_BASED_ECHO_QUALITY_BAD
,
85 DELAY_BASED_ECHO_QUALITY_MAX
88 DelayBasedEchoQuality
EchoDelayFrequencyToQuality(float delay_frequency
) {
89 const float kEchoDelayFrequencyLowerLimit
= 0.1f
;
90 const float kEchoDelayFrequencyUpperLimit
= 0.8f
;
91 // DELAY_BASED_ECHO_QUALITY_GOOD
92 // delay is out of bounds during at most 10 % of the time.
93 // DELAY_BASED_ECHO_QUALITY_SPURIOUS
94 // delay is out of bounds 10-80 % of the time.
95 // DELAY_BASED_ECHO_QUALITY_BAD
96 // delay is mostly out of bounds >= 80 % of the time.
97 if (delay_frequency
<= kEchoDelayFrequencyLowerLimit
)
98 return DELAY_BASED_ECHO_QUALITY_GOOD
;
99 else if (delay_frequency
< kEchoDelayFrequencyUpperLimit
)
100 return DELAY_BASED_ECHO_QUALITY_SPURIOUS
;
102 return DELAY_BASED_ECHO_QUALITY_BAD
;
107 // TODO(xians): Remove this method after the APM in WebRtc is deprecated.
108 void MediaAudioConstraints::ApplyFixedAudioConstraints(
109 RTCMediaConstraints
* constraints
) {
110 for (size_t i
= 0; i
< arraysize(kDefaultAudioConstraints
); ++i
) {
111 bool already_set_value
;
112 if (!webrtc::FindConstraint(constraints
, kDefaultAudioConstraints
[i
].key
,
113 &already_set_value
, NULL
)) {
114 const std::string value
= kDefaultAudioConstraints
[i
].value
?
115 webrtc::MediaConstraintsInterface::kValueTrue
:
116 webrtc::MediaConstraintsInterface::kValueFalse
;
117 constraints
->AddOptional(kDefaultAudioConstraints
[i
].key
, value
, false);
119 DVLOG(1) << "Constraint " << kDefaultAudioConstraints
[i
].key
120 << " already set to " << already_set_value
;
125 MediaAudioConstraints::MediaAudioConstraints(
126 const blink::WebMediaConstraints
& constraints
, int effects
)
127 : constraints_(constraints
),
129 default_audio_processing_constraint_value_(true) {
130 // The default audio processing constraints are turned off when
131 // - gUM has a specific kMediaStreamSource, which is used by tab capture
132 // and screen capture.
133 // - |kEchoCancellation| is explicitly set to false.
134 std::string value_str
;
135 bool value_bool
= false;
136 if ((GetConstraintValueAsString(constraints
, kMediaStreamSource
,
138 (GetConstraintValueAsBoolean(constraints_
, kEchoCancellation
,
139 &value_bool
) && !value_bool
)) {
140 default_audio_processing_constraint_value_
= false;
144 MediaAudioConstraints::~MediaAudioConstraints() {}
146 bool MediaAudioConstraints::GetProperty(const std::string
& key
) {
147 // Return the value if the constraint is specified in |constraints|,
148 // otherwise return the default value.
150 if (!GetConstraintValueAsBoolean(constraints_
, key
, &value
))
151 value
= GetDefaultValueForConstraint(constraints_
, key
);
156 bool MediaAudioConstraints::GetEchoCancellationProperty() {
157 // If platform echo canceller is enabled, disable the software AEC.
158 if (effects_
& media::AudioParameters::ECHO_CANCELLER
)
161 // If |kEchoCancellation| is specified in the constraints, it will
162 // override the value of |kGoogEchoCancellation|.
164 if (GetConstraintValueAsBoolean(constraints_
, kEchoCancellation
, &value
))
167 return GetProperty(kGoogEchoCancellation
);
170 bool MediaAudioConstraints::IsValid() {
171 blink::WebVector
<blink::WebMediaConstraint
> mandatory
;
172 constraints_
.getMandatoryConstraints(mandatory
);
173 for (size_t i
= 0; i
< mandatory
.size(); ++i
) {
174 const std::string key
= mandatory
[i
].m_name
.utf8();
175 if (key
== kMediaStreamSource
|| key
== kMediaStreamSourceId
||
176 key
== MediaStreamSource::kSourceId
) {
177 // Ignore Chrome specific Tab capture and |kSourceId| constraints.
182 for (size_t j
= 0; j
< arraysize(kDefaultAudioConstraints
); ++j
) {
183 if (key
== kDefaultAudioConstraints
[j
].key
) {
185 valid
= GetMandatoryConstraintValueAsBoolean(constraints_
, key
, &value
);
191 DLOG(ERROR
) << "Invalid MediaStream constraint. Name: " << key
;
199 bool MediaAudioConstraints::GetDefaultValueForConstraint(
200 const blink::WebMediaConstraints
& constraints
, const std::string
& key
) {
201 // |kMediaStreamAudioDucking| is not restricted by
202 // |default_audio_processing_constraint_value_| since it does not require
204 if (!default_audio_processing_constraint_value_
&&
205 IsAudioProcessingConstraint(key
))
208 for (size_t i
= 0; i
< arraysize(kDefaultAudioConstraints
); ++i
) {
209 if (kDefaultAudioConstraints
[i
].key
== key
)
210 return kDefaultAudioConstraints
[i
].value
;
216 EchoInformation::EchoInformation()
217 : echo_poor_delay_counts_(0),
218 echo_total_delay_counts_(0),
219 last_log_time_(base::TimeTicks::Now()) {}
221 EchoInformation::~EchoInformation() {}
223 void EchoInformation::UpdateAecDelayStats(int delay
) {
224 // One way to get an indication of how well the echo cancellation performs is
225 // to compare the, by AEC, estimated delay with the AEC filter length.
226 // |kMaxAecFilterLengthMs| is the maximum delay we can allow before we
227 // consider the AEC to fail. This value should not be larger than the filter
228 // length used inside AEC. This is for now set to match the extended filter
229 // mode which is turned on for all platforms.
230 const int kMaxAecFilterLengthMs
= 128;
231 if ((delay
< -2) || (delay
> kMaxAecFilterLengthMs
)) {
232 // The |delay| is out of bounds which indicates that the echo cancellation
233 // filter can not handle the echo. Hence, we have a potential full echo
234 // case. |delay| values {-1, -2} are reserved for errors.
235 ++echo_poor_delay_counts_
;
237 ++echo_total_delay_counts_
;
241 void EchoInformation::LogAecDelayStats() {
242 // We update the UMA statistics every 5 seconds.
243 const int kTimeBetweenLogsInSeconds
= 5;
244 const base::TimeDelta time_since_last_log
=
245 base::TimeTicks::Now() - last_log_time_
;
246 if (time_since_last_log
.InSeconds() < kTimeBetweenLogsInSeconds
)
249 // Calculate how frequent the AEC delay was out of bounds since last time we
250 // updated UMA histograms. Then store the result into one of three histogram
251 // buckets; see DelayBasedEchoQuality.
252 float poor_delay_frequency
= 0.f
;
253 if (echo_total_delay_counts_
> 0) {
254 poor_delay_frequency
= static_cast<float>(echo_poor_delay_counts_
) /
255 static_cast<float>(echo_total_delay_counts_
);
256 UMA_HISTOGRAM_ENUMERATION("WebRTC.AecDelayBasedQuality",
257 EchoDelayFrequencyToQuality(poor_delay_frequency
),
258 DELAY_BASED_ECHO_QUALITY_MAX
);
260 echo_poor_delay_counts_
= 0;
261 echo_total_delay_counts_
= 0;
262 last_log_time_
= base::TimeTicks::Now();
265 void EnableEchoCancellation(AudioProcessing
* audio_processing
) {
266 #if defined(OS_ANDROID) || defined(OS_IOS)
267 const std::string group_name
=
268 base::FieldTrialList::FindFullName("ReplaceAECMWithAEC");
269 if (group_name
.empty() ||
270 !(group_name
== "Enabled" || group_name
== "DefaultEnabled")) {
271 // Mobile devices are using AECM.
272 int err
= audio_processing
->echo_control_mobile()->set_routing_mode(
273 webrtc::EchoControlMobile::kSpeakerphone
);
274 err
|= audio_processing
->echo_control_mobile()->Enable(true);
279 int err
= audio_processing
->echo_cancellation()->set_suppression_level(
280 webrtc::EchoCancellation::kHighSuppression
);
282 // Enable the metrics for AEC.
283 err
|= audio_processing
->echo_cancellation()->enable_metrics(true);
284 err
|= audio_processing
->echo_cancellation()->enable_delay_logging(true);
285 err
|= audio_processing
->echo_cancellation()->Enable(true);
289 void EnableNoiseSuppression(AudioProcessing
* audio_processing
) {
290 int err
= audio_processing
->noise_suppression()->set_level(
291 webrtc::NoiseSuppression::kHigh
);
292 err
|= audio_processing
->noise_suppression()->Enable(true);
296 void EnableHighPassFilter(AudioProcessing
* audio_processing
) {
297 CHECK_EQ(audio_processing
->high_pass_filter()->Enable(true), 0);
300 void EnableTypingDetection(AudioProcessing
* audio_processing
,
301 webrtc::TypingDetection
* typing_detector
) {
302 int err
= audio_processing
->voice_detection()->Enable(true);
303 err
|= audio_processing
->voice_detection()->set_likelihood(
304 webrtc::VoiceDetection::kVeryLowLikelihood
);
307 // Configure the update period to 1s (100 * 10ms) in the typing detector.
308 typing_detector
->SetParameters(0, 0, 0, 0, 0, 100);
311 void StartEchoCancellationDump(AudioProcessing
* audio_processing
,
312 base::File aec_dump_file
) {
313 DCHECK(aec_dump_file
.IsValid());
315 FILE* stream
= base::FileToFILE(aec_dump_file
.Pass(), "w");
317 LOG(ERROR
) << "Failed to open AEC dump file";
321 if (audio_processing
->StartDebugRecording(stream
))
322 DLOG(ERROR
) << "Fail to start AEC debug recording";
325 void StopEchoCancellationDump(AudioProcessing
* audio_processing
) {
326 if (audio_processing
->StopDebugRecording())
327 DLOG(ERROR
) << "Fail to stop AEC debug recording";
330 void EnableAutomaticGainControl(AudioProcessing
* audio_processing
) {
331 #if defined(OS_ANDROID) || defined(OS_IOS)
332 const webrtc::GainControl::Mode mode
= webrtc::GainControl::kFixedDigital
;
334 const webrtc::GainControl::Mode mode
= webrtc::GainControl::kAdaptiveAnalog
;
336 int err
= audio_processing
->gain_control()->set_mode(mode
);
337 err
|= audio_processing
->gain_control()->Enable(true);
341 void GetAecStats(AudioProcessing
* audio_processing
,
342 webrtc::AudioProcessorInterface::AudioProcessorStats
* stats
) {
343 // These values can take on valid negative values, so use the lowest possible
344 // level as default rather than -1.
345 stats
->echo_return_loss
= -100;
346 stats
->echo_return_loss_enhancement
= -100;
348 // These values can also be negative, but in practice -1 is only used to
349 // signal insufficient data, since the resolution is limited to multiples
351 stats
->echo_delay_median_ms
= -1;
352 stats
->echo_delay_std_ms
= -1;
354 // TODO(ajm): Re-enable this metric once we have a reliable implementation.
355 stats
->aec_quality_min
= -1.0f
;
357 if (!audio_processing
->echo_cancellation()->are_metrics_enabled() ||
358 !audio_processing
->echo_cancellation()->is_delay_logging_enabled() ||
359 !audio_processing
->echo_cancellation()->is_enabled()) {
363 // TODO(ajm): we may want to use VoECallReport::GetEchoMetricsSummary
364 // here, but it appears to be unsuitable currently. Revisit after this is
365 // investigated: http://b/issue?id=5666755
366 webrtc::EchoCancellation::Metrics echo_metrics
;
367 if (!audio_processing
->echo_cancellation()->GetMetrics(&echo_metrics
)) {
368 stats
->echo_return_loss
= echo_metrics
.echo_return_loss
.instant
;
369 stats
->echo_return_loss_enhancement
=
370 echo_metrics
.echo_return_loss_enhancement
.instant
;
373 int median
= 0, std
= 0;
374 if (!audio_processing
->echo_cancellation()->GetDelayMetrics(&median
, &std
)) {
375 stats
->echo_delay_median_ms
= median
;
376 stats
->echo_delay_std_ms
= std
;
380 } // namespace content