Bug 907352 - Part 4: Normalized constraints to relieve downstream width/height/frameRate implementation. r=mt

This commit is contained in:
Jan-Ivar Bruaroey 2014-04-18 15:16:08 -04:00
parent 6335863bae
commit 2ff8cc766a
6 changed files with 143 additions and 128 deletions

View File

@ -0,0 +1,93 @@
/* 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/. */
// This file should not be included by other includes, as it contains code
#ifndef MEDIATRACKCONSTRAINTS_H_
#define MEDIATRACKCONSTRAINTS_H_
#include "mozilla/dom/MediaStreamTrackBinding.h"
namespace mozilla {
// Normalized internal version of MediaTrackConstraints to simplify downstream
// processing. This implementation-only helper is included as needed by both
// MediaManager (for gUM camera selection) and MediaEngine (for applyConstraints).
template<typename T>
class MediaTrackConstraintsN : public dom::MediaTrackConstraints
{
public:
typedef T Kind;
dom::Sequence<Kind> mRequireN;
bool mUnsupportedRequirement;
MediaTrackConstraintSet mRequired;
dom::Sequence<MediaTrackConstraintSet> mNonrequired;
MediaTrackConstraintsN(const dom::MediaTrackConstraints &aOther,
const dom::EnumEntry* aStrings)
: dom::MediaTrackConstraints(aOther)
, mUnsupportedRequirement(false)
, mStrings(aStrings)
{
if (mRequire.WasPassed()) {
auto& array = mRequire.Value();
for (uint32_t i = 0; i < array.Length(); i++) {
auto value = ToEnum(array[i]);
if (value != Kind::Other) {
mRequireN.AppendElement(value);
} else {
mUnsupportedRequirement = true;
}
}
}
}
protected:
MediaTrackConstraintSet& Triage(const Kind kind) {
if (mRequireN.IndexOf(kind) != mRequireN.NoIndex) {
return mRequired;
} else {
mNonrequired.AppendElement(MediaTrackConstraintSet());
return mNonrequired[mNonrequired.Length()-1];
}
}
private:
Kind ToEnum(const nsAString& aSrc) {
for (size_t i = 0; mStrings[i].value; i++) {
if (aSrc.EqualsASCII(mStrings[i].value)) {
return Kind(i);
}
}
return Kind::Other;
}
const dom::EnumEntry* mStrings;
};
struct AudioTrackConstraintsN :
public MediaTrackConstraintsN<dom::SupportedAudioConstraints>
{
AudioTrackConstraintsN(const dom::MediaTrackConstraints &aOther)
: MediaTrackConstraintsN<dom::SupportedAudioConstraints>(aOther, // B2G ICS compiler bug
dom::SupportedAudioConstraintsValues::strings) {}
};
struct VideoTrackConstraintsN :
public MediaTrackConstraintsN<dom::SupportedVideoConstraints>
{
VideoTrackConstraintsN(const dom::MediaTrackConstraints &aOther)
: MediaTrackConstraintsN<dom::SupportedVideoConstraints>(aOther,
dom::SupportedVideoConstraintsValues::strings) {
if (mFacingMode.WasPassed()) {
Triage(Kind::FacingMode).mFacingMode.Construct(mFacingMode.Value());
}
// Reminder: add handling for new constraints both here & SatisfyConstraintSet
Triage(Kind::Width).mWidth = mWidth;
Triage(Kind::Height).mHeight = mHeight;
Triage(Kind::FrameRate).mFrameRate = mFrameRate;
}
};
}
#endif /* MEDIATRACKCONSTRAINTS_H_ */

View File

@ -9,6 +9,7 @@ XPIDL_MODULE = 'content_webrtc'
EXPORTS += [
'MediaEngine.h',
'MediaEngineDefault.h',
'MediaTrackConstraints.h',
]
if CONFIG['MOZ_WEBRTC']:

View File

