Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / dom / media / VideoUtils.cpp
blob7e03989ece95305b009c06e09d575dc2367fdecc
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"
7 #include <stdint.h>
9 #include "CubebUtils.h"
10 #include "H264.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"
32 #include "nsNetCID.h"
33 #include "nsServiceManagerUtils.h"
34 #include "nsThreadUtils.h"
36 #ifdef XP_WIN
37 # include "WMFDecoderModule.h"
38 #endif
40 namespace mozilla {
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) {
50 double integer;
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
67 // audio rate.
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
73 // audio rate.
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));
89 return NS_OK;
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));
97 return 0;
100 void ScaleDisplayByAspectRatio(gfx::IntSize& aDisplay, float aAspectRatio) {
101 if (aAspectRatio > 1.0) {
102 // Increase the intrinsic width
103 aDisplay.width =
104 ConditionDimension(aAspectRatio * AssertedCast<float>(aDisplay.width));
105 } else {
106 // Increase the intrinsic height
107 aDisplay.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);
115 if (r > 1.0) {
116 r = 1.0;
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) {
126 return buffered;
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));
133 return buffered;
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) {
142 return buffered;
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);
160 return buffered;
163 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer, uint32_t aFrames) {
164 MOZ_ASSERT(aBuffer);
165 const int channels = 2;
166 for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
167 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
168 float sample = 0.0;
169 #else
170 int sample = 0;
171 #endif
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()) {
180 return 1;
183 if (StaticPrefs::media_forcestereo_enabled()) {
184 return 2;
187 return info.mChannels;
190 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo& aInfo,
191 bool aShouldResistFingerprinting) {
192 bool resampling = StaticPrefs::media_resampling_enabled();
194 uint32_t rate = 0;
196 if (resampling) {
197 rate = 48000;
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);
203 } else {
204 // We will resample all data to match cubeb's preferred sampling rate.
205 rate = CubebUtils::PreferredSampleRate(aShouldResistFingerprinting);
206 if (rate > 768000) {
207 // bogus rate, fall back to something else;
208 rate = 48000;
211 MOZ_DIAGNOSTIC_ASSERT(rate, "output rate can't be 0.");
213 return rate;
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 &&
229 aFrame.height > 0 &&
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) {
250 const char* name;
251 uint32_t threads = 4;
252 switch (aType) {
253 case MediaThreadType::PLATFORM_DECODER:
254 name = "MediaPDecoder";
255 break;
256 case MediaThreadType::WEBRTC_CALL_THREAD:
257 name = "WebrtcCallThread";
258 threads = 1;
259 break;
260 case MediaThreadType::WEBRTC_WORKER:
261 name = "WebrtcWorker";
262 break;
263 case MediaThreadType::MDSM:
264 name = "MediaDecoderStateMachine";
265 threads = 1;
266 break;
267 case MediaThreadType::PLATFORM_ENCODER:
268 name = "MediaPEncoder";
269 break;
270 default:
271 MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
272 case MediaThreadType::SUPERVISOR:
273 name = "MediaSupervisor";
274 break;
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;
283 uint32_t stackSize;
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")) {
312 // Invalid 4CC
313 return false;
315 ++fieldsItr;
316 uint8_t primary, transfer, matrix, range;
317 uint8_t* fields[] = {&aProfile, &aLevel, &aBitDepth, &aChromaSubsampling,
318 &primary, &transfer, &matrix, &range};
319 int fieldsCount = 0;
320 nsresult rv;
321 for (; fieldsItr != splitter.end(); ++fieldsItr, ++fieldsCount) {
322 if (fieldsCount > 7) {
323 // No more than 8 fields are expected.
324 return false;
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);
331 // Mandatory Fields
332 // <sample entry 4CC>.<profile>.<level>.<bitDepth>.
333 // Optional Fields
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.
339 return false;
341 // Start to validate the parsing value.
343 // profile should be 0,1,2 or 3.
344 // See https://www.webmproject.org/vp9/profiles/
345 if (aProfile > 3) {
346 // Invalid profile.
347 return false;
350 // level, See https://www.webmproject.org/vp9/mp4/#semantics_1
351 switch (aLevel) {
352 case 10:
353 case 11:
354 case 20:
355 case 21:
356 case 30:
357 case 31:
358 case 40:
359 case 41:
360 case 50:
361 case 51:
362 case 52:
363 case 60:
364 case 61:
365 case 62:
366 break;
367 default:
368 // Invalid level.
369 return false;
372 if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
373 // Invalid bitDepth:
374 return false;
377 if (fieldsCount == 3) {
378 // No more options.
379 return true;
382 // chromaSubsampling should be 0,1,2,3...4~7 are reserved.
383 if (aChromaSubsampling > 3) {
384 return false;
387 if (fieldsCount == 4) {
388 // No more options.
389 return true;
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) {
396 // reserved value.
397 return false;
399 if (primary > 12 && primary < 22) {
400 // 13~21 are reserved values.
401 return false;
403 aColorSpace.mPrimaries = static_cast<ColourPrimaries>(primary);
405 if (fieldsCount == 5) {
406 // No more options.
407 return true;
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) {
414 // reserved value.
415 return false;
417 aColorSpace.mTransfer = static_cast<TransferCharacteristics>(transfer);
419 if (fieldsCount == 6) {
420 // No more options.
421 return true;
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) {
428 return false;
430 aColorSpace.mMatrix = static_cast<MatrixCoefficients>(matrix);
432 // If matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3
433 // (4:4:4).
434 if (aColorSpace.mMatrix == MatrixCoefficients::MC_IDENTITY &&
435 aChromaSubsampling != 3) {
436 return false;
439 if (fieldsCount == 7) {
440 // No more options.
441 return true;
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);
448 return range <= 1;
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.
459 // See
460 // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
461 // for more details.
462 if (aCodec.Length() != strlen("avc1.PPCCLL")) {
463 return false;
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.")) {
469 return false;
472 // Extract the profile_idc, constraint_flags and level_idc.
473 nsresult rv = NS_OK;
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);
485 if (level == 9) {
486 level = static_cast<uint8_t>(H264_LEVEL::H264_LEVEL_1_b);
487 } else if (level <= 5) {
488 level *= 10;
491 if (aStrictness == H264CodecStringStrictness::Lenient) {
492 aLevel = static_cast<H264_LEVEL>(level);
493 return true;
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) {
500 return false;
502 if ((level % 10) > 2) {
503 if (level != 13) {
504 return false;
508 return true;
511 bool IsH265ProfileRecognizable(uint8_t aProfile,
512 int32_t aProfileCompabilityFlags) {
513 enum Profile {
514 eUnknown,
515 eHighThroughputScreenExtended,
516 eScalableRangeExtension,
517 eScreenExtended,
518 e3DMain,
519 eScalableMain,
520 eMultiviewMain,
521 eHighThroughput,
522 eRangeExtension,
523 eMain10,
524 eMain,
525 eMainStillPicture
527 Profile p = eUnknown;
529 // Spec A.3.8
530 if (aProfile == 11 || (aProfileCompabilityFlags & 0x800)) {
531 p = eHighThroughputScreenExtended;
533 // Spec H.11.1.2
534 if (aProfile == 10 || (aProfileCompabilityFlags & 0x400)) {
535 p = eScalableRangeExtension;
537 // Spec A.3.7
538 if (aProfile == 9 || (aProfileCompabilityFlags & 0x200)) {
539 p = eScreenExtended;
541 // Spec I.11.1.1
542 if (aProfile == 8 || (aProfileCompabilityFlags & 0x100)) {
543 p = e3DMain;
545 // Spec H.11.1.1
546 if (aProfile == 7 || (aProfileCompabilityFlags & 0x80)) {
547 p = eScalableMain;
549 // Spec G.11.1.1
550 if (aProfile == 6 || (aProfileCompabilityFlags & 0x40)) {
551 p = eMultiviewMain;
553 // Spec A.3.6
554 if (aProfile == 5 || (aProfileCompabilityFlags & 0x20)) {
555 p = eHighThroughput;
557 // Spec A.3.5
558 if (aProfile == 4 || (aProfileCompabilityFlags & 0x10)) {
559 p = eRangeExtension;
561 // Spec A.3.3
562 // NOTICE: Do not change the order of below sections
563 if (aProfile == 2 || (aProfileCompabilityFlags & 0x4)) {
564 p = eMain10;
566 // Spec A.3.2
567 // When aProfileCompabilityFlags[1] is equal to 1,
568 // aProfileCompabilityFlags[2] should be equal to 1 as well.
569 if (aProfile == 1 || (aProfileCompabilityFlags & 0x2)) {
570 p = eMain;
572 // Spec A.3.4
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) {
594 return false;
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.")) {
600 return false;
603 nsresult rv;
604 CheckedUint8 profile;
605 int32_t compabilityFlags = 0;
606 CheckedUint8 level = 0;
607 nsTArray<uint8_t> constraints;
609 auto splitter = aCodec.Split(u'.');
610 size_t count = 0;
611 for (auto iter = splitter.begin(); iter != splitter.end(); ++iter, ++count) {
612 const auto& fieldStr = *iter;
613 if (fieldStr.IsEmpty()) {
614 return false;
617 if (count == 0) {
618 MOZ_RELEASE_ASSERT(fieldStr.EqualsASCII("hev1") ||
619 fieldStr.EqualsASCII("hvc1"));
620 continue;
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) {
633 return false;
635 continue;
638 if (count == 2) { // profile compatibility flags
639 compabilityFlags = fieldStr.ToInteger(&rv, 16);
640 NS_ENSURE_SUCCESS(rv, false);
641 continue;
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()) {
653 return false;
655 continue;
658 // The rest is constraint bytes.
659 if (count > 10) {
660 return false;
663 CheckedUint8 byte(fieldStr.ToInteger(&rv, 16));
664 if (NS_FAILED(rv) || !byte.isValid()) {
665 return false;
667 constraints.AppendElement(byte.value());
670 if (count < 4 /* Parse til level at least */ || constraints.Length() > 6 ||
671 !IsH265ProfileRecognizable(profile.value(), compabilityFlags)) {
672 return false;
675 aProfile = profile.value();
676 aLevel = level.value();
677 aConstraints = std::move(constraints);
678 return true;
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")) {
689 // Invalid 4CC
690 return false;
693 // Format is:
694 // av01.N.NN[MH].NN.B.BBN.NN.NN.NN.B
695 // where
696 // N = decimal digit
697 // [] = single character
698 // B = binary digit
699 // Field order:
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
712 struct AV1Field {
713 uint8_t* field;
714 size_t length;
716 uint8_t monochrome;
717 uint8_t subsampling;
718 uint8_t primary;
719 uint8_t transfer;
720 uint8_t matrix;
721 uint8_t range;
722 AV1Field fields[] = {{&aProfile, 1},
723 {&aLevel, 2},
724 // parsing loop skips tier
725 {&aBitDepth, 2},
726 {&monochrome, 1},
727 {&subsampling, 3},
728 {&primary, 2},
729 {&transfer, 2},
730 {&matrix, 2},
731 {&range, 1}};
733 auto splitter = aCodec.Split(u'.');
734 auto iter = splitter.begin();
735 ++iter;
736 size_t fieldCount = 0;
737 while (iter != splitter.end()) {
738 // Exit if there are too many fields.
739 if (fieldCount >= 9) {
740 return false;
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) {
749 return false;
751 auto tier = fieldStr[2];
752 switch (tier) {
753 case 'M':
754 aTier = 0;
755 break;
756 case 'H':
757 aTier = 1;
758 break;
759 default:
760 return false;
762 fieldStr.SetLength(2);
765 if (fieldStr.Length() < field.length) {
766 return false;
769 // Manually parse values since nsString.ToInteger silently stops parsing
770 // upon encountering unknown characters.
771 uint8_t value = 0;
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');
777 } else {
778 return false;
780 if (value < oldValue) {
781 // Overflow is possible on the 3-digit subsampling field.
782 return false;
786 *field.field = value;
788 ++fieldCount;
789 ++iter;
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') {
796 return false;
798 break;
802 // Spec requires profile, level/tier, bitdepth, or for all possible fields to
803 // be present.
804 if (fieldCount != 3 && fieldCount != 9) {
805 return false;
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)) {
811 return false;
814 if (fieldCount == 3) {
815 // If only required fields are included, set to the spec defaults for the
816 // rest and continue validating.
817 aMonochrome = false;
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;
825 } else {
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) {
831 return false;
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) {
842 return false;
844 aSubsamplingX = !!subsamplingX;
845 aSubsamplingY = !!subsamplingY;
846 aChromaSamplePosition = subsampling % 10;
847 if (aChromaSamplePosition > 2) {
848 return false;
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)) {
859 return false;
861 // Range is a boolean, true meaning full and false meaning limited range.
862 if (range > 1) {
863 return false;
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) {
877 return false;
880 // Supported bit depths are 8, 10 and 12.
881 if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
882 return false;
884 // Profiles 0 and 1 only support 8-bit and 10-bit.
885 if (aProfile < 2 && aBitDepth == 12) {
886 return false;
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) {
896 return false;
898 // Profile 1 only supports 4:4:4.
899 if (aProfile == 1 && !is444) {
900 return false;
902 // Profile 2 only allows 4:2:2 at 10 bits and below.
903 if (aProfile == 2 && aBitDepth < 12 && !is422) {
904 return false;
906 // Chroma sample position can only be specified with 4:2:0.
907 if (aChromaSamplePosition != 0 && !is420or400) {
908 return false;
911 // When video is monochrome, subsampling must be 4:0:0.
912 if (aMonochrome && (aChromaSamplePosition != 0 || !is420or400)) {
913 return false;
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) {
919 return false;
922 // Identity matrix requires 4:4:4 subsampling.
923 if (aColorSpace.mMatrix == MatrixCoefficients::MC_IDENTITY &&
924 (aSubsamplingX || aSubsamplingY ||
925 aColorSpace.mRange != gfx::ColorRange::FULL)) {
926 return false;
929 return true;
932 nsresult GenerateRandomName(nsCString& aOutSalt, uint32_t aLength) {
933 nsresult rv;
934 nsCOMPtr<nsIRandomGenerator> rg =
935 do_GetService("@mozilla.org/security/random-generator;1", &rv);
936 if (NS_FAILED(rv)) {
937 return 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);
944 uint8_t* buffer;
945 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
946 if (NS_FAILED(rv)) {
947 return rv;
950 nsCString temp;
951 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
952 requiredBytesLength);
953 rv = Base64Encode(randomData, temp);
954 free(buffer);
955 buffer = nullptr;
956 if (NS_FAILED(rv)) {
957 return rv;
960 aOutSalt = std::move(temp);
961 return NS_OK;
964 nsresult GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength) {
965 nsresult rv = GenerateRandomName(aOutSalt, aLength);
966 if (NS_FAILED(rv)) {
967 return rv;
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, '_');
973 return NS_OK;
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() {
983 if (mTimer) {
984 #ifdef DEBUG
985 nsCOMPtr<nsIEventTarget> target;
986 mTimer->GetTarget(getter_AddRefs(target));
987 bool onCurrent;
988 nsresult rv = target->IsOnCurrentThread(&onCurrent);
989 MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrent);
990 #endif
991 mTimer->Cancel();
992 mTimer = nullptr;
994 mTask = nullptr;
997 NS_IMETHODIMP
998 SimpleTimer::Notify(nsITimer* timer) {
999 RefPtr<SimpleTimer> deathGrip(this);
1000 if (mTask) {
1001 mTask->Run();
1002 mTask = nullptr;
1004 return NS_OK;
1007 NS_IMETHODIMP
1008 SimpleTimer::GetName(nsACString& aName) {
1009 aName.AssignLiteral("SimpleTimer");
1010 return NS_OK;
1013 nsresult SimpleTimer::Init(nsIRunnable* aTask, uint32_t aTimeoutMs,
1014 nsIEventTarget* aTarget) {
1015 nsresult rv;
1017 // Get target thread first, so we don't have to cancel the timer if it fails.
1018 nsCOMPtr<nsIEventTarget> target;
1019 if (aTarget) {
1020 target = aTarget;
1021 } else {
1022 target = GetMainThreadSerialEventTarget();
1023 if (!target) {
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)) {
1031 return rv;
1034 mTask = aTask;
1035 return NS_OK;
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))) {
1045 return nullptr;
1047 return t.forget();
1050 void LogToBrowserConsole(const nsAString& aMsg) {
1051 if (!NS_IsMainThread()) {
1052 nsString msg(aMsg);
1053 nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
1054 "LogToBrowserConsole", [msg]() { LogToBrowserConsole(msg); });
1055 SchedulerGroup::Dispatch(task.forget());
1056 return;
1058 nsCOMPtr<nsIConsoleService> console(
1059 do_GetService("@mozilla.org/consoleservice;1"));
1060 if (!console) {
1061 NS_WARNING("Failed to log message to console.");
1062 return;
1064 nsAutoString msg(aMsg);
1065 console->LogStringMessage(msg.get());
1068 bool ParseCodecsString(const nsAString& aCodecs,
1069 nsTArray<nsString>& aOutCodecs) {
1070 aOutCodecs.Clear();
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)) {
1087 return false;
1090 nsString codecsStr;
1091 parser.GetParameter("codecs", codecsStr);
1092 return ParseCodecsString(codecsStr, aOutCodecs);
1095 template <int N>
1096 static bool StartsWith(const nsACString& string, const char (&prefix)[N]) {
1097 if (N - 1 > string.Length()) {
1098 return false;
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;
1106 H264_LEVEL level;
1107 return ExtractH264CodecDetails(aCodec, profile, constraint, level,
1108 H264CodecStringStrictness::Lenient);
1111 bool IsH265CodecString(const nsAString& aCodec) {
1112 uint8_t profile = 0;
1113 uint8_t level = 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;
1131 uint8_t level = 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;
1140 uint8_t level = 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;
1168 return trackInfo;
1171 UniquePtr<TrackInfo> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
1172 const nsACString& aCodecMIMEType,
1173 const MediaContainerType& aContainerType) {
1174 UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
1175 if (trackInfo) {
1176 VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
1177 if (videoInfo) {
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;
1202 return trackInfo;
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();
1210 if (!cpc) {
1211 NS_WARNING("Can't get ContentChild singleton in content process!");
1212 return false;
1214 linkType = cpc->NetworkLinkType();
1215 } else {
1216 nsresult rv;
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.");
1221 return false;
1224 rv = nls->GetLinkType(&linkType);
1225 if (NS_FAILED(rv)) {
1226 NS_WARNING("Can't get network link type.");
1227 return false;
1231 switch (linkType) {
1232 case nsINetworkLinkService::LINK_TYPE_UNKNOWN:
1233 case nsINetworkLinkService::LINK_TYPE_ETHERNET:
1234 case nsINetworkLinkService::LINK_TYPE_USB:
1235 case nsINetworkLinkService::LINK_TYPE_WIFI:
1236 return false;
1237 case nsINetworkLinkService::LINK_TYPE_WIMAX:
1238 case nsINetworkLinkService::LINK_TYPE_MOBILE:
1239 return true;
1242 return false;
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,");
1258 } else {
1259 aResolutionOut.AppendASCII("V,");
1261 static const struct {
1262 int32_t mH;
1263 const char* mRes;
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;
1272 break;
1275 aResolutionOut.AppendASCII(resolution);
1278 bool ContainHardwareCodecsSupported(
1279 const media::MediaCodecsSupported& aSupport) {
1280 return aSupport.contains(
1281 mozilla::media::MediaCodecsSupport::H264HardwareDecode) ||
1282 aSupport.contains(
1283 mozilla::media::MediaCodecsSupport::VP8HardwareDecode) ||
1284 aSupport.contains(
1285 mozilla::media::MediaCodecsSupport::VP9HardwareDecode) ||
1286 aSupport.contains(
1287 mozilla::media::MediaCodecsSupport::AV1HardwareDecode) ||
1288 aSupport.contains(
1289 mozilla::media::MediaCodecsSupport::HEVCHardwareDecode);
1292 } // end namespace mozilla