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