Commit 2aa26b30 authored by Guido Urdaneta's avatar Guido Urdaneta Committed by Commit Bot

Report video group ID in low-level device enumerations and notifications.

The video-capture subsystem currently does not support the concept
of group IDs, which exists in the MediaStreams spec.
To support video group IDs, we use a heuristic that finds
associations between video and audio devices.
Prior to this CL, this heuristic was used to patch results
requested by the MediaDevices.enumerateDevices() JavaScript function,
but low-level enumerations used by other parts of Chrome
may have kept video group IDs empty.
To ensure that device associations are found, low-level video
enumerations will also trigger a possibly cached audio enumeration
and the heuristic is run after both results are obtained.

This CL is preparation for full support for the groupId constrainable
property in getUserMedia() and related MediaStreamTrack methods.

Bug: 834281
Change-Id: I428c9a2cb65f755cdee366d6e43e4fe9ad898e20
Reviewed-on: https://chromium-review.googlesource.com/1019245
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Reviewed-by: default avatarTommi <tommi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555697}
parent 14976350
......@@ -160,7 +160,9 @@ MediaDeviceInfo TranslateMediaDeviceInfo(
? std::string()
: GetHMACForMediaDeviceID(salt_and_origin.group_id_salt,
salt_and_origin.origin,
device_info.group_id));
device_info.group_id),
has_permission ? device_info.video_facing
: media::MEDIA_VIDEO_FACING_NONE);
}
MediaDeviceInfoArray TranslateMediaDeviceInfoArray(
......
......@@ -110,6 +110,11 @@ bool IsRealAudioDeviceID(const std::string& device_id) {
!media::AudioDeviceDescription::IsCommunicationsDevice(device_id);
}
static bool EqualDeviceAndGroupID(const MediaDeviceInfo& lhs,
const MediaDeviceInfo& rhs) {
return lhs == rhs && lhs.group_id == rhs.group_id;
}
} // namespace
std::string GuessVideoGroupID(const MediaDeviceInfoArray& audio_infos,
......@@ -521,8 +526,23 @@ void MediaDevicesManager::OnPermissionsCheckDone(
MediaDeviceSaltAndOrigin salt_and_origin,
const MediaDevicesManager::BoolDeviceTypes& has_permissions) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// The video-capture subsystem currently does not support group IDs.
// If video input devices are requested, also request audio input devices in
// order to be able to use an heuristic that guesses group IDs for video
// devices by finding matches in audio input devices.
// TODO(crbug.com/627793): Remove |internal_requested_types| and use
// |requested_types| directly when video capture supports group IDs.
BoolDeviceTypes internal_requested_types;
internal_requested_types[MEDIA_DEVICE_TYPE_AUDIO_INPUT] =
requested_types[MEDIA_DEVICE_TYPE_AUDIO_INPUT] ||
requested_types[MEDIA_DEVICE_TYPE_VIDEO_INPUT];
internal_requested_types[MEDIA_DEVICE_TYPE_VIDEO_INPUT] =
requested_types[MEDIA_DEVICE_TYPE_VIDEO_INPUT];
internal_requested_types[MEDIA_DEVICE_TYPE_AUDIO_OUTPUT] =
requested_types[MEDIA_DEVICE_TYPE_AUDIO_OUTPUT];
EnumerateDevices(
requested_types,
internal_requested_types,
base::BindRepeating(&MediaDevicesManager::OnDevicesEnumerated,
weak_factory_.GetWeakPtr(), requested_types,
request_video_input_capabilities,
......@@ -542,33 +562,16 @@ void MediaDevicesManager::OnDevicesEnumerated(
has_permissions[MEDIA_DEVICE_TYPE_VIDEO_INPUT] &&
request_video_input_capabilities;
MediaDeviceInfoArray video_device_infos =
enumeration[MEDIA_DEVICE_TYPE_VIDEO_INPUT];
for (auto& video_device_info : video_device_infos) {
video_device_info.group_id = GuessVideoGroupID(
enumeration[MEDIA_DEVICE_TYPE_AUDIO_INPUT], video_device_info);
}
std::vector<MediaDeviceInfoArray> result(NUM_MEDIA_DEVICE_TYPES);
for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) {
if (!requested_types[i])
continue;
if (i == MEDIA_DEVICE_TYPE_VIDEO_INPUT) {
for (const auto& device_info : video_device_infos) {
MediaDeviceInfo translated_device_info = TranslateMediaDeviceInfo(
has_permissions[i], salt_and_origin, device_info);
if (video_input_capabilities_requested)
translated_device_info.video_facing = device_info.video_facing;
result[i].push_back(translated_device_info);
}
} else {
for (const auto& device_info : enumeration[i]) {
result[i].push_back(TranslateMediaDeviceInfo(
has_permissions[i], salt_and_origin, device_info));
}
}
}
std::move(callback).Run(
std::move(result),
......@@ -685,19 +688,29 @@ void MediaDevicesManager::DevicesEnumerated(
void MediaDevicesManager::UpdateSnapshot(
MediaDeviceType type,
const MediaDeviceInfoArray& new_snapshot) {
const MediaDeviceInfoArray& new_snapshot,
bool ignore_group_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(IsValidMediaDeviceType(type));
// Only cache the device list when the device list has been changed.
bool need_update_device_change_subscribers = false;
MediaDeviceInfoArray& old_snapshot = current_snapshot_[type];
// Update the cached snapshot and send notifications only if the device list
// has changed.
if (old_snapshot.size() != new_snapshot.size() ||
!std::equal(new_snapshot.begin(), new_snapshot.end(),
old_snapshot.begin())) {
if (type == MEDIA_DEVICE_TYPE_AUDIO_INPUT ||
type == MEDIA_DEVICE_TYPE_VIDEO_INPUT) {
old_snapshot.begin(),
ignore_group_id ? operator== : EqualDeviceAndGroupID)) {
// Prevent sending notifications until group IDs are updated using
// a heuristic in ProcessRequests().
// TODO(crbug.com/627793): Remove |is_video_with_group_ids| and the
// corresponding checks when the video-capture subsystem supports
// group IDs.
bool is_video_with_good_group_ids =
type == MEDIA_DEVICE_TYPE_VIDEO_INPUT &&
(new_snapshot.size() == 0 || !new_snapshot[0].group_id.empty());
if (type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || is_video_with_good_group_ids) {
NotifyMediaStreamManager(type, new_snapshot);
}
......@@ -705,7 +718,8 @@ void MediaDevicesManager::UpdateSnapshot(
// result, since it is not due to an actual device change.
need_update_device_change_subscribers =
has_seen_result_[type] &&
(old_snapshot.size() != 0 || new_snapshot.size() != 0);
(old_snapshot.size() != 0 || new_snapshot.size() != 0) &&
(type != MEDIA_DEVICE_TYPE_VIDEO_INPUT || is_video_with_good_group_ids);
current_snapshot_[type] = new_snapshot;
}
......@@ -715,6 +729,21 @@ void MediaDevicesManager::UpdateSnapshot(
void MediaDevicesManager::ProcessRequests() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Populate the group ID field for video devices using a heuristic that looks
// for device coincidences with audio input devices.
// TODO(crbug.com/627793): Remove this once the video-capture subsystem
// supports group IDs.
if (has_seen_result_[MEDIA_DEVICE_TYPE_VIDEO_INPUT]) {
MediaDeviceInfoArray video_devices =
current_snapshot_[MEDIA_DEVICE_TYPE_VIDEO_INPUT];
for (auto& video_device_info : video_devices) {
video_device_info.group_id = GuessVideoGroupID(
current_snapshot_[MEDIA_DEVICE_TYPE_AUDIO_INPUT], video_device_info);
}
UpdateSnapshot(MEDIA_DEVICE_TYPE_VIDEO_INPUT, video_devices,
false /* ignore_group_id */);
}
requests_.erase(std::remove_if(requests_.begin(), requests_.end(),
[this](const EnumerationRequest& request) {
if (IsEnumerationRequestReady(request)) {
......
......@@ -212,7 +212,8 @@ class CONTENT_EXPORT MediaDevicesManager
void DevicesEnumerated(MediaDeviceType type,
const MediaDeviceInfoArray& snapshot);
void UpdateSnapshot(MediaDeviceType type,
const MediaDeviceInfoArray& new_snapshot);
const MediaDeviceInfoArray& new_snapshot,
bool ignore_group_id = true);
void ProcessRequests();
bool IsEnumerationRequestReady(const EnumerationRequest& request_info);
......
......@@ -149,6 +149,15 @@ class MockMediaDevicesListener : public blink::mojom::MediaDevicesListener {
mojo::BindingSet<blink::mojom::MediaDevicesListener> bindings_;
};
void VerifyDeviceAndGroupID(const std::vector<MediaDeviceInfoArray>& array) {
for (const auto& device_infos : array) {
for (const auto& device_info : device_infos) {
EXPECT_FALSE(device_info.device_id.empty());
EXPECT_FALSE(device_info.group_id.empty());
}
}
}
} // namespace
class MediaDevicesManagerTest : public ::testing::Test {
......@@ -162,6 +171,12 @@ class MediaDevicesManagerTest : public ::testing::Test {
void EnumerateCallback(base::RunLoop* run_loop,
const MediaDeviceEnumeration& result) {
for (int i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) {
for (const auto& device_info : result[i]) {
EXPECT_FALSE(device_info.device_id.empty());
EXPECT_FALSE(device_info.group_id.empty());
}
}
MockCallback(result);
run_loop->Quit();
}
......@@ -597,6 +612,10 @@ TEST_F(MediaDevicesManagerTest, SubscribeDeviceChanges) {
EXPECT_EQ(num_audio_input_devices, notification_all_audio_input.size());
EXPECT_EQ(num_video_input_devices, notification_all_video_input.size());
EXPECT_EQ(num_audio_output_devices, notification_all_audio_output.size());
VerifyDeviceAndGroupID(
{notification_audio_input, notification_video_input,
notification_audio_output, notification_all_audio_input,
notification_all_video_input, notification_all_audio_output});
media_devices_manager_->UnsubscribeDeviceChangeNotifications(
audio_input_subscription_id);
......@@ -621,6 +640,9 @@ TEST_F(MediaDevicesManagerTest, SubscribeDeviceChanges) {
EXPECT_EQ(num_audio_input_devices, notification_all_audio_input.size());
EXPECT_EQ(num_video_input_devices, notification_all_video_input.size());
EXPECT_EQ(num_audio_output_devices, notification_all_audio_output.size());
VerifyDeviceAndGroupID({notification_all_audio_input,
notification_all_video_input,
notification_all_audio_output});
}
TEST_F(MediaDevicesManagerTest, GuessVideoGroupID) {
......
......@@ -17,11 +17,12 @@ MediaDeviceInfo::MediaDeviceInfo(MediaDeviceInfo&& other) = default;
MediaDeviceInfo::MediaDeviceInfo(const std::string& device_id,
const std::string& label,
const std::string& group_id)
const std::string& group_id,
media::VideoFacingMode video_facing)
: device_id(device_id),
label(label),
group_id(group_id),
video_facing(media::VideoFacingMode::MEDIA_VIDEO_FACING_NONE) {}
video_facing(video_facing) {}
MediaDeviceInfo::MediaDeviceInfo(
const media::AudioDeviceDescription& device_description)
......@@ -44,9 +45,11 @@ MediaDeviceInfo& MediaDeviceInfo::operator=(const MediaDeviceInfo& other) =
MediaDeviceInfo& MediaDeviceInfo::operator=(MediaDeviceInfo&& other) = default;
bool operator==(const MediaDeviceInfo& first, const MediaDeviceInfo& second) {
return first.device_id == second.device_id && first.label == second.label &&
first.group_id == second.group_id &&
first.video_facing == second.video_facing;
// Do not use the |group_id| and |video_facing| fields for equality comparison
// since they are currently not fully supported by the video-capture layer.
// The modification of those fields by heuristics in upper layers does not
// result in a different device.
return first.device_id == second.device_id && first.label == second.label;
}
} // namespace content
......@@ -14,7 +14,7 @@
namespace media {
struct AudioDeviceDescription;
struct VideoCaptureDeviceDescriptor;
}
} // namespace media
namespace content {
......@@ -29,9 +29,11 @@ struct CONTENT_EXPORT MediaDeviceInfo {
MediaDeviceInfo();
MediaDeviceInfo(const MediaDeviceInfo& other);
MediaDeviceInfo(MediaDeviceInfo&& other);
MediaDeviceInfo(const std::string& device_id,
MediaDeviceInfo(
const std::string& device_id,
const std::string& label,
const std::string& group_id);
const std::string& group_id,
media::VideoFacingMode video_facing = media::MEDIA_VIDEO_FACING_NONE);
explicit MediaDeviceInfo(const media::AudioDeviceDescription& description);
explicit MediaDeviceInfo(
const media::VideoCaptureDeviceDescriptor& descriptor);
......
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