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/. */
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/SharedThreadPool.h"
21 #include "mozilla/TaskQueue.h"
22 #include "mozilla/UniquePtr.h"
23 #include "mozilla/gfx/Point.h" // for gfx::IntSize
24 #include "mozilla/gfx/Types.h"
27 #include "nsIThread.h"
29 #include "nsThreadUtils.h"
32 using mozilla::CheckedInt32
;
33 using mozilla::CheckedInt64
;
34 using mozilla::CheckedUint32
;
35 using mozilla::CheckedUint64
;
37 // This file contains stuff we'd rather put elsewhere, but which is
38 // dependent on other changes which we don't want to wait for. We plan to
39 // remove this file in the near future.
41 // This belongs in xpcom/monitor/Monitor.h, once we've made
42 // mozilla::Monitor non-reentrant.
45 class MediaContainerType
;
48 * ReentrantMonitorConditionallyEnter
50 * Enters the supplied monitor only if the conditional value |aEnter| is true.
51 * E.g. Used to allow unmonitored read access on the decode thread,
52 * and monitored access on all other threads.
54 class MOZ_STACK_CLASS ReentrantMonitorConditionallyEnter
{
56 ReentrantMonitorConditionallyEnter(bool aEnter
,
57 ReentrantMonitor
& aReentrantMonitor
)
58 : mReentrantMonitor(nullptr) {
59 MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter
);
61 mReentrantMonitor
= &aReentrantMonitor
;
62 NS_ASSERTION(mReentrantMonitor
, "null monitor");
63 mReentrantMonitor
->Enter();
66 ~ReentrantMonitorConditionallyEnter(void) {
67 if (mReentrantMonitor
) {
68 mReentrantMonitor
->Exit();
70 MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter
);
74 // Restrict to constructor and destructor defined above.
75 ReentrantMonitorConditionallyEnter();
76 ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter
&);
77 ReentrantMonitorConditionallyEnter
& operator=(
78 const ReentrantMonitorConditionallyEnter
&);
79 static void* operator new(size_t) noexcept(true);
80 static void operator delete(void*);
82 ReentrantMonitor
* mReentrantMonitor
;
85 // Shuts down a thread asynchronously.
86 class ShutdownThreadEvent
: public Runnable
{
88 explicit ShutdownThreadEvent(nsIThread
* aThread
)
89 : Runnable("ShutdownThreadEvent"), mThread(aThread
) {}
90 ~ShutdownThreadEvent() = default;
91 NS_IMETHOD
Run() override
{
98 nsCOMPtr
<nsIThread
> mThread
;
103 // Estimates the buffered ranges of a MediaResource using a simple
104 // (byteOffset/length)*duration method. Probably inaccurate, but won't
105 // do file I/O, and can be used when we don't have detailed knowledge
106 // of the byte->time mapping of a resource. aDurationUsecs is the duration
107 // of the media in microseconds. Estimated buffered ranges are stored in
108 // aOutBuffered. Ranges are 0-normalized, i.e. in the range of (0,duration].
109 media::TimeIntervals
GetEstimatedBufferedTimeRanges(
110 mozilla::MediaResource
* aStream
, int64_t aDurationUsecs
);
112 // Converts from number of audio frames (aFrames) to microseconds, given
113 // the specified audio rate (aRate).
114 CheckedInt64
FramesToUsecs(int64_t aFrames
, uint32_t aRate
);
115 // Converts from number of audio frames (aFrames) TimeUnit, given
116 // the specified audio rate (aRate).
117 media::TimeUnit
FramesToTimeUnit(int64_t aFrames
, uint32_t aRate
);
118 // Perform aValue * aMul / aDiv, reducing the possibility of overflow due to
119 // aValue * aMul overflowing.
120 CheckedInt64
SaferMultDiv(int64_t aValue
, uint64_t aMul
, uint64_t aDiv
);
122 // Converts from microseconds (aUsecs) to number of audio frames, given the
123 // specified audio rate (aRate). Stores the result in aOutFrames. Returns
124 // true if the operation succeeded, or false if there was an integer
125 // overflow while calulating the conversion.
126 CheckedInt64
UsecsToFrames(int64_t aUsecs
, uint32_t aRate
);
128 // Format TimeUnit as number of frames at given rate.
129 CheckedInt64
TimeUnitToFrames(const media::TimeUnit
& aTime
, uint32_t aRate
);
131 // Converts milliseconds to seconds.
132 #define MS_TO_SECONDS(ms) ((double)(ms) / (PR_MSEC_PER_SEC))
134 // Converts seconds to milliseconds.
135 #define SECONDS_TO_MS(s) ((int)((s) * (PR_MSEC_PER_SEC)))
137 // Converts from seconds to microseconds. Returns failure if the resulting
138 // integer is too big to fit in an int64_t.
139 nsresult
SecondsToUsecs(double aSeconds
, int64_t& aOutUsecs
);
141 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
142 // Note that aDisplay must be validated by IsValidVideoRegion()
143 // before being used!
144 void ScaleDisplayByAspectRatio(gfx::IntSize
& aDisplay
, float aAspectRatio
);
146 // Downmix Stereo audio samples to Mono.
147 // Input are the buffer contains stereo data and the number of frames.
148 void DownmixStereoToMono(mozilla::AudioDataValue
* aBuffer
, uint32_t aFrames
);
150 // Decide the number of playback channels according to the
151 // given AudioInfo and the prefs that are being set.
152 uint32_t DecideAudioPlaybackChannels(const AudioInfo
& info
);
154 // Decide the sample-rate to use for audio output according to the
155 // given AudioInfo and the prefs that are being set.
156 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo
& info
);
158 bool IsDefaultPlaybackDeviceMono();
160 bool IsVideoContentType(const nsCString
& aContentType
);
162 // Returns true if it's safe to use aPicture as the picture to be
163 // extracted inside a frame of size aFrame, and scaled up to and displayed
164 // at a size of aDisplay. You should validate the frame, picture, and
165 // display regions before using them to display video frames.
166 bool IsValidVideoRegion(const gfx::IntSize
& aFrame
,
167 const gfx::IntRect
& aPicture
,
168 const gfx::IntSize
& aDisplay
);
170 // Template to automatically set a variable to a value on scope exit.
171 // Useful for unsetting flags, etc.
172 template <typename T
>
173 class AutoSetOnScopeExit
{
175 AutoSetOnScopeExit(T
& aVar
, T aValue
) : mVar(aVar
), mValue(aValue
) {}
176 ~AutoSetOnScopeExit() { mVar
= mValue
; }
183 enum class MediaThreadType
{
184 SUPERVISOR
, // MediaFormatReader, RemoteDecoderManager, MediaDecodeTask and
186 PLATFORM_DECODER
, // MediaDataDecoder
187 PLATFORM_ENCODER
, // MediaDataEncoder
190 MDSM
, // MediaDecoderStateMachine
192 // Returns the thread pool that is shared amongst all decoder state machines
193 // for decoding streams.
194 already_AddRefed
<SharedThreadPool
> GetMediaThreadPool(MediaThreadType aType
);
197 H264_PROFILE_UNKNOWN
= 0,
198 H264_PROFILE_BASE
= 0x42,
199 H264_PROFILE_MAIN
= 0x4D,
200 H264_PROFILE_EXTENDED
= 0x58,
201 H264_PROFILE_HIGH
= 0x64,
224 // Extracts the H.264/AVC profile and level from an H.264 codecs string.
225 // H.264 codecs parameters have a type defined as avc1.PPCCLL, where
226 // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
228 // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
230 // Returns false on failure.
231 bool ExtractH264CodecDetails(const nsAString
& aCodecs
, uint8_t& aProfile
,
232 uint8_t& aConstraint
, uint8_t& aLevel
);
234 struct VideoColorSpace
{
235 // Default values are set according to
236 // https://www.webmproject.org/vp9/mp4/#optional-fields
237 // and https://aomediacodec.github.io/av1-isobmff/#codecsparam
238 gfx::CICP::ColourPrimaries mPrimaries
= gfx::CICP::CP_BT709
;
239 gfx::CICP::TransferCharacteristics mTransfer
= gfx::CICP::TC_BT709
;
240 gfx::CICP::MatrixCoefficients mMatrix
= gfx::CICP::MC_BT709
;
241 gfx::ColorRange mRange
= gfx::ColorRange::LIMITED
;
243 bool operator==(const VideoColorSpace
& aOther
) const {
244 return mPrimaries
== aOther
.mPrimaries
&& mTransfer
== aOther
.mTransfer
&&
245 mMatrix
== aOther
.mMatrix
&& mRange
== aOther
.mRange
;
247 bool operator!=(const VideoColorSpace
& aOther
) const {
248 return !(*this == aOther
);
252 // Extracts the VPX codecs parameter string.
253 // See https://www.webmproject.org/vp9/mp4/#codecs-parameter-string
255 // Returns false on failure.
256 bool ExtractVPXCodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
257 uint8_t& aLevel
, uint8_t& aBitDepth
);
258 bool ExtractVPXCodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
259 uint8_t& aLevel
, uint8_t& aBitDepth
,
260 uint8_t& aChromaSubsampling
,
261 VideoColorSpace
& aColorSpace
);
263 // Extracts AV1 codecs parameter string.
264 // See https://aomediacodec.github.io/av1-isobmff/#codecsparam
265 // Returns false if the codec is invalid.
266 bool ExtractAV1CodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
267 uint8_t& aLevel
, uint8_t& aTier
, uint8_t& aBitDepth
,
268 bool& aMonochrome
, bool& aSubsamplingX
,
269 bool& aSubsamplingY
, uint8_t& aChromaSamplePosition
,
270 VideoColorSpace
& aColorSpace
);
272 // Use a cryptographic quality PRNG to generate raw random bytes
273 // and convert that to a base64 string.
274 nsresult
GenerateRandomName(nsCString
& aOutSalt
, uint32_t aLength
);
276 // This version returns a string suitable for use as a file or URL
277 // path. This is based on code from nsExternalAppHandler::SetUpTempFile.
278 nsresult
GenerateRandomPathName(nsCString
& aOutSalt
, uint32_t aLength
);
280 already_AddRefed
<TaskQueue
> CreateMediaDecodeTaskQueue(const char* aName
);
282 // Iteratively invokes aWork until aCondition returns true, or aWork returns
283 // false. Use this rather than a while loop to avoid bogarting the task queue.
284 template <class Work
, class Condition
>
285 RefPtr
<GenericPromise
> InvokeUntil(Work aWork
, Condition aCondition
) {
286 RefPtr
<GenericPromise::Private
> p
= new GenericPromise::Private(__func__
);
289 p
->Resolve(true, __func__
);
293 static void Iteration(const RefPtr
<GenericPromise::Private
>& aPromise
,
294 Work aLocalWork
, Condition aLocalCondition
) {
296 aPromise
->Reject(NS_ERROR_FAILURE
, __func__
);
297 } else if (aLocalCondition()) {
298 aPromise
->Resolve(true, __func__
);
300 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
301 "InvokeUntil::Helper::Iteration",
302 [aPromise
, aLocalWork
, aLocalCondition
]() {
303 Iteration(aPromise
, aLocalWork
, aLocalCondition
);
305 AbstractThread::GetCurrent()->Dispatch(r
.forget());
310 Helper::Iteration(p
, aWork
, aCondition
);
314 // Simple timer to run a runnable after a timeout.
315 class SimpleTimer
: public nsITimerCallback
, public nsINamed
{
320 // Create a new timer to run aTask after aTimeoutMs milliseconds
321 // on thread aTarget. If aTarget is null, task is run on the main thread.
322 static already_AddRefed
<SimpleTimer
> Create(
323 nsIRunnable
* aTask
, uint32_t aTimeoutMs
,
324 nsIEventTarget
* aTarget
= nullptr);
327 NS_IMETHOD
Notify(nsITimer
* timer
) override
;
330 virtual ~SimpleTimer() = default;
331 nsresult
Init(nsIRunnable
* aTask
, uint32_t aTimeoutMs
,
332 nsIEventTarget
* aTarget
);
334 RefPtr
<nsIRunnable
> mTask
;
335 nsCOMPtr
<nsITimer
> mTimer
;
338 void LogToBrowserConsole(const nsAString
& aMsg
);
340 bool ParseMIMETypeString(const nsAString
& aMIMEType
,
341 nsString
& aOutContainerType
,
342 nsTArray
<nsString
>& aOutCodecs
);
344 bool ParseCodecsString(const nsAString
& aCodecs
,
345 nsTArray
<nsString
>& aOutCodecs
);
347 bool IsH264CodecString(const nsAString
& aCodec
);
349 bool IsAACCodecString(const nsAString
& aCodec
);
351 bool IsVP8CodecString(const nsAString
& aCodec
);
353 bool IsVP9CodecString(const nsAString
& aCodec
);
355 bool IsAV1CodecString(const nsAString
& aCodec
);
357 // Try and create a TrackInfo with a given codec MIME type.
358 UniquePtr
<TrackInfo
> CreateTrackInfoWithMIMEType(
359 const nsACString
& aCodecMIMEType
);
361 // Try and create a TrackInfo with a given codec MIME type, and optional extra
362 // parameters from a container type (its MIME type and codecs are ignored).
363 UniquePtr
<TrackInfo
> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
364 const nsACString
& aCodecMIMEType
, const MediaContainerType
& aContainerType
);
368 // aString should start with aMajor + '/'.
369 constexpr bool StartsWithMIMETypeMajor(const char* aString
, const char* aMajor
,
370 size_t aMajorRemaining
) {
371 return (aMajorRemaining
== 0 && *aString
== '/') ||
372 (*aString
== *aMajor
&&
373 StartsWithMIMETypeMajor(aString
+ 1, aMajor
+ 1,
374 aMajorRemaining
- 1));
377 // aString should only contain [a-z0-9\-\.] and a final '\0'.
378 constexpr bool EndsWithMIMESubtype(const char* aString
, size_t aRemaining
) {
379 return aRemaining
== 0 || (((*aString
>= 'a' && *aString
<= 'z') ||
380 (*aString
>= '0' && *aString
<= '9') ||
381 *aString
== '-' || *aString
== '.') &&
382 EndsWithMIMESubtype(aString
+ 1, aRemaining
- 1));
385 // Simple MIME-type literal string checker with a given (major) type.
386 // Only accepts "{aMajor}/[a-z0-9\-\.]+".
387 template <size_t MajorLengthPlus1
>
388 constexpr bool IsMIMETypeWithMajor(const char* aString
, size_t aLength
,
389 const char (&aMajor
)[MajorLengthPlus1
]) {
390 return aLength
> MajorLengthPlus1
&& // Major + '/' + at least 1 char
391 StartsWithMIMETypeMajor(aString
, aMajor
, MajorLengthPlus1
- 1) &&
392 EndsWithMIMESubtype(aString
+ MajorLengthPlus1
,
393 aLength
- MajorLengthPlus1
);
396 } // namespace detail
398 // Simple MIME-type string checker.
399 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
400 // Add more if necessary.
401 constexpr bool IsMediaMIMEType(const char* aString
, size_t aLength
) {
402 return detail::IsMIMETypeWithMajor(aString
, aLength
, "application") ||
403 detail::IsMIMETypeWithMajor(aString
, aLength
, "audio") ||
404 detail::IsMIMETypeWithMajor(aString
, aLength
, "video");
407 // Simple MIME-type string literal checker.
408 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
409 // Add more if necessary.
410 template <size_t LengthPlus1
>
411 constexpr bool IsMediaMIMEType(const char (&aString
)[LengthPlus1
]) {
412 return IsMediaMIMEType(aString
, LengthPlus1
- 1);
415 // Simple MIME-type string checker.
416 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
417 // Add more if necessary.
418 inline bool IsMediaMIMEType(const nsACString
& aString
) {
419 return IsMediaMIMEType(aString
.Data(), aString
.Length());
422 enum class StringListRangeEmptyItems
{
423 // Skip all empty items (empty string will process nothing)
424 // E.g.: "a,,b" -> ["a", "b"], "" -> nothing
426 // Process all, except if string is empty
427 // E.g.: "a,,b" -> ["a", "", "b"], "" -> nothing
429 // Process all, including 1 empty item in an empty string
430 // E.g.: "a,,b" -> ["a", "", "b"], "" -> [""]
434 template <typename String
,
435 StringListRangeEmptyItems empties
= StringListRangeEmptyItems::Skip
>
436 class StringListRange
{
437 typedef typename
String::char_type CharType
;
438 typedef const CharType
* Pointer
;
441 // Iterator into range, trims items and optionally skips empty items.
444 bool operator!=(const Iterator
& a
) const {
445 return mStart
!= a
.mStart
|| mEnd
!= a
.mEnd
;
447 Iterator
& operator++() {
448 SearchItemAt(mComma
+ 1);
451 // DereferencedType should be 'const nsDependent[C]String' pointing into
452 // mList (which is 'const ns[C]String&').
453 typedef decltype(Substring(Pointer(), Pointer())) DereferencedType
;
454 DereferencedType
operator*() { return Substring(mStart
, mEnd
); }
457 friend class StringListRange
;
458 Iterator(const CharType
* aRangeStart
, uint32_t aLength
)
459 : mRangeEnd(aRangeStart
+ aLength
),
463 SearchItemAt(aRangeStart
);
465 void SearchItemAt(Pointer start
) {
466 // First, skip leading whitespace.
467 for (Pointer p
= start
;; ++p
) {
468 if (p
>= mRangeEnd
) {
470 (empties
!= StringListRangeEmptyItems::Skip
? 1 : 0)) {
472 (empties
!= StringListRangeEmptyItems::Skip
? 1 : 0);
474 mStart
= mEnd
= mComma
= p
;
478 if (c
== CharType(',')) {
479 // Comma -> Empty item -> Skip or process?
480 if (empties
!= StringListRangeEmptyItems::Skip
) {
481 mStart
= mEnd
= mComma
= p
;
484 } else if (c
!= CharType(' ')) {
489 // Find comma, recording start of trailing space.
490 Pointer trailingWhitespace
= nullptr;
491 for (Pointer p
= mStart
+ 1;; ++p
) {
492 if (p
>= mRangeEnd
) {
493 mEnd
= trailingWhitespace
? trailingWhitespace
: p
;
498 if (c
== CharType(',')) {
499 mEnd
= trailingWhitespace
? trailingWhitespace
: p
;
503 if (c
== CharType(' ')) {
504 // Found a whitespace -> Record as trailing if not first one.
505 if (!trailingWhitespace
) {
506 trailingWhitespace
= p
;
509 // Found a non-whitespace -> Reset trailing whitespace if needed.
510 if (trailingWhitespace
) {
511 trailingWhitespace
= nullptr;
516 const Pointer mRangeEnd
;
522 explicit StringListRange(const String
& aList
) : mList(aList
) {}
523 Iterator
begin() const {
526 ((empties
== StringListRangeEmptyItems::ProcessEmptyItems
&&
532 Iterator
end() const {
533 return Iterator(mList
.Data() + mList
.Length() +
534 (empties
!= StringListRangeEmptyItems::Skip
? 1 : 0),
542 template <StringListRangeEmptyItems empties
= StringListRangeEmptyItems::Skip
,
544 StringListRange
<String
, empties
> MakeStringListRange(const String
& aList
) {
545 return StringListRange
<String
, empties
>(aList
);
548 template <StringListRangeEmptyItems empties
= StringListRangeEmptyItems::Skip
,
549 typename ListString
, typename ItemString
>
550 static bool StringListContains(const ListString
& aList
,
551 const ItemString
& aItem
) {
552 for (const auto& listItem
: MakeStringListRange
<empties
>(aList
)) {
553 if (listItem
.Equals(aItem
)) {
560 inline void AppendStringIfNotEmpty(nsACString
& aDest
, nsACString
&& aSrc
) {
561 if (!aSrc
.IsEmpty()) {
562 aDest
.Append("\n"_ns
);
567 // Returns true if we're running on a cellular connection; 2G, 3G, etc.
569 bool OnCellularConnection();
571 inline gfx::YUVColorSpace
DefaultColorSpace(const gfx::IntSize
& aSize
) {
572 return aSize
.height
< 720 ? gfx::YUVColorSpace::BT601
573 : gfx::YUVColorSpace::BT709
;
576 } // end namespace mozilla