Bug 1119335 - Streamline camera capabilities (remove alternate algorithm for OSX/B2G). r=jesup, r=ayang
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 18 Feb 2015 13:06:01 -0500
changeset 258766 a1463070ce7162fe452fb5e32ae27d2baac2f36a
parent 258765 eb836d0e41ff38f41af1585fc3c0fed85ef3faab
child 258767 a30cdc2cc8d856aa2e06a7d2fd7bc03c7cf8e05e
push id721
push userjlund@mozilla.com
push dateTue, 21 Apr 2015 23:03:33 +0000
treeherdermozilla-release@d27c9211ebb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, ayang
bugs1119335
milestone38.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 1119335 - Streamline camera capabilities (remove alternate algorithm for OSX/B2G). r=jesup, r=ayang
dom/media/webrtc/MediaEngineCameraVideoSource.cpp
dom/media/webrtc/MediaEngineCameraVideoSource.h
dom/media/webrtc/MediaEngineGonkVideoSource.cpp
dom/media/webrtc/MediaEngineGonkVideoSource.h
dom/media/webrtc/MediaEngineWebRTC.h
dom/media/webrtc/MediaEngineWebRTCVideo.cpp
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -63,94 +63,182 @@ bool MediaEngineCameraVideoSource::Appen
 
   // This is safe from any thread, and is safe if the track is Finished
   // or Destroyed.
   // This can fail if either a) we haven't added the track yet, or b)
   // we've removed or finished the track.
   return aSource->AppendToTrack(aID, &(segment));
 }
 
-// A special version of the algorithm for cameras that don't list capabilities.
+// Sub-classes (B2G or desktop) should overload one of both of these two methods
+// to provide capabilities
+size_t
+MediaEngineCameraVideoSource::NumCapabilities()
+{
+  return mHardcodedCapabilities.Length();
+}
+
 void
-MediaEngineCameraVideoSource::GuessCapability(
-    const VideoTrackConstraintsN& aConstraints,
-    const MediaEnginePrefs& aPrefs)
+MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
+                                            webrtc::CaptureCapability& aOut)
 {
-  LOG(("GuessCapability: prefs: %dx%d @%d-%dfps",
-       aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS));
+  MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
+  aOut = mHardcodedCapabilities[aIndex];
+}
 
-  // In short: compound constraint-ranges and use pref as ideal.
+// The full algorithm for all cameras. Sources that don't list capabilities
+// need to fake it and hardcode some by populating mHardcodedCapabilities above.
 
-  ConstrainLongRange cWidth(aConstraints.mRequired.mWidth);
-  ConstrainLongRange cHeight(aConstraints.mRequired.mHeight);
+/*static*/
+bool
+MediaEngineCameraVideoSource::SatisfiesConstraintSet(const MediaTrackConstraintSet &aConstraints,
+                                                     const webrtc::CaptureCapability& aCandidate) {
+  if (!IsWithin(aCandidate.width, aConstraints.mWidth) ||
+      !IsWithin(aCandidate.height, aConstraints.mHeight)) {
+    return false;
+  }
+  if (!IsWithin(aCandidate.maxFPS, aConstraints.mFrameRate)) {
+    return false;
+  }
+  return true;
+}
+
+// SatisfiesConstraintSets (plural) answers for the capture device as a whole
+// whether it can satisfy an accumulated number of capabilitySets.
 
