VideoUtils.h (20728B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef VideoUtils_h 8 #define VideoUtils_h 9 10 #include "AudioSampleFormat.h" 11 #include "MediaCodecsSupport.h" 12 #include "MediaInfo.h" 13 #include "VideoLimits.h" 14 #include "mozilla/AbstractThread.h" 15 #include "mozilla/Attributes.h" 16 #include "mozilla/CheckedInt.h" 17 #include "mozilla/MozPromise.h" 18 #include "mozilla/ReentrantMonitor.h" 19 #include "mozilla/RefPtr.h" 20 #include "mozilla/SharedThreadPool.h" 21 #include "mozilla/TaskQueue.h" 22 #include "mozilla/UniquePtr.h" 23 #include "mozilla/gfx/Point.h" // for gfx::IntSize 24 #include "mozilla/gfx/Types.h" 25 #include "nsCOMPtr.h" 26 #include "nsINamed.h" 27 #include "nsIThread.h" 28 #include "nsITimer.h" 29 #include "nsThreadUtils.h" 30 #include "prtime.h" 31 32 using mozilla::CheckedInt32; 33 using mozilla::CheckedInt64; 34 using mozilla::CheckedUint32; 35 using mozilla::CheckedUint64; 36 37 // This file contains stuff we'd rather put elsewhere, but which is 38 // dependent on other changes which we don't want to wait for. We plan to 39 // remove this file in the near future. 40 41 // This belongs in xpcom/monitor/Monitor.h, once we've made 42 // mozilla::Monitor non-reentrant. 43 namespace mozilla { 44 45 class MediaContainerType; 46 47 /** 48 * ReentrantMonitorConditionallyEnter 49 * 50 * Enters the supplied monitor only if the conditional value |aEnter| is true. 51 * E.g. Used to allow unmonitored read access on the decode thread, 52 * and monitored access on all other threads. 53 */ 54 class MOZ_STACK_CLASS ReentrantMonitorConditionallyEnter { 55 public: 56 ReentrantMonitorConditionallyEnter(bool aEnter, 57 ReentrantMonitor& aReentrantMonitor) 58 : mReentrantMonitor(nullptr) { 59 MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter); 60 if (aEnter) { 61 mReentrantMonitor = &aReentrantMonitor; 62 NS_ASSERTION(mReentrantMonitor, "null monitor"); 63 mReentrantMonitor->Enter(); 64 } 65 } 66 ~ReentrantMonitorConditionallyEnter(void) { 67 if (mReentrantMonitor) { 68 mReentrantMonitor->Exit(); 69 } 70 MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter); 71 } 72 73 private: 74 // Restrict to constructor and destructor defined above. 75 ReentrantMonitorConditionallyEnter(); 76 ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&); 77 ReentrantMonitorConditionallyEnter& operator=( 78 const ReentrantMonitorConditionallyEnter&); 79 static void* operator new(size_t) noexcept(true); 80 static void operator delete(void*); 81 82 ReentrantMonitor* mReentrantMonitor; 83 }; 84 85 // Shuts down a thread asynchronously. 86 class ShutdownThreadEvent : public Runnable { 87 public: 88 explicit ShutdownThreadEvent(nsIThread* aThread) 89 : Runnable("ShutdownThreadEvent"), mThread(aThread) {} 90 ~ShutdownThreadEvent() = default; 91 NS_IMETHOD Run() override { 92 mThread->Shutdown(); 93 mThread = nullptr; 94 return NS_OK; 95 } 96 97 private: 98 nsCOMPtr<nsIThread> mThread; 99 }; 100 101 class MediaResource; 102 103 // Estimates the buffered ranges of a MediaResource using a simple 104 // (byteOffset/length)*duration method. Probably inaccurate, but won't 105 // do file I/O, and can be used when we don't have detailed knowledge 106 // of the byte->time mapping of a resource. aDurationUsecs is the duration 107 // of the media in microseconds. Estimated buffered ranges are stored in 108 // aOutBuffered. Ranges are 0-normalized, i.e. in the range of (0,duration]. 109 media::TimeIntervals GetEstimatedBufferedTimeRanges( 110 mozilla::MediaResource* aStream, int64_t aDurationUsecs); 111 112 double ToMicrosecondResolution(double aSeconds); 113 // Converts from number of audio frames (aFrames) to microseconds, given 114 // the specified audio rate (aRate). 115 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate); 116 // Converts from number of audio frames (aFrames) TimeUnit, given 117 // the specified audio rate (aRate). 118 media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate); 119 // Perform aValue * aMul / aDiv, reducing the possibility of overflow due to 120 // aValue * aMul overflowing. 121 CheckedInt64 SaferMultDiv(int64_t aValue, uint64_t aMul, uint64_t aDiv); 122 123 // Converts from microseconds (aUsecs) to number of audio frames, given the 124 // specified audio rate (aRate). Stores the result in aOutFrames. Returns 125 // true if the operation succeeded, or false if there was an integer 126 // overflow while calulating the conversion. 127 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate); 128 129 // Format TimeUnit as number of frames at given rate. 130 CheckedInt64 TimeUnitToFrames(const media::TimeUnit& aTime, uint32_t aRate); 131 132 // Converts from seconds to microseconds. Returns failure if the resulting 133 // integer is too big to fit in an int64_t. 134 nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs); 135 136 // Scales the display rect aDisplay by aspect ratio aAspectRatio. 137 // Note that aDisplay must be validated by IsValidVideoRegion() 138 // before being used! 139 void ScaleDisplayByAspectRatio(gfx::IntSize& aDisplay, float aAspectRatio); 140 141 // Downmix Stereo audio samples to Mono. 142 // Input are the buffer contains stereo data and the number of frames. 143 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer, uint32_t aFrames); 144 145 // Decide the number of playback channels according to the 146 // given AudioInfo and the prefs that are being set. 147 uint32_t DecideAudioPlaybackChannels(const AudioInfo& info); 148 149 // Decide the sample-rate to use for audio output according to the 150 // given AudioInfo and the prefs that are being set. 151 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo& info, 152 bool aShouldResistFingerprinting); 153 154 bool IsDefaultPlaybackDeviceMono(); 155 156 bool IsVideoContentType(const nsCString& aContentType); 157 158 // Returns true if it's safe to use aPicture as the picture to be 159 // extracted inside a frame of size aFrame, and scaled up to and displayed 160 // at a size of aDisplay. You should validate the frame, picture, and 161 // display regions before using them to display video frames. 162 bool IsValidVideoRegion(const gfx::IntSize& aFrame, 163 const gfx::IntRect& aPicture, 164 const gfx::IntSize& aDisplay); 165 166 // Template to automatically set a variable to a value on scope exit. 167 // Useful for unsetting flags, etc. 168 template <typename T> 169 class AutoSetOnScopeExit { 170 public: 171 AutoSetOnScopeExit(T& aVar, T aValue) : mVar(aVar), mValue(aValue) {} 172 ~AutoSetOnScopeExit() { mVar = mValue; } 173 174 private: 175 T& mVar; 176 const T mValue; 177 }; 178 179 enum class MediaThreadType { 180 SUPERVISOR, // MediaFormatReader, RemoteDecoderManager, MediaDecodeTask and 181 // others 182 PLATFORM_DECODER, // MediaDataDecoder 183 PLATFORM_ENCODER, // MediaDataEncoder 184 WEBRTC_CALL_THREAD, 185 WEBRTC_WORKER, 186 MDSM, // MediaDecoderStateMachine 187 }; 188 // Returns the thread pool that is shared amongst all decoder state machines 189 // for decoding streams. 190 already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType); 191 192 // Extracts the H.264/AVC profile and level from an H.264 codecs string. 193 // H.264 codecs parameters have a type defined as avc1.PPCCLL, where 194 // PP = profile_idc, CC = constraint_set flags, LL = level_idc. 195 // See 196 // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html 197 // for more details. 198 // Returns false on failure. 199 enum class H264CodecStringStrictness { 200 Lenient, // Allow and returns invalid profile, constraint and level values. 201 // It is necessary to accept those strings for Web Compat reasons. 202 Strict // Rejects invalid profile, constraint and level values. 203 }; 204 bool ExtractH264CodecDetails(const nsAString& aCodecs, uint8_t& aProfile, 205 uint8_t& aConstraint, H264_LEVEL& aLevel, 206 H264CodecStringStrictness aStrictness); 207 208 struct VideoColorSpace { 209 // Default values are set according to 210 // https://www.webmproject.org/vp9/mp4/#optional-fields 211 // and https://aomediacodec.github.io/av1-isobmff/#codecsparam 212 gfx::CICP::ColourPrimaries mPrimaries = gfx::CICP::CP_BT709; 213 gfx::CICP::TransferCharacteristics mTransfer = gfx::CICP::TC_BT709; 214 gfx::CICP::MatrixCoefficients mMatrix = gfx::CICP::MC_BT709; 215 gfx::ColorRange mRange = gfx::ColorRange::LIMITED; 216 217 bool operator==(const VideoColorSpace& aOther) const { 218 return mPrimaries == aOther.mPrimaries && mTransfer == aOther.mTransfer && 219 mMatrix == aOther.mMatrix && mRange == aOther.mRange; 220 } 221 bool operator!=(const VideoColorSpace& aOther) const { 222 return !(*this == aOther); 223 } 224 }; 225 226 // Extracts the VPX codecs parameter string. 227 // See https://www.webmproject.org/vp9/mp4/#codecs-parameter-string 228 // for more details. 229 // Returns false on failure. 230 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile, 231 uint8_t& aLevel, uint8_t& aBitDepth); 232 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile, 233 uint8_t& aLevel, uint8_t& aBitDepth, 234 uint8_t& aChromaSubsampling, 235 mozilla::VideoColorSpace& aColorSpace); 236 237 // Extracts AV1 codecs parameter string. 238 // See https://aomediacodec.github.io/av1-isobmff/#codecsparam 239 // Returns false if the codec is invalid. 240 bool ExtractAV1CodecDetails(const nsAString& aCodec, uint8_t& aProfile, 241 uint8_t& aLevel, uint8_t& aTier, uint8_t& aBitDepth, 242 bool& aMonochrome, bool& aSubsamplingX, 243 bool& aSubsamplingY, uint8_t& aChromaSamplePosition, 244 mozilla::VideoColorSpace& aColorSpace); 245 246 // Use a cryptographic quality PRNG to generate raw random bytes 247 // and convert that to a base64 string. 248 nsresult GenerateRandomName(nsCString& aOutSalt, uint32_t aLength); 249 250 // This version returns a string suitable for use as a file or URL 251 // path. This is based on code from nsExternalAppHandler::SetUpTempFile. 252 nsresult GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength); 253 254 already_AddRefed<TaskQueue> CreateMediaDecodeTaskQueue(const char* aName); 255 256 // Iteratively invokes aWork until aCondition returns true, or aWork returns 257 // false. Use this rather than a while loop to avoid bogarting the task queue. 258 template <class Work, class Condition> 259 RefPtr<GenericPromise> InvokeUntil(Work aWork, Condition aCondition) { 260 RefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__); 261 262 if (aCondition()) { 263 p->Resolve(true, __func__); 264 } 265 266 struct Helper { 267 static void Iteration(const RefPtr<GenericPromise::Private>& aPromise, 268 Work aLocalWork, Condition aLocalCondition) { 269 if (!aLocalWork()) { 270 aPromise->Reject(NS_ERROR_FAILURE, __func__); 271 } else if (aLocalCondition()) { 272 aPromise->Resolve(true, __func__); 273 } else { 274 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 275 "InvokeUntil::Helper::Iteration", 276 [aPromise, aLocalWork, aLocalCondition]() { 277 Iteration(aPromise, aLocalWork, aLocalCondition); 278 }); 279 AbstractThread::GetCurrent()->Dispatch(r.forget()); 280 } 281 } 282 }; 283 284 Helper::Iteration(p, aWork, aCondition); 285 return p; 286 } 287 288 // Simple timer to run a runnable after a timeout. 289 class SimpleTimer : public nsITimerCallback, public nsINamed { 290 public: 291 NS_DECL_ISUPPORTS 292 NS_DECL_NSINAMED 293 294 // Create a new timer to run aTask after aTimeoutMs milliseconds 295 // on thread aTarget. If aTarget is null, task is run on the main thread. 296 static already_AddRefed<SimpleTimer> Create( 297 nsIRunnable* aTask, uint32_t aTimeoutMs, 298 nsIEventTarget* aTarget = nullptr); 299 void Cancel(); 300 301 NS_IMETHOD Notify(nsITimer* timer) override; 302 303 private: 304 virtual ~SimpleTimer() = default; 305 nsresult Init(nsIRunnable* aTask, uint32_t aTimeoutMs, 306 nsIEventTarget* aTarget); 307 308 RefPtr<nsIRunnable> mTask; 309 nsCOMPtr<nsITimer> mTimer; 310 }; 311 312 void LogToBrowserConsole(const nsAString& aMsg); 313 314 bool ParseMIMETypeString(const nsAString& aMIMEType, 315 nsString& aOutContainerType, 316 nsTArray<nsString>& aOutCodecs); 317 318 bool ParseCodecsString(const nsAString& aCodecs, 319 nsTArray<nsString>& aOutCodecs); 320 321 bool IsH264CodecString(const nsAString& aCodec); 322 bool IsAllowedH264Codec(const nsAString& aCodec); 323 324 bool IsH265CodecString(const nsAString& aCodec); 325 326 bool IsAACCodecString(const nsAString& aCodec); 327 328 bool IsVP8CodecString(const nsAString& aCodec); 329 330 bool IsVP9CodecString(const nsAString& aCodec); 331 332 bool IsAV1CodecString(const nsAString& aCodec); 333 334 // Try and create a TrackInfo with a given codec MIME type. 335 UniquePtr<TrackInfo> CreateTrackInfoWithMIMEType( 336 const nsACString& aCodecMIMEType); 337 338 // Try and create a TrackInfo with a given codec MIME type, and optional extra 339 // parameters from a container type (its MIME type and codecs are ignored). 340 UniquePtr<TrackInfo> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( 341 const nsACString& aCodecMIMEType, const MediaContainerType& aContainerType); 342 343 namespace detail { 344 345 // aString should start with aMajor + '/'. 346 constexpr bool StartsWithMIMETypeMajor(const char* aString, const char* aMajor, 347 size_t aMajorRemaining) { 348 return (aMajorRemaining == 0 && *aString == '/') || 349 (*aString == *aMajor && 350 StartsWithMIMETypeMajor(aString + 1, aMajor + 1, 351 aMajorRemaining - 1)); 352 } 353 354 // aString should only contain [a-z0-9\-\.] and a final '\0'. 355 constexpr bool EndsWithMIMESubtype(const char* aString, size_t aRemaining) { 356 return aRemaining == 0 || (((*aString >= 'a' && *aString <= 'z') || 357 (*aString >= '0' && *aString <= '9') || 358 *aString == '-' || *aString == '.') && 359 EndsWithMIMESubtype(aString + 1, aRemaining - 1)); 360 } 361 362 // Simple MIME-type literal string checker with a given (major) type. 363 // Only accepts "{aMajor}/[a-z0-9\-\.]+". 364 template <size_t MajorLengthPlus1> 365 constexpr bool IsMIMETypeWithMajor(const char* aString, size_t aLength, 366 const char (&aMajor)[MajorLengthPlus1]) { 367 return aLength > MajorLengthPlus1 && // Major + '/' + at least 1 char 368 StartsWithMIMETypeMajor(aString, aMajor, MajorLengthPlus1 - 1) && 369 EndsWithMIMESubtype(aString + MajorLengthPlus1, 370 aLength - MajorLengthPlus1); 371 } 372 373 } // namespace detail 374 375 // Simple MIME-type string checker. 376 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+". 377 // Add more if necessary. 378 constexpr bool IsMediaMIMEType(const char* aString, size_t aLength) { 379 return detail::IsMIMETypeWithMajor(aString, aLength, "application") || 380 detail::IsMIMETypeWithMajor(aString, aLength, "audio") || 381 detail::IsMIMETypeWithMajor(aString, aLength, "video"); 382 } 383 384 // Simple MIME-type string literal checker. 385 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+". 386 // Add more if necessary. 387 template <size_t LengthPlus1> 388 constexpr bool IsMediaMIMEType(const char (&aString)[LengthPlus1]) { 389 return IsMediaMIMEType(aString, LengthPlus1 - 1); 390 } 391 392 // Simple MIME-type string checker. 393 // Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+". 394 // Add more if necessary. 395 inline bool IsMediaMIMEType(const nsACString& aString) { 396 return IsMediaMIMEType(aString.Data(), aString.Length()); 397 } 398 399 enum class StringListRangeEmptyItems { 400 // Skip all empty items (empty string will process nothing) 401 // E.g.: "a,,b" -> ["a", "b"], "" -> nothing 402 Skip, 403 // Process all, except if string is empty 404 // E.g.: "a,,b" -> ["a", "", "b"], "" -> nothing 405 ProcessEmptyItems, 406 // Process all, including 1 empty item in an empty string 407 // E.g.: "a,,b" -> ["a", "", "b"], "" -> [""] 408 ProcessAll 409 }; 410 411 template <typename String, 412 StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip> 413 class StringListRange { 414 using CharType = typename String::char_type; 415 using Pointer = const CharType*; 416 417 public: 418 // Iterator into range, trims items and optionally skips empty items. 419 class Iterator { 420 public: 421 bool operator!=(const Iterator& a) const { 422 return mStart != a.mStart || mEnd != a.mEnd; 423 } 424 Iterator& operator++() { 425 SearchItemAt(mComma + 1); 426 return *this; 427 } 428 // DereferencedType should be 'const nsDependent[C]String' pointing into 429 // mList (which is 'const ns[C]String&'). 430 using DereferencedType = decltype(Substring(Pointer(), Pointer())); 431 DereferencedType operator*() { return Substring(mStart, mEnd); } 432 433 private: 434 friend class StringListRange; 435 Iterator(const CharType* aRangeStart, uint32_t aLength) 436 : mRangeEnd(aRangeStart + aLength), 437 mStart(nullptr), 438 mEnd(nullptr), 439 mComma(nullptr) { 440 SearchItemAt(aRangeStart); 441 } 442 void SearchItemAt(Pointer start) { 443 // First, skip leading whitespace. 444 for (Pointer p = start;; ++p) { 445 if (p >= mRangeEnd) { 446 if (p > mRangeEnd + 447 (empties != StringListRangeEmptyItems::Skip ? 1 : 0)) { 448 p = mRangeEnd + 449 (empties != StringListRangeEmptyItems::Skip ? 1 : 0); 450 } 451 mStart = mEnd = mComma = p; 452 return; 453 } 454 auto c = *p; 455 if (c == CharType(',')) { 456 // Comma -> Empty item -> Skip or process? 457 if (empties != StringListRangeEmptyItems::Skip) { 458 mStart = mEnd = mComma = p; 459 return; 460 } 461 } else if (c != CharType(' ')) { 462 mStart = p; 463 break; 464 } 465 } 466 // Find comma, recording start of trailing space. 467 Pointer trailingWhitespace = nullptr; 468 for (Pointer p = mStart + 1;; ++p) { 469 if (p >= mRangeEnd) { 470 mEnd = trailingWhitespace ? trailingWhitespace : p; 471 mComma = p; 472 return; 473 } 474 auto c = *p; 475 if (c == CharType(',')) { 476 mEnd = trailingWhitespace ? trailingWhitespace : p; 477 mComma = p; 478 return; 479 } 480 if (c == CharType(' ')) { 481 // Found a whitespace -> Record as trailing if not first one. 482 if (!trailingWhitespace) { 483 trailingWhitespace = p; 484 } 485 } else { 486 // Found a non-whitespace -> Reset trailing whitespace if needed. 487 if (trailingWhitespace) { 488 trailingWhitespace = nullptr; 489 } 490 } 491 } 492 } 493 const Pointer mRangeEnd; 494 Pointer mStart; 495 Pointer mEnd; 496 Pointer mComma; 497 }; 498 499 explicit StringListRange(const String& aList) : mList(aList) {} 500 Iterator begin() const { 501 return Iterator( 502 mList.Data() + 503 ((empties == StringListRangeEmptyItems::ProcessEmptyItems && 504 mList.Length() == 0) 505 ? 1 506 : 0), 507 mList.Length()); 508 } 509 Iterator end() const { 510 return Iterator(mList.Data() + mList.Length() + 511 (empties != StringListRangeEmptyItems::Skip ? 1 : 0), 512 0); 513 } 514 515 private: 516 const String& mList; 517 }; 518 519 template <StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip, 520 typename String> 521 StringListRange<String, empties> MakeStringListRange(const String& aList) { 522 return StringListRange<String, empties>(aList); 523 } 524 525 template <StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip, 526 typename ListString, typename ItemString> 527 static bool StringListContains(const ListString& aList, 528 const ItemString& aItem) { 529 for (const auto& listItem : MakeStringListRange<empties>(aList)) { 530 if (listItem.Equals(aItem)) { 531 return true; 532 } 533 } 534 return false; 535 } 536 537 inline void AppendStringIfNotEmpty(nsACString& aDest, nsACString&& aSrc) { 538 if (!aSrc.IsEmpty()) { 539 aDest.Append("\n"_ns); 540 aDest.Append(aSrc); 541 } 542 } 543 544 // Returns true if we're running on a cellular connection; 2G, 3G, etc. 545 // Main thread only. 546 bool OnCellularConnection(); 547 548 inline gfx::YUVColorSpace DefaultColorSpace(const gfx::IntSize& aSize) { 549 return aSize.height < 720 ? gfx::YUVColorSpace::BT601 550 : gfx::YUVColorSpace::BT709; 551 } 552 553 bool IsWaveMimetype(const nsACString& aMimeType); 554 555 void DetermineResolutionForTelemetry(const MediaInfo& aInfo, 556 nsCString& aResolutionOut); 557 558 // True if given MediaCodecsSupported contains any hardware decoding support. 559 bool ContainHardwareCodecsSupported( 560 const media::MediaCodecsSupported& aSupport); 561 562 } // end namespace mozilla 563 564 #endif