@ -28,6 +28,7 @@
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/GetUserMediaRequestBinding.h"
#include "MediaTrackConstraints.h"
#include "Latency.h"
@ -80,6 +81,8 @@ using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject)
using dom::GetUserMediaRequest;
using dom::Sequence;
using dom::OwningBooleanOrMediaTrackConstraints;
using dom::SupportedAudioConstraints;
using dom::SupportedVideoConstraints;
ErrorCallbackRunnable::ErrorCallbackRunnable(
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
@ -655,20 +658,6 @@ GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
* http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
*/
#define lengthof(a) (sizeof(a) / sizeof(*a))
static auto
GetSupportedConstraintNames(const MediaEngineVideoSource *) ->
decltype((dom::SupportedVideoConstraintsValues::strings)) {
return dom::SupportedVideoConstraintsValues::strings;
}
static auto
GetSupportedConstraintNames(const MediaEngineAudioSource *) ->
decltype((dom::SupportedAudioConstraintsValues::strings)) {
return dom::SupportedAudioConstraintsValues::strings;
}
// Reminder: add handling for new constraints both here and in GetSources below!
static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
@ -695,61 +684,19 @@ static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
return true;
}
// Triage constraints into required and nonrequired + detect missing requireds
class TriageHelper
{
public:
TriageHelper(const nsTArray<nsString>& aRequire)
: mRequire(aRequire)
, mNumRequirementsMet(0) {}
MediaTrackConstraintSet& Triage(dom::SupportedVideoConstraints kind) {
return Triage(NS_ConvertUTF8toUTF16(
dom::SupportedVideoConstraintsValues::strings[uint32_t(kind)].value));
}
MediaTrackConstraintSet& Triage(dom::SupportedAudioConstraints kind) {
return Triage(NS_ConvertUTF8toUTF16(
dom::SupportedAudioConstraintsValues::strings[uint32_t(kind)].value));
}
private:
MediaTrackConstraintSet& Triage(const nsAString &name) {
if (mRequire.IndexOf(name) != mRequire.NoIndex) {
mNumRequirementsMet++;
return mRequired;
} else {
return mNonrequired;
}
}
public:
bool RequirementsAreMet() {
MOZ_ASSERT(mNumRequirementsMet <= mRequire.Length());
return mNumRequirementsMet == mRequire.Length();
}
MediaTrackConstraintSet mRequired;
MediaTrackConstraintSet mNonrequired;
private:
const nsTArray<nsString> mRequire;
uint32_t mNumRequirementsMet;
};
typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
// Source getter that constrains list returned
template<class SourceType>
template<class SourceType, class ConstraintsType>
static SourceSet *
GetSources(MediaEngine *engine,
const OwningBooleanOrMediaTrackConstraints &aConstraints,
ConstraintsType &aConstraints,
void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
char* media_device_name = nullptr)
{
ScopedDeletePtr<SourceSet> result(new SourceSet);
if (!IsOn(aConstraints)) {
return result.forget();
}
const SourceType * const type = nullptr;
nsString deviceName;
// First collect sources
@ -782,54 +729,14 @@ static SourceSet *
// Apply constraints to the list of sources.
auto& c = GetInvariant(aConstraints);
const nsTArray<nsString> empty;
const auto &require = c.mRequire.WasPassed()? c.mRequire.Value() : empty;
{
auto& c = aConstraints;
if (c.mUnsupportedRequirement) {
// Check upfront the names of required constraints that are unsupported for
// this media-type. The spec requires these to fail, so getting them out of
// the way early provides a necessary invariant for the remaining algorithm
// which maximizes code-reuse by ignoring constraints of the other type
// (specifically, SatisfyConstraintSet is reused for the advanced algorithm
// where the spec requires it to ignore constraints of the other type)
const auto& supported = GetSupportedConstraintNames(type);
for (uint32_t i = 0; i < require.Length(); i++) {
bool found = false;
// EnumType arrays have a zero-terminator entry at the end. Skip.
for (size_t j = 0; j < sizeof(supported)/sizeof(*supported) - 1; j++) {
if (require[i].EqualsASCII(supported[j].value)) {
found = true;
break;
}
}
if (!found) {
return result.forget();
}
}
}
// Before we start, triage constraints into required and nonrequired.
// This part is type-agnostic because it can be.
// Reminder: add handling for new constraints both here & SatisfyConstraintSet
TriageHelper helper(require);
if (c.mFacingMode.WasPassed()) {
helper.Triage(dom::SupportedVideoConstraints::FacingMode).
mFacingMode.Construct(c.mFacingMode.Value());
}
if (c.mWidth.mMin.WasPassed() || c.mWidth.mMax.WasPassed()) {
helper.Triage(dom::SupportedVideoConstraints::Width).mWidth = c.mWidth;
}
if (c.mHeight.mMin.WasPassed() || c.mHeight.mMax.WasPassed()) {
helper.Triage(dom::SupportedVideoConstraints::Height).mHeight = c.mHeight;
}
if (c.mFrameRate.mMin.WasPassed() || c.mFrameRate.mMax.WasPassed()) {
helper.Triage(dom::SupportedVideoConstraints::FrameRate).mFrameRate =
c.mFrameRate;
}
if (!helper.RequirementsAreMet()) {
return result.forget();
}
@ -837,13 +744,25 @@ static SourceSet *
for (uint32_t i = 0; i < candidateSet.Length();) {
// Overloading instead of template specialization keeps things local
if (!SatisfyConstraintSet(type, helper.mRequired, *candidateSet[i])) {
if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) {
candidateSet.RemoveElementAt(i);
} else {
++i;
}
}
// TODO(jib): Proper non-ordered handling of nonrequired constraints (907352)
//
// For now, put nonrequired constraints at tail of Advanced list.
// This isn't entirely accurate, as order will matter, but few will notice
// the difference until we get camera selection and a few more constraints.
if (c.mNonrequired.Length()) {
if (!c.mAdvanced.WasPassed()) {
c.mAdvanced.Construct();
}
c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired);
}
// Then apply advanced (formerly known as optional) constraints.
//
// These are only effective when there are multiple sources to pick from.
@ -861,7 +780,8 @@ static SourceSet *
SourceSet tailSet;
if (c.mAdvanced.WasPassed()) {
const auto &array = c.mAdvanced.Value();
auto &array = c.mAdvanced.Value();
for (int i = 0; i < int(array.Length()); i++) {
SourceSet rejects;
for (uint32_t j = 0; j < candidateSet.Length();) {
@ -876,9 +796,7 @@ static SourceSet *
}
}
// Finally, order any remaining sources by how many nonrequired constraints
// they satisfy. TODO(jib): TBD once we implement >1 constraint (Bug 907352)
// TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
result->MoveElementsFrom(candidateSet);
result->MoveElementsFrom(tailSet);
@ -1053,8 +971,9 @@ public:
MOZ_ASSERT(mSuccess);
MOZ_ASSERT(mError);
if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
ScopedDeletePtr<SourceSet> sources (GetSources(backend,
mConstraints.mVideo, &MediaEngine::EnumerateVideoDevices));
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
&MediaEngine::EnumerateVideoDevices));
if (!sources->Length()) {
Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
@ -1066,8 +985,9 @@ public:
}
if (IsOn(mConstraints.mAudio)) {
ScopedDeletePtr<SourceSet> sources (GetSources(backend,
mConstraints.mAudio, &MediaEngine::EnumerateAudioDevices));
AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
&MediaEngine::EnumerateAudioDevices));
if (!sources->Length()) {
Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
@ -1203,13 +1123,19 @@ public:
else
backend = mManager->GetBackend(mWindowId);
ScopedDeletePtr<SourceSet> final (GetSources(backend, mConstraints.mVideo,
&MediaEngine::EnumerateVideoDevices,
mLoopbackVideoDevice));
{
ScopedDeletePtr<SourceSet> s (GetSources(backend, mConstraints.mAudio,
&MediaEngine::EnumerateAudioDevices,
mLoopbackAudioDevice));
ScopedDeletePtr<SourceSet> final(new SourceSet);
if (IsOn(mConstraints.mVideo)) {
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints,
&MediaEngine::EnumerateVideoDevices,
mLoopbackVideoDevice));
final->MoveElementsFrom(*s);
}
if (IsOn(mConstraints.mAudio)) {
AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints,
&MediaEngine::EnumerateAudioDevices,
mLoopbackAudioDevice));
final->MoveElementsFrom(*s);
}
NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,

