nsresult MediaEngineTabVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints, const MediaEnginePrefs& aPrefs) { ConstrainLongRange cWidth(aConstraints.mRequired.mWidth); ConstrainLongRange cHeight(aConstraints.mRequired.mHeight); if (aConstraints.mAdvanced.WasPassed()) { const auto& advanced = aConstraints.mAdvanced.Value(); for (uint32_t i = 0; i < advanced.Length(); i++) { if (cWidth.mMax >= advanced[i].mWidth.mMin && cWidth.mMin <= advanced[i].mWidth.mMax && cHeight.mMax >= advanced[i].mHeight.mMin && cHeight.mMin <= advanced[i].mHeight.mMax) { cWidth.mMin = std::max(cWidth.mMin, advanced[i].mWidth.mMin); cHeight.mMin = std::max(cHeight.mMin, advanced[i].mHeight.mMin); } } } mBufW = aPrefs.GetWidth(false); mBufH = aPrefs.GetHeight(false); if (cWidth.mMin > mBufW) { mBufW = cWidth.mMin; } else if (cWidth.mMax < mBufW) { mBufW = cWidth.mMax; } if (cHeight.mMin > mBufH) { mBufH = cHeight.mMin; } else if (cHeight.mMax < mBufH) { mBufH = cHeight.mMax; } mTimePerFrame = aPrefs.mFPS ? 1000 / aPrefs.mFPS : aPrefs.mFPS; return NS_OK; }
bool MediaEngineCameraVideoSource::ChooseCapability( const MediaTrackConstraints &aConstraints, const MediaEnginePrefs &aPrefs, const nsString& aDeviceId) { if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) { LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps", aPrefs.GetWidth(), aPrefs.GetHeight(), aPrefs.mFPS, aPrefs.mMinFPS)); LogConstraints(aConstraints, false); if (aConstraints.mAdvanced.WasPassed()) { LOG(("Advanced array[%u]:", aConstraints.mAdvanced.Value().Length())); for (auto& advanced : aConstraints.mAdvanced.Value()) { LogConstraints(advanced, true); } } } size_t num = NumCapabilities(); CapabilitySet candidateSet; for (size_t i = 0; i < num; i++) { candidateSet.AppendElement(i); } // First, filter capabilities by required constraints (min, max, exact). for (size_t i = 0; i < candidateSet.Length();) { auto& candidate = candidateSet[i]; webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId); LogCapability("Capability", cap, candidate.mDistance); if (candidate.mDistance == UINT32_MAX) { candidateSet.RemoveElementAt(i); } else { ++i; } } // Filter further with all advanced constraints (that don't overconstrain). if (aConstraints.mAdvanced.WasPassed()) { for (const MediaTrackConstraintSet &cs : aConstraints.mAdvanced.Value()) { CapabilitySet rejects; for (size_t i = 0; i < candidateSet.Length();) { auto& candidate = candidateSet[i]; webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); if (GetFitnessDistance(cap, cs, true, aDeviceId) == UINT32_MAX) { rejects.AppendElement(candidate); candidateSet.RemoveElementAt(i); } else { ++i; } } if (!candidateSet.Length()) { candidateSet.AppendElements(Move(rejects)); } } } if (!candidateSet.Length()) { LOG(("failed to find capability match from %d choices",num)); return false; } // Remaining algorithm is up to the UA. TrimLessFitCandidates(candidateSet); // Any remaining multiples all have the same distance. A common case of this // occurs when no ideal is specified. Lean toward defaults. uint32_t sameDistance = candidateSet[0].mDistance; { MediaTrackConstraintSet prefs; prefs.mWidth.SetAsLong() = aPrefs.GetWidth(); prefs.mHeight.SetAsLong() = aPrefs.GetHeight(); prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS; for (auto& candidate : candidateSet) { webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); candidate.mDistance = GetFitnessDistance(cap, prefs, false, aDeviceId); } TrimLessFitCandidates(candidateSet); } // Any remaining multiples all have the same distance, but may vary on // format. Some formats are more desirable for certain use like WebRTC. // E.g. I420 over RGB24 can remove a needless format conversion. bool found = false; for (auto& candidate : candidateSet) { webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); if (cap.rawType == webrtc::RawVideoType::kVideoI420 || cap.rawType == webrtc::RawVideoType::kVideoYUY2 || cap.rawType == webrtc::RawVideoType::kVideoYV12) { mCapability = cap; found = true; break; } } if (!found) { GetCapability(candidateSet[0].mIndex, mCapability); } LogCapability("Chosen capability", mCapability, sameDistance); return true; }
// A special version of the algorithm for cameras that don't list capabilities. void MediaEngineCameraVideoSource::GuessCapability( const VideoTrackConstraintsN& aConstraints, const MediaEnginePrefs& aPrefs) { LOG(("GuessCapability: prefs: %dx%d @%d-%dfps", aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS)); // In short: compound constraint-ranges and use pref as ideal. ConstrainLongRange cWidth(aConstraints.mRequired.mWidth); ConstrainLongRange cHeight(aConstraints.mRequired.mHeight); 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); } } } // 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. 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); // Clamp width and height without distorting inherent aspect too much. 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); } else { mCapability.width = Clamp(prefWidth, cWidth); mCapability.height = Clamp((mCapability.width * prefHeight) / prefWidth, cHeight); } } mCapability.maxFPS = MediaEngine::DEFAULT_VIDEO_FPS; LOG(("chose cap %dx%d @%dfps", mCapability.width, mCapability.height, mCapability.maxFPS)); }
void MediaEngineWebRTCVideoSource::ChooseCapability( const VideoTrackConstraintsN &aConstraints, const MediaEnginePrefs &aPrefs) { 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)); typedef nsTArray<uint8_t> SourceSet; SourceSet 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 (!SatisfyConstraintSet(aConstraints.mRequired, cap)) { candidateSet.RemoveElementAt(i); } else { ++i; } } SourceSet 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++) { SourceSet rejects; for (uint32_t j = 0; j < candidateSet.Length();) { webrtc::CaptureCapability cap; mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength, candidateSet[j], cap); if (!SatisfyConstraintSet(array[i], cap)) { rejects.AppendElement(candidateSet[j]); candidateSet.RemoveElementAt(j); } else { ++j; } } (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects); } } 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)); }