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
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "VideoUtils.h"
9 #include "CubebUtils.h"
11 #include "ImageContainer.h"
12 #include "MediaContainerType.h"
13 #include "MediaResource.h"
14 #include "PDMFactory.h"
15 #include "TimeUnits.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/EnumeratedRange.h"
18 #include "mozilla/dom/ContentChild.h"
19 #include "mozilla/gfx/gfxVars.h"
20 #include "mozilla/SchedulerGroup.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/SharedThreadPool.h"
23 #include "mozilla/StaticPrefs_accessibility.h"
24 #include "mozilla/StaticPrefs_media.h"
25 #include "mozilla/TaskQueue.h"
26 #include "nsCharSeparatedTokenizer.h"
27 #include "nsContentTypeParser.h"
28 #include "nsIConsoleService.h"
29 #include "nsINetworkLinkService.h"
30 #include "nsIRandomGenerator.h"
31 #include "nsMathUtils.h"
33 #include "nsServiceManagerUtils.h"
34 #include "nsThreadUtils.h"
37 # include "WMFDecoderModule.h"
42 using gfx::ColorRange
;
43 using gfx::CICP::ColourPrimaries
;
44 using gfx::CICP::MatrixCoefficients
;
45 using gfx::CICP::TransferCharacteristics
;
46 using layers::PlanarYCbCrImage
;
47 using media::TimeUnit
;
49 double ToMicrosecondResolution(double aSeconds
) {
51 modf(aSeconds
* USECS_PER_S
, &integer
);
52 return integer
/ USECS_PER_S
;
55 CheckedInt64
SaferMultDiv(int64_t aValue
, uint64_t aMul
, uint64_t aDiv
) {
56 if (aMul
> INT64_MAX
|| aDiv
> INT64_MAX
) {
57 return CheckedInt64(INT64_MAX
) + 1; // Return an invalid checked int.
59 int64_t mul
= AssertedCast
<int64_t>(aMul
);
60 int64_t div
= AssertedCast
<int64_t>(aDiv
);
61 int64_t major
= aValue
/ div
;
62 int64_t remainder
= aValue
% div
;
63 return CheckedInt64(remainder
) * mul
/ div
+ CheckedInt64(major
) * mul
;
66 // Converts from number of audio frames to microseconds, given the specified
68 CheckedInt64
FramesToUsecs(int64_t aFrames
, uint32_t aRate
) {
69 return SaferMultDiv(aFrames
, USECS_PER_S
, aRate
);
72 // Converts from microseconds to number of audio frames, given the specified
74 CheckedInt64
UsecsToFrames(int64_t aUsecs
, uint32_t aRate
) {
75 return SaferMultDiv(aUsecs
, aRate
, USECS_PER_S
);
78 // Format TimeUnit as number of frames at given rate.
79 CheckedInt64
TimeUnitToFrames(const TimeUnit
& aTime
, uint32_t aRate
) {
80 return aTime
.IsValid() ? UsecsToFrames(aTime
.ToMicroseconds(), aRate
)
81 : CheckedInt64(INT64_MAX
) + 1;
84 nsresult
SecondsToUsecs(double aSeconds
, int64_t& aOutUsecs
) {
85 if (aSeconds
* double(USECS_PER_S
) > double(INT64_MAX
)) {
86 return NS_ERROR_FAILURE
;
88 aOutUsecs
= int64_t(aSeconds
* double(USECS_PER_S
));
92 static int32_t ConditionDimension(float aValue
) {
93 // This will exclude NaNs and too-big values.
94 if (aValue
> 1.0 && aValue
<= float(INT32_MAX
)) {
95 return int32_t(NS_round(aValue
));
100 void ScaleDisplayByAspectRatio(gfx::IntSize
& aDisplay
, float aAspectRatio
) {
101 if (aAspectRatio
> 1.0) {
102 // Increase the intrinsic width
104 ConditionDimension(aAspectRatio
* AssertedCast
<float>(aDisplay
.width
));
106 // Increase the intrinsic height
108 ConditionDimension(AssertedCast
<float>(aDisplay
.height
) / aAspectRatio
);
112 static int64_t BytesToTime(int64_t offset
, int64_t length
, int64_t durationUs
) {
113 NS_ASSERTION(length
> 0, "Must have positive length");
114 double r
= double(offset
) / double(length
);
118 return int64_t(double(durationUs
) * r
);
121 media::TimeIntervals
GetEstimatedBufferedTimeRanges(
122 mozilla::MediaResource
* aStream
, int64_t aDurationUsecs
) {
123 media::TimeIntervals buffered
;
124 // Nothing to cache if the media takes 0us to play.
125 if (aDurationUsecs
<= 0 || !aStream
) {
129 // Special case completely cached files. This also handles local files.
130 if (aStream
->IsDataCachedToEndOfResource(0)) {
131 buffered
+= media::TimeInterval(TimeUnit::Zero(),
132 TimeUnit::FromMicroseconds(aDurationUsecs
));
136 int64_t totalBytes
= aStream
->GetLength();
138 // If we can't determine the total size, pretend that we have nothing
139 // buffered. This will put us in a state of eternally-low-on-undecoded-data
140 // which is not great, but about the best we can do.
141 if (totalBytes
<= 0) {
145 int64_t startOffset
= aStream
->GetNextCachedData(0);
146 while (startOffset
>= 0) {
147 int64_t endOffset
= aStream
->GetCachedDataEnd(startOffset
);
148 // Bytes [startOffset..endOffset] are cached.
149 NS_ASSERTION(startOffset
>= 0, "Integer underflow in GetBuffered");
150 NS_ASSERTION(endOffset
>= 0, "Integer underflow in GetBuffered");
152 int64_t startUs
= BytesToTime(startOffset
, totalBytes
, aDurationUsecs
);
153 int64_t endUs
= BytesToTime(endOffset
, totalBytes
, aDurationUsecs
);
154 if (startUs
!= endUs
) {
155 buffered
+= media::TimeInterval(TimeUnit::FromMicroseconds(startUs
),
156 TimeUnit::FromMicroseconds(endUs
));
158 startOffset
= aStream
->GetNextCachedData(endOffset
);
163 void DownmixStereoToMono(mozilla::AudioDataValue
* aBuffer
, uint32_t aFrames
) {
165 const int channels
= 2;
166 for (uint32_t fIdx
= 0; fIdx
< aFrames
; ++fIdx
) {
167 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
172 // The sample of the buffer would be interleaved.
173 sample
= (aBuffer
[fIdx
* channels
] + aBuffer
[fIdx
* channels
+ 1]) * 0.5f
;
174 aBuffer
[fIdx
* channels
] = aBuffer
[fIdx
* channels
+ 1] = sample
;
178 uint32_t DecideAudioPlaybackChannels(const AudioInfo
& info
) {
179 if (StaticPrefs::accessibility_monoaudio_enable()) {
183 if (StaticPrefs::media_forcestereo_enabled()) {
187 return info
.mChannels
;
190 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo
& aInfo
,
191 bool aShouldResistFingerprinting
) {
192 bool resampling
= StaticPrefs::media_resampling_enabled();
198 } else if (aInfo
.mRate
>= 44100) {
199 // The original rate is of good quality and we want to minimize unecessary
200 // resampling, so we let cubeb decide how to resample (if needed). Cap to
201 // 384kHz for good measure.
202 rate
= std::min
<unsigned>(aInfo
.mRate
, 384000u);
204 // We will resample all data to match cubeb's preferred sampling rate.
205 rate
= CubebUtils::PreferredSampleRate(aShouldResistFingerprinting
);
207 // bogus rate, fall back to something else;
211 MOZ_DIAGNOSTIC_ASSERT(rate
, "output rate can't be 0.");
216 bool IsDefaultPlaybackDeviceMono() {
217 return CubebUtils::MaxNumberOfChannels() == 1;
220 bool IsVideoContentType(const nsCString
& aContentType
) {
221 constexpr auto video
= "video"_ns
;
222 return FindInReadable(video
, aContentType
);
225 bool IsValidVideoRegion(const gfx::IntSize
& aFrame
,
226 const gfx::IntRect
& aPicture
,
227 const gfx::IntSize
& aDisplay
) {
228 return aFrame
.width
> 0 && aFrame
.width
<= PlanarYCbCrImage::MAX_DIMENSION
&&
230 aFrame
.height
<= PlanarYCbCrImage::MAX_DIMENSION
&&
231 aFrame
.width
* aFrame
.height
<= MAX_VIDEO_WIDTH
* MAX_VIDEO_HEIGHT
&&
232 aPicture
.width
> 0 &&
233 aPicture
.width
<= PlanarYCbCrImage::MAX_DIMENSION
&&
234 aPicture
.x
< PlanarYCbCrImage::MAX_DIMENSION
&&
235 aPicture
.x
+ aPicture
.width
< PlanarYCbCrImage::MAX_DIMENSION
&&
236 aPicture
.height
> 0 &&
237 aPicture
.height
<= PlanarYCbCrImage::MAX_DIMENSION
&&
238 aPicture
.y
< PlanarYCbCrImage::MAX_DIMENSION
&&
239 aPicture
.y
+ aPicture
.height
< PlanarYCbCrImage::MAX_DIMENSION
&&
240 aPicture
.width
* aPicture
.height
<=
241 MAX_VIDEO_WIDTH
* MAX_VIDEO_HEIGHT
&&
242 aDisplay
.width
> 0 &&
243 aDisplay
.width
<= PlanarYCbCrImage::MAX_DIMENSION
&&
244 aDisplay
.height
> 0 &&
245 aDisplay
.height
<= PlanarYCbCrImage::MAX_DIMENSION
&&
246 aDisplay
.width
* aDisplay
.height
<= MAX_VIDEO_WIDTH
* MAX_VIDEO_HEIGHT
;
249 already_AddRefed
<SharedThreadPool
> GetMediaThreadPool(MediaThreadType aType
) {
251 uint32_t threads
= 4;
253 case MediaThreadType::PLATFORM_DECODER
:
254 name
= "MediaPDecoder";
256 case MediaThreadType::WEBRTC_CALL_THREAD
:
257 name
= "WebrtcCallThread";
260 case MediaThreadType::WEBRTC_WORKER
:
261 name
= "WebrtcWorker";
263 case MediaThreadType::MDSM
:
264 name
= "MediaDecoderStateMachine";
267 case MediaThreadType::PLATFORM_ENCODER
:
268 name
= "MediaPEncoder";
271 MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
272 case MediaThreadType::SUPERVISOR
:
273 name
= "MediaSupervisor";
277 RefPtr
<SharedThreadPool
> pool
=
278 SharedThreadPool::Get(nsDependentCString(name
), threads
);
280 // Ensure a larger stack for platform decoder threads
281 if (aType
== MediaThreadType::PLATFORM_DECODER
) {
282 const uint32_t minStackSize
= 512 * 1024;
284 MOZ_ALWAYS_SUCCEEDS(pool
->GetThreadStackSize(&stackSize
));
285 if (stackSize
< minStackSize
) {
286 MOZ_ALWAYS_SUCCEEDS(pool
->SetThreadStackSize(minStackSize
));
290 return pool
.forget();
293 bool ExtractVPXCodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
294 uint8_t& aLevel
, uint8_t& aBitDepth
) {
295 uint8_t dummyChromaSubsampling
= 1;
296 VideoColorSpace dummyColorspace
;
297 return ExtractVPXCodecDetails(aCodec
, aProfile
, aLevel
, aBitDepth
,
298 dummyChromaSubsampling
, dummyColorspace
);
301 bool ExtractVPXCodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
302 uint8_t& aLevel
, uint8_t& aBitDepth
,
303 uint8_t& aChromaSubsampling
,
304 VideoColorSpace
& aColorSpace
) {
305 // Assign default value.
306 aChromaSubsampling
= 1;
307 auto splitter
= aCodec
.Split(u
'.');
308 auto fieldsItr
= splitter
.begin();
309 auto fourCC
= *fieldsItr
;
311 if (!fourCC
.EqualsLiteral("vp09") && !fourCC
.EqualsLiteral("vp08")) {
316 uint8_t primary
, transfer
, matrix
, range
;
317 uint8_t* fields
[] = {&aProfile
, &aLevel
, &aBitDepth
, &aChromaSubsampling
,
318 &primary
, &transfer
, &matrix
, &range
};
321 for (; fieldsItr
!= splitter
.end(); ++fieldsItr
, ++fieldsCount
) {
322 if (fieldsCount
> 7) {
323 // No more than 8 fields are expected.
326 *(fields
[fieldsCount
]) =
327 static_cast<uint8_t>((*fieldsItr
).ToInteger(&rv
, 10));
328 // We got invalid field value, parsing error.
329 NS_ENSURE_SUCCESS(rv
, false);
332 // <sample entry 4CC>.<profile>.<level>.<bitDepth>.
334 // <chromaSubsampling>.<colourPrimaries>.<transferCharacteristics>.
335 // <matrixCoefficients>.<videoFullRangeFlag>
336 // First three fields are mandatory(we have parsed 4CC).
337 if (fieldsCount
< 3) {
338 // Invalid number of fields.
341 // Start to validate the parsing value.
343 // profile should be 0,1,2 or 3.
344 // See https://www.webmproject.org/vp9/profiles/
350 // level, See https://www.webmproject.org/vp9/mp4/#semantics_1
372 if (aBitDepth
!= 8 && aBitDepth
!= 10 && aBitDepth
!= 12) {
377 if (fieldsCount
== 3) {
382 // chromaSubsampling should be 0,1,2,3...4~7 are reserved.
383 if (aChromaSubsampling
> 3) {
387 if (fieldsCount
== 4) {
392 // It is an integer that is defined by the "Colour primaries"
393 // section of ISO/IEC 23001-8:2016 Table 2.
394 // We treat reserved value as false case.
395 if (primary
== 0 || primary
== 3 || primary
> 22) {
399 if (primary
> 12 && primary
< 22) {
400 // 13~21 are reserved values.
403 aColorSpace
.mPrimaries
= static_cast<ColourPrimaries
>(primary
);
405 if (fieldsCount
== 5) {
410 // It is an integer that is defined by the
411 // "Transfer characteristics" section of ISO/IEC 23001-8:2016 Table 3.
412 // We treat reserved value as false case.
413 if (transfer
== 0 || transfer
== 3 || transfer
> 18) {
417 aColorSpace
.mTransfer
= static_cast<TransferCharacteristics
>(transfer
);
419 if (fieldsCount
== 6) {
424 // It is an integer that is defined by the
425 // "Matrix coefficients" section of ISO/IEC 23001-8:2016 Table 4.
426 // We treat reserved value as false case.
427 if (matrix
== 3 || matrix
> 11) {
430 aColorSpace
.mMatrix
= static_cast<MatrixCoefficients
>(matrix
);
432 // If matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3
434 if (aColorSpace
.mMatrix
== MatrixCoefficients::MC_IDENTITY
&&
435 aChromaSubsampling
!= 3) {
439 if (fieldsCount
== 7) {
444 // videoFullRangeFlag indicates the black level and range of the luma and
445 // chroma signals. 0 = legal range (e.g. 16-235 for 8 bit sample depth);
446 // 1 = full range (e.g. 0-255 for 8-bit sample depth).
447 aColorSpace
.mRange
= static_cast<ColorRange
>(range
);
451 bool ExtractH264CodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
452 uint8_t& aConstraint
, H264_LEVEL
& aLevel
,
453 H264CodecStringStrictness aStrictness
) {
454 // H.264 codecs parameters have a type defined as avcN.PPCCLL, where
455 // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
456 // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
457 // We ignore the constraint_set flags, as it's not clear from any
458 // documentation what constraints the platform decoders support.
460 // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
462 if (aCodec
.Length() != strlen("avc1.PPCCLL")) {
466 // Verify the codec starts with "avc1." or "avc3.".
467 const nsAString
& sample
= Substring(aCodec
, 0, 5);
468 if (!sample
.EqualsASCII("avc1.") && !sample
.EqualsASCII("avc3.")) {
472 // Extract the profile_idc, constraint_flags and level_idc.
474 aProfile
= Substring(aCodec
, 5, 2).ToInteger(&rv
, 16);
475 NS_ENSURE_SUCCESS(rv
, false);
477 // Constraint flags are stored on the 6 most significant bits, first two bits
478 // are reserved_zero_2bits.
479 aConstraint
= Substring(aCodec
, 7, 2).ToInteger(&rv
, 16);
480 NS_ENSURE_SUCCESS(rv
, false);
482 uint8_t level
= Substring(aCodec
, 9, 2).ToInteger(&rv
, 16);
483 NS_ENSURE_SUCCESS(rv
, false);
486 level
= static_cast<uint8_t>(H264_LEVEL::H264_LEVEL_1_b
);
487 } else if (level
<= 5) {
491 if (aStrictness
== H264CodecStringStrictness::Lenient
) {
492 aLevel
= static_cast<H264_LEVEL
>(level
);
496 // Check if valid level value
497 aLevel
= static_cast<H264_LEVEL
>(level
);
498 if (aLevel
< H264_LEVEL::H264_LEVEL_1
||
499 aLevel
> H264_LEVEL::H264_LEVEL_6_2
) {
502 if ((level
% 10) > 2) {
511 bool IsH265ProfileRecognizable(uint8_t aProfile
,
512 int32_t aProfileCompabilityFlags
) {
515 eHighThroughputScreenExtended
,
516 eScalableRangeExtension
,
527 Profile p
= eUnknown
;
530 if (aProfile
== 11 || (aProfileCompabilityFlags
& 0x800)) {
531 p
= eHighThroughputScreenExtended
;
534 if (aProfile
== 10 || (aProfileCompabilityFlags
& 0x400)) {
535 p
= eScalableRangeExtension
;
538 if (aProfile
== 9 || (aProfileCompabilityFlags
& 0x200)) {
542 if (aProfile
== 8 || (aProfileCompabilityFlags
& 0x100)) {
546 if (aProfile
== 7 || (aProfileCompabilityFlags
& 0x80)) {
550 if (aProfile
== 6 || (aProfileCompabilityFlags
& 0x40)) {
554 if (aProfile
== 5 || (aProfileCompabilityFlags
& 0x20)) {
558 if (aProfile
== 4 || (aProfileCompabilityFlags
& 0x10)) {
562 // NOTICE: Do not change the order of below sections
563 if (aProfile
== 2 || (aProfileCompabilityFlags
& 0x4)) {
567 // When aProfileCompabilityFlags[1] is equal to 1,
568 // aProfileCompabilityFlags[2] should be equal to 1 as well.
569 if (aProfile
== 1 || (aProfileCompabilityFlags
& 0x2)) {
573 // When aProfileCompabilityFlags[3] is equal to 1,
574 // aProfileCompabilityFlags[1] and
575 // aProfileCompabilityFlags[2] should be equal to 1 as well.
576 if (aProfile
== 3 || (aProfileCompabilityFlags
& 0x8)) {
577 p
= eMainStillPicture
;
580 return p
!= eUnknown
;
583 bool ExtractH265CodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
584 uint8_t& aLevel
, nsTArray
<uint8_t>& aConstraints
) {
585 // HEVC codec id consists of:
586 const size_t maxHevcCodecIdLength
=
587 5 + // 'hev1.' or 'hvc1.' prefix (5 chars)
588 4 + // profile, e.g. '.A12' (max 4 chars)
589 9 + // profile_compatibility, dot + 32-bit hex number (max 9 chars)
590 5 + // tier and level, e.g. '.H120' (max 5 chars)
591 18; // up to 6 constraint bytes, bytes are dot-separated and hex-encoded.
593 if (aCodec
.Length() > maxHevcCodecIdLength
) {
597 // Verify the codec starts with "hev1." or "hvc1.".
598 const nsAString
& sample
= Substring(aCodec
, 0, 5);
599 if (!sample
.EqualsASCII("hev1.") && !sample
.EqualsASCII("hvc1.")) {
604 CheckedUint8 profile
;
605 int32_t compabilityFlags
= 0;
606 CheckedUint8 level
= 0;
607 nsTArray
<uint8_t> constraints
;
609 auto splitter
= aCodec
.Split(u
'.');
611 for (auto iter
= splitter
.begin(); iter
!= splitter
.end(); ++iter
, ++count
) {
612 const auto& fieldStr
= *iter
;
613 if (fieldStr
.IsEmpty()) {
618 MOZ_RELEASE_ASSERT(fieldStr
.EqualsASCII("hev1") ||
619 fieldStr
.EqualsASCII("hvc1"));
623 if (count
== 1) { // profile
624 Maybe
<uint8_t> validProfileSpace
;
625 if (fieldStr
.First() == u
'A' || fieldStr
.First() == u
'B' ||
626 fieldStr
.First() == u
'C') {
627 validProfileSpace
.emplace(1 + (fieldStr
.First() - 'A'));
629 // If fieldStr.First() is not A, B, C or a digit, ToInteger() should fail.
630 profile
= validProfileSpace
? Substring(fieldStr
, 1).ToInteger(&rv
)
631 : fieldStr
.ToInteger(&rv
);
632 if (NS_FAILED(rv
) || !profile
.isValid() || profile
.value() > 0x1F) {
638 if (count
== 2) { // profile compatibility flags
639 compabilityFlags
= fieldStr
.ToInteger(&rv
, 16);
640 NS_ENSURE_SUCCESS(rv
, false);
644 if (count
== 3) { // tier and level
645 Maybe
<uint8_t> validProfileTier
;
646 if (fieldStr
.First() == u
'L' || fieldStr
.First() == u
'H') {
647 validProfileTier
.emplace(fieldStr
.First() == u
'L' ? 0 : 1);
649 // If fieldStr.First() is not L, H, or a digit, ToInteger() should fail.
650 level
= validProfileTier
? Substring(fieldStr
, 1).ToInteger(&rv
)
651 : fieldStr
.ToInteger(&rv
);
652 if (NS_FAILED(rv
) || !level
.isValid()) {
658 // The rest is constraint bytes.
663 CheckedUint8
byte(fieldStr
.ToInteger(&rv
, 16));
664 if (NS_FAILED(rv
) || !byte
.isValid()) {
667 constraints
.AppendElement(byte
.value());
670 if (count
< 4 /* Parse til level at least */ || constraints
.Length() > 6 ||
671 !IsH265ProfileRecognizable(profile
.value(), compabilityFlags
)) {
675 aProfile
= profile
.value();
676 aLevel
= level
.value();
677 aConstraints
= std::move(constraints
);
681 bool ExtractAV1CodecDetails(const nsAString
& aCodec
, uint8_t& aProfile
,
682 uint8_t& aLevel
, uint8_t& aTier
, uint8_t& aBitDepth
,
683 bool& aMonochrome
, bool& aSubsamplingX
,
684 bool& aSubsamplingY
, uint8_t& aChromaSamplePosition
,
685 VideoColorSpace
& aColorSpace
) {
686 auto fourCC
= Substring(aCodec
, 0, 4);
688 if (!fourCC
.EqualsLiteral("av01")) {
694 // av01.N.NN[MH].NN.B.BBN.NN.NN.NN.B
697 // [] = single character
700 // <sample entry 4CC>.<profile>.<level><tier>.<bitDepth>
701 // [.<monochrome>.<chromaSubsampling>
702 // .<colorPrimaries>.<transferCharacteristics>.<matrixCoefficients>
703 // .<videoFullRangeFlag>]
705 // If any optional field is found, all the rest must be included.
707 // Parsing stops but does not fail upon encountering unexpected characters
708 // at the end of an otherwise well-formed string.
710 // See https://aomediacodec.github.io/av1-isobmff/#codecsparam
722 AV1Field fields
[] = {{&aProfile
, 1},
724 // parsing loop skips tier
733 auto splitter
= aCodec
.Split(u
'.');
734 auto iter
= splitter
.begin();
736 size_t fieldCount
= 0;
737 while (iter
!= splitter
.end()) {
738 // Exit if there are too many fields.
739 if (fieldCount
>= 9) {
743 AV1Field
& field
= fields
[fieldCount
];
744 auto fieldStr
= *iter
;
746 if (field
.field
== &aLevel
) {
747 // Parse tier and remove it from the level field.
748 if (fieldStr
.Length() < 3) {
751 auto tier
= fieldStr
[2];
762 fieldStr
.SetLength(2);
765 if (fieldStr
.Length() < field
.length
) {
769 // Manually parse values since nsString.ToInteger silently stops parsing
770 // upon encountering unknown characters.
772 for (size_t i
= 0; i
< field
.length
; i
++) {
773 uint8_t oldValue
= value
;
774 char16_t character
= fieldStr
[i
];
775 if ('0' <= character
&& character
<= '9') {
776 value
= (value
* 10) + (character
- '0');
780 if (value
< oldValue
) {
781 // Overflow is possible on the 3-digit subsampling field.
786 *field
.field
= value
;
791 // Field had extra characters, exit early.
792 if (fieldStr
.Length() > field
.length
) {
793 // Disallow numbers as unexpected characters.
794 char16_t character
= fieldStr
[field
.length
];
795 if ('0' <= character
&& character
<= '9') {
802 // Spec requires profile, level/tier, bitdepth, or for all possible fields to
804 if (fieldCount
!= 3 && fieldCount
!= 9) {
808 // Valid profiles are: Main (0), High (1), Professional (2).
809 // Levels range from 0 to 23, or 31 to remove level restrictions.
810 if (aProfile
> 2 || (aLevel
> 23 && aLevel
!= 31)) {
814 if (fieldCount
== 3) {
815 // If only required fields are included, set to the spec defaults for the
816 // rest and continue validating.
818 aSubsamplingX
= true;
819 aSubsamplingY
= true;
820 aChromaSamplePosition
= 0;
821 aColorSpace
.mPrimaries
= ColourPrimaries::CP_BT709
;
822 aColorSpace
.mTransfer
= TransferCharacteristics::TC_BT709
;
823 aColorSpace
.mMatrix
= MatrixCoefficients::MC_BT709
;
824 aColorSpace
.mRange
= ColorRange::LIMITED
;
826 // Extract the individual values for the remaining fields, and check for
827 // valid values for each.
829 // Monochrome is a boolean.
830 if (monochrome
> 1) {
833 aMonochrome
= !!monochrome
;
835 // Extract individual digits of the subsampling field.
836 // Subsampling is two binary digits for x and y
837 // and one enumerated sample position field of
838 // Unknown (0), Vertical (1), Colocated (2).
839 uint8_t subsamplingX
= (subsampling
/ 100) % 10;
840 uint8_t subsamplingY
= (subsampling
/ 10) % 10;
841 if (subsamplingX
> 1 || subsamplingY
> 1) {
844 aSubsamplingX
= !!subsamplingX
;
845 aSubsamplingY
= !!subsamplingY
;
846 aChromaSamplePosition
= subsampling
% 10;
847 if (aChromaSamplePosition
> 2) {
851 // We can validate the color space values using CICP enums, as the values
852 // are standardized in Rec. ITU-T H.273.
853 aColorSpace
.mPrimaries
= static_cast<ColourPrimaries
>(primary
);
854 aColorSpace
.mTransfer
= static_cast<TransferCharacteristics
>(transfer
);
855 aColorSpace
.mMatrix
= static_cast<MatrixCoefficients
>(matrix
);
856 if (gfx::CICP::IsReserved(aColorSpace
.mPrimaries
) ||
857 gfx::CICP::IsReserved(aColorSpace
.mTransfer
) ||
858 gfx::CICP::IsReserved(aColorSpace
.mMatrix
)) {
861 // Range is a boolean, true meaning full and false meaning limited range.
865 aColorSpace
.mRange
= static_cast<ColorRange
>(range
);
868 // Begin validating all parameter values:
870 // Only Levels 8 and above (4.0 and greater) can specify Tier.
871 // See: 5.5.1. General sequence header OBU syntax,
872 // if ( seq_level_idx[ i ] > 7 ) seq_tier[ i ] = f(1)
873 // https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=42
874 // Also: Annex A, A.3. Levels, columns MainMbps and HighMbps
875 // at https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=652
876 if (aLevel
< 8 && aTier
> 0) {
880 // Supported bit depths are 8, 10 and 12.
881 if (aBitDepth
!= 8 && aBitDepth
!= 10 && aBitDepth
!= 12) {
884 // Profiles 0 and 1 only support 8-bit and 10-bit.
885 if (aProfile
< 2 && aBitDepth
== 12) {
889 // x && y subsampling is used to specify monochrome 4:0:0 as well
890 bool is420or400
= aSubsamplingX
&& aSubsamplingY
;
891 bool is422
= aSubsamplingX
&& !aSubsamplingY
;
892 bool is444
= !aSubsamplingX
&& !aSubsamplingY
;
894 // Profile 0 only supports 4:2:0.
895 if (aProfile
== 0 && !is420or400
) {
898 // Profile 1 only supports 4:4:4.
899 if (aProfile
== 1 && !is444
) {
902 // Profile 2 only allows 4:2:2 at 10 bits and below.
903 if (aProfile
== 2 && aBitDepth
< 12 && !is422
) {
906 // Chroma sample position can only be specified with 4:2:0.
907 if (aChromaSamplePosition
!= 0 && !is420or400
) {
911 // When video is monochrome, subsampling must be 4:0:0.
912 if (aMonochrome
&& (aChromaSamplePosition
!= 0 || !is420or400
)) {
915 // Monochrome can only be signaled when profile is 0 or 2.
916 // Note: This check is redundant with the above subsampling check,
917 // as profile 1 only supports 4:4:4.
918 if (aMonochrome
&& aProfile
!= 0 && aProfile
!= 2) {
922 // Identity matrix requires 4:4:4 subsampling.
923 if (aColorSpace
.mMatrix
== MatrixCoefficients::MC_IDENTITY
&&
924 (aSubsamplingX
|| aSubsamplingY
||
925 aColorSpace
.mRange
!= gfx::ColorRange::FULL
)) {
932 nsresult
GenerateRandomName(nsCString
& aOutSalt
, uint32_t aLength
) {
934 nsCOMPtr
<nsIRandomGenerator
> rg
=
935 do_GetService("@mozilla.org/security/random-generator;1", &rv
);
940 // For each three bytes of random data we will get four bytes of ASCII.
941 const uint32_t requiredBytesLength
=
942 static_cast<uint32_t>((aLength
+ 3) / 4 * 3);
945 rv
= rg
->GenerateRandomBytes(requiredBytesLength
, &buffer
);
951 nsDependentCSubstring
randomData(reinterpret_cast<const char*>(buffer
),
952 requiredBytesLength
);
953 rv
= Base64Encode(randomData
, temp
);
960 aOutSalt
= std::move(temp
);
964 nsresult
GenerateRandomPathName(nsCString
& aOutSalt
, uint32_t aLength
) {
965 nsresult rv
= GenerateRandomName(aOutSalt
, aLength
);
970 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
971 // to replace illegal characters -- notably '/'
972 aOutSalt
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
976 already_AddRefed
<TaskQueue
> CreateMediaDecodeTaskQueue(const char* aName
) {
977 RefPtr
<TaskQueue
> queue
= TaskQueue::Create(
978 GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER
), aName
);
979 return queue
.forget();
982 void SimpleTimer::Cancel() {
985 nsCOMPtr
<nsIEventTarget
> target
;
986 mTimer
->GetTarget(getter_AddRefs(target
));
988 nsresult rv
= target
->IsOnCurrentThread(&onCurrent
);
989 MOZ_ASSERT(NS_SUCCEEDED(rv
) && onCurrent
);
998 SimpleTimer::Notify(nsITimer
* timer
) {
999 RefPtr
<SimpleTimer
> deathGrip(this);
1008 SimpleTimer::GetName(nsACString
& aName
) {
1009 aName
.AssignLiteral("SimpleTimer");
1013 nsresult
SimpleTimer::Init(nsIRunnable
* aTask
, uint32_t aTimeoutMs
,
1014 nsIEventTarget
* aTarget
) {
1017 // Get target thread first, so we don't have to cancel the timer if it fails.
1018 nsCOMPtr
<nsIEventTarget
> target
;
1022 target
= GetMainThreadSerialEventTarget();
1024 return NS_ERROR_NOT_AVAILABLE
;
1028 rv
= NS_NewTimerWithCallback(getter_AddRefs(mTimer
), this, aTimeoutMs
,
1029 nsITimer::TYPE_ONE_SHOT
, target
);
1030 if (NS_FAILED(rv
)) {
1038 NS_IMPL_ISUPPORTS(SimpleTimer
, nsITimerCallback
, nsINamed
)
1040 already_AddRefed
<SimpleTimer
> SimpleTimer::Create(nsIRunnable
* aTask
,
1041 uint32_t aTimeoutMs
,
1042 nsIEventTarget
* aTarget
) {
1043 RefPtr
<SimpleTimer
> t(new SimpleTimer());
1044 if (NS_FAILED(t
->Init(aTask
, aTimeoutMs
, aTarget
))) {
1050 void LogToBrowserConsole(const nsAString
& aMsg
) {
1051 if (!NS_IsMainThread()) {
1053 nsCOMPtr
<nsIRunnable
> task
= NS_NewRunnableFunction(
1054 "LogToBrowserConsole", [msg
]() { LogToBrowserConsole(msg
); });
1055 SchedulerGroup::Dispatch(task
.forget());
1058 nsCOMPtr
<nsIConsoleService
> console(
1059 do_GetService("@mozilla.org/consoleservice;1"));
1061 NS_WARNING("Failed to log message to console.");
1064 nsAutoString
msg(aMsg
);
1065 console
->LogStringMessage(msg
.get());
1068 bool ParseCodecsString(const nsAString
& aCodecs
,
1069 nsTArray
<nsString
>& aOutCodecs
) {
1071 bool expectMoreTokens
= false;
1072 nsCharSeparatedTokenizer
tokenizer(aCodecs
, ',');
1073 while (tokenizer
.hasMoreTokens()) {
1074 const nsAString
& token
= tokenizer
.nextToken();
1075 expectMoreTokens
= tokenizer
.separatorAfterCurrentToken();
1076 aOutCodecs
.AppendElement(token
);
1078 return !expectMoreTokens
;
1081 bool ParseMIMETypeString(const nsAString
& aMIMEType
,
1082 nsString
& aOutContainerType
,
1083 nsTArray
<nsString
>& aOutCodecs
) {
1084 nsContentTypeParser
parser(aMIMEType
);
1085 nsresult rv
= parser
.GetType(aOutContainerType
);
1086 if (NS_FAILED(rv
)) {
1091 parser
.GetParameter("codecs", codecsStr
);
1092 return ParseCodecsString(codecsStr
, aOutCodecs
);
1096 static bool StartsWith(const nsACString
& string
, const char (&prefix
)[N
]) {
1097 if (N
- 1 > string
.Length()) {
1100 return memcmp(string
.Data(), prefix
, N
- 1) == 0;
1103 bool IsH264CodecString(const nsAString
& aCodec
) {
1104 uint8_t profile
= 0;
1105 uint8_t constraint
= 0;
1107 return ExtractH264CodecDetails(aCodec
, profile
, constraint
, level
,
1108 H264CodecStringStrictness::Lenient
);
1111 bool IsH265CodecString(const nsAString
& aCodec
) {
1112 uint8_t profile
= 0;
1114 nsTArray
<uint8_t> constraints
;
1115 return ExtractH265CodecDetails(aCodec
, profile
, level
, constraints
);
1118 bool IsAACCodecString(const nsAString
& aCodec
) {
1119 return aCodec
.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
1120 aCodec
.EqualsLiteral(
1121 "mp4a.40.02") || // MPEG4 AAC-LC(for compatibility)
1122 aCodec
.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
1123 aCodec
.EqualsLiteral(
1124 "mp4a.40.05") || // MPEG4 HE-AAC(for compatibility)
1125 aCodec
.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
1126 aCodec
.EqualsLiteral("mp4a.40.29"); // MPEG4 HE-AACv2
1129 bool IsVP8CodecString(const nsAString
& aCodec
) {
1130 uint8_t profile
= 0;
1132 uint8_t bitDepth
= 0;
1133 return aCodec
.EqualsLiteral("vp8") || aCodec
.EqualsLiteral("vp8.0") ||
1134 (StartsWith(NS_ConvertUTF16toUTF8(aCodec
), "vp08") &&
1135 ExtractVPXCodecDetails(aCodec
, profile
, level
, bitDepth
));
1138 bool IsVP9CodecString(const nsAString
& aCodec
) {
1139 uint8_t profile
= 0;
1141 uint8_t bitDepth
= 0;
1142 return aCodec
.EqualsLiteral("vp9") || aCodec
.EqualsLiteral("vp9.0") ||
1143 (StartsWith(NS_ConvertUTF16toUTF8(aCodec
), "vp09") &&
1144 ExtractVPXCodecDetails(aCodec
, profile
, level
, bitDepth
));
1147 bool IsAV1CodecString(const nsAString
& aCodec
) {
1148 uint8_t profile
, level
, tier
, bitDepth
, chromaPosition
;
1149 bool monochrome
, subsamplingX
, subsamplingY
;
1150 VideoColorSpace colorSpace
;
1151 return aCodec
.EqualsLiteral("av1") ||
1152 (StartsWith(NS_ConvertUTF16toUTF8(aCodec
), "av01") &&
1153 ExtractAV1CodecDetails(aCodec
, profile
, level
, tier
, bitDepth
,
1154 monochrome
, subsamplingX
, subsamplingY
,
1155 chromaPosition
, colorSpace
));
1158 UniquePtr
<TrackInfo
> CreateTrackInfoWithMIMEType(
1159 const nsACString
& aCodecMIMEType
) {
1160 UniquePtr
<TrackInfo
> trackInfo
;
1161 if (StartsWith(aCodecMIMEType
, "audio/")) {
1162 trackInfo
.reset(new AudioInfo());
1163 trackInfo
->mMimeType
= aCodecMIMEType
;
1164 } else if (StartsWith(aCodecMIMEType
, "video/")) {
1165 trackInfo
.reset(new VideoInfo());
1166 trackInfo
->mMimeType
= aCodecMIMEType
;
1171 UniquePtr
<TrackInfo
> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
1172 const nsACString
& aCodecMIMEType
,
1173 const MediaContainerType
& aContainerType
) {
1174 UniquePtr
<TrackInfo
> trackInfo
= CreateTrackInfoWithMIMEType(aCodecMIMEType
);
1176 VideoInfo
* videoInfo
= trackInfo
->GetAsVideoInfo();
1178 Maybe
<int32_t> maybeWidth
= aContainerType
.ExtendedType().GetWidth();
1179 if (maybeWidth
&& *maybeWidth
> 0) {
1180 videoInfo
->mImage
.width
= *maybeWidth
;
1181 videoInfo
->mDisplay
.width
= *maybeWidth
;
1183 Maybe
<int32_t> maybeHeight
= aContainerType
.ExtendedType().GetHeight();
1184 if (maybeHeight
&& *maybeHeight
> 0) {
1185 videoInfo
->mImage
.height
= *maybeHeight
;
1186 videoInfo
->mDisplay
.height
= *maybeHeight
;
1188 } else if (trackInfo
->GetAsAudioInfo()) {
1189 AudioInfo
* audioInfo
= trackInfo
->GetAsAudioInfo();
1190 Maybe
<int32_t> maybeChannels
=
1191 aContainerType
.ExtendedType().GetChannels();
1192 if (maybeChannels
&& *maybeChannels
> 0) {
1193 audioInfo
->mChannels
= *maybeChannels
;
1195 Maybe
<int32_t> maybeSamplerate
=
1196 aContainerType
.ExtendedType().GetSamplerate();
1197 if (maybeSamplerate
&& *maybeSamplerate
> 0) {
1198 audioInfo
->mRate
= *maybeSamplerate
;
1205 bool OnCellularConnection() {
1206 uint32_t linkType
= nsINetworkLinkService::LINK_TYPE_UNKNOWN
;
1207 if (XRE_IsContentProcess()) {
1208 mozilla::dom::ContentChild
* cpc
=
1209 mozilla::dom::ContentChild::GetSingleton();
1211 NS_WARNING("Can't get ContentChild singleton in content process!");
1214 linkType
= cpc
->NetworkLinkType();
1217 nsCOMPtr
<nsINetworkLinkService
> nls
=
1218 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID
, &rv
);
1219 if (NS_FAILED(rv
)) {
1220 NS_WARNING("Can't get nsINetworkLinkService.");
1224 rv
= nls
->GetLinkType(&linkType
);
1225 if (NS_FAILED(rv
)) {
1226 NS_WARNING("Can't get network link type.");
1232 case nsINetworkLinkService::LINK_TYPE_UNKNOWN
:
1233 case nsINetworkLinkService::LINK_TYPE_ETHERNET
:
1234 case nsINetworkLinkService::LINK_TYPE_USB
:
1235 case nsINetworkLinkService::LINK_TYPE_WIFI
:
1237 case nsINetworkLinkService::LINK_TYPE_WIMAX
:
1238 case nsINetworkLinkService::LINK_TYPE_MOBILE
:
1245 bool IsWaveMimetype(const nsACString
& aMimeType
) {
1246 return aMimeType
.EqualsLiteral("audio/x-wav") ||
1247 aMimeType
.EqualsLiteral("audio/wave; codecs=1") ||
1248 aMimeType
.EqualsLiteral("audio/wave; codecs=3") ||
1249 aMimeType
.EqualsLiteral("audio/wave; codecs=6") ||
1250 aMimeType
.EqualsLiteral("audio/wave; codecs=7") ||
1251 aMimeType
.EqualsLiteral("audio/wave; codecs=65534");
1254 void DetermineResolutionForTelemetry(const MediaInfo
& aInfo
,
1255 nsCString
& aResolutionOut
) {
1256 if (aInfo
.HasAudio()) {
1257 aResolutionOut
.AppendASCII("AV,");
1259 aResolutionOut
.AppendASCII("V,");
1261 static const struct {
1264 } sResolutions
[] = {{240, "0<h<=240"}, {480, "240<h<=480"},
1265 {576, "480<h<=576"}, {720, "576<h<=720"},
1266 {1080, "720<h<=1080"}, {2160, "1080<h<=2160"}};
1267 const char* resolution
= "h>2160";
1268 int32_t height
= aInfo
.mVideo
.mDisplay
.height
;
1269 for (const auto& res
: sResolutions
) {
1270 if (height
<= res
.mH
) {
1271 resolution
= res
.mRes
;
1275 aResolutionOut
.AppendASCII(resolution
);
1278 bool ContainHardwareCodecsSupported(
1279 const media::MediaCodecsSupported
& aSupport
) {
1280 return aSupport
.contains(
1281 mozilla::media::MediaCodecsSupport::H264HardwareDecode
) ||
1283 mozilla::media::MediaCodecsSupport::VP8HardwareDecode
) ||
1285 mozilla::media::MediaCodecsSupport::VP9HardwareDecode
) ||
1287 mozilla::media::MediaCodecsSupport::AV1HardwareDecode
) ||
1289 mozilla::media::MediaCodecsSupport::HEVCHardwareDecode
);
1292 } // end namespace mozilla