-  if (aConstraints.mAdvanced.WasPassed()) {
-    const auto& advanced = aConstraints.mAdvanced.Value();
-    for (uint32_t i = 0; i < advanced.Length(); i++) {
-      if (AreIntersecting(cWidth, advanced[i].mWidth) &&
-          AreIntersecting(cHeight, advanced[i].mHeight)) {
-        Intersect(cWidth, advanced[i].mWidth);
-        Intersect(cHeight, advanced[i].mHeight);
+bool
+MediaEngineCameraVideoSource::SatisfiesConstraintSets(
+    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
+{
+  size_t num = NumCapabilities();
+
+  CapabilitySet candidateSet;
+  for (size_t i = 0; i < num; i++) {
+    candidateSet.AppendElement(i);
+  }
+
+  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
+    for (size_t i = 0; i < candidateSet.Length();  ) {
+      webrtc::CaptureCapability cap;
+      GetCapability(candidateSet[i], cap);
+      if (!SatisfiesConstraintSet(*cs, cap)) {
+        candidateSet.RemoveElementAt(i);
+      } else {
+        ++i;
       }
     }
   }
-  // Detect Mac HD cams and give them some love in the form of a dynamic default
-  // since that hardware switches between 4:3 at low res and 16:9 at higher res.
-  //
-  // Logic is: if we're relying on defaults in aPrefs, then
-  // only use HD pref when non-HD pref is too small and HD pref isn't too big.
+  return !!candidateSet.Length();
+}
 
-  bool macHD = ((!aPrefs.mWidth || !aPrefs.mHeight) &&
-                mDeviceName.EqualsASCII("FaceTime HD Camera (Built-in)") &&
-                (aPrefs.GetWidth() < cWidth.mMin ||
-                 aPrefs.GetHeight() < cHeight.mMin) &&
-                !(aPrefs.GetWidth(true) > cWidth.mMax ||
-                  aPrefs.GetHeight(true) > cHeight.mMax));
-  int prefWidth = aPrefs.GetWidth(macHD);
-  int prefHeight = aPrefs.GetHeight(macHD);
+void
+MediaEngineCameraVideoSource::ChooseCapability(
+    const VideoTrackConstraintsN &aConstraints,
+    const MediaEnginePrefs &aPrefs)
+{
+  LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
+       aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS));
 
-  // Clamp width and height without distorting inherent aspect too much.
+  size_t num = NumCapabilities();
+
+  CapabilitySet candidateSet;
+  for (size_t i = 0; i < num; i++) {
+    candidateSet.AppendElement(i);
+  }
+
+  // Pick among capabilities: First apply required constraints.
 
-  if (IsWithin(prefWidth, cWidth) == IsWithin(prefHeight, cHeight)) {
-    // If both are within, we get the default (pref) aspect.
-    // If neither are within, we get the aspect of the enclosing constraint.
-    // Either are presumably reasonable (presuming constraints are sane).
-    mCapability.width = Clamp(prefWidth, cWidth);
-    mCapability.height = Clamp(prefHeight, cHeight);
-  } else {
-    // But if only one clips (e.g. width), the resulting skew is undesirable:
-    //       .------------.
-    //       | constraint |
-    //  .----+------------+----.
-    //  |    |            |    |
-    //  |pref|  result    |    |   prefAspect != resultAspect
-    //  |    |            |    |
-    //  '----+------------+----'
-    //       '------------'
-    //  So in this case, preserve prefAspect instead:
-    //  .------------.
-    //  | constraint |
-    //  .------------.
-    //  |pref        |             prefAspect is unchanged
-    //  '------------'
-    //  |            |
-    //  '------------'
-    if (IsWithin(prefWidth, cWidth)) {
-      mCapability.height = Clamp(prefHeight, cHeight);
-      mCapability.width = Clamp((mCapability.height * prefWidth) /
-                                prefHeight, cWidth);
+  for (size_t i = 0; i < candidateSet.Length();) {
+    webrtc::CaptureCapability cap;
+    GetCapability(candidateSet[i], cap);
+    if (!SatisfiesConstraintSet(aConstraints.mRequired, cap)) {
+      candidateSet.RemoveElementAt(i);
     } else {
-      mCapability.width = Clamp(prefWidth, cWidth);
-      mCapability.height = Clamp((mCapability.width * prefHeight) /
-                                 prefWidth, cHeight);
+      ++i;
+    }
+  }
+
+  CapabilitySet tailSet;
+
+  // Then apply advanced (formerly known as optional) constraints.
+
+  if (aConstraints.mAdvanced.WasPassed()) {
+    for (const MediaTrackConstraintSet &cs : aConstraints.mAdvanced.Value()) {
+      CapabilitySet rejects;
+      for (size_t i = 0; i < candidateSet.Length();) {
+        webrtc::CaptureCapability cap;
+        GetCapability(candidateSet[i], cap);
+        if (!SatisfiesConstraintSet(cs, cap)) {
+          rejects.AppendElement(candidateSet[i]);
+          candidateSet.RemoveElementAt(i);
+        } else {
+          ++i;
+        }
+      }
+      (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
     }
   }
-  mCapability.maxFPS = MediaEngine::DEFAULT_VIDEO_FPS;
-  LOG(("chose cap %dx%d @%dfps",
-       mCapability.width, mCapability.height, mCapability.maxFPS));
+
+  if (!candidateSet.Length()) {
+    candidateSet.AppendElement(0);
+  }
+
+  int prefWidth = aPrefs.GetWidth();
+  int prefHeight = aPrefs.GetHeight();
+
+  // Default is closest to available capability but equal to or below;
+  // otherwise closest above.  Since we handle the num=0 case above and
+  // take the first entry always, we can never exit uninitialized.
+
+  webrtc::CaptureCapability cap;
+  bool higher = true;
+  for (size_t i = 0; i < candidateSet.Length(); i++) {
+    GetCapability(candidateSet[i], cap);
+    if (higher) {
+      if (i == 0 ||
+          (mCapability.width > cap.width && mCapability.height > cap.height)) {
+        // closer than the current choice
+        mCapability = cap;
+        // FIXME: expose expected capture delay?
+      }
+      if (cap.width <= (uint32_t) prefWidth && cap.height <= (uint32_t) prefHeight) {
+        higher = false;
+      }
+    } else {
+      if (cap.width > (uint32_t) prefWidth || cap.height > (uint32_t) prefHeight ||
+          cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
+        continue;
+      }
+      if (mCapability.width < cap.width && mCapability.height < cap.height) {
+        mCapability = cap;
+        // FIXME: expose expected capture delay?
+      }
+    }
+    // Same resolution, maybe better format or FPS match
+    if (mCapability.width == cap.width && mCapability.height == cap.height) {
+      // FPS too low
+      if (cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
+        continue;
+      }
+      // Better match
+      if (cap.maxFPS < mCapability.maxFPS) {
+        mCapability = cap;
+      } else if (cap.maxFPS == mCapability.maxFPS) {
+        // Resolution and FPS the same, check format
+        if (cap.rawType == webrtc::RawVideoType::kVideoI420
+          || cap.rawType == webrtc::RawVideoType::kVideoYUY2
+          || cap.rawType == webrtc::RawVideoType::kVideoYV12) {
+          mCapability = cap;
+        }
+      }
+    }
+  }
+  LOG(("chose cap %dx%d @%dfps codec %d raw %d",
+       mCapability.width, mCapability.height, mCapability.maxFPS,
+       mCapability.codecType, mCapability.rawType));
 }
 
 void
 MediaEngineCameraVideoSource::GetName(nsAString& aName)
 {
   aName = mDeviceName;
 }
 
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -53,33 +53,42 @@ public:
       return dom::MediaSourceEnum::Camera;
   }
 
   virtual nsresult TakePhoto(PhotoCallback* aCallback) MOZ_OVERRIDE
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  bool SatisfiesConstraintSets(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE;
+
 protected:
+  typedef nsTArray<size_t> CapabilitySet;
+
   ~MediaEngineCameraVideoSource() {}
 
   // guts for appending data to the MSG track
   virtual bool AppendToTrack(SourceMediaStream* aSource,
                              layers::Image* aImage,
                              TrackID aID,
                              StreamTime delta);
 
   static bool IsWithin(int32_t n, const dom::ConstrainLongRange& aRange);
   static bool IsWithin(double n, const dom::ConstrainDoubleRange& aRange);
   static int32_t Clamp(int32_t n, const dom::ConstrainLongRange& aRange);
   static bool AreIntersecting(const dom::ConstrainLongRange& aA,
                               const dom::ConstrainLongRange& aB);
   static bool Intersect(dom::ConstrainLongRange& aA, const dom::ConstrainLongRange& aB);
-  void GuessCapability(const VideoTrackConstraintsN& aConstraints,
-                       const MediaEnginePrefs& aPrefs);
+  static bool SatisfiesConstraintSet(const dom::MediaTrackConstraintSet& aConstraints,
+                                     const webrtc::CaptureCapability& aCandidate);
+  virtual size_t NumCapabilities();
+  virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
+  void ChooseCapability(const VideoTrackConstraintsN &aConstraints,
+                        const MediaEnginePrefs &aPrefs);
 
   // Engine variables.
 
   // mMonitor protects mImage access/changes, and transitions of mState
   // from kStarted to kStopped (which are combined with EndTrack() and
   // image changes).
   // mMonitor also protects mSources[] access/changes.
   // mSources[] is accessed from webrtc threads.
@@ -96,15 +105,16 @@ protected:
   bool mInitDone;
   bool mHasDirectListeners;
   int mCaptureIndex;
   TrackID mTrackID;
   int mFps; // Track rate (30 fps by default)
 
   webrtc::CaptureCapability mCapability; // Doesn't work on OS X.
 
+  nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities; // For OSX & B2G
   nsString mDeviceName;
   nsString mUniqueId;
 };
 
 
 } // namespace mozilla
 #endif // MediaEngineCameraVideoSource_h
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -102,21 +102,57 @@ MediaEngineGonkVideoSource::NotifyPull(M
     IntSize size(image ? mWidth : 0, image ? mHeight : 0);
     segment.AppendFrame(image.forget(), delta, size);
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
     aSource->AppendToTrack(aID, &(segment));
   }
 }
 
-void
-MediaEngineGonkVideoSource::ChooseCapability(const VideoTrackConstraintsN& aConstraints,
-                                             const MediaEnginePrefs& aPrefs)
+size_t
+MediaEngineGonkVideoSource::NumCapabilities()
 {
-  return GuessCapability(aConstraints, aPrefs);
+  // TODO: Stop hardcoding. Use GetRecorderProfiles+GetProfileInfo (Bug 1128550)
+  //
+  // The camera-selecting constraints algorithm needs a set of capabilities to
+  // work on. In lieu of something better, here are some generic values based on
+  // http://en.wikipedia.org/wiki/Comparison_of_Firefox_OS_devices on Jan 2015.
+  // When unknown, better overdo it with choices to not block legitimate asks.
+  // TODO: Match with actual hardware or add code to query hardware.
+
+  if (mHardcodedCapabilities.IsEmpty()) {
+    const struct { int width, height; } hardcodes[] = {
+      { 800, 1280 },
+      { 720, 1280 },
+      { 600, 1024 },
+      { 540, 960 },
+      { 480, 854 },
+      { 480, 800 },
+      { 320, 480 },
+      { 240, 320 }, // sole mode supported by emulator on try
+    };
+    const int framerates[] = { 15, 30 };
+
+    for (auto& hardcode : hardcodes) {
+      webrtc::CaptureCapability c;
+      c.width = hardcode.width;
+      c.height = hardcode.height;
+      for (int framerate : framerates) {
+        c.maxFPS = framerate;
+        mHardcodedCapabilities.AppendElement(c); // portrait
+      }
+      c.width = hardcode.height;
+      c.height = hardcode.width;
+      for (int framerate : framerates) {
+        c.maxFPS = framerate;
+        mHardcodedCapabilities.AppendElement(c); // landscape
+      }
+    }
+  }
+  return mHardcodedCapabilities.Length();
 }
 
 nsresult
 MediaEngineGonkVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints,
                                      const MediaEnginePrefs& aPrefs)
 {
   LOG((__FUNCTION__));
 
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.h
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h
@@ -64,21 +64,16 @@ public:
                             const MediaEnginePrefs &aPrefs) MOZ_OVERRIDE;
   virtual nsresult Deallocate() MOZ_OVERRIDE;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) MOZ_OVERRIDE;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) MOZ_OVERRIDE;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) MOZ_OVERRIDE;
