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