Bug 1213453 - Correlate video device group id based on device name. r=pehrsons
authorAlex Chronopoulos <achronop@gmail.com>
Fri, 08 Mar 2019 11:52:18 +0000
changeset 521025 a90a2cb536f39a37905aeb7c19ae5c04014b290a
parent 521024 1cac58480c6a9a8acc8a11219c8c230e8ff921bb
child 521026 75033c11fa3c9f10f2697badbf7c0887a5fd7e5b
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspehrsons
bugs1213453
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1213453 - Correlate video device group id based on device name. r=pehrsons Differential Revision: https://phabricator.services.mozilla.com/D20372
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/gtest/TestGroupId.cpp
dom/media/gtest/moz.build
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1774,16 +1774,80 @@ class GetUserMediaRunnableWrapper : publ
     return NS_OK;
   }
 
  private:
   nsAutoPtr<GetUserMediaTask> mTask;
 };
 #endif
 
+// This function tries to guess the group id for a video device
+// based on the device name. If only one audio device's name contains
+// the name of the video device, then, this video device will take
+// the group id of the audio device. Since this is a guess we try
+// to minimize the probability of false positive. If we fail to find
+// a correlation we leave the video group id untouched. In that case the
+// group id will be the video device name.
+/* static */
+void MediaManager::GuessVideoDeviceGroupIDs(MediaDeviceSet& aDevices) {
+  // Run the logic in a lambda to avoid duplication.
+  auto updateGroupIdIfNeeded = [&](RefPtr<MediaDevice>& aVideo,
+                                   const dom::MediaDeviceKind aKind) -> bool {
+    MOZ_ASSERT(aVideo->mKind == dom::MediaDeviceKind::Videoinput);
+    MOZ_ASSERT(aKind == dom::MediaDeviceKind::Audioinput ||
+               aKind == dom::MediaDeviceKind::Audiooutput);
+    // This will store the new group id if a match is found.
+    nsString newVideoGroupID;
+    // If the group id needs to be updated this will become true. It is
+    // necessary when the new group id is an empty string. Without this extra
+    // variable to signal the update, we would resort to test if
+    // `newVideoGroupId` is empty. However,
+    // that check does not work when the new group id is an empty string.
+    bool updateGroupId = false;
+    for (const RefPtr<MediaDevice>& dev : aDevices) {
+      if (dev->mKind != aKind) {
+        continue;
+      }
+      if (!FindInReadable(aVideo->mName, dev->mName)) {
+        continue;
+      }
+      if (newVideoGroupID.IsEmpty()) {
+        // This is only expected on first match. If that's the only match group
+        // id will be updated to this one at the end of the loop.
+        updateGroupId = true;
+        newVideoGroupID = dev->mGroupID;
+      } else {
+        // More than one device found, it is impossible to know which group id
+        // is the correct one.
+        updateGroupId = false;
+        newVideoGroupID = NS_LITERAL_STRING("");
+        break;
+      }
+    }
+    if (updateGroupId) {
+      aVideo =
+          new MediaDevice(aVideo, aVideo->mID, newVideoGroupID, aVideo->mRawID);
+      return true;
+    }
+    return false;
+  };
+
+  for (RefPtr<MediaDevice>& video : aDevices) {
+    if (video->mKind != dom::MediaDeviceKind::Videoinput) {
+      continue;
+    }
+    if (updateGroupIdIfNeeded(video, dom::MediaDeviceKind::Audioinput)) {
+      // GroupId has been updated, continue to the next video device
+      continue;
+    }
+    // GroupId has not been updated, check among the outputs
+    updateGroupIdIfNeeded(video, dom::MediaDeviceKind::Audiooutput);
+  }
+}
+
 /**
  * EnumerateRawDevices - Enumerate a list of audio & video devices that
  * satisfy passed-in constraints. List contains raw id's.
  */
 
 RefPtr<MediaManager::MgrPromise> MediaManager::EnumerateRawDevices(
     uint64_t aWindowId, MediaSourceEnum aVideoInputType,
     MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
@@ -1886,16 +1950,19 @@ RefPtr<MediaManager::MgrPromise> MediaMa
     }
     if (hasAudioOutput) {
       MediaDeviceSet outputs;
       MOZ_ASSERT(realBackend);
       realBackend->EnumerateDevices(aWindowId, MediaSourceEnum::Other,
                                     MediaSinkEnum::Speaker, &outputs);
       aOutDevices->AppendElements(outputs);
     }