View File

@ -19,12 +19,6 @@ var common_tests = [
constraints: { audio: { somethingUnknown:0, require:["somethingUnknown"] },
fake: true },
error: "NO_DEVICES_FOUND" },
{ message: "missing required constraint on video fails",
constraints: { video: { require:["facingMode"] }, fake: true },
error: "NO_DEVICES_FOUND" },
{ message: "missing required constraint on audio fails",
constraints: { audio: { require:["facingMode"] }, fake: true },
error: "NO_DEVICES_FOUND" },
{ message: "video overconstrained by facingMode fails",
constraints: { video: { facingMode:'left', require:["facingMode"] },
fake: true },

View File

@ -15,11 +15,11 @@ enum VideoFacingModeEnum {
};
dictionary ConstrainLongRange {
long min;
long max;
long min = -2147483647; // +1 works around windows compiler bug
long max = 2147483647;
};
dictionary ConstrainDoubleRange {
double min;
double max;
unrestricted double min = -Infinity;
unrestricted double max = Infinity;
};

View File

@ -8,14 +8,15 @@
*/
enum SupportedVideoConstraints {
"other",
"facingMode",
"width",
"height",
"frameRate"
"frameRate",
};
enum SupportedAudioConstraints {
"dummy"
"other"
};
dictionary MediaTrackConstraintSet {