1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef VideoUtils_h
8 #define VideoUtils_h
9 
10 #include "MediaInfo.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/CheckedInt.h"
13 #include "mozilla/MozPromise.h"
14 #include "mozilla/ReentrantMonitor.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/UniquePtr.h"
17 
18 #include "nsAutoPtr.h"
19 #include "nsIThread.h"
20 #include "nsSize.h"
21 #include "nsRect.h"
22 
23 #include "nsThreadUtils.h"
24 #include "prtime.h"
25 #include "AudioSampleFormat.h"
26 #include "TimeUnits.h"
27 #include "nsITimer.h"
28 #include "nsCOMPtr.h"
29 #include "VideoLimits.h"
30 
31 using mozilla::CheckedInt64;
32 using mozilla::CheckedUint64;
33 using mozilla::CheckedInt32;
34 using mozilla::CheckedUint32;
35 
36 // This file contains stuff we'd rather put elsewhere, but which is
37 // dependent on other changes which we don't want to wait for. We plan to
38 // remove this file in the near future.
39 
40 
41 // This belongs in xpcom/monitor/Monitor.h, once we've made
42 // mozilla::Monitor non-reentrant.
43 namespace mozilla {
44 
45 class MediaContentType;
46 
47 // EME Key System String.
48 extern const nsLiteralCString kEMEKeySystemClearkey;
49 extern const nsLiteralCString kEMEKeySystemWidevine;
50 extern const nsLiteralCString kEMEKeySystemPrimetime;
51 
52 /**
53  * ReentrantMonitorConditionallyEnter
54  *
55  * Enters the supplied monitor only if the conditional value |aEnter| is true.
56  * E.g. Used to allow unmonitored read access on the decode thread,
57  * and monitored access on all other threads.
58  */
59 class MOZ_STACK_CLASS ReentrantMonitorConditionallyEnter
60 {
61 public:
ReentrantMonitorConditionallyEnter(bool aEnter,ReentrantMonitor & aReentrantMonitor)62   ReentrantMonitorConditionallyEnter(bool aEnter,
63                                      ReentrantMonitor &aReentrantMonitor) :
64     mReentrantMonitor(nullptr)
65   {
66     MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter);
67     if (aEnter) {
68       mReentrantMonitor = &aReentrantMonitor;
69       NS_ASSERTION(mReentrantMonitor, "null monitor");
70       mReentrantMonitor->Enter();
71     }
72   }
~ReentrantMonitorConditionallyEnter(void)73   ~ReentrantMonitorConditionallyEnter(void)
74   {
75     if (mReentrantMonitor) {
76       mReentrantMonitor->Exit();
77     }
78     MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter);
79   }
80 private:
81   // Restrict to constructor and destructor defined above.
82   ReentrantMonitorConditionallyEnter();
83   ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
84   ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
85   static void* operator new(size_t) CPP_THROW_NEW;
86   static void operator delete(void*);
87 
88   ReentrantMonitor* mReentrantMonitor;
89 };
90 
91 // Shuts down a thread asynchronously.
92 class ShutdownThreadEvent : public Runnable
93 {
94 public:
ShutdownThreadEvent(nsIThread * aThread)95   explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
~ShutdownThreadEvent()96   ~ShutdownThreadEvent() {}
Run()97   NS_IMETHOD Run() override {
98     mThread->Shutdown();
99     mThread = nullptr;
100     return NS_OK;
101   }
102 private:
103   nsCOMPtr<nsIThread> mThread;
104 };
105 
106 template<class T>
107 class DeleteObjectTask: public Runnable {
108 public:
DeleteObjectTask(nsAutoPtr<T> & aObject)109   explicit DeleteObjectTask(nsAutoPtr<T>& aObject)
110     : mObject(aObject)
111   {
112   }
Run()113   NS_IMETHOD Run() override {
114     NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
115     mObject = nullptr;
116     return NS_OK;
117   }
118 private:
119   nsAutoPtr<T> mObject;
120 };
121 
122 template<class T>
DeleteOnMainThread(nsAutoPtr<T> & aObject)123 void DeleteOnMainThread(nsAutoPtr<T>& aObject) {
124   NS_DispatchToMainThread(new DeleteObjectTask<T>(aObject));
125 }
126 
127 class MediaResource;
128 
129 // Estimates the buffered ranges of a MediaResource using a simple
130 // (byteOffset/length)*duration method. Probably inaccurate, but won't
131 // do file I/O, and can be used when we don't have detailed knowledge
132 // of the byte->time mapping of a resource. aDurationUsecs is the duration
133 // of the media in microseconds. Estimated buffered ranges are stored in
134 // aOutBuffered. Ranges are 0-normalized, i.e. in the range of (0,duration].
135 media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStream,
136                                                     int64_t aDurationUsecs);
137 
138 // Converts from number of audio frames (aFrames) to microseconds, given
139 // the specified audio rate (aRate).
140 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
141 // Converts from number of audio frames (aFrames) TimeUnit, given
142 // the specified audio rate (aRate).
143 media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate);
144 // Perform aValue * aMul / aDiv, reducing the possibility of overflow due to
145 // aValue * aMul overflowing.
146 CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv);
147 
148 // Converts from microseconds (aUsecs) to number of audio frames, given the
149 // specified audio rate (aRate). Stores the result in aOutFrames. Returns
150 // true if the operation succeeded, or false if there was an integer
151 // overflow while calulating the conversion.
152 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate);
153 
154 // Format TimeUnit as number of frames at given rate.
155 CheckedInt64 TimeUnitToFrames(const media::TimeUnit& aTime, uint32_t aRate);
156 
157 // Converts milliseconds to seconds.
158 #define MS_TO_SECONDS(ms) ((double)(ms) / (PR_MSEC_PER_SEC))
159 
160 // Converts seconds to milliseconds.
161 #define SECONDS_TO_MS(s) ((int)((s) * (PR_MSEC_PER_SEC)))
162 
163 // Converts from seconds to microseconds. Returns failure if the resulting
164 // integer is too big to fit in an int64_t.
165 nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs);
166 
167 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
168 // Note that aDisplay must be validated by IsValidVideoRegion()
169 // before being used!
170 void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
171 
172 // Downmix Stereo audio samples to Mono.
173 // Input are the buffer contains stereo data and the number of frames.
174 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
175                          uint32_t aFrames);
176 
177 bool IsVideoContentType(const nsCString& aContentType);
178 
179 // Returns true if it's safe to use aPicture as the picture to be
180 // extracted inside a frame of size aFrame, and scaled up to and displayed
181 // at a size of aDisplay. You should validate the frame, picture, and
182 // display regions before using them to display video frames.
183 bool IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
184                         const nsIntSize& aDisplay);
185 
186 // Template to automatically set a variable to a value on scope exit.
187 // Useful for unsetting flags, etc.
188 template<typename T>
189 class AutoSetOnScopeExit {
190 public:
AutoSetOnScopeExit(T & aVar,T aValue)191   AutoSetOnScopeExit(T& aVar, T aValue)
192     : mVar(aVar)
193     , mValue(aValue)
194   {}
~AutoSetOnScopeExit()195   ~AutoSetOnScopeExit() {
196     mVar = mValue;
197   }
198 private:
199   T& mVar;
200   const T mValue;
201 };
202 
203 class SharedThreadPool;
204 
205 // The MediaDataDecoder API blocks, with implementations waiting on platform
206 // decoder tasks.  These platform decoder tasks are queued on a separate
207 // thread pool to ensure they can run when the MediaDataDecoder clients'
208 // thread pool is blocked.  Tasks on the PLATFORM_DECODER thread pool must not
209 // wait on tasks in the PLAYBACK thread pool.
210 //
211 // No new dependencies on this mechanism should be added, as methods are being
212 // made async supported by MozPromise, making this unnecessary and
213 // permitting unifying the pool.
214 enum class MediaThreadType {
215   PLAYBACK, // MediaDecoderStateMachine and MediaDecoderReader
216   PLATFORM_DECODER
217 };
218 // Returns the thread pool that is shared amongst all decoder state machines
219 // for decoding streams.
220 already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType);
221 
222 enum H264_PROFILE {
223   H264_PROFILE_UNKNOWN                     = 0,
224   H264_PROFILE_BASE                        = 0x42,
225   H264_PROFILE_MAIN                        = 0x4D,
226   H264_PROFILE_EXTENDED                    = 0x58,
227   H264_PROFILE_HIGH                        = 0x64,
228 };
229 
230 enum H264_LEVEL {
231     H264_LEVEL_1         = 10,
232     H264_LEVEL_1_b       = 11,
233     H264_LEVEL_1_1       = 11,
234     H264_LEVEL_1_2       = 12,
235     H264_LEVEL_1_3       = 13,
236     H264_LEVEL_2         = 20,
237     H264_LEVEL_2_1       = 21,
238     H264_LEVEL_2_2       = 22,
239     H264_LEVEL_3         = 30,
240     H264_LEVEL_3_1       = 31,
241     H264_LEVEL_3_2       = 32,
242     H264_LEVEL_4         = 40,
243     H264_LEVEL_4_1       = 41,
244     H264_LEVEL_4_2       = 42,
245     H264_LEVEL_5         = 50,
246     H264_LEVEL_5_1       = 51,
247     H264_LEVEL_5_2       = 52
248 };
249 
250 // Extracts the H.264/AVC profile and level from an H.264 codecs string.
251 // H.264 codecs parameters have a type defined as avc1.PPCCLL, where
252 // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
253 // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
254 // for more details.
255 // Returns false on failure.
256 bool
257 ExtractH264CodecDetails(const nsAString& aCodecs,
258                         int16_t& aProfile,
259                         int16_t& aLevel);
260 
261 // Use a cryptographic quality PRNG to generate raw random bytes
262 // and convert that to a base64 string.
263 nsresult
264 GenerateRandomName(nsCString& aOutSalt, uint32_t aLength);
265 
266 // This version returns a string suitable for use as a file or URL
267 // path. This is based on code from nsExternalAppHandler::SetUpTempFile.
268 nsresult
269 GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength);
270 
271 already_AddRefed<TaskQueue>
272 CreateMediaDecodeTaskQueue();
273 
274 // Iteratively invokes aWork until aCondition returns true, or aWork returns false.
275 // Use this rather than a while loop to avoid bogarting the task queue.
276 template<class Work, class Condition>
InvokeUntil(Work aWork,Condition aCondition)277 RefPtr<GenericPromise> InvokeUntil(Work aWork, Condition aCondition) {
278   RefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__);
279 
280   if (aCondition()) {
281     p->Resolve(true, __func__);
282   }
283 
284   struct Helper {
285     static void Iteration(RefPtr<GenericPromise::Private> aPromise, Work aLocalWork, Condition aLocalCondition) {
286       if (!aLocalWork()) {
287         aPromise->Reject(NS_ERROR_FAILURE, __func__);
288       } else if (aLocalCondition()) {
289         aPromise->Resolve(true, __func__);
290       } else {
291         nsCOMPtr<nsIRunnable> r =
292           NS_NewRunnableFunction([aPromise, aLocalWork, aLocalCondition] () { Iteration(aPromise, aLocalWork, aLocalCondition); });
293         AbstractThread::GetCurrent()->Dispatch(r.forget());
294       }
295     }
296   };
297 
298   Helper::Iteration(p, aWork, aCondition);
299   return p.forget();
300 }
301 
302 // Simple timer to run a runnable after a timeout.
303 class SimpleTimer : public nsITimerCallback
304 {
305 public:
306   NS_DECL_ISUPPORTS
307 
308   // Create a new timer to run aTask after aTimeoutMs milliseconds
309   // on thread aTarget. If aTarget is null, task is run on the main thread.
310   static already_AddRefed<SimpleTimer> Create(nsIRunnable* aTask,
311                                               uint32_t aTimeoutMs,
312                                               nsIThread* aTarget = nullptr);
313   void Cancel();
314 
315   NS_IMETHOD Notify(nsITimer *timer) override;
316 
317 private:
~SimpleTimer()318   virtual ~SimpleTimer() {}
319   nsresult Init(nsIRunnable* aTask, uint32_t aTimeoutMs, nsIThread* aTarget);
320 
321   RefPtr<nsIRunnable> mTask;
322   nsCOMPtr<nsITimer> mTimer;
323 };
324 
325 void
326 LogToBrowserConsole(const nsAString& aMsg);
327 
328 bool
329 ParseMIMETypeString(const nsAString& aMIMEType,
330                     nsString& aOutContainerType,
331                     nsTArray<nsString>& aOutCodecs);
332 
333 bool
334 ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs);
335 
336 bool
337 IsH264CodecString(const nsAString& aCodec);
338 
339 bool
340 IsAACCodecString(const nsAString& aCodec);
341 
342 bool
343 IsVP8CodecString(const nsAString& aCodec);
344 
345 bool
346 IsVP9CodecString(const nsAString& aCodec);
347 
348 // Try and create a TrackInfo with a given codec MIME type.
349 UniquePtr<TrackInfo>
350 CreateTrackInfoWithMIMEType(const nsACString& aCodecMIMEType);
351 
352 // Try and create a TrackInfo with a given codec MIME type, and optional extra
353 // parameters from a content type (its MIME type and codecs are ignored).
354 UniquePtr<TrackInfo>
355 CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
356   const nsACString& aCodecMIMEType,
357   const MediaContentType& aContentType);
358 
359 template <typename String>
360 class StringListRange
361 {
362   typedef typename String::char_type CharType;
363   typedef const CharType* Pointer;
364 
365 public:
366   // Iterator into range, trims items and skips empty items.
367   class Iterator
368   {
369   public:
370     bool operator!=(const Iterator& a) const
371     {
372       return mStart != a.mStart || mEnd != a.mEnd;
373     }
374     Iterator& operator++()
375     {
376       SearchItemAt(mComma + 1);
377       return *this;
378     }
379     typedef decltype(Substring(Pointer(), Pointer())) DereferencedType;
380     DereferencedType operator*()
381     {
382       return Substring(mStart, mEnd);
383     }
384   private:
385     friend class StringListRange;
Iterator(const CharType * aRangeStart,uint32_t aLength)386     Iterator(const CharType* aRangeStart, uint32_t aLength)
387       : mRangeEnd(aRangeStart + aLength)
388     {
389       SearchItemAt(aRangeStart);
390     }
SearchItemAt(Pointer start)391     void SearchItemAt(Pointer start)
392     {
393       // First, skip leading whitespace.
394       for (Pointer p = start; ; ++p) {
395         if (p >= mRangeEnd) {
396           mStart = mEnd = mComma = mRangeEnd;
397           return;
398         }
399         auto c = *p;
400         if (c == CharType(',')) {
401           // Comma -> Empty item -> Skip.
402         } else if (c != CharType(' ')) {
403           mStart = p;
404           break;
405         }
406       }
407       // Find comma, recording start of trailing space.
408       Pointer trailingWhitespace = nullptr;
409       for (Pointer p = mStart + 1; ; ++p) {
410         if (p >= mRangeEnd) {
411           mEnd = trailingWhitespace ? trailingWhitespace : p;
412           mComma = p;
413           return;
414         }
415         auto c = *p;
416         if (c == CharType(',')) {
417           mEnd = trailingWhitespace ? trailingWhitespace : p;
418           mComma = p;
419           return;
420         }
421         if (c == CharType(' ')) {
422           // Found a whitespace -> Record as trailing if not first one.
423           if (!trailingWhitespace) {
424             trailingWhitespace = p;
425           }
426         } else {
427           // Found a non-whitespace -> Reset trailing whitespace if needed.
428           if (trailingWhitespace) {
429             trailingWhitespace = nullptr;
430           }
431         }
432       }
433     }
434     const Pointer mRangeEnd;
435     Pointer mStart;
436     Pointer mEnd;
437     Pointer mComma;
438   };
439 
StringListRange(const String & aList)440   explicit StringListRange(const String& aList) : mList(aList) {}
begin()441   Iterator begin()
442   {
443     return Iterator(mList.Data(), mList.Length());
444   }
end()445   Iterator end()
446   {
447     return Iterator(mList.Data() + mList.Length(), 0);
448   }
449 private:
450   const String& mList;
451 };
452 
453 template <typename String>
454 StringListRange<String>
MakeStringListRange(const String & aList)455 MakeStringListRange(const String& aList)
456 {
457   return StringListRange<String>(aList);
458 }
459 
460 template <typename ListString, typename ItemString>
461 static bool
StringListContains(const ListString & aList,const ItemString & aItem)462 StringListContains(const ListString& aList, const ItemString& aItem)
463 {
464   for (const auto& listItem : MakeStringListRange(aList)) {
465     if (listItem.Equals(aItem)) {
466       return true;
467     }
468   }
469   return false;
470 }
471 
472 } // end namespace mozilla
473 
474 #endif
475