1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "MediaEngineCameraVideoSource.h"
6
7 #include <limits>
8
9 namespace mozilla {
10
11 using namespace mozilla::gfx;
12 using namespace mozilla::dom;
13
14 extern LogModule* GetMediaManagerLog();
15 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
16 #define LOGFRAME(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
17
18 // guts for appending data to the MSG track
AppendToTrack(SourceMediaStream * aSource,layers::Image * aImage,TrackID aID,StreamTime delta,const PrincipalHandle & aPrincipalHandle)19 bool MediaEngineCameraVideoSource::AppendToTrack(SourceMediaStream* aSource,
20 layers::Image* aImage,
21 TrackID aID,
22 StreamTime delta,
23 const PrincipalHandle& aPrincipalHandle)
24 {
25 MOZ_ASSERT(aSource);
26
27 VideoSegment segment;
28 RefPtr<layers::Image> image = aImage;
29 IntSize size(image ? mWidth : 0, image ? mHeight : 0);
30 segment.AppendFrame(image.forget(), delta, size, aPrincipalHandle);
31
32 // This is safe from any thread, and is safe if the track is Finished
33 // or Destroyed.
34 // This can fail if either a) we haven't added the track yet, or b)
35 // we've removed or finished the track.
36 return aSource->AppendToTrack(aID, &(segment));
37 }
38
39 // Sub-classes (B2G or desktop) should overload one of both of these two methods
40 // to provide capabilities
41 size_t
NumCapabilities() const42 MediaEngineCameraVideoSource::NumCapabilities() const
43 {
44 return mHardcodedCapabilities.Length();
45 }
46
47 void
GetCapability(size_t aIndex,webrtc::CaptureCapability & aOut) const48 MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
49 webrtc::CaptureCapability& aOut) const
50 {
51 MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
52 aOut = mHardcodedCapabilities.SafeElementAt(aIndex, webrtc::CaptureCapability());
53 }
54
55 uint32_t
GetFitnessDistance(const webrtc::CaptureCapability & aCandidate,const NormalizedConstraintSet & aConstraints,const nsString & aDeviceId) const56 MediaEngineCameraVideoSource::GetFitnessDistance(
57 const webrtc::CaptureCapability& aCandidate,
58 const NormalizedConstraintSet &aConstraints,
59 const nsString& aDeviceId) const
60 {
61 // Treat width|height|frameRate == 0 on capability as "can do any".
62 // This allows for orthogonal capabilities that are not in discrete steps.
63
64 uint64_t distance =
65 uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId)) +
66 uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
67 uint64_t(aCandidate.width? FitnessDistance(int32_t(aCandidate.width),
68 aConstraints.mWidth) : 0) +
69 uint64_t(aCandidate.height? FitnessDistance(int32_t(aCandidate.height),
70 aConstraints.mHeight) : 0) +
71 uint64_t(aCandidate.maxFPS? FitnessDistance(double(aCandidate.maxFPS),
72 aConstraints.mFrameRate) : 0);
73 return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
74 }
75
76 // Find best capability by removing inferiors. May leave >1 of equal distance
77
78 /* static */ void
TrimLessFitCandidates(CapabilitySet & set)79 MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) {
80 uint32_t best = UINT32_MAX;
81 for (auto& candidate : set) {
82 if (best > candidate.mDistance) {
83 best = candidate.mDistance;
84 }
85 }
86 for (size_t i = 0; i < set.Length();) {
87 if (set[i].mDistance > best) {
88 set.RemoveElementAt(i);
89 } else {
90 ++i;
91 }
92 }
93 MOZ_ASSERT(set.Length());
94 }
95
96 // GetBestFitnessDistance returns the best distance the capture device can offer
97 // as a whole, given an accumulated number of ConstraintSets.
98 // Ideal values are considered in the first ConstraintSet only.
99 // Plain values are treated as Ideal in the first ConstraintSet.
100 // Plain values are treated as Exact in subsequent ConstraintSets.
101 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
102 // A finite result may be used to calculate this device's ranking as a choice.
103
104 uint32_t
GetBestFitnessDistance(const nsTArray<const NormalizedConstraintSet * > & aConstraintSets,const nsString & aDeviceId) const105 MediaEngineCameraVideoSource::GetBestFitnessDistance(
106 const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
107 const nsString& aDeviceId) const
108 {
109 size_t num = NumCapabilities();
110
111 CapabilitySet candidateSet;
112 for (size_t i = 0; i < num; i++) {
113 candidateSet.AppendElement(i);
114 }
115
116 bool first = true;
117 for (const NormalizedConstraintSet* ns : aConstraintSets) {
118 for (size_t i = 0; i < candidateSet.Length(); ) {
119 auto& candidate = candidateSet[i];
120 webrtc::CaptureCapability cap;
121 GetCapability(candidate.mIndex, cap);
122 uint32_t distance = GetFitnessDistance(cap, *ns, aDeviceId);
123 if (distance == UINT32_MAX) {
124 candidateSet.RemoveElementAt(i);
125 } else {
126 ++i;
127 if (first) {
128 candidate.mDistance = distance;
129 }
130 }
131 }
132 first = false;
133 }
134 if (!candidateSet.Length()) {
135 return UINT32_MAX;
136 }
137 TrimLessFitCandidates(candidateSet);
138 return candidateSet[0].mDistance;
139 }
140
141 void
LogConstraints(const NormalizedConstraintSet & aConstraints)142 MediaEngineCameraVideoSource::LogConstraints(
143 const NormalizedConstraintSet& aConstraints)
144 {
145 auto& c = aConstraints;
146 LOG(((c.mWidth.mIdeal.isSome()?
147 "Constraints: width: { min: %d, max: %d, ideal: %d }" :
148 "Constraints: width: { min: %d, max: %d }"),
149 c.mWidth.mMin, c.mWidth.mMax,
150 c.mWidth.mIdeal.valueOr(0)));
151 LOG(((c.mHeight.mIdeal.isSome()?
152 " height: { min: %d, max: %d, ideal: %d }" :
153 " height: { min: %d, max: %d }"),
154 c.mHeight.mMin, c.mHeight.mMax,
155 c.mHeight.mIdeal.valueOr(0)));
156 LOG(((c.mFrameRate.mIdeal.isSome()?
157 " frameRate: { min: %f, max: %f, ideal: %f }" :
158 " frameRate: { min: %f, max: %f }"),
159 c.mFrameRate.mMin, c.mFrameRate.mMax,
160 c.mFrameRate.mIdeal.valueOr(0)));
161 }
162
163 void
LogCapability(const char * aHeader,const webrtc::CaptureCapability & aCapability,uint32_t aDistance)164 MediaEngineCameraVideoSource::LogCapability(const char* aHeader,
165 const webrtc::CaptureCapability &aCapability, uint32_t aDistance)
166 {
167 // RawVideoType and VideoCodecType media/webrtc/trunk/webrtc/common_types.h
168 static const char* const types[] = {
169 "I420",
170 "YV12",
171 "YUY2",
172 "UYVY",
173 "IYUV",
174 "ARGB",
175 "RGB24",
176 "RGB565",
177 "ARGB4444",
178 "ARGB1555",
179 "MJPEG",
180 "NV12",
181 "NV21",
182 "BGRA",
183 "Unknown type"
184 };
185
186 static const char* const codec[] = {
187 "VP8",
188 "VP9",
189 "H264",
190 "I420",
191 "RED",
192 "ULPFEC",
193 "Generic codec",
194 "Unknown codec"
195 };
196
197 LOG(("%s: %4u x %4u x %2u maxFps, %s, %s. Distance = %lu",
198 aHeader, aCapability.width, aCapability.height, aCapability.maxFPS,
199 types[std::min(std::max(uint32_t(0), uint32_t(aCapability.rawType)),
200 uint32_t(sizeof(types) / sizeof(*types) - 1))],
201 codec[std::min(std::max(uint32_t(0), uint32_t(aCapability.codecType)),
202 uint32_t(sizeof(codec) / sizeof(*codec) - 1))],
203 aDistance));
204 }
205
206 bool
ChooseCapability(const NormalizedConstraints & aConstraints,const MediaEnginePrefs & aPrefs,const nsString & aDeviceId)207 MediaEngineCameraVideoSource::ChooseCapability(
208 const NormalizedConstraints &aConstraints,
209 const MediaEnginePrefs &aPrefs,
210 const nsString& aDeviceId)
211 {
212 if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
213 LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
214 aPrefs.GetWidth(), aPrefs.GetHeight(),
215 aPrefs.mFPS, aPrefs.mMinFPS));
216 LogConstraints(aConstraints);
217 if (aConstraints.mAdvanced.size()) {
218 LOG(("Advanced array[%u]:", aConstraints.mAdvanced.size()));
219 for (auto& advanced : aConstraints.mAdvanced) {
220 LogConstraints(advanced);
221 }
222 }
223 }
224
225 size_t num = NumCapabilities();
226
227 CapabilitySet candidateSet;
228 for (size_t i = 0; i < num; i++) {
229 candidateSet.AppendElement(i);
230 }
231
232 // First, filter capabilities by required constraints (min, max, exact).
233
234 for (size_t i = 0; i < candidateSet.Length();) {
235 auto& candidate = candidateSet[i];
236 webrtc::CaptureCapability cap;
237 GetCapability(candidate.mIndex, cap);
238 candidate.mDistance = GetFitnessDistance(cap, aConstraints, aDeviceId);
239 LogCapability("Capability", cap, candidate.mDistance);
240 if (candidate.mDistance == UINT32_MAX) {
241 candidateSet.RemoveElementAt(i);
242 } else {
243 ++i;
244 }
245 }
246
247 if (!candidateSet.Length()) {
248 LOG(("failed to find capability match from %d choices",num));
249 return false;
250 }
251
252 // Filter further with all advanced constraints (that don't overconstrain).
253
254 for (const auto &cs : aConstraints.mAdvanced) {
255 CapabilitySet rejects;
256 for (size_t i = 0; i < candidateSet.Length();) {
257 auto& candidate = candidateSet[i];
258 webrtc::CaptureCapability cap;
259 GetCapability(candidate.mIndex, cap);
260 if (GetFitnessDistance(cap, cs, aDeviceId) == UINT32_MAX) {
261 rejects.AppendElement(candidate);
262 candidateSet.RemoveElementAt(i);
263 } else {
264 ++i;
265 }
266 }
267 if (!candidateSet.Length()) {
268 candidateSet.AppendElements(Move(rejects));
269 }
270 }
271 MOZ_ASSERT(candidateSet.Length(),
272 "advanced constraints filtering step can't reduce candidates to zero");
273
274 // Remaining algorithm is up to the UA.
275
276 TrimLessFitCandidates(candidateSet);
277
278 // Any remaining multiples all have the same distance. A common case of this
279 // occurs when no ideal is specified. Lean toward defaults.
280 uint32_t sameDistance = candidateSet[0].mDistance;
281 {
282 MediaTrackConstraintSet prefs;
283 prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
284 prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
285 prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
286 NormalizedConstraintSet normPrefs(prefs, false);
287
288 for (auto& candidate : candidateSet) {
289 webrtc::CaptureCapability cap;
290 GetCapability(candidate.mIndex, cap);
291 candidate.mDistance = GetFitnessDistance(cap, normPrefs, aDeviceId);
292 }
293 TrimLessFitCandidates(candidateSet);
294 }
295
296 // Any remaining multiples all have the same distance, but may vary on
297 // format. Some formats are more desirable for certain use like WebRTC.
298 // E.g. I420 over RGB24 can remove a needless format conversion.
299
300 bool found = false;
301 for (auto& candidate : candidateSet) {
302 webrtc::CaptureCapability cap;
303 GetCapability(candidate.mIndex, cap);
304 if (cap.rawType == webrtc::RawVideoType::kVideoI420 ||
305 cap.rawType == webrtc::RawVideoType::kVideoYUY2 ||
306 cap.rawType == webrtc::RawVideoType::kVideoYV12) {
307 mCapability = cap;
308 found = true;
309 break;
310 }
311 }
312 if (!found) {
313 GetCapability(candidateSet[0].mIndex, mCapability);
314 }
315
316 LogCapability("Chosen capability", mCapability, sameDistance);
317 return true;
318 }
319
320 void
SetName(nsString aName)321 MediaEngineCameraVideoSource::SetName(nsString aName)
322 {
323 mDeviceName = aName;
324 bool hasFacingMode = false;
325 VideoFacingModeEnum facingMode = VideoFacingModeEnum::User;
326
327 // Set facing mode based on device name.
328 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
329 // Names are generated. Example: "Camera 0, Facing back, Orientation 90"
330 //
331 // See media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/
332 // webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java
333
334 if (aName.Find(NS_LITERAL_STRING("Facing back")) != kNotFound) {
335 hasFacingMode = true;
336 facingMode = VideoFacingModeEnum::Environment;
337 } else if (aName.Find(NS_LITERAL_STRING("Facing front")) != kNotFound) {
338 hasFacingMode = true;
339 facingMode = VideoFacingModeEnum::User;
340 }
341 #endif // ANDROID
342 #ifdef XP_MACOSX
343 // Kludge to test user-facing cameras on OSX.
344 if (aName.Find(NS_LITERAL_STRING("Face")) != -1) {
345 hasFacingMode = true;
346 facingMode = VideoFacingModeEnum::User;
347 }
348 #endif
349 #ifdef XP_WIN
350 // The cameras' name of Surface book are "Microsoft Camera Front" and
351 // "Microsoft Camera Rear" respectively.
352
353 if (aName.Find(NS_LITERAL_STRING("Front")) != kNotFound) {
354 hasFacingMode = true;
355 facingMode = VideoFacingModeEnum::User;
356 } else if (aName.Find(NS_LITERAL_STRING("Rear")) != kNotFound) {
357 hasFacingMode = true;
358 facingMode = VideoFacingModeEnum::Environment;
359 }
360 #endif // WINDOWS
361 if (hasFacingMode) {
362 mFacingMode.Assign(NS_ConvertUTF8toUTF16(
363 VideoFacingModeEnumValues::strings[uint32_t(facingMode)].value));
364 } else {
365 mFacingMode.Truncate();
366 }
367 }
368
369 void
GetName(nsAString & aName) const370 MediaEngineCameraVideoSource::GetName(nsAString& aName) const
371 {
372 aName = mDeviceName;
373 }
374
375 void
SetUUID(const char * aUUID)376 MediaEngineCameraVideoSource::SetUUID(const char* aUUID)
377 {
378 mUniqueId.Assign(aUUID);
379 }
380
381 void
GetUUID(nsACString & aUUID) const382 MediaEngineCameraVideoSource::GetUUID(nsACString& aUUID) const
383 {
384 aUUID = mUniqueId;
385 }
386
387 const nsCString&
GetUUID() const388 MediaEngineCameraVideoSource::GetUUID() const
389 {
390 return mUniqueId;
391 }
392
393 void
SetDirectListeners(bool aHasDirectListeners)394 MediaEngineCameraVideoSource::SetDirectListeners(bool aHasDirectListeners)
395 {
396 LOG((__FUNCTION__));
397 mHasDirectListeners = aHasDirectListeners;
398 }
399
operator ==(const webrtc::CaptureCapability & a,const webrtc::CaptureCapability & b)400 bool operator == (const webrtc::CaptureCapability& a,
401 const webrtc::CaptureCapability& b)
402 {
403 return a.width == b.width &&
404 a.height == b.height &&
405 a.maxFPS == b.maxFPS &&
406 a.rawType == b.rawType &&
407 a.codecType == b.codecType &&
408 a.expectedCaptureDelay == b.expectedCaptureDelay &&
409 a.interlaced == b.interlaced;
410 };
411
operator !=(const webrtc::CaptureCapability & a,const webrtc::CaptureCapability & b)412 bool operator != (const webrtc::CaptureCapability& a,
413 const webrtc::CaptureCapability& b)
414 {
415 return !(a == b);
416 }
417
418 } // namespace mozilla
419