+    if (hasVideo) {
+      GuessVideoDeviceGroupIDs(*aOutDevices);
+    }
 
     holder->Resolve(false, __func__);
   });
 
   if (realDeviceRequested && aForceNoPermRequest &&
       Preferences::GetBool("media.navigator.permission.device", false)) {
     // Need to ask permission to retrieve list of all devices;
     // notify frontend observer and wait for callback notification to post task.
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -275,16 +275,17 @@ class MediaManager final : public nsIMed
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 
  public:  // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(MediaDeviceSet& aDevices,
                                const nsACString& aOriginKey,
                                const uint64_t aWindowId);
   static already_AddRefed<nsIWritableVariant> ToJSArray(
       MediaDeviceSet& aDevices);
+  static void GuessVideoDeviceGroupIDs(MediaManager::MediaDeviceSet& aDevices);
 
  private:
   enum class DeviceEnumerationType : uint8_t {
     Normal,  // Enumeration should not return loopback or fake devices
     Fake,    // Enumeration should return fake device(s)
     Loopback /* Enumeration should return loopback device(s) (possibly in
              addition to normal devices) */
   };
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestGroupId.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioDeviceInfo.h"
+#include "MediaManager.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest-printers.h"
+#include "gtest/gtest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "webrtc/MediaEngineSource.h"
+
+using ::testing::Return;
+
+void PrintTo(const nsString& aValue, ::std::ostream* aStream) {
+  NS_ConvertUTF16toUTF8 str(aValue);
+  (*aStream) << str.get();
+}
+void PrintTo(const nsCString& aValue, ::std::ostream* aStream) {
+  (*aStream) << aValue.get();
+}
+
+class MockMediaEngineSource : public MediaEngineSource {
+ public:
+  MOCK_CONST_METHOD0(GetMediaSource, dom::MediaSourceEnum());
+
+  /* Unused overrides */
+  MOCK_CONST_METHOD0(GetName, nsString());
+  MOCK_CONST_METHOD0(GetUUID, nsCString());
+  MOCK_CONST_METHOD0(GetGroupId, nsString());
+  MOCK_METHOD6(Allocate, nsresult(const dom::MediaTrackConstraints&,
+                                  const MediaEnginePrefs&, const nsString&,
+                                  const ipc::PrincipalInfo&, AllocationHandle**,
+                                  const char**));
+  MOCK_METHOD4(SetTrack, void(const RefPtr<const AllocationHandle>&,
+                              const RefPtr<SourceMediaStream>&, TrackID,
+                              const PrincipalHandle&));
+  MOCK_METHOD1(Start, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_METHOD5(Reconfigure, nsresult(const RefPtr<AllocationHandle>&,
+                                     const dom::MediaTrackConstraints&,
+                                     const MediaEnginePrefs&, const nsString&,
+                                     const char**));
+  MOCK_METHOD1(Stop, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_METHOD1(Deallocate, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_CONST_METHOD2(GetBestFitnessDistance,
+                     uint32_t(const nsTArray<const NormalizedConstraintSet*>&,
+                              const nsString&));
+  MOCK_METHOD6(Pull,
+               void(const RefPtr<const AllocationHandle>& aHandle,
+                    const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
+                    StreamTime aEndOfAppendedData, StreamTime aDesiredTime,
+                    const PrincipalHandle& aPrincipalHandle));
+};
+
+RefPtr<AudioDeviceInfo> MakeAudioDeviceInfo(const nsString aName) {
+  return MakeRefPtr<AudioDeviceInfo>(
+      nullptr, aName, NS_LITERAL_STRING("GroupId"), NS_LITERAL_STRING("Vendor"),
+      AudioDeviceInfo::TYPE_OUTPUT, AudioDeviceInfo::STATE_ENABLED,
+      AudioDeviceInfo::PREF_NONE, AudioDeviceInfo::FMT_F32LE,
+      AudioDeviceInfo::FMT_F32LE, 2u, 44100u, 44100u, 44100u, 0, 0);
+}
+
+RefPtr<MediaDevice> MakeCameraDevice(const nsString& aName,
+                                     const nsString& aGroupId) {
+  auto v = MakeRefPtr<MockMediaEngineSource>();
+  EXPECT_CALL(*v, GetMediaSource())
+      .WillRepeatedly(Return(dom::MediaSourceEnum::Camera));
+
+  return MakeRefPtr<MediaDevice>(v, aName, NS_LITERAL_STRING(""), aGroupId,
+                                 NS_LITERAL_STRING(""));
+}
+
+RefPtr<MediaDevice> MakeMicDevice(const nsString& aName,
+                                  const nsString& aGroupId) {
+  auto a = MakeRefPtr<MockMediaEngineSource>();
+  EXPECT_CALL(*a, GetMediaSource())
+      .WillRepeatedly(Return(dom::MediaSourceEnum::Microphone));
+
+  return MakeRefPtr<MediaDevice>(a, aName, NS_LITERAL_STRING(""), aGroupId,
+                                 NS_LITERAL_STRING(""));
+}
+
+RefPtr<MediaDevice> MakeSpeakerDevice(const nsString& aName,
+                                      const nsString& aGroupId) {
+  return MakeRefPtr<MediaDevice>(MakeAudioDeviceInfo(aName),
+                                 NS_LITERAL_STRING("ID"), aGroupId,
+                                 NS_LITERAL_STRING("RawID"));
+}
+
+/* Verify that when an audio input device name contains the video input device
+ * name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchInput_PartOfName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name is the same as the video input
+ * device name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchInput_FullName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Vendor Model"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name does not contain the video input
+ * device name the video device group id does not change. */
+TEST(TestGroupId, NoMatchInput) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Model Analog Stereo"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+}
+
+/* Verify that when more that one audio input and more than one audio output
+ * device name contain the video input device name the video device group id
+ * does not change. */
+TEST(TestGroupId, NoMatch_TwoIdenticalDevices) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+  EXPECT_NE(devices[0]->mGroupID, devices[3]->mGroupID)
+      << "Video group id is different than audio output group id.";
+}
+
+/* Verify that when more that one audio input device name contain the video
+ * input device name the video device group id is not updated by audio input
+ * device group id but it continues looking at audio output devices where it
+ * finds a match so video input group id is updated by audio output group id. */
+TEST(TestGroupId, Match_TwoIdenticalInputsMatchOutput) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[3]->mGroupID)
+      << "Video group id is the same as audio output group id.";
+}
+
+/* Verify that when more that one audio input and more than one audio output
+ * device names contain the video input device name the video device group id
+ * does not change. */
+TEST(TestGroupId, NoMatch_ThreeIdenticalDevices) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+  EXPECT_NE(devices[0]->mGroupID, devices[4]->mGroupID)
+      << "Video group id is different than audio output group id.";
+}
+
+/* Verify that when an audio output device name contains the video input device
+ * name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchOutput) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Mic Analog Stereo"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[2]->mGroupID)
+      << "Video group id is the same as audio output group id.";
+}
+
+/* Verify that when an audio input device name is the same as audio output
+ * device and video input device name the video device group id is updated to
+ * become equal to the audio input device group id. */
+TEST(TestGroupId, InputOutputSameName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Vendor Model"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name contains the video input device
+ * and the audio input group id is an empty string, the video device group id
+ * is updated to become equal to the audio device group id. */
+TEST(TestGroupId, InputEmptyGroupId) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model"), NS_LITERAL_STRING("")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio output device name contains the video input device
+ * and the audio output group id is an empty string, the video device group id
+ * is updated to become equal to the audio output device group id. */
+TEST(TestGroupId, OutputEmptyGroupId) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model"),
+                                          NS_LITERAL_STRING("")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio output group id.";
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -23,16 +23,17 @@ UNIFIED_SOURCES += [
     'TestAudioTrackEncoder.cpp',
     'TestBitWriter.cpp',
     'TestBlankVideoDataCreator.cpp',
     'TestCDMStorage.cpp',
     'TestDataMutex.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
+    'TestGroupId.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
     'TestMediaDataEncoder.cpp',
     'TestMediaEventSource.cpp',
     'TestMediaMIMETypes.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
     'TestOpusParser.cpp',