Backed out 15 changesets (bug 1852806) for causing mda failures on test_video_low_pow...
[gecko.git] / dom / media / VideoUtils.cpp
blob296b97dadb074cfa247e62351a2e0f596f6e131a
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 <functional>
8 #include <stdint.h>
10 #include "CubebUtils.h"
11 #include "ImageContainer.h"
12 #include "MediaContainerType.h"
13 #include "MediaResource.h"
14 #include "TimeUnits.h"
15 #include "mozilla/Base64.h"
16 #include "mozilla/dom/ContentChild.h"
17 #include "mozilla/SchedulerGroup.h"
18 #include "mozilla/SharedThreadPool.h"
19 #include "mozilla/StaticPrefs_accessibility.h"
20 #include "mozilla/StaticPrefs_media.h"
21 #include "mozilla/TaskCategory.h"
22 #include "mozilla/TaskQueue.h"
23 #include "mozilla/Telemetry.h"
24 #include "nsCharSeparatedTokenizer.h"
25 #include "nsContentTypeParser.h"
26 #include "nsIConsoleService.h"
27 #include "nsINetworkLinkService.h"
28 #include "nsIRandomGenerator.h"
29 #include "nsMathUtils.h"
30 #include "nsNetCID.h"
31 #include "nsServiceManagerUtils.h"
32 #include "nsThreadUtils.h"
33 #include "AudioStream.h"
35 namespace mozilla {
37 using gfx::ColorRange;
38 using gfx::CICP::ColourPrimaries;
39 using gfx::CICP::MatrixCoefficients;
40 using gfx::CICP::TransferCharacteristics;
41 using layers::PlanarYCbCrImage;
42 using media::TimeUnit;
44 double ToMicrosecondResolution(double aSeconds) {
45 double integer;
46 modf(aSeconds * USECS_PER_S, &integer);
47 return integer / USECS_PER_S;
50 CheckedInt64 SaferMultDiv(int64_t aValue, uint64_t aMul, uint64_t aDiv) {
51 if (aMul > INT64_MAX || aDiv > INT64_MAX) {
52 return CheckedInt64(INT64_MAX) + 1; // Return an invalid checked int.
54 int64_t mul = AssertedCast<int64_t>(aMul);
55 int64_t div = AssertedCast<int64_t>(aDiv);
56 int64_t major = aValue / div;
57 int64_t remainder = aValue % div;
58 return CheckedInt64(remainder) * mul / div + CheckedInt64(major) * mul;
61 // Converts from number of audio frames to microseconds, given the specified
62 // audio rate.
63 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
64 return SaferMultDiv(aFrames, USECS_PER_S, aRate);
67 // Converts from microseconds to number of audio frames, given the specified
68 // audio rate.
69 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
70 return SaferMultDiv(aUsecs, aRate, USECS_PER_S);
73 // Format TimeUnit as number of frames at given rate.
74 CheckedInt64 TimeUnitToFrames(const TimeUnit& aTime, uint32_t aRate) {
75 return aTime.IsValid() ? UsecsToFrames(aTime.ToMicroseconds(), aRate)
76 : CheckedInt64(INT64_MAX) + 1;
79 nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs) {
80 if (aSeconds * double(USECS_PER_S) > double(INT64_MAX)) {
81 return NS_ERROR_FAILURE;
83 aOutUsecs = int64_t(aSeconds * double(USECS_PER_S));
84 return NS_OK;
87 static int32_t ConditionDimension(float aValue) {
88 // This will exclude NaNs and too-big values.
89 if (aValue > 1.0 && aValue <= float(INT32_MAX)) {
90 return int32_t(NS_round(aValue));
92 return 0;
95 void ScaleDisplayByAspectRatio(gfx::IntSize& aDisplay, float aAspectRatio) {
96 if (aAspectRatio > 1.0) {
97 // Increase the intrinsic width
98 aDisplay.width =
99 ConditionDimension(aAspectRatio * AssertedCast<float>(aDisplay.width));
100 } else {
101 // Increase the intrinsic height
102 aDisplay.height =
103 ConditionDimension(AssertedCast<float>(aDisplay.height) / aAspectRatio);
107 static int64_t BytesToTime(int64_t offset, int64_t length, int64_t durationUs) {
108 NS_ASSERTION(length > 0, "Must have positive length");
109 double r = double(offset) / double(length);
110 if (r > 1.0) {
111 r = 1.0;
113 return int64_t(double(durationUs) * r);
116 media::TimeIntervals GetEstimatedBufferedTimeRanges(
117 mozilla::MediaResource* aStream, int64_t aDurationUsecs) {
118 media::TimeIntervals buffered;
119 // Nothing to cache if the media takes 0us to play.
120 if (aDurationUsecs <= 0 || !aStream) {
121 return buffered;
124 // Special case completely cached files. This also handles local files.
125 if (aStream->IsDataCachedToEndOfResource(0)) {
126 buffered += media::TimeInterval(TimeUnit::Zero(),
127 TimeUnit::FromMicroseconds(aDurationUsecs));
128 return buffered;
131 int64_t totalBytes = aStream->GetLength();
133 // If we can't determine the total size, pretend that we have nothing
134 // buffered. This will put us in a state of eternally-low-on-undecoded-data
135 // which is not great, but about the best we can do.
136 if (totalBytes <= 0) {
137 return buffered;
140 int64_t startOffset = aStream->GetNextCachedData(0);
141 while (startOffset >= 0) {
142 int64_t endOffset = aStream->GetCachedDataEnd(startOffset);
143 // Bytes [startOffset..endOffset] are cached.
144 NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered");
145 NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered");
147 int64_t startUs = BytesToTime(startOffset, totalBytes, aDurationUsecs);
148 int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
149 if (startUs != endUs) {
150 buffered += media::TimeInterval(TimeUnit::FromMicroseconds(startUs),
151 TimeUnit::FromMicroseconds(endUs));
153 startOffset = aStream->GetNextCachedData(endOffset);
155 return buffered;
158 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer, uint32_t aFrames) {
159 MOZ_ASSERT(aBuffer);
160 const int channels = 2;
161 for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
162 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
163 float sample = 0.0;
164 #else
165 int sample = 0;
166 #endif
167 // The sample of the buffer would be interleaved.
168 sample = (aBuffer[fIdx * channels] + aBuffer[fIdx * channels + 1]) * 0.5f;
169 aBuffer[fIdx * channels] = aBuffer[fIdx * channels + 1] = sample;
173 uint32_t DecideAudioPlaybackChannels(const AudioInfo& info) {
174 if (StaticPrefs::accessibility_monoaudio_enable()) {
175 return 1;
178 if (StaticPrefs::media_forcestereo_enabled()) {
179 return 2;
182 return info.mChannels;
185 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo& aInfo,
186 bool aShouldResistFingerprinting) {
187 bool resampling = StaticPrefs::media_resampling_enabled();
189 uint32_t rate = 0;
191 if (resampling) {
192 rate = 48000;
193 } else if (aInfo.mRate >= 44100) {
194 // The original rate is of good quality and we want to minimize unecessary
195 // resampling, so we let cubeb decide how to resample (if needed).
196 rate = aInfo.mRate;
197 } else {
198 // We will resample all data to match cubeb's preferred sampling rate.
199 rate = CubebUtils::PreferredSampleRate(aShouldResistFingerprinting);
200 if (rate > 384000) {
201 // bogus rate, fall back to something else;
202 rate = 48000;
205 MOZ_DIAGNOSTIC_ASSERT(rate, "output rate can't be 0.");
207 return rate;
210 bool IsDefaultPlaybackDeviceMono() {
211 return CubebUtils::MaxNumberOfChannels() == 1;
214 bool IsVideoContentType(const nsCString& aContentType) {
215 constexpr auto video = "video"_ns;
216 return FindInReadable(video, aContentType);
219 bool IsValidVideoRegion(const gfx::IntSize& aFrame,
220 const gfx::IntRect& aPicture,
221 const gfx::IntSize& aDisplay) {
222 return aFrame.width > 0 && aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
223 aFrame.height > 0 &&
224 aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
225 aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
226 aPicture.width > 0 &&
227 aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
228 aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
229 aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
230 aPicture.height > 0 &&
231 aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
232 aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
233 aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
234 aPicture.width * aPicture.height <=
235 MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
236 aDisplay.width > 0 &&
237 aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
238 aDisplay.height > 0 &&
239 aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
240 aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT;
243 already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType) {
244 const char* name;
245 uint32_t threads = 4;
246 switch (aType) {
247 case MediaThreadType::PLATFORM_DECODER:
248 name = "MediaPDecoder";
249 break;
250 case MediaThreadType::WEBRTC_CALL_THREAD:
251 name = "WebrtcCallThread";
252 threads = 1;
253 break;
254 case MediaThreadType::WEBRTC_WORKER:
255 name = "WebrtcWorker";
256 break;
257 case MediaThreadType::MDSM:
258 name = "MediaDecoderStateMachine";
259 threads = 1;
260 break;
261 case MediaThreadType::PLATFORM_ENCODER:
262 name = "MediaPEncoder";
263 break;
264 default:
265 MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
266 case MediaThreadType::SUPERVISOR:
267 name = "MediaSupervisor";
268 break;
271 RefPtr<SharedThreadPool> pool =
272 SharedThreadPool::Get(nsDependentCString(name), threads);
274 // Ensure a larger stack for platform decoder threads
275 if (aType == MediaThreadType::PLATFORM_DECODER) {
276 const uint32_t minStackSize = 512 * 1024;
277 uint32_t stackSize;
278 MOZ_ALWAYS_SUCCEEDS(pool->GetThreadStackSize(&stackSize));
279 if (stackSize < minStackSize) {
280 MOZ_ALWAYS_SUCCEEDS(pool->SetThreadStackSize(minStackSize));
284 return pool.forget();
287 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile,
288 uint8_t& aLevel, uint8_t& aBitDepth) {
289 uint8_t dummyChromaSubsampling = 1;
290 VideoColorSpace dummyColorspace;
291 return ExtractVPXCodecDetails(aCodec, aProfile, aLevel, aBitDepth,
292 dummyChromaSubsampling, dummyColorspace);
295 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile,
296 uint8_t& aLevel, uint8_t& aBitDepth,
297 uint8_t& aChromaSubsampling,
298 VideoColorSpace& aColorSpace) {
299 // Assign default value.
300 aChromaSubsampling = 1;
301 auto splitter = aCodec.Split(u'.');
302 auto fieldsItr = splitter.begin();
303 auto fourCC = *fieldsItr;
305 if (!fourCC.EqualsLiteral("vp09") && !fourCC.EqualsLiteral("vp08")) {
306 // Invalid 4CC
307 return false;
309 ++fieldsItr;
310 uint8_t primary, transfer, matrix, range;
311 uint8_t* fields[] = {&aProfile, &aLevel, &aBitDepth, &aChromaSubsampling,
312 &primary, &transfer, &matrix, &range};
313 int fieldsCount = 0;
314 nsresult rv;
315 for (; fieldsItr != splitter.end(); ++fieldsItr, ++fieldsCount) {
316 if (fieldsCount > 7) {
317 // No more than 8 fields are expected.
318 return false;
320 *(fields[fieldsCount]) =
321 static_cast<uint8_t>((*fieldsItr).ToInteger(&rv, 10));
322 // We got invalid field value, parsing error.
323 NS_ENSURE_SUCCESS(rv, false);
325 // Mandatory Fields
326 // <sample entry 4CC>.<profile>.<level>.<bitDepth>.
327 // Optional Fields
328 // <chromaSubsampling>.<colourPrimaries>.<transferCharacteristics>.
329 // <matrixCoefficients>.<videoFullRangeFlag>
330 // First three fields are mandatory(we have parsed 4CC).
331 if (fieldsCount < 3) {
332 // Invalid number of fields.
333 return false;
335 // Start to validate the parsing value.
337 // profile should be 0,1,2 or 3.
338 // See https://www.webmproject.org/vp9/profiles/
339 if (aProfile > 3) {
340 // Invalid profile.
341 return false;
344 // level, See https://www.webmproject.org/vp9/mp4/#semantics_1
345 switch (aLevel) {
346 case 10:
347 case 11:
348 case 20:
349 case 21:
350 case 30:
351 case 31:
352 case 40:
353 case 41:
354 case 50:
355 case 51:
356 case 52:
357 case 60:
358 case 61:
359 case 62:
360 break;
361 default:
362 // Invalid level.
363 return false;
366 if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
367 // Invalid bitDepth:
368 return false;
371 if (fieldsCount == 3) {
372 // No more options.
373 return true;
376 // chromaSubsampling should be 0,1,2,3...4~7 are reserved.
377 if (aChromaSubsampling > 3) {
378 return false;
381 if (fieldsCount == 4) {
382 // No more options.
383 return true;
386 // It is an integer that is defined by the "Colour primaries"
387 // section of ISO/IEC 23001-8:2016 Table 2.
388 // We treat reserved value as false case.
389 if (primary == 0 || primary == 3 || primary > 22) {
390 // reserved value.
391 return false;
393 if (primary > 12 && primary < 22) {
394 // 13~21 are reserved values.
395 return false;
397 aColorSpace.mPrimaries = static_cast<ColourPrimaries>(primary);
399 if (fieldsCount == 5) {
400 // No more options.
401 return true;
404 // It is an integer that is defined by the
405 // "Transfer characteristics" section of ISO/IEC 23001-8:2016 Table 3.
406 // We treat reserved value as false case.
407 if (transfer == 0 || transfer == 3 || transfer > 18) {
408 // reserved value.
409 return false;
411 aColorSpace.mTransfer = static_cast<TransferCharacteristics>(transfer);
413 if (fieldsCount == 6) {
414 // No more options.
415 return true;
418 // It is an integer that is defined by the
419 // "Matrix coefficients" section of ISO/IEC 23001-8:2016 Table 4.
420 // We treat reserved value as false case.
421 if (matrix == 3 || matrix > 11) {
422 return false;
424 aColorSpace.mMatrix = static_cast<MatrixCoefficients>(matrix);
426 // If matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3
427 // (4:4:4).
428 if (aColorSpace.mMatrix == MatrixCoefficients::MC_IDENTITY &&
429 aChromaSubsampling != 3) {
430 return false;
433 if (fieldsCount == 7) {
434 // No more options.
435 return true;
438 // videoFullRangeFlag indicates the black level and range of the luma and
439 // chroma signals. 0 = legal range (e.g. 16-235 for 8 bit sample depth);
440 // 1 = full range (e.g. 0-255 for 8-bit sample depth).
441 aColorSpace.mRange = static_cast<ColorRange>(range);
442 return range <= 1;
445 bool ExtractH264CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
446 uint8_t& aConstraint, uint8_t& aLevel) {
447 // H.264 codecs parameters have a type defined as avcN.PPCCLL, where
448 // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
449 // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
450 // We ignore the constraint_set flags, as it's not clear from any
451 // documentation what constraints the platform decoders support.
452 // See
453 // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
454 // for more details.
455 if (aCodec.Length() != strlen("avc1.PPCCLL")) {
456 return false;
459 // Verify the codec starts with "avc1." or "avc3.".
460 const nsAString& sample = Substring(aCodec, 0, 5);
461 if (!sample.EqualsASCII("avc1.") && !sample.EqualsASCII("avc3.")) {
462 return false;
465 // Extract the profile_idc, constraint_flags and level_idc.
466 nsresult rv = NS_OK;
467 aProfile = Substring(aCodec, 5, 2).ToInteger(&rv, 16);
468 NS_ENSURE_SUCCESS(rv, false);
470 // Constraint flags are stored on the 6 most significant bits, first two bits
471 // are reserved_zero_2bits.
472 aConstraint = Substring(aCodec, 7, 2).ToInteger(&rv, 16);
473 NS_ENSURE_SUCCESS(rv, false);
475 aLevel = Substring(aCodec, 9, 2).ToInteger(&rv, 16);
476 NS_ENSURE_SUCCESS(rv, false);
478 if (aLevel == 9) {
479 aLevel = H264_LEVEL_1_b;
480 } else if (aLevel <= 5) {
481 aLevel *= 10;
484 return true;
487 bool IsH265ProfileRecognizable(uint8_t aProfile,
488 int32_t aProfileCompabilityFlags) {
489 enum Profile {
490 eUnknown,
491 eHighThroughputScreenExtended,
492 eScalableRangeExtension,
493 eScreenExtended,
494 e3DMain,
495 eScalableMain,
496 eMultiviewMain,
497 eHighThroughput,
498 eRangeExtension,
499 eMain10,
500 eMain,
501 eMainStillPicture
503 Profile p = eUnknown;
505 // Spec A.3.8
506 if (aProfile == 11 || (aProfileCompabilityFlags & 0x800)) {
507 p = eHighThroughputScreenExtended;
509 // Spec H.11.1.2
510 if (aProfile == 10 || (aProfileCompabilityFlags & 0x400)) {
511 p = eScalableRangeExtension;
513 // Spec A.3.7
514 if (aProfile == 9 || (aProfileCompabilityFlags & 0x200)) {
515 p = eScreenExtended;
517 // Spec I.11.1.1
518 if (aProfile == 8 || (aProfileCompabilityFlags & 0x100)) {
519 p = e3DMain;
521 // Spec H.11.1.1
522 if (aProfile == 7 || (aProfileCompabilityFlags & 0x80)) {
523 p = eScalableMain;
525 // Spec G.11.1.1
526 if (aProfile == 6 || (aProfileCompabilityFlags & 0x40)) {
527 p = eMultiviewMain;
529 // Spec A.3.6
530 if (aProfile == 5 || (aProfileCompabilityFlags & 0x20)) {
531 p = eHighThroughput;
533 // Spec A.3.5
534 if (aProfile == 4 || (aProfileCompabilityFlags & 0x10)) {
535 p = eRangeExtension;
537 // Spec A.3.3
538 // NOTICE: Do not change the order of below sections
539 if (aProfile == 2 || (aProfileCompabilityFlags & 0x4)) {
540 p = eMain10;
542 // Spec A.3.2
543 // When aProfileCompabilityFlags[1] is equal to 1,
544 // aProfileCompabilityFlags[2] should be equal to 1 as well.
545 if (aProfile == 1 || (aProfileCompabilityFlags & 0x2)) {
546 p = eMain;
548 // Spec A.3.4
549 // When aProfileCompabilityFlags[3] is equal to 1,
550 // aProfileCompabilityFlags[1] and
551 // aProfileCompabilityFlags[2] should be equal to 1 as well.
552 if (aProfile == 3 || (aProfileCompabilityFlags & 0x8)) {
553 p = eMainStillPicture;
556 return p != eUnknown;
559 bool ExtractH265CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
560 uint8_t& aLevel, nsTArray<uint8_t>& aConstraints) {
561 // HEVC codec id consists of:
562 const size_t maxHevcCodecIdLength =
563 5 + // 'hev1.' or 'hvc1.' prefix (5 chars)
564 4 + // profile, e.g. '.A12' (max 4 chars)
565 9 + // profile_compatibility, dot + 32-bit hex number (max 9 chars)
566 5 + // tier and level, e.g. '.H120' (max 5 chars)
567 18; // up to 6 constraint bytes, bytes are dot-separated and hex-encoded.
569 if (aCodec.Length() > maxHevcCodecIdLength) {
570 return false;
573 // Verify the codec starts with "hev1." or "hvc1.".
574 const nsAString& sample = Substring(aCodec, 0, 5);
575 if (!sample.EqualsASCII("hev1.") && !sample.EqualsASCII("hvc1.")) {
576 return false;
579 nsresult rv;
580 CheckedUint8 profile;
581 int32_t compabilityFlags = 0;
582 CheckedUint8 level = 0;
583 nsTArray<uint8_t> constraints;
585 auto splitter = aCodec.Split(u'.');
586 size_t count = 0;
587 for (auto iter = splitter.begin(); iter != splitter.end(); ++iter, ++count) {
588 const auto& fieldStr = *iter;
589 if (fieldStr.IsEmpty()) {
590 return false;
593 if (count == 0) {
594 MOZ_RELEASE_ASSERT(fieldStr.EqualsASCII("hev1") ||
595 fieldStr.EqualsASCII("hvc1"));
596 continue;
599 if (count == 1) { // profile
600 Maybe<uint8_t> validProfileSpace;
601 if (fieldStr.First() == u'A' || fieldStr.First() == u'B' ||
602 fieldStr.First() == u'C') {
603 validProfileSpace.emplace(1 + (fieldStr.First() - 'A'));
605 // If fieldStr.First() is not A, B, C or a digit, ToInteger() should fail.
606 profile = validProfileSpace ? Substring(fieldStr, 1).ToInteger(&rv)
607 : fieldStr.ToInteger(&rv);
608 if (NS_FAILED(rv) || !profile.isValid() || profile.value() > 0x1F) {
609 return false;
611 continue;
614 if (count == 2) { // profile compatibility flags
615 compabilityFlags = fieldStr.ToInteger(&rv, 16);
616 NS_ENSURE_SUCCESS(rv, false);
617 continue;
620 if (count == 3) { // tier and level
621 Maybe<uint8_t> validProfileTier;
622 if (fieldStr.First() == u'L' || fieldStr.First() == u'H') {
623 validProfileTier.emplace(fieldStr.First() == u'L' ? 0 : 1);
625 // If fieldStr.First() is not L, H, or a digit, ToInteger() should fail.
626 level = validProfileTier ? Substring(fieldStr, 1).ToInteger(&rv)
627 : fieldStr.ToInteger(&rv);
628 if (NS_FAILED(rv) || !level.isValid()) {
629 return false;
631 continue;
634 // The rest is constraint bytes.
635 if (count > 10) {
636 return false;
639 CheckedUint8 byte(fieldStr.ToInteger(&rv, 16));
640 if (NS_FAILED(rv) || !byte.isValid()) {
641 return false;
643 constraints.AppendElement(byte.value());
646 if (count < 4 /* Parse til level at least */ || constraints.Length() > 6 ||
647 !IsH265ProfileRecognizable(profile.value(), compabilityFlags)) {
648 return false;
651 aProfile = profile.value();
652 aLevel = level.value();
653 aConstraints = std::move(constraints);
654 return true;
657 bool ExtractAV1CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
658 uint8_t& aLevel, uint8_t& aTier, uint8_t& aBitDepth,
659 bool& aMonochrome, bool& aSubsamplingX,
660 bool& aSubsamplingY, uint8_t& aChromaSamplePosition,
661 VideoColorSpace& aColorSpace) {
662 auto fourCC = Substring(aCodec, 0, 4);
664 if (!fourCC.EqualsLiteral("av01")) {
665 // Invalid 4CC
666 return false;
669 // Format is:
670 // av01.N.NN[MH].NN.B.BBN.NN.NN.NN.B
671 // where
672 // N = decimal digit
673 // [] = single character
674 // B = binary digit
675 // Field order:
676 // <sample entry 4CC>.<profile>.<level><tier>.<bitDepth>
677 // [.<monochrome>.<chromaSubsampling>
678 // .<colorPrimaries>.<transferCharacteristics>.<matrixCoefficients>
679 // .<videoFullRangeFlag>]
681 // If any optional field is found, all the rest must be included.
683 // Parsing stops but does not fail upon encountering unexpected characters
684 // at the end of an otherwise well-formed string.
686 // See https://aomediacodec.github.io/av1-isobmff/#codecsparam
688 struct AV1Field {
689 uint8_t* field;
690 size_t length;
692 uint8_t monochrome;
693 uint8_t subsampling;
694 uint8_t primary;
695 uint8_t transfer;
696 uint8_t matrix;
697 uint8_t range;
698 AV1Field fields[] = {{&aProfile, 1},
699 {&aLevel, 2},
700 // parsing loop skips tier
701 {&aBitDepth, 2},
702 {&monochrome, 1},
703 {&subsampling, 3},
704 {&primary, 2},
705 {&transfer, 2},
706 {&matrix, 2},
707 {&range, 1}};
709 auto splitter = aCodec.Split(u'.');
710 auto iter = splitter.begin();
711 ++iter;
712 size_t fieldCount = 0;
713 while (iter != splitter.end()) {
714 // Exit if there are too many fields.
715 if (fieldCount >= 9) {
716 return false;
719 AV1Field& field = fields[fieldCount];
720 auto fieldStr = *iter;
722 if (field.field == &aLevel) {
723 // Parse tier and remove it from the level field.
724 if (fieldStr.Length() < 3) {
725 return false;
727 auto tier = fieldStr[2];
728 switch (tier) {
729 case 'M':
730 aTier = 0;
731 break;
732 case 'H':
733 aTier = 1;
734 break;
735 default:
736 return false;
738 fieldStr.SetLength(2);
741 if (fieldStr.Length() < field.length) {
742 return false;
745 // Manually parse values since nsString.ToInteger silently stops parsing
746 // upon encountering unknown characters.
747 uint8_t value = 0;
748 for (size_t i = 0; i < field.length; i++) {
749 uint8_t oldValue = value;
750 char16_t character = fieldStr[i];
751 if ('0' <= character && character <= '9') {
752 value = (value * 10) + (character - '0');
753 } else {
754 return false;
756 if (value < oldValue) {
757 // Overflow is possible on the 3-digit subsampling field.
758 return false;
762 *field.field = value;
764 ++fieldCount;
765 ++iter;
767 // Field had extra characters, exit early.
768 if (fieldStr.Length() > field.length) {
769 // Disallow numbers as unexpected characters.
770 char16_t character = fieldStr[field.length];
771 if ('0' <= character && character <= '9') {
772 return false;
774 break;
778 // Spec requires profile, level/tier, bitdepth, or for all possible fields to
779 // be present.
780 if (fieldCount != 3 && fieldCount != 9) {
781 return false;
784 // Valid profiles are: Main (0), High (1), Professional (2).
785 // Levels range from 0 to 23, or 31 to remove level restrictions.
786 if (aProfile > 2 || (aLevel > 23 && aLevel != 31)) {
787 return false;
790 if (fieldCount == 3) {
791 // If only required fields are included, set to the spec defaults for the
792 // rest and continue validating.
793 aMonochrome = false;
794 aSubsamplingX = true;
795 aSubsamplingY = true;
796 aChromaSamplePosition = 0;
797 aColorSpace.mPrimaries = ColourPrimaries::CP_BT709;
798 aColorSpace.mTransfer = TransferCharacteristics::TC_BT709;
799 aColorSpace.mMatrix = MatrixCoefficients::MC_BT709;
800 aColorSpace.mRange = ColorRange::LIMITED;
801 } else {
802 // Extract the individual values for the remaining fields, and check for
803 // valid values for each.
805 // Monochrome is a boolean.
806 if (monochrome > 1) {
807 return false;
809 aMonochrome = !!monochrome;
811 // Extract individual digits of the subsampling field.
812 // Subsampling is two binary digits for x and y
813 // and one enumerated sample position field of
814 // Unknown (0), Vertical (1), Colocated (2).
815 uint8_t subsamplingX = (subsampling / 100) % 10;
816 uint8_t subsamplingY = (subsampling / 10) % 10;
817 if (subsamplingX > 1 || subsamplingY > 1) {
818 return false;
820 aSubsamplingX = !!subsamplingX;
821 aSubsamplingY = !!subsamplingY;
822 aChromaSamplePosition = subsampling % 10;
823 if (aChromaSamplePosition > 2) {
824 return false;
827 // We can validate the color space values using CICP enums, as the values
828 // are standardized in Rec. ITU-T H.273.
829 aColorSpace.mPrimaries = static_cast<ColourPrimaries>(primary);
830 aColorSpace.mTransfer = static_cast<TransferCharacteristics>(transfer);
831 aColorSpace.mMatrix = static_cast<MatrixCoefficients>(matrix);
832 if (gfx::CICP::IsReserved(aColorSpace.mPrimaries) ||
833 gfx::CICP::IsReserved(aColorSpace.mTransfer) ||
834 gfx::CICP::IsReserved(aColorSpace.mMatrix)) {
835 return false;
837 // Range is a boolean, true meaning full and false meaning limited range.
838 if (range > 1) {
839 return false;
841 aColorSpace.mRange = static_cast<ColorRange>(range);
844 // Begin validating all parameter values:
846 // Only Levels 8 and above (4.0 and greater) can specify Tier.
847 // See: 5.5.1. General sequence header OBU syntax,
848 // if ( seq_level_idx[ i ] > 7 ) seq_tier[ i ] = f(1)
849 // https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=42
850 // Also: Annex A, A.3. Levels, columns MainMbps and HighMbps
851 // at https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=652
852 if (aLevel < 8 && aTier > 0) {
853 return false;
856 // Supported bit depths are 8, 10 and 12.
857 if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
858 return false;
860 // Profiles 0 and 1 only support 8-bit and 10-bit.
861 if (aProfile < 2 && aBitDepth == 12) {
862 return false;
865 // x && y subsampling is used to specify monochrome 4:0:0 as well
866 bool is420or400 = aSubsamplingX && aSubsamplingY;
867 bool is422 = aSubsamplingX && !aSubsamplingY;
868 bool is444 = !aSubsamplingX && !aSubsamplingY;
870 // Profile 0 only supports 4:2:0.
871 if (aProfile == 0 && !is420or400) {
872 return false;
874 // Profile 1 only supports 4:4:4.
875 if (aProfile == 1 && !is444) {
876 return false;
878 // Profile 2 only allows 4:2:2 at 10 bits and below.
879 if (aProfile == 2 && aBitDepth < 12 && !is422) {
880 return false;
882 // Chroma sample position can only be specified with 4:2:0.
883 if (aChromaSamplePosition != 0 && !is420or400) {
884 return false;
887 // When video is monochrome, subsampling must be 4:0:0.
888 if (aMonochrome && (aChromaSamplePosition != 0 || !is420or400)) {
889 return false;
891 // Monochrome can only be signaled when profile is 0 or 2.
892 // Note: This check is redundant with the above subsampling check,
893 // as profile 1 only supports 4:4:4.
894 if (aMonochrome && aProfile != 0 && aProfile != 2) {
895 return false;
898 // Identity matrix requires 4:4:4 subsampling.
899 if (aColorSpace.mMatrix == MatrixCoefficients::MC_IDENTITY &&
900 (aSubsamplingX || aSubsamplingY ||
901 aColorSpace.mRange != gfx::ColorRange::FULL)) {
902 return false;
905 return true;
908 nsresult GenerateRandomName(nsCString& aOutSalt, uint32_t aLength) {
909 nsresult rv;
910 nsCOMPtr<nsIRandomGenerator> rg =
911 do_GetService("@mozilla.org/security/random-generator;1", &rv);
912 if (NS_FAILED(rv)) {
913 return rv;
916 // For each three bytes of random data we will get four bytes of ASCII.
917 const uint32_t requiredBytesLength =
918 static_cast<uint32_t>((aLength + 3) / 4 * 3);
920 uint8_t* buffer;
921 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
922 if (NS_FAILED(rv)) {
923 return rv;
926 nsCString temp;
927 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
928 requiredBytesLength);
929 rv = Base64Encode(randomData, temp);
930 free(buffer);
931 buffer = nullptr;
932 if (NS_FAILED(rv)) {
933 return rv;
936 aOutSalt = std::move(temp);
937 return NS_OK;
940 nsresult GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength) {
941 nsresult rv = GenerateRandomName(aOutSalt, aLength);
942 if (NS_FAILED(rv)) {
943 return rv;
946 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
947 // to replace illegal characters -- notably '/'
948 aOutSalt.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
949 return NS_OK;
952 already_AddRefed<TaskQueue> CreateMediaDecodeTaskQueue(const char* aName) {
953 RefPtr<TaskQueue> queue = TaskQueue::Create(
954 GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), aName);
955 return queue.forget();
958 void SimpleTimer::Cancel() {
959 if (mTimer) {
960 #ifdef DEBUG
961 nsCOMPtr<nsIEventTarget> target;
962 mTimer->GetTarget(getter_AddRefs(target));
963 bool onCurrent;
964 nsresult rv = target->IsOnCurrentThread(&onCurrent);
965 MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrent);
966 #endif
967 mTimer->Cancel();
968 mTimer = nullptr;
970 mTask = nullptr;
973 NS_IMETHODIMP
974 SimpleTimer::Notify(nsITimer* timer) {
975 RefPtr<SimpleTimer> deathGrip(this);
976 if (mTask) {
977 mTask->Run();
978 mTask = nullptr;
980 return NS_OK;
983 NS_IMETHODIMP
984 SimpleTimer::GetName(nsACString& aName) {
985 aName.AssignLiteral("SimpleTimer");
986 return NS_OK;
989 nsresult SimpleTimer::Init(nsIRunnable* aTask, uint32_t aTimeoutMs,
990 nsIEventTarget* aTarget) {
991 nsresult rv;
993 // Get target thread first, so we don't have to cancel the timer if it fails.
994 nsCOMPtr<nsIEventTarget> target;
995 if (aTarget) {
996 target = aTarget;
997 } else {
998 target = GetMainThreadSerialEventTarget();
999 if (!target) {
1000 return NS_ERROR_NOT_AVAILABLE;
1004 rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aTimeoutMs,
1005 nsITimer::TYPE_ONE_SHOT, target);
1006 if (NS_FAILED(rv)) {
1007 return rv;
1010 mTask = aTask;
1011 return NS_OK;
1014 NS_IMPL_ISUPPORTS(SimpleTimer, nsITimerCallback, nsINamed)
1016 already_AddRefed<SimpleTimer> SimpleTimer::Create(nsIRunnable* aTask,
1017 uint32_t aTimeoutMs,
1018 nsIEventTarget* aTarget) {
1019 RefPtr<SimpleTimer> t(new SimpleTimer());
1020 if (NS_FAILED(t->Init(aTask, aTimeoutMs, aTarget))) {
1021 return nullptr;
1023 return t.forget();
1026 void LogToBrowserConsole(const nsAString& aMsg) {
1027 if (!NS_IsMainThread()) {
1028 nsString msg(aMsg);
1029 nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
1030 "LogToBrowserConsole", [msg]() { LogToBrowserConsole(msg); });
1031 SchedulerGroup::Dispatch(TaskCategory::Other, task.forget());
1032 return;
1034 nsCOMPtr<nsIConsoleService> console(
1035 do_GetService("@mozilla.org/consoleservice;1"));
1036 if (!console) {
1037 NS_WARNING("Failed to log message to console.");
1038 return;
1040 nsAutoString msg(aMsg);
1041 console->LogStringMessage(msg.get());
1044 bool ParseCodecsString(const nsAString& aCodecs,
1045 nsTArray<nsString>& aOutCodecs) {
1046 aOutCodecs.Clear();
1047 bool expectMoreTokens = false;
1048 nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
1049 while (tokenizer.hasMoreTokens()) {
1050 const nsAString& token = tokenizer.nextToken();
1051 expectMoreTokens = tokenizer.separatorAfterCurrentToken();
1052 aOutCodecs.AppendElement(token);
1054 return !expectMoreTokens;
1057 bool ParseMIMETypeString(const nsAString& aMIMEType,
1058 nsString& aOutContainerType,
1059 nsTArray<nsString>& aOutCodecs) {
1060 nsContentTypeParser parser(aMIMEType);
1061 nsresult rv = parser.GetType(aOutContainerType);
1062 if (NS_FAILED(rv)) {
1063 return false;
1066 nsString codecsStr;
1067 parser.GetParameter("codecs", codecsStr);
1068 return ParseCodecsString(codecsStr, aOutCodecs);
1071 template <int N>
1072 static bool StartsWith(const nsACString& string, const char (&prefix)[N]) {
1073 if (N - 1 > string.Length()) {
1074 return false;
1076 return memcmp(string.Data(), prefix, N - 1) == 0;
1079 bool IsH264CodecString(const nsAString& aCodec) {
1080 uint8_t profile = 0;
1081 uint8_t constraint = 0;
1082 uint8_t level = 0;
1083 return ExtractH264CodecDetails(aCodec, profile, constraint, level);
1086 bool IsH265CodecString(const nsAString& aCodec) {
1087 uint8_t profile = 0;
1088 uint8_t level = 0;
1089 nsTArray<uint8_t> constraints;
1090 return ExtractH265CodecDetails(aCodec, profile, level, constraints);
1093 bool IsAACCodecString(const nsAString& aCodec) {
1094 return aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
1095 aCodec.EqualsLiteral(
1096 "mp4a.40.02") || // MPEG4 AAC-LC(for compatibility)
1097 aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
1098 aCodec.EqualsLiteral(
1099 "mp4a.40.05") || // MPEG4 HE-AAC(for compatibility)
1100 aCodec.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
1101 aCodec.EqualsLiteral("mp4a.40.29"); // MPEG4 HE-AACv2
1104 bool IsVP8CodecString(const nsAString& aCodec) {
1105 uint8_t profile = 0;
1106 uint8_t level = 0;
1107 uint8_t bitDepth = 0;
1108 return aCodec.EqualsLiteral("vp8") || aCodec.EqualsLiteral("vp8.0") ||
1109 (StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp08") &&
1110 ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
1113 bool IsVP9CodecString(const nsAString& aCodec) {
1114 uint8_t profile = 0;
1115 uint8_t level = 0;
1116 uint8_t bitDepth = 0;
1117 return aCodec.EqualsLiteral("vp9") || aCodec.EqualsLiteral("vp9.0") ||
1118 (StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp09") &&
1119 ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
1122 bool IsAV1CodecString(const nsAString& aCodec) {
1123 uint8_t profile, level, tier, bitDepth, chromaPosition;
1124 bool monochrome, subsamplingX, subsamplingY;
1125 VideoColorSpace colorSpace;
1126 return aCodec.EqualsLiteral("av1") ||
1127 (StartsWith(NS_ConvertUTF16toUTF8(aCodec), "av01") &&
1128 ExtractAV1CodecDetails(aCodec, profile, level, tier, bitDepth,
1129 monochrome, subsamplingX, subsamplingY,
1130 chromaPosition, colorSpace));
1133 UniquePtr<TrackInfo> CreateTrackInfoWithMIMEType(
1134 const nsACString& aCodecMIMEType) {
1135 UniquePtr<TrackInfo> trackInfo;
1136 if (StartsWith(aCodecMIMEType, "audio/")) {
1137 trackInfo.reset(new AudioInfo());
1138 trackInfo->mMimeType = aCodecMIMEType;
1139 } else if (StartsWith(aCodecMIMEType, "video/")) {
1140 trackInfo.reset(new VideoInfo());
1141 trackInfo->mMimeType = aCodecMIMEType;
1143 return trackInfo;
1146 UniquePtr<TrackInfo> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
1147 const nsACString& aCodecMIMEType,
1148 const MediaContainerType& aContainerType) {
1149 UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
1150 if (trackInfo) {
1151 VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
1152 if (videoInfo) {
1153 Maybe<int32_t> maybeWidth = aContainerType.ExtendedType().GetWidth();
1154 if (maybeWidth && *maybeWidth > 0) {
1155 videoInfo->mImage.width = *maybeWidth;
1156 videoInfo->mDisplay.width = *maybeWidth;
1158 Maybe<int32_t> maybeHeight = aContainerType.ExtendedType().GetHeight();
1159 if (maybeHeight && *maybeHeight > 0) {
1160 videoInfo->mImage.height = *maybeHeight;
1161 videoInfo->mDisplay.height = *maybeHeight;
1163 } else if (trackInfo->GetAsAudioInfo()) {
1164 AudioInfo* audioInfo = trackInfo->GetAsAudioInfo();
1165 Maybe<int32_t> maybeChannels =
1166 aContainerType.ExtendedType().GetChannels();
1167 if (maybeChannels && *maybeChannels > 0) {
1168 audioInfo->mChannels = *maybeChannels;
1170 Maybe<int32_t> maybeSamplerate =
1171 aContainerType.ExtendedType().GetSamplerate();
1172 if (maybeSamplerate && *maybeSamplerate > 0) {
1173 audioInfo->mRate = *maybeSamplerate;
1177 return trackInfo;
1180 bool OnCellularConnection() {
1181 uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
1182 if (XRE_IsContentProcess()) {
1183 mozilla::dom::ContentChild* cpc =
1184 mozilla::dom::ContentChild::GetSingleton();
1185 if (!cpc) {
1186 NS_WARNING("Can't get ContentChild singleton in content process!");
1187 return false;
1189 linkType = cpc->NetworkLinkType();
1190 } else {
1191 nsresult rv;
1192 nsCOMPtr<nsINetworkLinkService> nls =
1193 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
1194 if (NS_FAILED(rv)) {
1195 NS_WARNING("Can't get nsINetworkLinkService.");
1196 return false;
1199 rv = nls->GetLinkType(&linkType);
1200 if (NS_FAILED(rv)) {
1201 NS_WARNING("Can't get network link type.");
1202 return false;
1206 switch (linkType) {
1207 case nsINetworkLinkService::LINK_TYPE_UNKNOWN:
1208 case nsINetworkLinkService::LINK_TYPE_ETHERNET:
1209 case nsINetworkLinkService::LINK_TYPE_USB:
1210 case nsINetworkLinkService::LINK_TYPE_WIFI:
1211 return false;
1212 case nsINetworkLinkService::LINK_TYPE_WIMAX:
1213 case nsINetworkLinkService::LINK_TYPE_MOBILE:
1214 return true;
1217 return false;
1220 } // end namespace mozilla