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