-  virtual bool SatisfiesConstraintSets(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets)
-  {
-    return true;
-  }
 
   void OnHardwareStateChange(HardwareState aState, nsresult aReason) MOZ_OVERRIDE;
   void GetRotation();
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) MOZ_OVERRIDE;
   void OnUserError(UserContext aContext, nsresult aError) MOZ_OVERRIDE;
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) MOZ_OVERRIDE;
 
   void AllocImpl();
@@ -104,18 +99,17 @@ public:
 protected:
   ~MediaEngineGonkVideoSource()
   {
     Shutdown();
   }
   // Initialize the needed Video engine interfaces.
   void Init();
   void Shutdown();
-  void ChooseCapability(const VideoTrackConstraintsN& aConstraints,
-                        const MediaEnginePrefs& aPrefs);
+  size_t NumCapabilities() MOZ_OVERRIDE;
   // Initialize the recording frame (MediaBuffer) callback and Gonk camera.
   // MediaBuffer will transfers to MediaStreamGraph via AppendToTrack.
   nsresult InitDirectMediaBuffer();
 
   mozilla::ReentrantMonitor mCallbackMonitor; // Monitor for camera callback handling
   // This is only modified on MainThread (AllocImpl and DeallocImpl)
   nsRefPtr<ICameraControl> mCameraControl;
   nsCOMPtr<nsIDOMFile> mLastCapture;
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -104,19 +104,16 @@ public:
   }
   virtual nsresult TakePhoto(PhotoCallback* aCallback) MOZ_OVERRIDE
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   void Refresh(int aIndex);
 
