VideoDecoder.cpp (34005B)
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 #include "mozilla/dom/VideoDecoder.h" 8 9 #include "DecoderTraits.h" 10 #include "DecoderTypes.h" 11 #include "GPUVideoImage.h" 12 #include "H264.h" 13 #include "ImageContainer.h" 14 #include "MediaContainerType.h" 15 #include "MediaData.h" 16 #include "VideoUtils.h" 17 #include "WebCodecsUtils.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/Logging.h" 20 #include "mozilla/Maybe.h" 21 #include "mozilla/RefPtr.h" 22 #include "mozilla/Try.h" 23 #include "mozilla/dom/EncodedVideoChunk.h" 24 #include "mozilla/dom/EncodedVideoChunkBinding.h" 25 #include "mozilla/dom/ImageUtils.h" 26 #include "mozilla/dom/Promise.h" 27 #include "mozilla/dom/VideoColorSpaceBinding.h" 28 #include "mozilla/dom/VideoDecoderBinding.h" 29 #include "mozilla/dom/VideoFrameBinding.h" 30 #include "mozilla/dom/WebCodecsUtils.h" 31 #include "nsPrintfCString.h" 32 33 #ifdef XP_MACOSX 34 # include "MacIOSurfaceImage.h" 35 #elif MOZ_WAYLAND 36 # include "mozilla/layers/DMABUFSurfaceImage.h" 37 # include "mozilla/widget/DMABufSurface.h" 38 #endif 39 40 extern mozilla::LazyLogModule gWebCodecsLog; 41 42 namespace mozilla::dom { 43 44 #ifdef LOG_INTERNAL 45 # undef LOG_INTERNAL 46 #endif // LOG_INTERNAL 47 #define LOG_INTERNAL(level, msg, ...) \ 48 MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__)) 49 50 #ifdef LOG 51 # undef LOG 52 #endif // LOG 53 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__) 54 55 #ifdef LOGW 56 # undef LOGW 57 #endif // LOGW 58 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__) 59 60 #ifdef LOGE 61 # undef LOGE 62 #endif // LOGE 63 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__) 64 65 #ifdef LOGV 66 # undef LOGV 67 #endif // LOGV 68 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__) 69 70 NS_IMPL_CYCLE_COLLECTION_INHERITED(VideoDecoder, DOMEventTargetHelper, 71 mErrorCallback, mOutputCallback) 72 NS_IMPL_ADDREF_INHERITED(VideoDecoder, DOMEventTargetHelper) 73 NS_IMPL_RELEASE_INHERITED(VideoDecoder, DOMEventTargetHelper) 74 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VideoDecoder) 75 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 76 77 /* 78 * Below are helper classes 79 */ 80 81 VideoDecoderConfigInternal::VideoDecoderConfigInternal( 82 const nsAString& aCodec, Maybe<uint32_t>&& aCodedHeight, 83 Maybe<uint32_t>&& aCodedWidth, Maybe<VideoColorSpaceInternal>&& aColorSpace, 84 already_AddRefed<MediaByteBuffer> aDescription, 85 Maybe<uint32_t>&& aDisplayAspectHeight, 86 Maybe<uint32_t>&& aDisplayAspectWidth, 87 const HardwareAcceleration& aHardwareAcceleration, 88 Maybe<bool>&& aOptimizeForLatency) 89 : mCodec(aCodec), 90 mCodedHeight(std::move(aCodedHeight)), 91 mCodedWidth(std::move(aCodedWidth)), 92 mColorSpace(std::move(aColorSpace)), 93 mDescription(aDescription), 94 mDisplayAspectHeight(std::move(aDisplayAspectHeight)), 95 mDisplayAspectWidth(std::move(aDisplayAspectWidth)), 96 mHardwareAcceleration(aHardwareAcceleration), 97 mOptimizeForLatency(std::move(aOptimizeForLatency)) {}; 98 99 /*static*/ 100 RefPtr<VideoDecoderConfigInternal> VideoDecoderConfigInternal::Create( 101 const VideoDecoderConfig& aConfig) { 102 nsCString errorMessage; 103 if (!VideoDecoderTraits::Validate(aConfig, errorMessage)) { 104 LOGE("Failed to create VideoDecoderConfigInternal: %s", errorMessage.get()); 105 return nullptr; 106 } 107 108 RefPtr<MediaByteBuffer> description; 109 if (aConfig.mDescription.WasPassed()) { 110 auto rv = GetExtraDataFromArrayBuffer(aConfig.mDescription.Value()); 111 if (rv.isErr()) { // Invalid description data. 112 LOGE( 113 "Failed to create VideoDecoderConfigInternal due to invalid " 114 "description data. Error: 0x%08" PRIx32, 115 static_cast<uint32_t>(rv.unwrapErr())); 116 return nullptr; 117 } 118 description = rv.unwrap(); 119 } 120 121 return MakeRefPtr<VideoDecoderConfigInternal>( 122 aConfig.mCodec, OptionalToMaybe(aConfig.mCodedHeight), 123 OptionalToMaybe(aConfig.mCodedWidth), 124 OptionalToMaybe(aConfig.mColorSpace), description.forget(), 125 OptionalToMaybe(aConfig.mDisplayAspectHeight), 126 OptionalToMaybe(aConfig.mDisplayAspectWidth), 127 aConfig.mHardwareAcceleration, 128 OptionalToMaybe(aConfig.mOptimizeForLatency)); 129 } 130 131 nsCString VideoDecoderConfigInternal::ToString() const { 132 nsCString rv; 133 134 rv.Append(NS_ConvertUTF16toUTF8(mCodec)); 135 if (mCodedWidth.isSome()) { 136 rv.AppendPrintf("coded: %dx%d", mCodedWidth.value(), mCodedHeight.value()); 137 } 138 if (mDisplayAspectWidth.isSome()) { 139 rv.AppendPrintf("display %dx%d", mDisplayAspectWidth.value(), 140 mDisplayAspectHeight.value()); 141 } 142 if (mColorSpace.isSome()) { 143 rv.AppendPrintf("colorspace %s", "todo"); 144 } 145 if (mDescription) { 146 rv.AppendPrintf("extradata: %zu bytes", mDescription->Length()); 147 } 148 rv.AppendPrintf("hw accel: %s", GetEnumString(mHardwareAcceleration).get()); 149 if (mOptimizeForLatency.isSome()) { 150 rv.AppendPrintf("optimize for latency: %s", 151 mOptimizeForLatency.value() ? "true" : "false"); 152 } 153 154 return rv; 155 } 156 157 /* 158 * The followings are helpers for VideoDecoder methods 159 */ 160 161 struct MIMECreateParam { 162 explicit MIMECreateParam(const VideoDecoderConfigInternal& aConfig) 163 : mCodec(aConfig.mCodec), 164 mWidth(aConfig.mCodedWidth), 165 mHeight(aConfig.mCodedHeight) {} 166 explicit MIMECreateParam(const VideoDecoderConfig& aConfig) 167 : mCodec(aConfig.mCodec), 168 mWidth(OptionalToMaybe(aConfig.mCodedWidth)), 169 mHeight(OptionalToMaybe(aConfig.mCodedHeight)) {} 170 171 const nsString mCodec; 172 const Maybe<uint32_t> mWidth; 173 const Maybe<uint32_t> mHeight; 174 }; 175 176 static nsTArray<nsCString> GuessMIMETypes(const MIMECreateParam& aParam) { 177 const auto codec = NS_ConvertUTF16toUTF8(aParam.mCodec); 178 nsTArray<nsCString> types; 179 for (const nsCString& container : GuessContainers(aParam.mCodec)) { 180 nsPrintfCString mime("video/%s; codecs=%s", container.get(), codec.get()); 181 if (aParam.mWidth) { 182 mime.AppendPrintf("; width=%d", *aParam.mWidth); 183 } 184 if (aParam.mHeight) { 185 mime.AppendPrintf("; height=%d", *aParam.mHeight); 186 } 187 types.AppendElement(mime); 188 } 189 return types; 190 } 191 192 // https://w3c.github.io/webcodecs/#check-configuration-support 193 template <typename Config> 194 static bool CanDecode(const Config& aConfig) { 195 // TODO: Enable WebCodecs on Android (Bug 1840508) 196 if (IsOnAndroid()) { 197 return false; 198 } 199 if (!IsSupportedVideoCodec(aConfig.mCodec)) { 200 return false; 201 } 202 203 // TODO (1880326): code below is wrongly using the logic of HTMLMediaElement 204 // for determining if a codec can be played, and incorrect codec string for 205 // h264 are accepted for HTMLMediaElement for compat reasons. Perform stricter 206 // check here until we fix it for real. 207 if (IsH264CodecString(aConfig.mCodec)) { 208 uint8_t profile, constraint; 209 H264_LEVEL level; 210 bool supported = 211 ExtractH264CodecDetails(aConfig.mCodec, profile, constraint, level, 212 H264CodecStringStrictness::Strict); 213 if (!supported) { 214 return false; 215 } 216 } 217 218 // TODO (1880326): Instead of calling CanHandleContainerType with the guessed 219 // the containers, DecoderTraits should provide an API to tell if a codec is 220 // decodable or not. 221 for (const nsCString& mime : GuessMIMETypes(MIMECreateParam(aConfig))) { 222 if (Maybe<MediaContainerType> containerType = 223 MakeMediaExtendedMIMEType(mime)) { 224 if (DecoderTraits::CanHandleContainerType( 225 *containerType, nullptr /* DecoderDoctorDiagnostics */) != 226 CANPLAY_NO) { 227 return true; 228 } 229 } 230 } 231 return false; 232 } 233 234 static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo( 235 const VideoDecoderConfigInternal& aConfig) { 236 // TODO: Instead of calling GetTracksInfo with the guessed containers, 237 // DecoderTraits should provide an API to create the TrackInfo directly. 238 for (const nsCString& mime : GuessMIMETypes(MIMECreateParam(aConfig))) { 239 if (Maybe<MediaContainerType> containerType = 240 MakeMediaExtendedMIMEType(mime)) { 241 if (nsTArray<UniquePtr<TrackInfo>> tracks = 242 DecoderTraits::GetTracksInfo(*containerType); 243 !tracks.IsEmpty()) { 244 return tracks; 245 } 246 } 247 } 248 return {}; 249 } 250 251 static Result<Ok, nsresult> CloneConfiguration( 252 RootedDictionary<VideoDecoderConfig>& aDest, JSContext* aCx, 253 const VideoDecoderConfig& aConfig, ErrorResult& aRv) { 254 DebugOnly<nsCString> str; 255 MOZ_ASSERT(VideoDecoderTraits::Validate(aConfig, str)); 256 257 aDest.mCodec = aConfig.mCodec; 258 if (aConfig.mCodedHeight.WasPassed()) { 259 aDest.mCodedHeight.Construct(aConfig.mCodedHeight.Value()); 260 } 261 if (aConfig.mCodedWidth.WasPassed()) { 262 aDest.mCodedWidth.Construct(aConfig.mCodedWidth.Value()); 263 } 264 if (aConfig.mColorSpace.WasPassed()) { 265 aDest.mColorSpace.Construct(aConfig.mColorSpace.Value()); 266 } 267 if (aConfig.mDescription.WasPassed()) { 268 aDest.mDescription.Construct(); 269 MOZ_TRY(CloneBuffer(aCx, aDest.mDescription.Value(), 270 aConfig.mDescription.Value(), aRv)); 271 } 272 if (aConfig.mDisplayAspectHeight.WasPassed()) { 273 aDest.mDisplayAspectHeight.Construct(aConfig.mDisplayAspectHeight.Value()); 274 } 275 if (aConfig.mDisplayAspectWidth.WasPassed()) { 276 aDest.mDisplayAspectWidth.Construct(aConfig.mDisplayAspectWidth.Value()); 277 } 278 aDest.mHardwareAcceleration = aConfig.mHardwareAcceleration; 279 if (aConfig.mOptimizeForLatency.WasPassed()) { 280 aDest.mOptimizeForLatency.Construct(aConfig.mOptimizeForLatency.Value()); 281 } 282 283 return Ok(); 284 } 285 286 static Maybe<VideoPixelFormat> GuessPixelFormat(layers::Image* aImage) { 287 if (aImage) { 288 // TODO: Implement ImageUtils::Impl for MacIOSurfaceImage and 289 // DMABUFSurfaceImage? 290 if (aImage->AsPlanarYCbCrImage() || aImage->AsNVImage()) { 291 const ImageUtils imageUtils(aImage); 292 Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat(); 293 Maybe<VideoPixelFormat> f = 294 format.isSome() ? ImageBitmapFormatToVideoPixelFormat(format.value()) 295 : Nothing(); 296 297 // ImageBitmapFormat cannot distinguish YUV420 or YUV420A. 298 bool hasAlpha = aImage->AsPlanarYCbCrImage() && 299 aImage->AsPlanarYCbCrImage()->GetData() && 300 aImage->AsPlanarYCbCrImage()->GetData()->mAlpha; 301 if (f && *f == VideoPixelFormat::I420 && hasAlpha) { 302 return Some(VideoPixelFormat::I420A); 303 } 304 return f; 305 } 306 if (layers::GPUVideoImage* image = aImage->AsGPUVideoImage()) { 307 RefPtr<layers::ImageBridgeChild> imageBridge = 308 layers::ImageBridgeChild::GetSingleton(); 309 layers::TextureClient* texture = image->GetTextureClient(imageBridge); 310 if (NS_WARN_IF(!texture)) { 311 return Nothing(); 312 } 313 return SurfaceFormatToVideoPixelFormat(texture->GetFormat()); 314 } 315 #ifdef XP_MACOSX 316 if (layers::MacIOSurfaceImage* image = aImage->AsMacIOSurfaceImage()) { 317 MOZ_ASSERT(image->GetSurface()); 318 return SurfaceFormatToVideoPixelFormat(image->GetSurface()->GetFormat()); 319 } 320 #endif 321 #ifdef MOZ_WAYLAND 322 if (layers::DMABUFSurfaceImage* image = aImage->AsDMABUFSurfaceImage()) { 323 MOZ_ASSERT(image->GetSurface()); 324 return SurfaceFormatToVideoPixelFormat(image->GetSurface()->GetFormat()); 325 } 326 #endif 327 } 328 LOGW("Failed to get pixel format from layers::Image"); 329 return Nothing(); 330 } 331 332 static VideoColorSpaceInternal GuessColorSpace( 333 const layers::PlanarYCbCrData* aData) { 334 if (!aData) { 335 LOGE("nullptr in GuessColorSpace"); 336 return {}; 337 } 338 339 VideoColorSpaceInternal colorSpace; 340 colorSpace.mFullRange = Some(ToFullRange(aData->mColorRange)); 341 if (Maybe<VideoMatrixCoefficients> m = 342 ToMatrixCoefficients(aData->mYUVColorSpace)) { 343 colorSpace.mMatrix = ToMatrixCoefficients(aData->mYUVColorSpace); 344 colorSpace.mPrimaries = ToPrimaries(aData->mColorPrimaries); 345 } 346 if (!colorSpace.mPrimaries) { 347 LOG("Missing primaries, guessing from colorspace"); 348 // Make an educated guess based on the coefficients. 349 colorSpace.mPrimaries = colorSpace.mMatrix.map([](const auto& aMatrix) { 350 switch (aMatrix) { 351 case VideoMatrixCoefficients::Bt2020_ncl: 352 return VideoColorPrimaries::Bt2020; 353 case VideoMatrixCoefficients::Rgb: 354 case VideoMatrixCoefficients::Bt470bg: 355 case VideoMatrixCoefficients::Smpte170m: 356 LOGW( 357 "Warning: Falling back to BT709 when attempting to determine the " 358 "primaries function of a YCbCr buffer"); 359 [[fallthrough]]; 360 case VideoMatrixCoefficients::Bt709: 361 return VideoColorPrimaries::Bt709; 362 } 363 MOZ_ASSERT_UNREACHABLE("Unexpected matrix coefficients"); 364 LOGW( 365 "Warning: Falling back to BT709 due to unexpected matrix " 366 "coefficients " 367 "when attempting to determine the primaries function of a YCbCr " 368 "buffer"); 369 return VideoColorPrimaries::Bt709; 370 }); 371 } 372 373 if (Maybe<VideoTransferCharacteristics> c = 374 ToTransferCharacteristics(aData->mTransferFunction)) { 375 colorSpace.mTransfer = Some(*c); 376 } 377 if (!colorSpace.mTransfer) { 378 LOG("Missing transfer characteristics, guessing from colorspace"); 379 colorSpace.mTransfer = Some(([&] { 380 switch (aData->mYUVColorSpace) { 381 case gfx::YUVColorSpace::Identity: 382 return VideoTransferCharacteristics::Iec61966_2_1; 383 case gfx::YUVColorSpace::BT2020: 384 return VideoTransferCharacteristics::Pq; 385 case gfx::YUVColorSpace::BT601: 386 LOGW( 387 "Warning: Falling back to BT709 when attempting to determine the " 388 "transfer function of a MacIOSurface"); 389 [[fallthrough]]; 390 case gfx::YUVColorSpace::BT709: 391 return VideoTransferCharacteristics::Bt709; 392 } 393 MOZ_ASSERT_UNREACHABLE("Unexpected color space"); 394 LOGW( 395 "Warning: Falling back to BT709 due to unexpected color space " 396 "when attempting to determine the transfer function of a " 397 "MacIOSurface"); 398 return VideoTransferCharacteristics::Bt709; 399 })()); 400 } 401 402 return colorSpace; 403 } 404 405 #ifdef XP_MACOSX 406 static VideoColorSpaceInternal GuessColorSpace(const MacIOSurface* aSurface) { 407 if (!aSurface) { 408 return {}; 409 } 410 VideoColorSpaceInternal colorSpace; 411 colorSpace.mFullRange = Some(aSurface->IsFullRange()); 412 if (Maybe<dom::VideoMatrixCoefficients> m = 413 ToMatrixCoefficients(aSurface->GetYUVColorSpace())) { 414 colorSpace.mMatrix = Some(*m); 415 } 416 if (Maybe<VideoColorPrimaries> p = ToPrimaries(aSurface->mColorPrimaries)) { 417 colorSpace.mPrimaries = Some(*p); 418 } 419 // Make an educated guess based on the coefficients. 420 if (aSurface->GetYUVColorSpace() == gfx::YUVColorSpace::Identity) { 421 colorSpace.mTransfer = Some(VideoTransferCharacteristics::Iec61966_2_1); 422 } else if (aSurface->GetYUVColorSpace() == gfx::YUVColorSpace::BT709) { 423 colorSpace.mTransfer = Some(VideoTransferCharacteristics::Bt709); 424 } else if (aSurface->GetYUVColorSpace() == gfx::YUVColorSpace::BT2020) { 425 colorSpace.mTransfer = Some(VideoTransferCharacteristics::Pq); 426 } else { 427 LOGW( 428 "Warning: Falling back to BT709 when attempting to determine the " 429 "transfer function of a MacIOSurface"); 430 colorSpace.mTransfer = Some(VideoTransferCharacteristics::Bt709); 431 } 432 433 return colorSpace; 434 } 435 #endif 436 #ifdef MOZ_WAYLAND 437 // TODO: Set DMABufSurface::IsFullRange() to const so aSurface can be const. 438 static VideoColorSpaceInternal GuessColorSpace(DMABufSurface* aSurface) { 439 if (!aSurface) { 440 return {}; 441 } 442 VideoColorSpaceInternal colorSpace; 443 colorSpace.mFullRange = Some(aSurface->IsFullRange()); 444 if (Maybe<dom::VideoMatrixCoefficients> m = 445 ToMatrixCoefficients(aSurface->GetYUVColorSpace())) { 446 colorSpace.mMatrix = Some(*m); 447 } 448 // No other color space information. 449 return colorSpace; 450 } 451 #endif 452 453 static VideoColorSpaceInternal GuessColorSpace(layers::Image* aImage) { 454 if (aImage) { 455 if (layers::PlanarYCbCrImage* image = aImage->AsPlanarYCbCrImage()) { 456 return GuessColorSpace(image->GetData()); 457 } 458 if (layers::NVImage* image = aImage->AsNVImage()) { 459 return GuessColorSpace(image->GetData()); 460 } 461 if (layers::GPUVideoImage* image = aImage->AsGPUVideoImage()) { 462 VideoColorSpaceInternal colorSpace; 463 colorSpace.mFullRange = 464 Some(image->GetColorRange() != gfx::ColorRange::LIMITED); 465 colorSpace.mMatrix = ToMatrixCoefficients(image->GetYUVColorSpace()); 466 colorSpace.mPrimaries = ToPrimaries(image->GetColorPrimaries()); 467 colorSpace.mTransfer = 468 ToTransferCharacteristics(image->GetTransferFunction()); 469 // In some circumstances, e.g. on Linux software decoding when using 470 // VPXDecoder and RDD, the primaries aren't set correctly. Make a good 471 // guess based on the other params. Fixing this is tracked in 472 // https://bugzilla.mozilla.org/show_bug.cgi?id=1869825 473 if (!colorSpace.mPrimaries) { 474 if (colorSpace.mMatrix.isSome()) { 475 switch (colorSpace.mMatrix.value()) { 476 case VideoMatrixCoefficients::Rgb: 477 case VideoMatrixCoefficients::Bt709: 478 colorSpace.mPrimaries = Some(VideoColorPrimaries::Bt709); 479 break; 480 case VideoMatrixCoefficients::Bt470bg: 481 case VideoMatrixCoefficients::Smpte170m: 482 colorSpace.mPrimaries = Some(VideoColorPrimaries::Bt470bg); 483 break; 484 case VideoMatrixCoefficients::Bt2020_ncl: 485 colorSpace.mPrimaries = Some(VideoColorPrimaries::Bt2020); 486 break; 487 }; 488 } 489 } 490 return colorSpace; 491 } 492 #ifdef XP_MACOSX 493 // TODO: Make sure VideoFrame can interpret its internal data in different 494 // formats. 495 if (layers::MacIOSurfaceImage* image = aImage->AsMacIOSurfaceImage()) { 496 return GuessColorSpace(image->GetSurface()); 497 } 498 #endif 499 #ifdef MOZ_WAYLAND 500 // TODO: Make sure VideoFrame can interpret its internal data in different 501 // formats. 502 if (layers::DMABUFSurfaceImage* image = aImage->AsDMABUFSurfaceImage()) { 503 return GuessColorSpace(image->GetSurface()); 504 } 505 #endif 506 } 507 LOGW("Failed to get color space from layers::Image"); 508 return {}; 509 } 510 511 static Result<gfx::IntSize, nsresult> AdjustDisplaySize( 512 const uint32_t aDisplayAspectWidth, const uint32_t aDisplayAspectHeight, 513 const gfx::IntSize& aDisplaySize) { 514 if (aDisplayAspectHeight == 0) { 515 return Err(NS_ERROR_ILLEGAL_VALUE); 516 } 517 518 const double aspectRatio = 519 static_cast<double>(aDisplayAspectWidth) / aDisplayAspectHeight; 520 521 double w = aDisplaySize.width; 522 double h = aDisplaySize.height; 523 524 if (aspectRatio >= w / h) { 525 // Adjust w to match the aspect ratio 526 w = aspectRatio * h; 527 } else { 528 // Adjust h to match the aspect ratio 529 h = w / aspectRatio; 530 } 531 532 w = std::round(w); 533 h = std::round(h); 534 constexpr double MAX = static_cast<double>( 535 std::numeric_limits<decltype(gfx::IntSize::width)>::max()); 536 if (w > MAX || h > MAX || w < 1.0 || h < 1.0) { 537 return Err(NS_ERROR_ILLEGAL_VALUE); 538 } 539 return gfx::IntSize(static_cast<decltype(gfx::IntSize::width)>(w), 540 static_cast<decltype(gfx::IntSize::height)>(h)); 541 } 542 543 // https://w3c.github.io/webcodecs/#create-a-videoframe 544 static RefPtr<VideoFrame> CreateVideoFrame( 545 nsIGlobalObject* aGlobalObject, const VideoData* aData, int64_t aTimestamp, 546 uint64_t aDuration, const Maybe<uint32_t> aDisplayAspectWidth, 547 const Maybe<uint32_t> aDisplayAspectHeight, 548 const VideoColorSpaceInternal& aColorSpace) { 549 MOZ_ASSERT(aGlobalObject); 550 MOZ_ASSERT(aData); 551 MOZ_ASSERT((!!aDisplayAspectWidth) == (!!aDisplayAspectHeight)); 552 553 Maybe<VideoPixelFormat> format = GuessPixelFormat(aData->mImage.get()); 554 gfx::IntSize displaySize = aData->mDisplay; 555 if (aDisplayAspectWidth && aDisplayAspectHeight) { 556 auto r = AdjustDisplaySize(*aDisplayAspectWidth, *aDisplayAspectHeight, 557 displaySize); 558 if (r.isOk()) { 559 displaySize = r.unwrap(); 560 } 561 } 562 563 return MakeRefPtr<VideoFrame>(aGlobalObject, aData->mImage, format, 564 aData->mImage->GetSize(), 565 aData->mImage->GetPictureRect(), displaySize, 566 Some(aDuration), aTimestamp, aColorSpace); 567 } 568 569 /* static */ 570 bool VideoDecoderTraits::IsSupported( 571 const VideoDecoderConfigInternal& aConfig) { 572 return CanDecode(aConfig); 573 } 574 575 /* static */ 576 Result<UniquePtr<TrackInfo>, nsresult> VideoDecoderTraits::CreateTrackInfo( 577 const VideoDecoderConfigInternal& aConfig) { 578 LOG("Create a VideoInfo from %s config", aConfig.ToString().get()); 579 580 nsTArray<UniquePtr<TrackInfo>> tracks = GetTracksInfo(aConfig); 581 if (tracks.Length() != 1 || tracks[0]->GetType() != TrackInfo::kVideoTrack) { 582 LOGE("Failed to get TrackInfo"); 583 return Err(NS_ERROR_INVALID_ARG); 584 } 585 586 UniquePtr<TrackInfo> track(std::move(tracks[0])); 587 VideoInfo* vi = track->GetAsVideoInfo(); 588 if (!vi) { 589 LOGE("Failed to get VideoInfo"); 590 return Err(NS_ERROR_INVALID_ARG); 591 } 592 593 constexpr uint32_t MAX = static_cast<uint32_t>( 594 std::numeric_limits<decltype(gfx::IntSize::width)>::max()); 595 if (aConfig.mCodedHeight.isSome()) { 596 if (aConfig.mCodedHeight.value() > MAX) { 597 LOGE("codedHeight overflows"); 598 return Err(NS_ERROR_INVALID_ARG); 599 } 600 vi->mImage.height = static_cast<decltype(gfx::IntSize::height)>( 601 aConfig.mCodedHeight.value()); 602 } 603 if (aConfig.mCodedWidth.isSome()) { 604 if (aConfig.mCodedWidth.value() > MAX) { 605 LOGE("codedWidth overflows"); 606 return Err(NS_ERROR_INVALID_ARG); 607 } 608 vi->mImage.width = 609 static_cast<decltype(gfx::IntSize::width)>(aConfig.mCodedWidth.value()); 610 } 611 612 if (aConfig.mDisplayAspectHeight.isSome()) { 613 if (aConfig.mDisplayAspectHeight.value() > MAX) { 614 LOGE("displayAspectHeight overflows"); 615 return Err(NS_ERROR_INVALID_ARG); 616 } 617 vi->mDisplay.height = static_cast<decltype(gfx::IntSize::height)>( 618 aConfig.mDisplayAspectHeight.value()); 619 } 620 if (aConfig.mDisplayAspectWidth.isSome()) { 621 if (aConfig.mDisplayAspectWidth.value() > MAX) { 622 LOGE("displayAspectWidth overflows"); 623 return Err(NS_ERROR_INVALID_ARG); 624 } 625 vi->mDisplay.width = static_cast<decltype(gfx::IntSize::width)>( 626 aConfig.mDisplayAspectWidth.value()); 627 } 628 629 if (aConfig.mColorSpace.isSome()) { 630 const VideoColorSpaceInternal& colorSpace(aConfig.mColorSpace.value()); 631 if (colorSpace.mFullRange.isSome()) { 632 vi->mColorRange = ToColorRange(colorSpace.mFullRange.value()); 633 } 634 if (colorSpace.mMatrix.isSome()) { 635 vi->mColorSpace.emplace(ToColorSpace(colorSpace.mMatrix.value())); 636 } 637 // Some decoders get their primaries and transfer function from the codec 638 // string, and it's already set here. This is the case for VP9 decoders. 639 if (colorSpace.mPrimaries.isSome()) { 640 auto primaries = ToPrimaries(colorSpace.mPrimaries.value()); 641 if (vi->mColorPrimaries.isSome()) { 642 if (vi->mColorPrimaries.value() != primaries) { 643 LOG("Conflict between decoder config and codec string, keeping codec " 644 "string primaries of %d", 645 static_cast<int>(primaries)); 646 } 647 } else { 648 vi->mColorPrimaries.emplace(primaries); 649 } 650 } 651 if (colorSpace.mTransfer.isSome()) { 652 auto primaries = ToTransferFunction(colorSpace.mTransfer.value()); 653 if (vi->mTransferFunction.isSome()) { 654 if (vi->mTransferFunction.value() != primaries) { 655 LOG("Conflict between decoder config and codec string, keeping codec " 656 "string transfer function of %d", 657 static_cast<int>(vi->mTransferFunction.value())); 658 } 659 } else { 660 vi->mTransferFunction.emplace( 661 ToTransferFunction(colorSpace.mTransfer.value())); 662 } 663 } 664 } 665 666 if (aConfig.mDescription) { 667 if (!aConfig.mDescription->IsEmpty()) { 668 LOG("The given config has %zu bytes of description data", 669 aConfig.mDescription->Length()); 670 if (vi->mExtraData) { 671 LOGW("The default extra data is overwritten"); 672 } 673 vi->mExtraData = aConfig.mDescription; 674 } 675 676 // TODO: Make this utility and replace the similar one in MP4Demuxer.cpp. 677 if (vi->mExtraData && !vi->mExtraData->IsEmpty() && 678 IsH264CodecString(aConfig.mCodec)) { 679 SPSData spsdata; 680 if (H264::DecodeSPSFromExtraData(vi->mExtraData.get(), spsdata) && 681 spsdata.pic_width > 0 && spsdata.pic_height > 0 && 682 H264::EnsureSPSIsSane(spsdata)) { 683 LOG("H264 sps data - pic size: %d x %d, display size: %d x %d", 684 spsdata.pic_width, spsdata.pic_height, spsdata.display_width, 685 spsdata.display_height); 686 687 if (spsdata.pic_width > MAX || spsdata.pic_height > MAX || 688 spsdata.display_width > MAX || spsdata.display_height > MAX) { 689 LOGE("H264 width or height in sps data overflows"); 690 return Err(NS_ERROR_INVALID_ARG); 691 } 692 693 vi->mImage.width = 694 static_cast<decltype(gfx::IntSize::width)>(spsdata.pic_width); 695 vi->mImage.height = 696 static_cast<decltype(gfx::IntSize::height)>(spsdata.pic_height); 697 vi->mDisplay.width = 698 static_cast<decltype(gfx::IntSize::width)>(spsdata.display_width); 699 vi->mDisplay.height = 700 static_cast<decltype(gfx::IntSize::height)>(spsdata.display_height); 701 } 702 } 703 } else { 704 vi->mExtraData = new MediaByteBuffer(); 705 } 706 707 LOG("Created a VideoInfo for decoder - %s", vi->ToString().get()); 708 709 return track; 710 } 711 712 // https://w3c.github.io/webcodecs/#valid-videodecoderconfig 713 /* static */ 714 bool VideoDecoderTraits::Validate(const VideoDecoderConfig& aConfig, 715 nsCString& aErrorMessage) { 716 Maybe<nsString> codec = ParseCodecString(aConfig.mCodec); 717 if (!codec || codec->IsEmpty()) { 718 aErrorMessage.AssignLiteral("Invalid codec string"); 719 LOGE("%s", aErrorMessage.get()); 720 return false; 721 } 722 723 if (aConfig.mCodedWidth.WasPassed() != aConfig.mCodedHeight.WasPassed()) { 724 aErrorMessage.AppendPrintf( 725 "Missing coded %s", 726 aConfig.mCodedWidth.WasPassed() ? "height" : "width"); 727 LOGE("%s", aErrorMessage.get()); 728 return false; 729 } 730 if (aConfig.mCodedWidth.WasPassed() && 731 (aConfig.mCodedWidth.Value() == 0 || aConfig.mCodedHeight.Value() == 0)) { 732 aErrorMessage.AssignLiteral("codedWidth and/or codedHeight can't be zero"); 733 LOGE("%s", aErrorMessage.get()); 734 return false; 735 } 736 737 if (aConfig.mDisplayAspectWidth.WasPassed() != 738 aConfig.mDisplayAspectHeight.WasPassed()) { 739 aErrorMessage.AppendPrintf( 740 "Missing display aspect %s", 741 aConfig.mDisplayAspectWidth.WasPassed() ? "height" : "width"); 742 LOGE("%s", aErrorMessage.get()); 743 return false; 744 } 745 if (aConfig.mDisplayAspectWidth.WasPassed() && 746 (aConfig.mDisplayAspectWidth.Value() == 0 || 747 aConfig.mDisplayAspectHeight.Value() == 0)) { 748 aErrorMessage.AssignLiteral( 749 "display aspect width and height cannot be zero"); 750 LOGE("%s", aErrorMessage.get()); 751 return false; 752 } 753 754 bool detached = 755 aConfig.mDescription.WasPassed() && 756 (aConfig.mDescription.Value().IsArrayBuffer() 757 ? JS::ArrayBuffer::fromObject( 758 aConfig.mDescription.Value().GetAsArrayBuffer().Obj()) 759 .isDetached() 760 : JS::ArrayBufferView::fromObject( 761 aConfig.mDescription.Value().GetAsArrayBufferView().Obj()) 762 .isDetached()); 763 764 if (detached) { 765 aErrorMessage.AssignLiteral("description is detached."); 766 LOGE("%s", aErrorMessage.get()); 767 return false; 768 } 769 770 return true; 771 } 772 773 /* static */ 774 RefPtr<VideoDecoderConfigInternal> VideoDecoderTraits::CreateConfigInternal( 775 const VideoDecoderConfig& aConfig) { 776 return VideoDecoderConfigInternal::Create(aConfig); 777 } 778 779 /* static */ 780 bool VideoDecoderTraits::IsKeyChunk(const EncodedVideoChunk& aInput) { 781 return aInput.Type() == EncodedVideoChunkType::Key; 782 } 783 784 /* static */ 785 UniquePtr<EncodedVideoChunkData> VideoDecoderTraits::CreateInputInternal( 786 const EncodedVideoChunk& aInput) { 787 return aInput.Clone(); 788 } 789 790 /* 791 * Below are VideoDecoder implementation 792 */ 793 794 VideoDecoder::VideoDecoder(nsIGlobalObject* aParent, 795 RefPtr<WebCodecsErrorCallback>&& aErrorCallback, 796 RefPtr<VideoFrameOutputCallback>&& aOutputCallback) 797 : DecoderTemplate(aParent, std::move(aErrorCallback), 798 std::move(aOutputCallback)) { 799 MOZ_ASSERT(mErrorCallback); 800 MOZ_ASSERT(mOutputCallback); 801 LOG("VideoDecoder %p ctor", this); 802 } 803 804 VideoDecoder::~VideoDecoder() { LOG("VideoDecoder %p dtor", this); } 805 806 JSObject* VideoDecoder::WrapObject(JSContext* aCx, 807 JS::Handle<JSObject*> aGivenProto) { 808 AssertIsOnOwningThread(); 809 810 return VideoDecoder_Binding::Wrap(aCx, this, aGivenProto); 811 } 812 813 // https://w3c.github.io/webcodecs/#dom-videodecoder-videodecoder 814 /* static */ 815 already_AddRefed<VideoDecoder> VideoDecoder::Constructor( 816 const GlobalObject& aGlobal, const VideoDecoderInit& aInit, 817 ErrorResult& aRv) { 818 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 819 if (!global) { 820 aRv.Throw(NS_ERROR_FAILURE); 821 return nullptr; 822 } 823 824 return MakeAndAddRef<VideoDecoder>( 825 global.get(), RefPtr<WebCodecsErrorCallback>(aInit.mError), 826 RefPtr<VideoFrameOutputCallback>(aInit.mOutput)); 827 } 828 829 // https://w3c.github.io/webcodecs/#dom-videodecoder-isconfigsupported 830 /* static */ 831 already_AddRefed<Promise> VideoDecoder::IsConfigSupported( 832 const GlobalObject& aGlobal, const VideoDecoderConfig& aConfig, 833 ErrorResult& aRv) { 834 LOG("VideoDecoder::IsConfigSupported, config: %s", 835 NS_ConvertUTF16toUTF8(aConfig.mCodec).get()); 836 837 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 838 if (!global) { 839 aRv.Throw(NS_ERROR_FAILURE); 840 return nullptr; 841 } 842 843 RefPtr<Promise> p = Promise::Create(global.get(), aRv); 844 if (NS_WARN_IF(aRv.Failed())) { 845 return p.forget(); 846 } 847 848 nsCString errorMessage; 849 if (!VideoDecoderTraits::Validate(aConfig, errorMessage)) { 850 p->MaybeRejectWithTypeError(nsPrintfCString( 851 "IsConfigSupported: config is invalid: %s", errorMessage.get())); 852 return p.forget(); 853 } 854 855 RootedDictionary<VideoDecoderConfig> config(aGlobal.Context()); 856 auto r = CloneConfiguration(config, aGlobal.Context(), aConfig, aRv); 857 if (r.isErr()) { 858 // This can only be an OOM: all members to clone are known to be valid 859 // because this is check by ::Validate above. 860 MOZ_ASSERT(r.inspectErr() == NS_ERROR_OUT_OF_MEMORY && 861 aRv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)); 862 return p.forget(); 863 } 864 865 // This is incomplete and will be implemented fully in bug 1967793 866 auto configInternal = VideoDecoderConfigInternal::Create(aConfig); 867 ApplyResistFingerprintingIfNeeded(configInternal, global); 868 869 bool canDecode = CanDecode(*configInternal); 870 RootedDictionary<VideoDecoderSupport> s(aGlobal.Context()); 871 s.mConfig.Construct(std::move(config)); 872 s.mSupported.Construct(canDecode); 873 874 p->MaybeResolve(s); 875 return p.forget(); 876 } 877 878 already_AddRefed<MediaRawData> VideoDecoder::InputDataToMediaRawData( 879 UniquePtr<EncodedVideoChunkData>&& aData, TrackInfo& aInfo, 880 const VideoDecoderConfigInternal& aConfig) { 881 AssertIsOnOwningThread(); 882 MOZ_ASSERT(aInfo.GetAsVideoInfo()); 883 884 if (!aData) { 885 LOGE("No data for conversion"); 886 return nullptr; 887 } 888 889 RefPtr<MediaRawData> sample = aData->TakeData(); 890 if (!sample) { 891 LOGE("Take no data for conversion"); 892 return nullptr; 893 } 894 895 // aExtraData is either provided by Configure() or a default one created for 896 // the decoder creation. If it's created for decoder creation only, we don't 897 // set it to sample. 898 if (aConfig.mDescription && aInfo.GetAsVideoInfo()->mExtraData) { 899 sample->mExtraData = aInfo.GetAsVideoInfo()->mExtraData; 900 } 901 902 LOGV( 903 "EncodedVideoChunkData %p converted to %zu-byte MediaRawData - time: " 904 "%" PRIi64 "us, timecode: %" PRIi64 "us, duration: %" PRIi64 905 "us, key-frame: %s, has extra data: %s", 906 aData.get(), sample->Size(), sample->mTime.ToMicroseconds(), 907 sample->mTimecode.ToMicroseconds(), sample->mDuration.ToMicroseconds(), 908 sample->mKeyframe ? "yes" : "no", sample->mExtraData ? "yes" : "no"); 909 910 return sample.forget(); 911 } 912 913 nsTArray<RefPtr<VideoFrame>> VideoDecoder::DecodedDataToOutputType( 914 nsIGlobalObject* aGlobalObject, const nsTArray<RefPtr<MediaData>>&& aData, 915 const VideoDecoderConfigInternal& aConfig) { 916 AssertIsOnOwningThread(); 917 918 nsTArray<RefPtr<VideoFrame>> frames; 919 for (const RefPtr<MediaData>& data : aData) { 920 MOZ_RELEASE_ASSERT(data->mType == MediaData::Type::VIDEO_DATA); 921 RefPtr<const VideoData> d(data->As<const VideoData>()); 922 VideoColorSpaceInternal colorSpace; 923 // Determine which color space to use: prefer the color space as configured 924 // at the decoder level, if it has one, otherwise look at the underlying 925 // image and make a guess. 926 if (aConfig.mColorSpace.isSome() && 927 aConfig.mColorSpace->mPrimaries.isSome() && 928 aConfig.mColorSpace->mTransfer.isSome() && 929 aConfig.mColorSpace->mMatrix.isSome()) { 930 colorSpace = aConfig.mColorSpace.value(); 931 } else { 932 colorSpace = GuessColorSpace(d->mImage.get()); 933 } 934 frames.AppendElement(CreateVideoFrame( 935 aGlobalObject, d.get(), d->mTime.ToMicroseconds(), 936 static_cast<uint64_t>(d->mDuration.ToMicroseconds()), 937 aConfig.mDisplayAspectWidth, aConfig.mDisplayAspectHeight, colorSpace)); 938 } 939 return frames; 940 } 941 942 #undef LOG 943 #undef LOGW 944 #undef LOGE 945 #undef LOGV 946 #undef LOG_INTERNAL 947 948 } // namespace mozilla::dom