blob: cb4278d516c880241630035ce409cb41c239b80e [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/media_stream_audio_processor_options.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/media/media_stream_options.h"
#include "content/renderer/media/media_stream_constraints_util.h"
#include "content/renderer/media/media_stream_source.h"
#include "content/renderer/media/rtc_media_constraints.h"
#include "media/audio/audio_parameters.h"
#include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
#include "third_party/webrtc/modules/audio_processing/typing_detection.h"
namespace content {
const char MediaAudioConstraints::kEchoCancellation[] = "echoCancellation";
const char MediaAudioConstraints::kGoogEchoCancellation[] =
"googEchoCancellation";
const char MediaAudioConstraints::kGoogExperimentalEchoCancellation[] =
"googEchoCancellation2";
const char MediaAudioConstraints::kGoogAutoGainControl[] =
"googAutoGainControl";
const char MediaAudioConstraints::kGoogExperimentalAutoGainControl[] =
"googAutoGainControl2";
const char MediaAudioConstraints::kGoogNoiseSuppression[] =
"googNoiseSuppression";
const char MediaAudioConstraints::kGoogExperimentalNoiseSuppression[] =
"googNoiseSuppression2";
const char MediaAudioConstraints::kGoogHighpassFilter[] = "googHighpassFilter";
const char MediaAudioConstraints::kGoogTypingNoiseDetection[] =
"googTypingNoiseDetection";
const char MediaAudioConstraints::kGoogAudioMirroring[] = "googAudioMirroring";
namespace {
// Constant constraint keys which enables default audio constraints on
// mediastreams with audio.
struct {
const char* key;
bool value;
} const kDefaultAudioConstraints[] = {
{ MediaAudioConstraints::kEchoCancellation, true },
{ MediaAudioConstraints::kGoogEchoCancellation, true },
#if defined(OS_ANDROID) || defined(OS_IOS)
{ MediaAudioConstraints::kGoogExperimentalEchoCancellation, false },
#else
// Enable the extended filter mode AEC on all non-mobile platforms.
{ MediaAudioConstraints::kGoogExperimentalEchoCancellation, true },
#endif
{ MediaAudioConstraints::kGoogAutoGainControl, true },
{ MediaAudioConstraints::kGoogExperimentalAutoGainControl, true },
{ MediaAudioConstraints::kGoogNoiseSuppression, true },
{ MediaAudioConstraints::kGoogHighpassFilter, true },
{ MediaAudioConstraints::kGoogTypingNoiseDetection, true },
{ MediaAudioConstraints::kGoogExperimentalNoiseSuppression, false },
#if defined(OS_WIN)
{ kMediaStreamAudioDucking, true },
#else
{ kMediaStreamAudioDucking, false },
#endif
};
bool IsAudioProcessingConstraint(const std::string& key) {
// |kMediaStreamAudioDucking| does not require audio processing.
return key != kMediaStreamAudioDucking;
}
} // namespace
// TODO(xians): Remove this method after the APM in WebRtc is deprecated.
void MediaAudioConstraints::ApplyFixedAudioConstraints(
RTCMediaConstraints* constraints) {
for (size_t i = 0; i < arraysize(kDefaultAudioConstraints); ++i) {
bool already_set_value;
if (!webrtc::FindConstraint(constraints, kDefaultAudioConstraints[i].key,
&already_set_value, NULL)) {
const std::string value = kDefaultAudioConstraints[i].value ?
webrtc::MediaConstraintsInterface::kValueTrue :
webrtc::MediaConstraintsInterface::kValueFalse;
constraints->AddOptional(kDefaultAudioConstraints[i].key, value, false);
} else {
DVLOG(1) << "Constraint " << kDefaultAudioConstraints[i].key
<< " already set to " << already_set_value;
}
}
}
MediaAudioConstraints::MediaAudioConstraints(
const blink::WebMediaConstraints& constraints, int effects)
: constraints_(constraints),
effects_(effects),
default_audio_processing_constraint_value_(true) {
// The default audio processing constraints are turned off when
// - gUM has a specific kMediaStreamSource, which is used by tab capture
// and screen capture.
// - |kEchoCancellation| is explicitly set to false.
std::string value_str;
bool value_bool = false;
if ((GetConstraintValueAsString(constraints, kMediaStreamSource,
&value_str)) ||
(GetConstraintValueAsBoolean(constraints_, kEchoCancellation,
&value_bool) && !value_bool)) {
default_audio_processing_constraint_value_ = false;
}
}
MediaAudioConstraints::~MediaAudioConstraints() {}
// TODO(xians): Remove this method after the APM in WebRtc is deprecated.
bool MediaAudioConstraints::NeedsAudioProcessing() {
if (GetEchoCancellationProperty())
return true;
for (size_t i = 0; i < arraysize(kDefaultAudioConstraints); ++i) {
// |kEchoCancellation| and |kGoogEchoCancellation| have been convered by
// GetEchoCancellationProperty().
if (kDefaultAudioConstraints[i].key != kEchoCancellation &&
kDefaultAudioConstraints[i].key != kGoogEchoCancellation &&
IsAudioProcessingConstraint(kDefaultAudioConstraints[i].key) &&
GetProperty(kDefaultAudioConstraints[i].key)) {
return true;
}
}
return false;
}
bool MediaAudioConstraints::GetProperty(const std::string& key) {
// Return the value if the constraint is specified in |constraints|,
// otherwise return the default value.
bool value = false;
if (!GetConstraintValueAsBoolean(constraints_, key, &value))
value = GetDefaultValueForConstraint(constraints_, key);
return value;
}
bool MediaAudioConstraints::GetEchoCancellationProperty() {
// If platform echo canceller is enabled, disable the software AEC.
if (effects_ & media::AudioParameters::ECHO_CANCELLER)
return false;
// If |kEchoCancellation| is specified in the constraints, it will
// override the value of |kGoogEchoCancellation|.
bool value = false;
if (GetConstraintValueAsBoolean(constraints_, kEchoCancellation, &value))
return value;
return GetProperty(kGoogEchoCancellation);
}
bool MediaAudioConstraints::IsValid() {
blink::WebVector<blink::WebMediaConstraint> mandatory;
constraints_.getMandatoryConstraints(mandatory);
for (size_t i = 0; i < mandatory.size(); ++i) {
const std::string key = mandatory[i].m_name.utf8();
if (key == kMediaStreamSource || key == kMediaStreamSourceId ||
key == MediaStreamSource::kSourceId) {
// Ignore Chrome specific Tab capture and |kSourceId| constraints.
continue;
}
bool valid = false;
for (size_t j = 0; j < arraysize(kDefaultAudioConstraints); ++j) {
if (key == kDefaultAudioConstraints[j].key) {
bool value = false;
valid = GetMandatoryConstraintValueAsBoolean(constraints_, key, &value);
break;
}
}
if (!valid) {
DLOG(ERROR) << "Invalid MediaStream constraint. Name: " << key;
return false;
}
}
return true;
}
bool MediaAudioConstraints::GetDefaultValueForConstraint(
const blink::WebMediaConstraints& constraints, const std::string& key) {
// |kMediaStreamAudioDucking| is not restricted by
// |default_audio_processing_constraint_value_| since it does not require
// audio processing.
if (!default_audio_processing_constraint_value_ &&
IsAudioProcessingConstraint(key))
return false;
for (size_t i = 0; i < arraysize(kDefaultAudioConstraints); ++i) {
if (kDefaultAudioConstraints[i].key == key)
return kDefaultAudioConstraints[i].value;
}
return false;
}
void EnableEchoCancellation(AudioProcessing* audio_processing) {
#if defined(OS_ANDROID) || defined(OS_IOS)
const std::string group_name =
base::FieldTrialList::FindFullName("ReplaceAECMWithAEC");
if (group_name.empty() || group_name != "Enabled") {
// Mobile devices are using AECM.
int err = audio_processing->echo_control_mobile()->set_routing_mode(
webrtc::EchoControlMobile::kSpeakerphone);
err |= audio_processing->echo_control_mobile()->Enable(true);
CHECK_EQ(err, 0);
return;
}
#endif
int err = audio_processing->echo_cancellation()->set_suppression_level(
webrtc::EchoCancellation::kHighSuppression);
// Enable the metrics for AEC.
err |= audio_processing->echo_cancellation()->enable_metrics(true);
err |= audio_processing->echo_cancellation()->enable_delay_logging(true);
err |= audio_processing->echo_cancellation()->Enable(true);
CHECK_EQ(err, 0);
}
void EnableNoiseSuppression(AudioProcessing* audio_processing) {
int err = audio_processing->noise_suppression()->set_level(
webrtc::NoiseSuppression::kHigh);
err |= audio_processing->noise_suppression()->Enable(true);
CHECK_EQ(err, 0);
}
void EnableHighPassFilter(AudioProcessing* audio_processing) {
CHECK_EQ(audio_processing->high_pass_filter()->Enable(true), 0);
}
void EnableTypingDetection(AudioProcessing* audio_processing,
webrtc::TypingDetection* typing_detector) {
int err = audio_processing->voice_detection()->Enable(true);
err |= audio_processing->voice_detection()->set_likelihood(
webrtc::VoiceDetection::kVeryLowLikelihood);
CHECK_EQ(err, 0);
// Configure the update period to 1s (100 * 10ms) in the typing detector.
typing_detector->SetParameters(0, 0, 0, 0, 0, 100);
}
void StartEchoCancellationDump(AudioProcessing* audio_processing,
base::File aec_dump_file) {
DCHECK(aec_dump_file.IsValid());
if (audio_processing->StartDebugRecordingForPlatformFile(
aec_dump_file.TakePlatformFile())) {
DLOG(ERROR) << "Fail to start AEC debug recording";
}
}
void StopEchoCancellationDump(AudioProcessing* audio_processing) {
if (audio_processing->StopDebugRecording())
DLOG(ERROR) << "Fail to stop AEC debug recording";
}
void EnableAutomaticGainControl(AudioProcessing* audio_processing) {
#if defined(OS_ANDROID) || defined(OS_IOS)
const webrtc::GainControl::Mode mode = webrtc::GainControl::kFixedDigital;
#else
const webrtc::GainControl::Mode mode = webrtc::GainControl::kAdaptiveAnalog;
#endif
int err = audio_processing->gain_control()->set_mode(mode);
err |= audio_processing->gain_control()->Enable(true);
CHECK_EQ(err, 0);
}
void GetAecStats(AudioProcessing* audio_processing,
webrtc::AudioProcessorInterface::AudioProcessorStats* stats) {
// These values can take on valid negative values, so use the lowest possible
// level as default rather than -1.
stats->echo_return_loss = -100;
stats->echo_return_loss_enhancement = -100;
// These values can also be negative, but in practice -1 is only used to
// signal insufficient data, since the resolution is limited to multiples
// of 4ms.
stats->echo_delay_median_ms = -1;
stats->echo_delay_std_ms = -1;
// TODO(ajm): Re-enable this metric once we have a reliable implementation.
stats->aec_quality_min = -1.0f;
if (!audio_processing->echo_cancellation()->are_metrics_enabled() ||
!audio_processing->echo_cancellation()->is_delay_logging_enabled() ||
!audio_processing->echo_cancellation()->is_enabled()) {
return;
}
// TODO(ajm): we may want to use VoECallReport::GetEchoMetricsSummary
// here, but it appears to be unsuitable currently. Revisit after this is
// investigated: http://b/issue?id=5666755
webrtc::EchoCancellation::Metrics echo_metrics;
if (!audio_processing->echo_cancellation()->GetMetrics(&echo_metrics)) {
stats->echo_return_loss = echo_metrics.echo_return_loss.instant;
stats->echo_return_loss_enhancement =
echo_metrics.echo_return_loss_enhancement.instant;
}
int median = 0, std = 0;
if (!audio_processing->echo_cancellation()->GetDelayMetrics(&median, &std)) {
stats->echo_delay_median_ms = median;
stats->echo_delay_std_ms = std;
}
}
} // namespace content