-  bool SatisfiesConstraintSets(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE;
-
 protected:
   ~MediaEngineWebRTCVideoSource() { Shutdown(); }
 
 private:
   // Initialize the needed Video engine interfaces.
   void Init();
   void Shutdown();
 
@@ -124,20 +121,18 @@ private:
   webrtc::VideoEngine* mVideoEngine; // Weak reference, don't free.
   webrtc::ViEBase* mViEBase;
   webrtc::ViECapture* mViECapture;
   webrtc::ViERender* mViERender;
 
   int mMinFps; // Min rate we want to accept
   dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
 
-  static bool SatisfiesConstraintSet(const dom::MediaTrackConstraintSet& aConstraints,
-                                     const webrtc::CaptureCapability& aCandidate);
-  void ChooseCapability(const VideoTrackConstraintsN& aConstraints,
-                        const MediaEnginePrefs& aPrefs);
+  size_t NumCapabilities() MOZ_OVERRIDE;
+  void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) MOZ_OVERRIDE;
 };
 
 class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource,
                                      public webrtc::VoEMediaProcess
 {
 public:
   MediaEngineWebRTCAudioSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr,
                                int aIndex, const char* name, const char* uuid)
--- a/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -148,182 +148,59 @@ MediaEngineWebRTCVideoSource::NotifyPull
   // Don't append if we've already provided a frame that supposedly goes past the current aDesiredTime
   // Doing so means a negative delta and thus messes up handling of the graph
   if (delta > 0) {
     // nullptr images are allowed
     AppendToTrack(aSource, mImage, aID, delta);
   }
 }
 
