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