VideoEncoder.cpp (23215B)
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/VideoEncoder.h" 8 9 #include "EncoderConfig.h" 10 #include "EncoderTraits.h" 11 #include "ImageContainer.h" 12 #include "VideoUtils.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/Logging.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/dom/EncodedVideoChunk.h" 17 #include "mozilla/dom/EncodedVideoChunkBinding.h" 18 #include "mozilla/dom/ImageUtils.h" 19 #include "mozilla/dom/Promise.h" 20 #include "mozilla/dom/VideoColorSpace.h" 21 #include "mozilla/dom/VideoColorSpaceBinding.h" 22 #include "mozilla/dom/VideoEncoderBinding.h" 23 #include "mozilla/dom/VideoFrame.h" 24 #include "mozilla/dom/VideoFrameBinding.h" 25 #include "mozilla/dom/WebCodecsUtils.h" 26 #include "nsIGlobalObject.h" 27 #include "nsPrintfCString.h" 28 #include "nsRFPService.h" 29 30 extern mozilla::LazyLogModule gWebCodecsLog; 31 32 namespace mozilla::dom { 33 34 #ifdef LOG_INTERNAL 35 # undef LOG_INTERNAL 36 #endif // LOG_INTERNAL 37 #define LOG_INTERNAL(level, msg, ...) \ 38 MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__)) 39 40 #ifdef LOG 41 # undef LOG 42 #endif // LOG 43 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__) 44 45 #ifdef LOGW 46 # undef LOGW 47 #endif // LOGW 48 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__) 49 50 #ifdef LOGE 51 # undef LOGE 52 #endif // LOGE 53 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__) 54 55 #ifdef LOGV 56 # undef LOGV 57 #endif // LOGV 58 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__) 59 60 NS_IMPL_CYCLE_COLLECTION_INHERITED(VideoEncoder, DOMEventTargetHelper, 61 mErrorCallback, mOutputCallback) 62 NS_IMPL_ADDREF_INHERITED(VideoEncoder, DOMEventTargetHelper) 63 NS_IMPL_RELEASE_INHERITED(VideoEncoder, DOMEventTargetHelper) 64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VideoEncoder) 65 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 66 67 VideoEncoderConfigInternal::VideoEncoderConfigInternal( 68 const nsAString& aCodec, uint32_t aWidth, uint32_t aHeight, 69 Maybe<uint32_t>&& aDisplayWidth, Maybe<uint32_t>&& aDisplayHeight, 70 Maybe<uint32_t>&& aBitrate, Maybe<double>&& aFramerate, 71 const HardwareAcceleration& aHardwareAcceleration, 72 const AlphaOption& aAlpha, Maybe<nsString>&& aScalabilityMode, 73 const VideoEncoderBitrateMode& aBitrateMode, 74 const LatencyMode& aLatencyMode, Maybe<nsString>&& aContentHint) 75 : mCodec(aCodec), 76 mWidth(aWidth), 77 mHeight(aHeight), 78 mDisplayWidth(std::move(aDisplayWidth)), 79 mDisplayHeight(std::move(aDisplayHeight)), 80 mBitrate(std::move(aBitrate)), 81 mFramerate(std::move(aFramerate)), 82 mHardwareAcceleration(aHardwareAcceleration), 83 mAlpha(aAlpha), 84 mScalabilityMode(std::move(aScalabilityMode)), 85 mBitrateMode(aBitrateMode), 86 mLatencyMode(aLatencyMode), 87 mContentHint(std::move(aContentHint)) {} 88 89 VideoEncoderConfigInternal::VideoEncoderConfigInternal( 90 const VideoEncoderConfigInternal& aConfig) 91 : mCodec(aConfig.mCodec), 92 mWidth(aConfig.mWidth), 93 mHeight(aConfig.mHeight), 94 mDisplayWidth(aConfig.mDisplayWidth), 95 mDisplayHeight(aConfig.mDisplayHeight), 96 mBitrate(aConfig.mBitrate), 97 mFramerate(aConfig.mFramerate), 98 mHardwareAcceleration(aConfig.mHardwareAcceleration), 99 mAlpha(aConfig.mAlpha), 100 mScalabilityMode(aConfig.mScalabilityMode), 101 mBitrateMode(aConfig.mBitrateMode), 102 mLatencyMode(aConfig.mLatencyMode), 103 mContentHint(aConfig.mContentHint), 104 mAvc(aConfig.mAvc) {} 105 106 VideoEncoderConfigInternal::VideoEncoderConfigInternal( 107 const VideoEncoderConfig& aConfig) 108 : mCodec(aConfig.mCodec), 109 mWidth(aConfig.mWidth), 110 mHeight(aConfig.mHeight), 111 mDisplayWidth(OptionalToMaybe(aConfig.mDisplayWidth)), 112 mDisplayHeight(OptionalToMaybe(aConfig.mDisplayHeight)), 113 mBitrate(OptionalToMaybe(aConfig.mBitrate)), 114 mFramerate(OptionalToMaybe(aConfig.mFramerate)), 115 mHardwareAcceleration(aConfig.mHardwareAcceleration), 116 mAlpha(aConfig.mAlpha), 117 mScalabilityMode(OptionalToMaybe(aConfig.mScalabilityMode)), 118 mBitrateMode(aConfig.mBitrateMode), 119 mLatencyMode(aConfig.mLatencyMode), 120 mContentHint(OptionalToMaybe(aConfig.mContentHint)), 121 mAvc(OptionalToMaybe(aConfig.mAvc)) {} 122 123 nsCString VideoEncoderConfigInternal::ToString() const { 124 nsCString rv; 125 126 rv.AppendLiteral("Codec: "); 127 rv.Append(NS_ConvertUTF16toUTF8(mCodec)); 128 rv.AppendPrintf(" [%" PRIu32 "x%" PRIu32 "]", mWidth, mHeight); 129 if (mDisplayWidth.isSome()) { 130 rv.AppendPrintf(", display[%" PRIu32 "x%" PRIu32 "]", mDisplayWidth.value(), 131 mDisplayHeight.value()); 132 } 133 if (mBitrate.isSome()) { 134 rv.AppendPrintf(", %" PRIu32 "kbps", mBitrate.value()); 135 } 136 if (mFramerate.isSome()) { 137 rv.AppendPrintf(", %lfHz", mFramerate.value()); 138 } 139 rv.AppendPrintf(", hw: %s", GetEnumString(mHardwareAcceleration).get()); 140 rv.AppendPrintf(", alpha: %s", GetEnumString(mAlpha).get()); 141 if (mScalabilityMode.isSome()) { 142 rv.AppendPrintf(", scalability mode: %s", 143 NS_ConvertUTF16toUTF8(mScalabilityMode.value()).get()); 144 } 145 rv.AppendPrintf(", bitrate mode: %s", GetEnumString(mBitrateMode).get()); 146 rv.AppendPrintf(", latency mode: %s", GetEnumString(mLatencyMode).get()); 147 if (mContentHint.isSome()) { 148 rv.AppendPrintf(", content hint: %s", 149 NS_ConvertUTF16toUTF8(mContentHint.value()).get()); 150 } 151 if (mAvc.isSome()) { 152 rv.AppendPrintf(", avc-specific: %s", GetEnumString(mAvc->mFormat).get()); 153 } 154 155 return rv; 156 } 157 158 template <typename T> 159 bool MaybeAreEqual(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { 160 if (aLHS.isSome() && aRHS.isSome()) { 161 return aLHS.value() == aRHS.value(); 162 } 163 if (aLHS.isNothing() && aRHS.isNothing()) { 164 return true; 165 } 166 return false; 167 } 168 169 bool VideoEncoderConfigInternal::Equals( 170 const VideoEncoderConfigInternal& aOther) const { 171 bool sameCodecSpecific = true; 172 if ((mAvc.isSome() && aOther.mAvc.isSome() && 173 mAvc->mFormat != aOther.mAvc->mFormat) || 174 mAvc.isSome() != aOther.mAvc.isSome()) { 175 sameCodecSpecific = false; 176 } 177 return mCodec.Equals(aOther.mCodec) && mWidth == aOther.mWidth && 178 mHeight == aOther.mHeight && 179 MaybeAreEqual(mDisplayWidth, aOther.mDisplayWidth) && 180 MaybeAreEqual(mDisplayHeight, aOther.mDisplayHeight) && 181 MaybeAreEqual(mBitrate, aOther.mBitrate) && 182 MaybeAreEqual(mFramerate, aOther.mFramerate) && 183 mHardwareAcceleration == aOther.mHardwareAcceleration && 184 mAlpha == aOther.mAlpha && 185 MaybeAreEqual(mScalabilityMode, aOther.mScalabilityMode) && 186 mBitrateMode == aOther.mBitrateMode && 187 mLatencyMode == aOther.mLatencyMode && 188 MaybeAreEqual(mContentHint, aOther.mContentHint) && sameCodecSpecific; 189 } 190 191 bool VideoEncoderConfigInternal::CanReconfigure( 192 const VideoEncoderConfigInternal& aOther) const { 193 return mCodec.Equals(aOther.mCodec) && 194 mHardwareAcceleration == aOther.mHardwareAcceleration; 195 } 196 197 EncoderConfig VideoEncoderConfigInternal::ToEncoderConfig() const { 198 Usage usage; 199 if (mLatencyMode == LatencyMode::Quality) { 200 usage = Usage::Record; 201 } else { 202 usage = Usage::Realtime; 203 } 204 HardwarePreference hwPref = HardwarePreference::None; 205 if (mHardwareAcceleration == 206 mozilla::dom::HardwareAcceleration::Prefer_hardware) { 207 hwPref = HardwarePreference::RequireHardware; 208 } else if (mHardwareAcceleration == 209 mozilla::dom::HardwareAcceleration::Prefer_software) { 210 hwPref = HardwarePreference::RequireSoftware; 211 } 212 CodecType codecType; 213 auto maybeCodecType = CodecStringToCodecType(mCodec); 214 if (maybeCodecType.isSome()) { 215 codecType = maybeCodecType.value(); 216 } else { 217 MOZ_CRASH("The string should always contain a valid codec at this point."); 218 } 219 EncoderConfig::CodecSpecific specific{void_t{}}; 220 if (codecType == CodecType::H264) { 221 uint8_t profile, constraints; 222 H264_LEVEL level; 223 H264BitStreamFormat format; 224 if (mAvc) { 225 format = mAvc->mFormat == AvcBitstreamFormat::Annexb 226 ? H264BitStreamFormat::ANNEXB 227 : H264BitStreamFormat::AVC; 228 } else { 229 format = H264BitStreamFormat::AVC; 230 } 231 if (ExtractH264CodecDetails(mCodec, profile, constraints, level, 232 H264CodecStringStrictness::Strict)) { 233 if (profile == H264_PROFILE_BASE || profile == H264_PROFILE_MAIN || 234 profile == H264_PROFILE_EXTENDED || profile == H264_PROFILE_HIGH) { 235 specific.emplace<H264Specific>(static_cast<H264_PROFILE>(profile), 236 level, format); 237 } 238 } 239 } 240 uint8_t numTemporalLayers = 1; 241 ScalabilityMode scalabilityMode; 242 if (mScalabilityMode) { 243 if (mScalabilityMode->EqualsLiteral("L1T2")) { 244 scalabilityMode = ScalabilityMode::L1T2; 245 numTemporalLayers = 2; 246 } else if (mScalabilityMode->EqualsLiteral("L1T3")) { 247 scalabilityMode = ScalabilityMode::L1T3; 248 numTemporalLayers = 3; 249 } else { 250 scalabilityMode = ScalabilityMode::None; 251 } 252 } else { 253 scalabilityMode = ScalabilityMode::None; 254 } 255 // Only for vp9, not vp8 256 if (codecType == CodecType::VP9) { 257 uint8_t profile, level, bitdepth, chromasubsampling; 258 mozilla::VideoColorSpace colorspace; 259 DebugOnly<bool> rv = ExtractVPXCodecDetails( 260 mCodec, profile, level, bitdepth, chromasubsampling, colorspace); 261 #ifdef DEBUG 262 if (!rv) { 263 LOGE("Error extracting VPX codec details, non fatal"); 264 } 265 #endif 266 specific.emplace<VP9Specific>( 267 VPXComplexity::Normal, /* Complexity */ 268 true, /* Resilience */ 269 numTemporalLayers, /* Number of temporal layers */ 270 true, /* Denoising */ 271 false, /* Auto resize */ 272 false, /* Frame dropping */ 273 true, /* Adaptive Qp */ 274 1, /* Number of spatial layers */ 275 false /* Flexible */ 276 ); 277 } 278 // For real-time usage, typically used in web conferencing, YUV420 is the most 279 // common format and is set as the default. Otherwise, Gecko's preferred 280 // format, BGRA, is assumed. 281 EncoderConfig::SampleFormat format(usage == Usage::Realtime 282 ? dom::ImageBitmapFormat::YUV420P 283 : dom::ImageBitmapFormat::BGRA32); 284 if (usage == Usage::Realtime) { 285 format.mColorSpace.mRange.emplace(gfx::ColorRange::LIMITED); 286 } 287 return EncoderConfig(codecType, {mWidth, mHeight}, usage, format, 288 SaturatingCast<uint32_t>(mFramerate.refOr(0.f)), 0, 289 mBitrate.refOr(0), 0, 0, 290 (mBitrateMode == VideoEncoderBitrateMode::Constant) 291 ? mozilla::BitrateMode::Constant 292 : mozilla::BitrateMode::Variable, 293 hwPref, scalabilityMode, specific); 294 } 295 already_AddRefed<WebCodecsConfigurationChangeList> 296 VideoEncoderConfigInternal::Diff( 297 const VideoEncoderConfigInternal& aOther) const { 298 auto list = MakeRefPtr<WebCodecsConfigurationChangeList>(); 299 if (!mCodec.Equals(aOther.mCodec)) { 300 list->Push(CodecChange{aOther.mCodec}); 301 } 302 // Both must always be present, when a `VideoEncoderConfig` is passed to 303 // `configure`. 304 if (mWidth != aOther.mWidth || mHeight != aOther.mHeight) { 305 list->Push(DimensionsChange{gfx::IntSize{aOther.mWidth, aOther.mHeight}}); 306 } 307 // Similarly, both must always be present, when a `VideoEncoderConfig` is 308 // passed to `configure`. 309 if (!MaybeAreEqual(mDisplayWidth, aOther.mDisplayWidth) || 310 !MaybeAreEqual(mDisplayHeight, aOther.mDisplayHeight)) { 311 Maybe<gfx::IntSize> displaySize = 312 aOther.mDisplayWidth.isSome() 313 ? Some(gfx::IntSize{aOther.mDisplayWidth.value(), 314 aOther.mDisplayHeight.value()}) 315 : Nothing(); 316 list->Push(DisplayDimensionsChange{displaySize}); 317 } 318 if (!MaybeAreEqual(mBitrate, aOther.mBitrate)) { 319 list->Push(BitrateChange{aOther.mBitrate}); 320 } 321 if (!MaybeAreEqual(mFramerate, aOther.mFramerate)) { 322 list->Push(FramerateChange{aOther.mFramerate}); 323 } 324 if (mHardwareAcceleration != aOther.mHardwareAcceleration) { 325 list->Push(HardwareAccelerationChange{aOther.mHardwareAcceleration}); 326 } 327 if (mAlpha != aOther.mAlpha) { 328 list->Push(AlphaChange{aOther.mAlpha}); 329 } 330 if (!MaybeAreEqual(mScalabilityMode, aOther.mScalabilityMode)) { 331 list->Push(ScalabilityModeChange{aOther.mScalabilityMode}); 332 } 333 if (mBitrateMode != aOther.mBitrateMode) { 334 list->Push(BitrateModeChange{aOther.mBitrateMode}); 335 } 336 if (mLatencyMode != aOther.mLatencyMode) { 337 list->Push(LatencyModeChange{aOther.mLatencyMode}); 338 } 339 if (!MaybeAreEqual(mContentHint, aOther.mContentHint)) { 340 list->Push(ContentHintChange{aOther.mContentHint}); 341 } 342 return list.forget(); 343 } 344 345 // https://w3c.github.io/webcodecs/#check-configuration-support 346 static bool CanEncode(const RefPtr<VideoEncoderConfigInternal>& aConfig, 347 nsIGlobalObject* aGlobal) { 348 // TODO: Enable WebCodecs on Android (Bug 1840508) 349 if (IsOnAndroid()) { 350 return false; 351 } 352 if (!IsSupportedVideoCodec(aConfig->mCodec)) { 353 return false; 354 } 355 if (aConfig->mScalabilityMode.isSome()) { 356 // Check if ScalabilityMode string is valid. 357 if (!aConfig->mScalabilityMode->EqualsLiteral("L1T2") && 358 !aConfig->mScalabilityMode->EqualsLiteral("L1T3")) { 359 LOGE("Scalability mode %s not supported for codec: %s", 360 NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.value()).get(), 361 NS_ConvertUTF16toUTF8(aConfig->mCodec).get()); 362 return false; 363 } 364 } 365 366 ApplyResistFingerprintingIfNeeded(aConfig, aGlobal); 367 368 return EncoderSupport::Supports(aConfig); 369 } 370 371 static Result<Ok, nsresult> CloneConfiguration( 372 VideoEncoderConfig& aDest, JSContext* aCx, 373 const VideoEncoderConfig& aConfig) { 374 nsCString errorMessage; 375 MOZ_ASSERT(VideoEncoderTraits::Validate(aConfig, errorMessage)); 376 377 aDest.mCodec = aConfig.mCodec; 378 aDest.mWidth = aConfig.mWidth; 379 aDest.mHeight = aConfig.mHeight; 380 aDest.mAlpha = aConfig.mAlpha; 381 if (aConfig.mBitrate.WasPassed()) { 382 aDest.mBitrate.Construct(aConfig.mBitrate.Value()); 383 } 384 aDest.mBitrateMode = aConfig.mBitrateMode; 385 if (aConfig.mContentHint.WasPassed()) { 386 aDest.mContentHint.Construct(aConfig.mContentHint.Value()); 387 } 388 if (aConfig.mDisplayWidth.WasPassed()) { 389 aDest.mDisplayWidth.Construct(aConfig.mDisplayWidth.Value()); 390 } 391 if (aConfig.mDisplayHeight.WasPassed()) { 392 aDest.mDisplayHeight.Construct(aConfig.mDisplayHeight.Value()); 393 } 394 if (aConfig.mFramerate.WasPassed()) { 395 aDest.mFramerate.Construct(aConfig.mFramerate.Value()); 396 } 397 aDest.mHardwareAcceleration = aConfig.mHardwareAcceleration; 398 aDest.mLatencyMode = aConfig.mLatencyMode; 399 if (aConfig.mScalabilityMode.WasPassed()) { 400 aDest.mScalabilityMode.Construct(aConfig.mScalabilityMode.Value()); 401 } 402 403 // AVC specific 404 if (aConfig.mAvc.WasPassed()) { 405 aDest.mAvc.Construct(aConfig.mAvc.Value()); 406 } 407 408 return Ok(); 409 } 410 411 /* static */ 412 bool VideoEncoderTraits::IsSupported( 413 const VideoEncoderConfigInternal& aConfig) { 414 return CanEncode(MakeRefPtr<VideoEncoderConfigInternal>(aConfig), nullptr); 415 } 416 417 // https://w3c.github.io/webcodecs/#valid-videoencoderconfig 418 /* static */ 419 bool VideoEncoderTraits::Validate(const VideoEncoderConfig& aConfig, 420 nsCString& aErrorMessage) { 421 Maybe<nsString> codec = ParseCodecString(aConfig.mCodec); 422 // 1. 423 if (!codec || codec->IsEmpty()) { 424 aErrorMessage.AssignLiteral( 425 "Invalid VideoEncoderConfig: invalid codec string"); 426 LOGE("%s", aErrorMessage.get()); 427 return false; 428 } 429 430 // 2. 431 if (aConfig.mWidth == 0 || aConfig.mHeight == 0) { 432 aErrorMessage.AppendPrintf("Invalid VideoEncoderConfig: %s equal to 0", 433 aConfig.mWidth == 0 ? "width" : "height"); 434 LOGE("%s", aErrorMessage.get()); 435 return false; 436 } 437 438 // 3. 439 if (aConfig.mDisplayWidth.WasPassed() && aConfig.mDisplayWidth.Value() == 0) { 440 aErrorMessage.AssignLiteral( 441 "Invalid VideoEncoderConfig: displayWidth equal to 0"); 442 LOGE("%s", aErrorMessage.get()); 443 return false; 444 } 445 if (aConfig.mDisplayHeight.WasPassed() && 446 aConfig.mDisplayHeight.Value() == 0) { 447 aErrorMessage.AssignLiteral( 448 "Invalid VideoEncoderConfig: displayHeight equal to 0"); 449 LOGE("%s", aErrorMessage.get()); 450 return false; 451 } 452 453 // https://github.com/w3c/webcodecs/issues/816 454 if ((aConfig.mBitrate.WasPassed() && aConfig.mBitrate.Value() == 0)) { 455 aErrorMessage.AssignLiteral( 456 "Invalid VideoEncoderConfig: bitrate equal to 0"); 457 LOGE("%s", aErrorMessage.get()); 458 return false; 459 } 460 461 return true; 462 } 463 464 /* static */ 465 RefPtr<VideoEncoderConfigInternal> VideoEncoderTraits::CreateConfigInternal( 466 const VideoEncoderConfig& aConfig) { 467 return MakeRefPtr<VideoEncoderConfigInternal>(aConfig); 468 } 469 470 /* static */ 471 RefPtr<mozilla::VideoData> VideoEncoderTraits::CreateInputInternal( 472 const dom::VideoFrame& aInput, 473 const dom::VideoEncoderEncodeOptions& aOptions) { 474 media::TimeUnit duration = 475 aInput.GetDuration().IsNull() 476 ? media::TimeUnit::Zero() 477 : media::TimeUnit::FromMicroseconds( 478 AssertedCast<int64_t>(aInput.GetDuration().Value())); 479 media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aInput.Timestamp()); 480 return VideoData::CreateFromImage( 481 gfx::IntSize{aInput.DisplayWidth(), aInput.DisplayHeight()}, 482 0 /* bytestream offset */, pts, duration, aInput.GetImage(), 483 aOptions.mKeyFrame, pts); 484 } 485 486 /* 487 * Below are VideoEncoder implementation 488 */ 489 490 VideoEncoder::VideoEncoder( 491 nsIGlobalObject* aParent, RefPtr<WebCodecsErrorCallback>&& aErrorCallback, 492 RefPtr<EncodedVideoChunkOutputCallback>&& aOutputCallback) 493 : EncoderTemplate(aParent, std::move(aErrorCallback), 494 std::move(aOutputCallback)) { 495 MOZ_ASSERT(mErrorCallback); 496 MOZ_ASSERT(mOutputCallback); 497 LOG("VideoEncoder %p ctor", this); 498 } 499 500 VideoEncoder::~VideoEncoder() { 501 LOG("VideoEncoder %p dtor", this); 502 (void)ResetInternal(NS_ERROR_DOM_ABORT_ERR); 503 } 504 505 JSObject* VideoEncoder::WrapObject(JSContext* aCx, 506 JS::Handle<JSObject*> aGivenProto) { 507 AssertIsOnOwningThread(); 508 509 return VideoEncoder_Binding::Wrap(aCx, this, aGivenProto); 510 } 511 512 // https://w3c.github.io/webcodecs/#dom-videoencoder-videoencoder 513 /* static */ 514 already_AddRefed<VideoEncoder> VideoEncoder::Constructor( 515 const GlobalObject& aGlobal, const VideoEncoderInit& aInit, 516 ErrorResult& aRv) { 517 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 518 if (!global) { 519 aRv.Throw(NS_ERROR_FAILURE); 520 return nullptr; 521 } 522 523 return MakeAndAddRef<VideoEncoder>( 524 global.get(), RefPtr<WebCodecsErrorCallback>(aInit.mError), 525 RefPtr<EncodedVideoChunkOutputCallback>(aInit.mOutput)); 526 } 527 528 // https://w3c.github.io/webcodecs/#dom-videoencoder-isconfigsupported 529 /* static */ 530 already_AddRefed<Promise> VideoEncoder::IsConfigSupported( 531 const GlobalObject& aGlobal, const VideoEncoderConfig& aConfig, 532 ErrorResult& aRv) { 533 LOG("VideoEncoder::IsConfigSupported, config: %s", 534 NS_ConvertUTF16toUTF8(aConfig.mCodec).get()); 535 536 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 537 if (!global) { 538 aRv.Throw(NS_ERROR_FAILURE); 539 return nullptr; 540 } 541 542 RefPtr<Promise> p = Promise::Create(global.get(), aRv); 543 if (NS_WARN_IF(aRv.Failed())) { 544 return p.forget(); 545 } 546 547 nsCString errorMessage; 548 if (!VideoEncoderTraits::Validate(aConfig, errorMessage)) { 549 p->MaybeRejectWithTypeError(nsPrintfCString( 550 "IsConfigSupported: config is invalid: %s", errorMessage.get())); 551 return p.forget(); 552 } 553 554 // TODO: Move the following works to another thread to unblock the current 555 // thread, as what spec suggests. 556 557 VideoEncoderConfig config; 558 auto r = CloneConfiguration(config, aGlobal.Context(), aConfig); 559 if (r.isErr()) { 560 nsresult e = r.unwrapErr(); 561 LOGE("Failed to clone VideoEncoderConfig. Error: 0x%08" PRIx32, 562 static_cast<uint32_t>(e)); 563 p->MaybeRejectWithTypeError("Failed to clone VideoEncoderConfig"); 564 aRv.Throw(e); 565 return p.forget(); 566 } 567 568 bool canEncode = 569 CanEncode(MakeRefPtr<VideoEncoderConfigInternal>(config), global); 570 VideoEncoderSupport s; 571 s.mConfig.Construct(std::move(config)); 572 s.mSupported.Construct(canEncode); 573 574 p->MaybeResolve(s); 575 return p.forget(); 576 } 577 578 RefPtr<EncodedVideoChunk> VideoEncoder::EncodedDataToOutputType( 579 nsIGlobalObject* aGlobalObject, const RefPtr<MediaRawData>& aData) { 580 AssertIsOnOwningThread(); 581 582 MOZ_RELEASE_ASSERT(aData->mType == MediaData::Type::RAW_DATA); 583 // Package into an EncodedVideoChunk 584 auto buffer = 585 MakeRefPtr<MediaAlignedByteBuffer>(aData->Data(), aData->Size()); 586 auto encodedVideoChunk = MakeRefPtr<EncodedVideoChunk>( 587 aGlobalObject, buffer.forget(), 588 aData->mKeyframe ? EncodedVideoChunkType::Key 589 : EncodedVideoChunkType::Delta, 590 aData->mTime.ToMicroseconds(), 591 aData->mDuration.IsZero() ? Nothing() 592 : Some(aData->mDuration.ToMicroseconds())); 593 return encodedVideoChunk; 594 } 595 596 void VideoEncoder::EncoderConfigToDecoderConfig( 597 JSContext* aCx, const RefPtr<MediaRawData>& aRawData, 598 const VideoEncoderConfigInternal& aSrcConfig, 599 VideoDecoderConfig& aDestConfig) const { 600 MOZ_ASSERT(aCx); 601 602 aDestConfig.mCodec = aSrcConfig.mCodec; 603 aDestConfig.mCodedHeight.Construct(aSrcConfig.mHeight); 604 aDestConfig.mCodedWidth.Construct(aSrcConfig.mWidth); 605 606 // Colorspace is mandatory when outputing a decoder config after encode 607 RootedDictionary<VideoColorSpaceInit> colorSpace(aCx); 608 colorSpace.mFullRange.SetValue(false); 609 colorSpace.mMatrix.SetValue(VideoMatrixCoefficients::Bt709); 610 colorSpace.mPrimaries.SetValue(VideoColorPrimaries::Bt709); 611 colorSpace.mTransfer.SetValue(VideoTransferCharacteristics::Bt709); 612 aDestConfig.mColorSpace.Construct(std::move(colorSpace)); 613 614 if (aRawData->mExtraData && !aRawData->mExtraData->IsEmpty()) { 615 Span<const uint8_t> description(aRawData->mExtraData->Elements(), 616 aRawData->mExtraData->Length()); 617 if (!CopyExtradataToDescription(aCx, description, 618 aDestConfig.mDescription.Construct())) { 619 LOGE("Failed to copy extra data"); 620 } 621 } 622 623 if (aSrcConfig.mDisplayHeight) { 624 aDestConfig.mDisplayAspectHeight.Construct( 625 aSrcConfig.mDisplayHeight.value()); 626 } 627 if (aSrcConfig.mDisplayWidth) { 628 aDestConfig.mDisplayAspectWidth.Construct(aSrcConfig.mDisplayWidth.value()); 629 } 630 aDestConfig.mHardwareAcceleration = aSrcConfig.mHardwareAcceleration; 631 } 632 633 #undef LOG 634 #undef LOGW 635 #undef LOGE 636 #undef LOGV 637 #undef LOG_INTERNAL 638 639 } // namespace mozilla::dom