-/*static*/
-bool
-MediaEngineWebRTCVideoSource::SatisfiesConstraintSet(const MediaTrackConstraintSet &aConstraints,
-                                                     const webrtc::CaptureCapability& aCandidate) {
-  if (!MediaEngineCameraVideoSource::IsWithin(aCandidate.width, aConstraints.mWidth) ||
-      !MediaEngineCameraVideoSource::IsWithin(aCandidate.height, aConstraints.mHeight)) {
-    return false;
+size_t
+MediaEngineWebRTCVideoSource::NumCapabilities()
+{
+  NS_ConvertUTF16toUTF8 uniqueId(mUniqueId); // TODO: optimize this?
+
+  int num = mViECapture->NumberOfCapabilities(uniqueId.get(), kMaxUniqueIdLength);
+  if (num > 0) {
+    return num;
   }
-  if (!MediaEngineCameraVideoSource::IsWithin(aCandidate.maxFPS, aConstraints.mFrameRate)) {
-    return false;
-  }
-  return true;
-}
-
-typedef nsTArray<uint8_t> CapabilitySet;
-
-// SatisfiesConstraintSets (plural) answers for the capture device as a whole
-// whether it can satisfy an accumulated number of capabilitySets.
+  // Mac doesn't support capabilities.
+  //
+  // Hardcode generic desktop capabilities modeled on OSX camera.
+  // Note: Values are empirically picked to be OSX friendly, as on OSX, values
+  // other than these cause the source to not produce.
 
-bool
-MediaEngineWebRTCVideoSource::SatisfiesConstraintSets(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
-{
-  NS_ConvertUTF16toUTF8 uniqueId(mUniqueId);
-  int num = mViECapture->NumberOfCapabilities(uniqueId.get(), kMaxUniqueIdLength);
-  if (num <= 0) {
-    return true;
-  }
-
-  CapabilitySet candidateSet;
-  for (int i = 0; i < num; i++) {
-    candidateSet.AppendElement(i);
-  }
-
-  for (size_t j = 0; j < aConstraintSets.Length(); j++) {
-    for (size_t i = 0; i < candidateSet.Length();  ) {
-      webrtc::CaptureCapability cap;
-      mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
-                                        candidateSet[i], cap);
-      if (!SatisfiesConstraintSet(*aConstraintSets[j], cap)) {
-        candidateSet.RemoveElementAt(i);
-      } else {
-        ++i;
-      }
+  if (mHardcodedCapabilities.IsEmpty()) {
+    for (int i = 0; i < 9; i++) {
+      webrtc::CaptureCapability c;
+      c.width = 1920 - i*128;
+      c.height = 1080 - i*72;
+      c.maxFPS = 30;
+      mHardcodedCapabilities.AppendElement(c);
+    }
+    for (int i = 0; i < 16; i++) {
+      webrtc::CaptureCapability c;
+      c.width = 640 - i*40;
+      c.height = 480 - i*30;
+      c.maxFPS = 30;
+      mHardcodedCapabilities.AppendElement(c);
     }
   }
-  return !!candidateSet.Length();
+  return mHardcodedCapabilities.Length();
 }
 
 void
-MediaEngineWebRTCVideoSource::ChooseCapability(
-    const VideoTrackConstraintsN &aConstraints,
-    const MediaEnginePrefs &aPrefs)
+MediaEngineWebRTCVideoSource::GetCapability(size_t aIndex,
+                                            webrtc::CaptureCapability& aOut)
 {
-  NS_ConvertUTF16toUTF8 uniqueId(mUniqueId);
-  int num = mViECapture->NumberOfCapabilities(uniqueId.get(), kMaxUniqueIdLength);
-  if (num <= 0) {
-    // Mac doesn't support capabilities.
-    return GuessCapability(aConstraints, aPrefs);
-  }
-
-  // The rest is the full algorithm for cameras that can list their capabilities.
-
-  LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
-       aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS));
-
-  CapabilitySet candidateSet;
-  for (int i = 0; i < num; i++) {
-    candidateSet.AppendElement(i);
-  }
-
-  // Pick among capabilities: First apply required constraints.
-
-  for (uint32_t i = 0; i < candidateSet.Length();) {
-    webrtc::CaptureCapability cap;
-    mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
-                                      candidateSet[i], cap);
-    if (!SatisfiesConstraintSet(aConstraints.mRequired, cap)) {
-      candidateSet.RemoveElementAt(i);
-    } else {
-      ++i;
-    }
-  }
-
-  CapabilitySet tailSet;
-
-  // Then apply advanced (formerly known as optional) constraints.
-
-  if (aConstraints.mAdvanced.WasPassed()) {
-    auto &array = aConstraints.mAdvanced.Value();
-
-    for (uint32_t i = 0; i < array.Length(); i++) {
-      CapabilitySet rejects;
-      for (uint32_t j = 0; j < candidateSet.Length();) {
-        webrtc::CaptureCapability cap;
-        mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
-                                          candidateSet[j], cap);
-        if (!SatisfiesConstraintSet(array[i], cap)) {
-          rejects.AppendElement(candidateSet[j]);
-          candidateSet.RemoveElementAt(j);
-        } else {
-          ++j;
-        }
-      }
-      (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
-    }
+  if (!mHardcodedCapabilities.IsEmpty()) {
+    MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
   }
-
-  if (!candidateSet.Length()) {
-    candidateSet.AppendElement(0);
-  }
-
-  int prefWidth = aPrefs.GetWidth();
-  int prefHeight = aPrefs.GetHeight();
-
-  // Default is closest to available capability but equal to or below;
-  // otherwise closest above.  Since we handle the num=0 case above and
-  // take the first entry always, we can never exit uninitialized.
-
-  webrtc::CaptureCapability cap;
-  bool higher = true;
-  for (uint32_t i = 0; i < candidateSet.Length(); i++) {
-    mViECapture->GetCaptureCapability(NS_ConvertUTF16toUTF8(mUniqueId).get(),
-                                      kMaxUniqueIdLength, candidateSet[i], cap);
-    if (higher) {
-      if (i == 0 ||
-          (mCapability.width > cap.width && mCapability.height > cap.height)) {
-        // closer than the current choice
-        mCapability = cap;
-        // FIXME: expose expected capture delay?
-      }
-      if (cap.width <= (uint32_t) prefWidth && cap.height <= (uint32_t) prefHeight) {
-        higher = false;
-      }
-    } else {
-      if (cap.width > (uint32_t) prefWidth || cap.height > (uint32_t) prefHeight ||
-          cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
-        continue;
-      }
-      if (mCapability.width < cap.width && mCapability.height < cap.height) {
-        mCapability = cap;
-        // FIXME: expose expected capture delay?
-      }
-    }
-    // Same resolution, maybe better format or FPS match
-    if (mCapability.width == cap.width && mCapability.height == cap.height) {
-      // FPS too low
-      if (cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
-        continue;
-      }
-      // Better match
-      if (cap.maxFPS < mCapability.maxFPS) {
-        mCapability = cap;
-      } else if (cap.maxFPS == mCapability.maxFPS) {
-        // Resolution and FPS the same, check format
-        if (cap.rawType == webrtc::RawVideoType::kVideoI420
-          || cap.rawType == webrtc::RawVideoType::kVideoYUY2
-          || cap.rawType == webrtc::RawVideoType::kVideoYV12) {
-          mCapability = cap;
-        }
-      }
-    }
-  }
-  LOG(("chose cap %dx%d @%dfps codec %d raw %d",
-       mCapability.width, mCapability.height, mCapability.maxFPS,
-       mCapability.codecType, mCapability.rawType));
+  NS_ConvertUTF16toUTF8 uniqueId(mUniqueId); // TODO: optimize this?
+  mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength, aIndex, aOut);
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Allocate(const VideoTrackConstraintsN &aConstraints,
                                        const MediaEnginePrefs &aPrefs)
 {
   LOG((__FUNCTION__));
   if (mState == kReleased && mInitDone) {