Commit 0b4af239 authored by Guido Urdaneta's avatar Guido Urdaneta Committed by Commit Bot

Update MediaStreamTrack constraints processing algorithm for audio

This CL introduces two main updates to constraints processing for audio:
1. The SelectSettings() algorithm now considers audio-processing
   properties as belonging to each device instead of being considered
   independent of the device. The reason is that, while the
   audio-processing module can be applied to any source, once a source
   has processing applied, all tracks for that source must use the same
   audio-processing settings.
2. The getUserMedia() implementation leverages (1) and makes sure that
   candidate settings for audio sources already in use are limited to
   their current configuration.

Bug: 802198
Change-Id: I3554206e852ff41116a1fe02be45a7a8dff4760d
Reviewed-on: https://chromium-review.googlesource.com/881183Reviewed-by: default avatarHenrik Boström <hbos@chromium.org>
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533288}
parent a97831b2
...@@ -811,4 +811,12 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest, ...@@ -811,4 +811,12 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest,
ExecuteJavascriptAndWaitForOk("getUserMediaAfterStopCanvasCapture()"); ExecuteJavascriptAndWaitForOk("getUserMediaAfterStopCanvasCapture()");
} }
IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest,
GetUserMediaEchoCancellationOnAndOff) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk("getUserMediaEchoCancellationOnAndOff()");
}
} // namespace content } // namespace content
...@@ -20,8 +20,63 @@ namespace content { ...@@ -20,8 +20,63 @@ namespace content {
class MediaStreamAudioSource; class MediaStreamAudioSource;
// This class represents the capability of an audio-capture device.
// It may represent three different things:
// 1. An audio-capture device that is currently in use.
// 2. An audio-capture device that is currently not in use, but whose ID and
// parameters are known (suitable for device capture, where device IDs are
// always known).
// 3. A "device" whose ID is not known (suitable for content capture, where
// it is not possible to have a list of known valid device IDs).
// In cases (1) and (2), the known device ID introduces a restriction on the
// acceptable values for the deviceId constraint, while in case (3) no such
// restriction is imposed and any requested deviceID value will be acceptable
// while processing constraints.
class CONTENT_EXPORT AudioDeviceCaptureCapability {
public:
// This creates an AudioDeviceCaptureCapability that admits all possible
// device names and settings. This is intended to be used as the single
// capability for getUserMedia() with content capture, where the set of valid
// device IDs is infinite.
AudioDeviceCaptureCapability();
// This creates an AudioDeviceCaptureCapability where the device ID is limited
// to |device_id| and other settings are limited by the given |parameters|.
// |device_id| must not be empty. Intended to be used by getUserMedia() with
// device capture for devices that are not currently in use.
AudioDeviceCaptureCapability(std::string device_id,
const media::AudioParameters& parameters);
// This creates an AudioDeviceCaptureCapability where the device ID and other
// settings are restricted to the current settings of |source|. Intended to be
// used by applyConstraints() for both device and content capture, and by
// getUserMedia() with device capture for devices that are currently in use.
explicit AudioDeviceCaptureCapability(MediaStreamAudioSource* source);
// If this capability represents a device currently in use, this method
// returns a pointer to the MediaStreamAudioSource object associated with the
// device. Otherwise, it returns null.
MediaStreamAudioSource* source() const { return source_; }
// Returns the ID of the device associated with this capability. If empty,
// it means that this capability is not associated with a known device and
// no restrictions are imposed on the deviceId or other constraints while
// processing constraints.
const std::string& DeviceID() const;
// Returns the audio parameters for the device associated with this
// capability. If DeviceID() returns an empty string, these parameters contain
// default values that work well for content capture.
const media::AudioParameters& Parameters() const;
private:
MediaStreamAudioSource* source_ = nullptr;
std::string device_id_;
media::AudioParameters parameters_;
};
using AudioDeviceCaptureCapabilities = using AudioDeviceCaptureCapabilities =
std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>; std::vector<AudioDeviceCaptureCapability>;
// This function implements the SelectSettings algorithm for audio tracks as // This function implements the SelectSettings algorithm for audio tracks as
// described in https://w3c.github.io/mediacapture-main/#dfn-selectsettings // described in https://w3c.github.io/mediacapture-main/#dfn-selectsettings
......
...@@ -63,39 +63,38 @@ class MediaStreamConstraintsUtilAudioTest ...@@ -63,39 +63,38 @@ class MediaStreamConstraintsUtilAudioTest
public: public:
void SetUp() override { void SetUp() override {
ResetFactory(); ResetFactory();
if (!IsDeviceCapture()) if (IsDeviceCapture()) {
return; capabilities_.emplace_back(
"default_device",
blink::mojom::AudioInputDeviceCapabilitiesPtr device = media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
blink::mojom::AudioInputDeviceCapabilities::New(); media::CHANNEL_LAYOUT_STEREO,
device->device_id = "default_device"; media::AudioParameters::kAudioCDSampleRate, 16,
device->parameters = media::AudioParameters( 1000));
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO, media::AudioParameters hw_echo_canceller_parameters(
media::AudioParameters::kAudioCDSampleRate, 16, 1000); media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
capabilities_.push_back(std::move(device)); media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 24, 1000);
device = blink::mojom::AudioInputDeviceCapabilities::New(); hw_echo_canceller_parameters.set_effects(
device->device_id = "hw_echo_canceller_device"; media::AudioParameters::ECHO_CANCELLER);
device->parameters = media::AudioParameters( capabilities_.emplace_back("hw_echo_canceller_device",
media::AudioParameters::AUDIO_PCM_LOW_LATENCY, hw_echo_canceller_parameters);
media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 24, 1000); media::AudioParameters geometry_parameters(
device->parameters.set_effects(media::AudioParameters::ECHO_CANCELLER); media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
capabilities_.push_back(std::move(device)); media::CHANNEL_LAYOUT_STEREO,
media::AudioParameters::kAudioCDSampleRate, 16, 1000);
device = blink::mojom::AudioInputDeviceCapabilities::New(); geometry_parameters.set_mic_positions(kMicPositions);
device->device_id = "geometry device"; capabilities_.emplace_back("geometry device", geometry_parameters);
device->parameters = media::AudioParameters(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY, default_device_ = &capabilities_[0];
media::CHANNEL_LAYOUT_STEREO, hw_echo_canceller_device_ = &capabilities_[1];
media::AudioParameters::kAudioCDSampleRate, 16, 1000); geometry_device_ = &capabilities_[2];
device->parameters.set_mic_positions(kMicPositions); } else {
capabilities_.push_back(std::move(device)); // For content capture, use a single capability that admits all possible
// settings.
default_device_ = capabilities_[0].get(); capabilities_.emplace_back();
hw_echo_canceller_device_ = capabilities_[1].get(); }
geometry_device_ = capabilities_[2].get();
} }
protected: protected:
...@@ -125,6 +124,7 @@ class MediaStreamConstraintsUtilAudioTest ...@@ -125,6 +124,7 @@ class MediaStreamConstraintsUtilAudioTest
bool disable_local_echo, bool disable_local_echo,
bool render_to_associated_sink) { bool render_to_associated_sink) {
MediaStreamDevice device; MediaStreamDevice device;
device.id = "processed_source";
device.type = GetMediaStreamType(); device.type = GetMediaStreamType();
if (render_to_associated_sink) if (render_to_associated_sink)
device.matched_output_device_id = std::string("some_device_id"); device.matched_output_device_id = std::string("some_device_id");
...@@ -318,17 +318,16 @@ class MediaStreamConstraintsUtilAudioTest ...@@ -318,17 +318,16 @@ class MediaStreamConstraintsUtilAudioTest
} }
} }
void CheckDevice( void CheckDevice(const AudioDeviceCaptureCapability& expected_device,
const blink::mojom::AudioInputDeviceCapabilities& expected_device, const AudioCaptureSettings& result) {
const AudioCaptureSettings& result) { EXPECT_EQ(expected_device.DeviceID(), result.device_id());
EXPECT_EQ(expected_device.device_id, result.device_id()); EXPECT_EQ(expected_device.Parameters().sample_rate(),
EXPECT_EQ(expected_device.parameters.sample_rate(),
result.device_parameters().sample_rate()); result.device_parameters().sample_rate());
EXPECT_EQ(expected_device.parameters.bits_per_sample(), EXPECT_EQ(expected_device.Parameters().bits_per_sample(),
result.device_parameters().bits_per_sample()); result.device_parameters().bits_per_sample());
EXPECT_EQ(expected_device.parameters.channels(), EXPECT_EQ(expected_device.Parameters().channels(),
result.device_parameters().channels()); result.device_parameters().channels());
EXPECT_EQ(expected_device.parameters.effects(), EXPECT_EQ(expected_device.Parameters().effects(),
result.device_parameters().effects()); result.device_parameters().effects());
} }
...@@ -355,10 +354,9 @@ class MediaStreamConstraintsUtilAudioTest ...@@ -355,10 +354,9 @@ class MediaStreamConstraintsUtilAudioTest
MockConstraintFactory constraint_factory_; MockConstraintFactory constraint_factory_;
AudioDeviceCaptureCapabilities capabilities_; AudioDeviceCaptureCapabilities capabilities_;
const blink::mojom::AudioInputDeviceCapabilities* default_device_ = nullptr; const AudioDeviceCaptureCapability* default_device_ = nullptr;
const blink::mojom::AudioInputDeviceCapabilities* hw_echo_canceller_device_ = const AudioDeviceCaptureCapability* hw_echo_canceller_device_ = nullptr;
nullptr; const AudioDeviceCaptureCapability* geometry_device_ = nullptr;
const blink::mojom::AudioInputDeviceCapabilities* geometry_device_ = nullptr;
const std::vector<media::Point> kMicPositions = {{8, 8, 8}, {4, 4, 4}}; const std::vector<media::Point> kMicPositions = {{8, 8, 8}, {4, 4, 4}};
private: private:
...@@ -520,18 +518,18 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, IdealArbitraryDeviceID) { ...@@ -520,18 +518,18 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, IdealArbitraryDeviceID) {
TEST_P(MediaStreamConstraintsUtilAudioTest, ExactValidDeviceID) { TEST_P(MediaStreamConstraintsUtilAudioTest, ExactValidDeviceID) {
for (const auto& device : capabilities_) { for (const auto& device : capabilities_) {
constraint_factory_.basic().device_id.SetExact( constraint_factory_.basic().device_id.SetExact(
blink::WebString::FromASCII(device->device_id)); blink::WebString::FromASCII(device.DeviceID()));
auto result = SelectSettings(); auto result = SelectSettings();
EXPECT_TRUE(result.HasValue()); EXPECT_TRUE(result.HasValue());
CheckDevice(*device, result); CheckDevice(device, result);
CheckBoolDefaults(AudioSettingsBoolMembers(), CheckBoolDefaults(AudioSettingsBoolMembers(),
{&AudioProcessingProperties::enable_sw_echo_cancellation}, {&AudioProcessingProperties::enable_sw_echo_cancellation},
result); result);
bool has_hw_echo_cancellation = bool has_hw_echo_cancellation =
device->parameters.effects() & media::AudioParameters::ECHO_CANCELLER; device.Parameters().effects() & media::AudioParameters::ECHO_CANCELLER;
EXPECT_EQ(!has_hw_echo_cancellation, EXPECT_EQ(IsDeviceCapture() && !has_hw_echo_cancellation,
result.audio_processing_properties().enable_sw_echo_cancellation); result.audio_processing_properties().enable_sw_echo_cancellation);
if (&*device == geometry_device_) { if (&device == geometry_device_) {
EXPECT_EQ(kMicPositions, EXPECT_EQ(kMicPositions,
result.audio_processing_properties().goog_array_geometry); result.audio_processing_properties().goog_array_geometry);
} else { } else {
...@@ -619,7 +617,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, EchoCancellationWithHw) { ...@@ -619,7 +617,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, EchoCancellationWithHw) {
for (bool value : kBoolValues) { for (bool value : kBoolValues) {
ResetFactory(); ResetFactory();
constraint_factory_.basic().device_id.SetExact( constraint_factory_.basic().device_id.SetExact(
blink::WebString::FromASCII(hw_echo_canceller_device_->device_id)); blink::WebString::FromASCII(hw_echo_canceller_device_->DeviceID()));
((constraint_factory_.*accessor)().echo_cancellation.* ((constraint_factory_.*accessor)().echo_cancellation.*
set_function)(value); set_function)(value);
auto result = SelectSettings(); auto result = SelectSettings();
...@@ -715,7 +713,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, GoogEchoCancellationWithHw) { ...@@ -715,7 +713,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, GoogEchoCancellationWithHw) {
for (bool value : kBoolValues) { for (bool value : kBoolValues) {
ResetFactory(); ResetFactory();
constraint_factory_.basic().device_id.SetExact( constraint_factory_.basic().device_id.SetExact(
blink::WebString::FromASCII(hw_echo_canceller_device_->device_id)); blink::WebString::FromASCII(hw_echo_canceller_device_->DeviceID()));
((constraint_factory_.*accessor)().goog_echo_cancellation.* ((constraint_factory_.*accessor)().goog_echo_cancellation.*
set_function)(value); set_function)(value);
auto result = SelectSettings(); auto result = SelectSettings();
...@@ -956,7 +954,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, DeviceGeometry) { ...@@ -956,7 +954,7 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, DeviceGeometry) {
return; return;
constraint_factory_.basic().device_id.SetExact( constraint_factory_.basic().device_id.SetExact(
blink::WebString::FromASCII(geometry_device_->device_id)); blink::WebString::FromASCII(geometry_device_->DeviceID()));
{ {
const blink::WebString kValidGeometry = const blink::WebString kValidGeometry =
...@@ -1236,6 +1234,52 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, SourceWithAudioProcessing) { ...@@ -1236,6 +1234,52 @@ TEST_P(MediaStreamConstraintsUtilAudioTest, SourceWithAudioProcessing) {
} }
} }
TEST_P(MediaStreamConstraintsUtilAudioTest, UsedAndUnusedSources) {
// The distinction of used and unused sources is relevant only for device
// capture.
if (!IsDeviceCapture())
return;
AudioProcessingProperties properties;
properties.enable_sw_echo_cancellation = true;
std::unique_ptr<ProcessedLocalAudioSource> processed_source =
GetProcessedLocalAudioSource(properties, false /* hotword_enabled */,
false /* disable_local_echo */,
false /* render_to_associated_sink */);
const std::string kUnusedDeviceID = "unused_device";
AudioDeviceCaptureCapabilities capabilities;
capabilities.emplace_back(processed_source.get());
capabilities.emplace_back(kUnusedDeviceID,
media::AudioParameters::UnavailableDeviceParams());
{
constraint_factory_.Reset();
constraint_factory_.basic().echo_cancellation.SetExact(false);
auto result = SelectSettingsAudioCapture(
capabilities, constraint_factory_.CreateWebMediaConstraints(),
false /* should_disable_hardware_noise_suppression */);
EXPECT_TRUE(result.HasValue());
EXPECT_EQ(result.device_id(), kUnusedDeviceID);
EXPECT_FALSE(
result.audio_processing_properties().enable_sw_echo_cancellation);
}
{
constraint_factory_.Reset();
constraint_factory_.basic().echo_cancellation.SetExact(true);
auto result = SelectSettingsAudioCapture(
capabilities, constraint_factory_.CreateWebMediaConstraints(),
false /* should_disable_hardware_noise_suppression */);
EXPECT_TRUE(result.HasValue());
EXPECT_EQ(result.device_id(), processed_source->device().id);
EXPECT_TRUE(
result.audio_processing_properties().enable_sw_echo_cancellation);
}
}
INSTANTIATE_TEST_CASE_P(, INSTANTIATE_TEST_CASE_P(,
MediaStreamConstraintsUtilAudioTest, MediaStreamConstraintsUtilAudioTest,
testing::Values("", testing::Values("",
......
...@@ -372,8 +372,8 @@ void UserMediaProcessor::SetupAudioInput() { ...@@ -372,8 +372,8 @@ void UserMediaProcessor::SetupAudioInput() {
current_request_info_->web_request().AudioConstraints(), &audio_controls); current_request_info_->web_request().AudioConstraints(), &audio_controls);
if (IsDeviceSource(audio_controls.stream_source)) { if (IsDeviceSource(audio_controls.stream_source)) {
GetMediaDevicesDispatcher()->GetAudioInputCapabilities(base::BindOnce( GetMediaDevicesDispatcher()->GetAudioInputCapabilities(base::BindOnce(
&UserMediaProcessor::SelectAudioSettings, weak_factory_.GetWeakPtr(), &UserMediaProcessor::SelectAudioDeviceSettings,
current_request_info_->web_request())); weak_factory_.GetWeakPtr(), current_request_info_->web_request()));
} else { } else {
if (!IsValidAudioContentSource(audio_controls.stream_source)) { if (!IsValidAudioContentSource(audio_controls.stream_source)) {
blink::WebString failed_constraint_name = blink::WebString failed_constraint_name =
...@@ -386,14 +386,41 @@ void UserMediaProcessor::SetupAudioInput() { ...@@ -386,14 +386,41 @@ void UserMediaProcessor::SetupAudioInput() {
return; return;
} }
SelectAudioSettings(current_request_info_->web_request(), SelectAudioSettings(current_request_info_->web_request(),
AudioDeviceCaptureCapabilities()); {AudioDeviceCaptureCapability()});
} }
} }
void UserMediaProcessor::SelectAudioSettings( void UserMediaProcessor::SelectAudioDeviceSettings(
const blink::WebUserMediaRequest& web_request, const blink::WebUserMediaRequest& web_request,
std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr> std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
audio_input_capabilities) { audio_input_capabilities) {
AudioDeviceCaptureCapabilities capabilities;
for (const auto& device : audio_input_capabilities) {
MediaStreamAudioSource* audio_source = nullptr;
auto it =
std::find_if(local_sources_.begin(), local_sources_.end(),
[&device](const blink::WebMediaStreamSource& web_source) {
DCHECK(!web_source.IsNull());
return web_source.Id().Utf8() == device->device_id;
});
if (it != local_sources_.end()) {
MediaStreamSource* const source =
static_cast<MediaStreamSource*>(it->GetExtraData());
if (source->device().type == MEDIA_DEVICE_AUDIO_CAPTURE)
audio_source = static_cast<MediaStreamAudioSource*>(source);
}
if (audio_source)
capabilities.emplace_back(audio_source);
else
capabilities.emplace_back(device->device_id, device->parameters);
}
SelectAudioSettings(web_request, capabilities);
}
void UserMediaProcessor::SelectAudioSettings(
const blink::WebUserMediaRequest& web_request,
const std::vector<AudioDeviceCaptureCapability>& capabilities) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The frame might reload or |web_request| might be cancelled while // The frame might reload or |web_request| might be cancelled while
// capabilities are queried. Do nothing if a different request is being // capabilities are queried. Do nothing if a different request is being
...@@ -403,7 +430,7 @@ void UserMediaProcessor::SelectAudioSettings( ...@@ -403,7 +430,7 @@ void UserMediaProcessor::SelectAudioSettings(
DCHECK(current_request_info_->stream_controls()->audio.requested); DCHECK(current_request_info_->stream_controls()->audio.requested);
auto settings = SelectSettingsAudioCapture( auto settings = SelectSettingsAudioCapture(
std::move(audio_input_capabilities), web_request.AudioConstraints(), capabilities, web_request.AudioConstraints(),
web_request.ShouldDisableHardwareNoiseSuppression()); web_request.ShouldDisableHardwareNoiseSuppression());
if (!settings.HasValue()) { if (!settings.HasValue()) {
blink::WebString failed_constraint_name = blink::WebString failed_constraint_name =
......
...@@ -32,6 +32,7 @@ class WebString; ...@@ -32,6 +32,7 @@ class WebString;
namespace content { namespace content {
class AudioCaptureSettings; class AudioCaptureSettings;
class AudioDeviceCaptureCapability;
class MediaStreamAudioSource; class MediaStreamAudioSource;
class MediaStreamDeviceObserver; class MediaStreamDeviceObserver;
class MediaStreamVideoSource; class MediaStreamVideoSource;
...@@ -239,10 +240,13 @@ class CONTENT_EXPORT UserMediaProcessor ...@@ -239,10 +240,13 @@ class CONTENT_EXPORT UserMediaProcessor
GetMediaDevicesDispatcher(); GetMediaDevicesDispatcher();
void SetupAudioInput(); void SetupAudioInput();
void SelectAudioSettings( void SelectAudioDeviceSettings(
const blink::WebUserMediaRequest& web_request, const blink::WebUserMediaRequest& web_request,
std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr> std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
audio_input_capabilities); audio_input_capabilities);
void SelectAudioSettings(
const blink::WebUserMediaRequest& web_request,
const std::vector<AudioDeviceCaptureCapability>& capabilities);
void SetupVideoInput(); void SetupVideoInput();
void SelectVideoDeviceSettings( void SelectVideoDeviceSettings(
......
...@@ -829,6 +829,34 @@ ...@@ -829,6 +829,34 @@
reportTestSuccess(); reportTestSuccess();
}).catch(failTest); }).catch(failTest);
} }
function getUserMediaEchoCancellationOnAndOff() {
var stream;
navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {exact: "default"},
echoCancellation: {exact: true}
}
}).then(s => {
stream = s;
assertTrue(stream.getAudioTracks()[0].getSettings().echoCancellation);
// Chromium's current implementation does not support tracks from the
// same source to have different echo cancellation settings. This
// getUserMedia() call is expected to fail.
navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {exact: "default"},
echoCancellation: {exact: false}
}
}).then(
s => failTest("Unexpected accepted promise from getUserMedia()"),
e => {
assertEquals(e.name, "OverconstrainedError");
stream.getAudioTracks()[0].stop();
reportTestSuccess();
});
}).catch(failTest);
}
</script> </script>
</head> </head>
<body> <body>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment