AppleVTEncoder.cpp (46853B)
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 "AppleVTEncoder.h" 8 9 #include <CoreFoundation/CFArray.h> 10 #include <CoreFoundation/CFByteOrder.h> 11 #include <CoreFoundation/CFDictionary.h> 12 #include <MacTypes.h> 13 14 #include "AnnexB.h" 15 #include "H264.h" 16 #include "ImageContainer.h" 17 #include "mozilla/dom/BindingUtils.h" 18 #include "mozilla/dom/ImageUtils.h" 19 20 namespace mozilla { 21 extern LazyLogModule sPEMLog; 22 #define LOGE(fmt, ...) \ 23 MOZ_LOG(sPEMLog, mozilla::LogLevel::Error, \ 24 ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) 25 #define LOGW(fmt, ...) \ 26 MOZ_LOG(sPEMLog, mozilla::LogLevel::Warning, \ 27 ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) 28 #define LOGD(fmt, ...) \ 29 MOZ_LOG(sPEMLog, mozilla::LogLevel::Debug, \ 30 ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) 31 #define LOGV(fmt, ...) \ 32 MOZ_LOG(sPEMLog, mozilla::LogLevel::Verbose, \ 33 ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) 34 35 static CFDictionaryRef BuildEncoderSpec(const bool aHardwareNotAllowed, 36 const bool aLowLatencyRateControl) { 37 if (__builtin_available(macos 11.3, *)) { 38 if (aLowLatencyRateControl) { 39 // If doing low-latency rate control, the hardware encoder is required. 40 const void* keys[] = { 41 kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, 42 kVTVideoEncoderSpecification_EnableLowLatencyRateControl}; 43 const void* values[] = {kCFBooleanTrue, kCFBooleanTrue}; 44 45 static_assert(std::size(keys) == std::size(values), 46 "Non matching keys/values array size"); 47 return CFDictionaryCreate(kCFAllocatorDefault, keys, values, 48 std::size(keys), &kCFTypeDictionaryKeyCallBacks, 49 &kCFTypeDictionaryValueCallBacks); 50 } 51 } 52 const void* keys[] = { 53 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder}; 54 const void* values[] = {aHardwareNotAllowed ? kCFBooleanFalse 55 : kCFBooleanTrue}; 56 57 static_assert(std::size(keys) == std::size(values), 58 "Non matching keys/values array size"); 59 return CFDictionaryCreate(kCFAllocatorDefault, keys, values, std::size(keys), 60 &kCFTypeDictionaryKeyCallBacks, 61 &kCFTypeDictionaryValueCallBacks); 62 } 63 64 static void FrameCallback(void* aEncoder, void* aFrameRefCon, OSStatus aStatus, 65 VTEncodeInfoFlags aInfoFlags, 66 CMSampleBufferRef aSampleBuffer) { 67 (static_cast<AppleVTEncoder*>(aEncoder)) 68 ->OutputFrame(aStatus, aInfoFlags, aSampleBuffer); 69 } 70 71 bool AppleVTEncoder::SetAverageBitrate(uint32_t aBitsPerSec) { 72 MOZ_ASSERT(mSession); 73 74 SessionPropertyManager mgr(mSession); 75 return mgr.Set(kVTCompressionPropertyKey_AverageBitRate, 76 int64_t(aBitsPerSec)) == noErr; 77 } 78 79 bool AppleVTEncoder::SetConstantBitrate(uint32_t aBitsPerSec) { 80 MOZ_ASSERT(mSession); 81 82 if (__builtin_available(macos 13.0, *)) { 83 SessionPropertyManager mgr(mSession); 84 OSStatus rv = mgr.Set(kVTCompressionPropertyKey_ConstantBitRate, 85 AssertedCast<int32_t>(aBitsPerSec)); 86 if (rv == kVTPropertyNotSupportedErr) { 87 LOGE("Constant bitrate not supported."); 88 } 89 return rv == noErr; 90 } 91 return false; 92 } 93 94 bool AppleVTEncoder::SetBitrateAndMode(BitrateMode aBitrateMode, 95 uint32_t aBitsPerSec) { 96 if (aBitrateMode == BitrateMode::Variable) { 97 return SetAverageBitrate(aBitsPerSec); 98 } 99 return SetConstantBitrate(aBitsPerSec); 100 } 101 102 bool AppleVTEncoder::SetFrameRate(int64_t aFPS) { 103 MOZ_ASSERT(mSession); 104 105 SessionPropertyManager mgr(mSession); 106 return mgr.Set(kVTCompressionPropertyKey_ExpectedFrameRate, aFPS) == noErr; 107 } 108 109 bool AppleVTEncoder::SetRealtime(bool aEnabled) { 110 MOZ_ASSERT(mSession); 111 112 // B-frames has been disabled in Init(), so no need to set it here. 113 114 SessionPropertyManager mgr(mSession); 115 OSStatus status = mgr.Set(kVTCompressionPropertyKey_RealTime, aEnabled); 116 LOGD("%s real time, status: %d", aEnabled ? "Enable" : "Disable", status); 117 if (status != noErr) { 118 return false; 119 } 120 121 if (__builtin_available(macos 11.0, *)) { 122 status = mgr.Set( 123 kVTCompressionPropertyKey_PrioritizeEncodingSpeedOverQuality, aEnabled); 124 LOGD("%s PrioritizeEncodingSpeedOverQuality, status: %d", 125 aEnabled ? "Enable" : "Disable", status); 126 if (status != noErr && status != kVTPropertyNotSupportedErr) { 127 return false; 128 } 129 } 130 131 int32_t maxFrameDelayCount = aEnabled ? 0 : kVTUnlimitedFrameDelayCount; 132 status = 133 mgr.Set(kVTCompressionPropertyKey_MaxFrameDelayCount, maxFrameDelayCount); 134 LOGD("Set max frame delay count to %d, status: %d", maxFrameDelayCount, 135 status); 136 if (status != noErr && status != kVTPropertyNotSupportedErr) { 137 return false; 138 } 139 140 return true; 141 } 142 143 bool AppleVTEncoder::SetProfileLevel(H264_PROFILE aValue) { 144 MOZ_ASSERT(mSession); 145 146 CFStringRef profileLevel = nullptr; 147 switch (aValue) { 148 case H264_PROFILE::H264_PROFILE_BASE: 149 profileLevel = kVTProfileLevel_H264_Baseline_AutoLevel; 150 break; 151 case H264_PROFILE::H264_PROFILE_MAIN: 152 profileLevel = kVTProfileLevel_H264_Main_AutoLevel; 153 break; 154 case H264_PROFILE::H264_PROFILE_HIGH: 155 profileLevel = kVTProfileLevel_H264_High_AutoLevel; 156 break; 157 default: 158 LOGE("Profile %d not handled", static_cast<int>(aValue)); 159 } 160 161 if (profileLevel == nullptr) { 162 return false; 163 } 164 165 SessionPropertyManager mgr(mSession); 166 return mgr.Set(kVTCompressionPropertyKey_ProfileLevel, profileLevel) == noErr; 167 } 168 169 static Maybe<CFStringRef> MapColorPrimaries( 170 const gfx::ColorSpace2& aPrimaries) { 171 switch (aPrimaries) { 172 case gfx::ColorSpace2::Display: 173 return Nothing(); 174 case gfx::ColorSpace2::SRGB: 175 return Some(kCVImageBufferColorPrimaries_P22); 176 case gfx::ColorSpace2::DISPLAY_P3: 177 return Some(kCVImageBufferColorPrimaries_P3_D65); 178 case gfx::ColorSpace2::BT601_525: 179 return Some(kCVImageBufferColorPrimaries_SMPTE_C); 180 case gfx::ColorSpace2::BT709: 181 return Some(kCVImageBufferColorPrimaries_ITU_R_709_2); 182 case gfx::ColorSpace2::BT2020: 183 return Some(kCVImageBufferColorPrimaries_ITU_R_2020); 184 } 185 186 MOZ_ASSERT_UNREACHABLE("Unsupported color primaries"); 187 return Nothing(); 188 } 189 190 static Maybe<CFStringRef> MapYCbCrMatrix(const gfx::YUVColorSpace& aMatrix) { 191 switch (aMatrix) { 192 case gfx::YUVColorSpace::BT601: 193 return Some(kCVImageBufferYCbCrMatrix_ITU_R_601_4); 194 case gfx::YUVColorSpace::BT709: 195 return Some(kCVImageBufferYCbCrMatrix_ITU_R_709_2); 196 case gfx::YUVColorSpace::BT2020: 197 return Some(kCVImageBufferYCbCrMatrix_ITU_R_2020); 198 case gfx::YUVColorSpace::Identity: 199 return Nothing(); 200 } 201 202 MOZ_ASSERT_UNREACHABLE("Unsupported YCbCr matrix"); 203 return Nothing(); 204 } 205 206 static Maybe<CFStringRef> MapTransferFunction( 207 const gfx::TransferFunction& aTransferFunction) { 208 switch (aTransferFunction) { 209 case gfx::TransferFunction::BT709: 210 return Some(kCVImageBufferTransferFunction_ITU_R_709_2); 211 case gfx::TransferFunction::SRGB: 212 return Some(kCVImageBufferTransferFunction_sRGB); 213 case gfx::TransferFunction::PQ: 214 return Some(kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ); 215 case gfx::TransferFunction::HLG: 216 return Some(kCVImageBufferTransferFunction_ITU_R_2100_HLG); 217 } 218 219 MOZ_ASSERT_UNREACHABLE("Unsupported transfer function"); 220 return Nothing(); 221 } 222 223 struct EncoderColorSpace { 224 CFStringRef mColorPrimaries = nullptr; 225 CFStringRef mYCbCrMatrix = nullptr; 226 CFStringRef mTransferFunction = nullptr; 227 }; 228 229 static Result<EncoderColorSpace, MediaResult> MapColorSpace( 230 const EncoderConfig::VideoColorSpace& aColorSpace) { 231 EncoderColorSpace colorSpace; 232 if (aColorSpace.mPrimaries) { 233 Maybe<CFStringRef> p = MapColorPrimaries(aColorSpace.mPrimaries.ref()); 234 if (p.isNothing()) { 235 return Err(MediaResult( 236 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 237 RESULT_DETAIL("Unsupported color primaries: %u", 238 static_cast<uint8_t>(aColorSpace.mPrimaries.ref())))); 239 } 240 colorSpace.mColorPrimaries = p.value(); 241 } 242 if (aColorSpace.mMatrix) { 243 Maybe<CFStringRef> m = MapYCbCrMatrix(aColorSpace.mMatrix.ref()); 244 if (m.isNothing()) { 245 return Err(MediaResult( 246 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 247 RESULT_DETAIL("Unsupported YCbCr matrix: %u", 248 static_cast<uint8_t>(aColorSpace.mMatrix.ref())))); 249 } 250 colorSpace.mYCbCrMatrix = m.value(); 251 } 252 if (aColorSpace.mTransferFunction) { 253 Maybe<CFStringRef> f = 254 MapTransferFunction(aColorSpace.mTransferFunction.ref()); 255 if (f.isNothing()) { 256 return Err(MediaResult( 257 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 258 RESULT_DETAIL( 259 "Unsupported transfer function: %u", 260 static_cast<uint8_t>(aColorSpace.mTransferFunction.ref())))); 261 } 262 colorSpace.mTransferFunction = f.value(); 263 } 264 return colorSpace; 265 } 266 267 bool AppleVTEncoder::IsSettingColorSpaceSupported() const { 268 SessionPropertyManager mgr(mSession); 269 return mgr.IsSupported(kVTCompressionPropertyKey_ColorPrimaries) && 270 mgr.IsSupported(kVTCompressionPropertyKey_YCbCrMatrix) && 271 mgr.IsSupported(kVTCompressionPropertyKey_TransferFunction); 272 } 273 274 MediaResult AppleVTEncoder::SetColorSpace( 275 const EncoderConfig::SampleFormat& aFormat) { 276 MOZ_ASSERT(mSession); 277 278 if (!aFormat.IsYUV()) { 279 return MediaResult(NS_OK, "Skip setting color space for non-YUV formats"); 280 } 281 282 if (!IsSettingColorSpaceSupported()) { 283 return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 284 "Setting color space not supported"); 285 } 286 287 auto r = MapColorSpace(aFormat.mColorSpace); 288 if (r.isErr()) { 289 return r.unwrapErr(); 290 } 291 292 EncoderColorSpace colorSpace = r.unwrap(); 293 294 SessionPropertyManager mgr(mSession); 295 AutoTArray<const char*, 3> properties; 296 297 if (colorSpace.mColorPrimaries) { 298 OSStatus status = mgr.Set(kVTCompressionPropertyKey_ColorPrimaries, 299 colorSpace.mColorPrimaries); 300 if (status != noErr) { 301 return MediaResult( 302 NS_ERROR_DOM_MEDIA_FATAL_ERR, 303 RESULT_DETAIL("Failed to set color primaries. Error: %d", status)); 304 } 305 properties.AppendElement("ColorPrimaries"); 306 } 307 if (colorSpace.mYCbCrMatrix) { 308 OSStatus status = 309 mgr.Set(kVTCompressionPropertyKey_YCbCrMatrix, colorSpace.mYCbCrMatrix); 310 if (status != noErr) { 311 return MediaResult( 312 NS_ERROR_DOM_MEDIA_FATAL_ERR, 313 RESULT_DETAIL("Failed to set YCbCr matrix. Error: %d", status)); 314 } 315 properties.AppendElement("YCbCrMatrix"); 316 } 317 if (colorSpace.mTransferFunction) { 318 OSStatus status = mgr.Set(kVTCompressionPropertyKey_TransferFunction, 319 colorSpace.mTransferFunction); 320 if (status != noErr) { 321 return MediaResult( 322 NS_ERROR_DOM_MEDIA_FATAL_ERR, 323 RESULT_DETAIL("Failed to set transfer function. Error: %d", status)); 324 } 325 properties.AppendElement("TransferFunction"); 326 } 327 328 nsCString msg; 329 if (properties.IsEmpty()) { 330 msg = "No color space properties set"_ns; 331 } else { 332 msg = StringJoin(","_ns, properties); 333 msg.Append(" set"); 334 } 335 336 return MediaResult(NS_OK, msg); 337 } 338 339 static Result<OSType, MediaResult> MapPixelFormat( 340 dom::ImageBitmapFormat aFormat, gfx::ColorRange aColorRange) { 341 const bool isFullRange = aColorRange == gfx::ColorRange::FULL; 342 343 Maybe<OSType> fmt; 344 switch (aFormat) { 345 case dom::ImageBitmapFormat::YUV444P: 346 return kCVPixelFormatType_444YpCbCr8; 347 case dom::ImageBitmapFormat::YUV420P: 348 return isFullRange ? kCVPixelFormatType_420YpCbCr8PlanarFullRange 349 : kCVPixelFormatType_420YpCbCr8Planar; 350 case dom::ImageBitmapFormat::YUV420SP_NV12: 351 return isFullRange ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 352 : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; 353 case dom::ImageBitmapFormat::RGBA32: 354 fmt.emplace(kCVPixelFormatType_32RGBA); 355 break; 356 case dom::ImageBitmapFormat::BGRA32: 357 fmt.emplace(kCVPixelFormatType_32BGRA); 358 break; 359 case dom::ImageBitmapFormat::RGB24: 360 fmt.emplace(kCVPixelFormatType_24RGB); 361 break; 362 case dom::ImageBitmapFormat::BGR24: 363 fmt.emplace(kCVPixelFormatType_24BGR); 364 break; 365 case dom::ImageBitmapFormat::GRAY8: 366 fmt.emplace(kCVPixelFormatType_OneComponent8); 367 break; 368 default: 369 MOZ_ASSERT_UNREACHABLE("Unsupported image format"); 370 } 371 372 // Limited RGB formats are not supported on MacOS (Bug 1957758). 373 if (fmt) { 374 if (!isFullRange) { 375 return Err(MediaResult( 376 NS_ERROR_NOT_IMPLEMENTED, 377 RESULT_DETAIL("format %s with limited colorspace is not supported", 378 dom::GetEnumString(aFormat).get()))); 379 } 380 return fmt.value(); 381 } 382 383 return Err(MediaResult(NS_ERROR_NOT_IMPLEMENTED, 384 RESULT_DETAIL("format %s is not supported", 385 dom::GetEnumString(aFormat).get()))); 386 } 387 388 RefPtr<MediaDataEncoder::InitPromise> AppleVTEncoder::Init() { 389 MOZ_ASSERT(!mSession, 390 "Cannot initialize encoder again without shutting down"); 391 392 MediaResult r = InitSession(); 393 if (NS_FAILED(r.Code())) { 394 LOGE("%s", r.Description().get()); 395 return InitPromise::CreateAndReject(r, __func__); 396 } 397 398 mError = NS_OK; 399 return InitPromise::CreateAndResolve(true, __func__); 400 } 401 402 MediaResult AppleVTEncoder::InitSession() { 403 MOZ_ASSERT(!mSession); 404 405 auto errorExit = MakeScopeExit([&] { InvalidateSessionIfNeeded(); }); 406 407 if (mConfig.mSize.width == 0 || mConfig.mSize.height == 0) { 408 return MediaResult( 409 NS_ERROR_ILLEGAL_VALUE, 410 RESULT_DETAIL("Neither width (%d) nor height (%d) can be zero", 411 mConfig.mSize.width, mConfig.mSize.height)); 412 } 413 414 if (mConfig.mScalabilityMode != ScalabilityMode::None && !OSSupportsSVC()) { 415 return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 416 "SVC only supported on macOS 11.3 and more recent"_ns); 417 } 418 419 bool lowLatencyRateControl = 420 mConfig.mUsage == Usage::Realtime || 421 mConfig.mScalabilityMode != ScalabilityMode::None; 422 LOGD("low latency rate control: %s, Hardware allowed: %s", 423 lowLatencyRateControl ? "yes" : "no", 424 mHardwareNotAllowed ? "no" : "yes"); 425 AutoCFTypeRef<CFDictionaryRef> spec( 426 BuildEncoderSpec(mHardwareNotAllowed, lowLatencyRateControl)); 427 428 // Bug 1955153: Set sourceImageBufferAttributes using the pixel format derived 429 // from mConfig.mFormat. 430 OSStatus status = VTCompressionSessionCreate( 431 kCFAllocatorDefault, mConfig.mSize.width, mConfig.mSize.height, 432 kCMVideoCodecType_H264, spec, nullptr /* sourceImageBufferAttributes */, 433 kCFAllocatorDefault, &FrameCallback, this /* outputCallbackRefCon */, 434 mSession.Receive()); 435 if (status != noErr) { 436 return MediaResult( 437 NS_ERROR_DOM_MEDIA_FATAL_ERR, 438 RESULT_DETAIL("fail to create encoder session. Error: %d", status)); 439 } 440 441 SessionPropertyManager mgr(mSession); 442 443 status = mgr.Set(kVTCompressionPropertyKey_AllowFrameReordering, false); 444 if (status != noErr) { 445 return MediaResult( 446 NS_ERROR_DOM_MEDIA_FATAL_ERR, 447 RESULT_DETAIL("Couldn't disable bframes. Error: %d", status)); 448 } 449 450 if (mConfig.mUsage == Usage::Realtime && !SetRealtime(true)) { 451 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 452 "fail to configure real-time"_ns); 453 } 454 455 if (mConfig.mBitrate) { 456 if (mConfig.mCodec == CodecType::H264 && 457 mConfig.mBitrateMode == BitrateMode::Constant) { 458 // Not supported, fall-back to VBR. 459 LOGD("H264 CBR not supported in VideoToolbox, falling back to VBR"); 460 mConfig.mBitrateMode = BitrateMode::Variable; 461 } 462 bool rv = SetBitrateAndMode(mConfig.mBitrateMode, mConfig.mBitrate); 463 if (!rv) { 464 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 465 "fail to configurate bitrate"_ns); 466 } 467 } 468 469 if (mConfig.mScalabilityMode != ScalabilityMode::None) { 470 if (__builtin_available(macos 11.3, *)) { 471 float baseLayerFPSRatio = 1.0f; 472 switch (mConfig.mScalabilityMode) { 473 case ScalabilityMode::L1T2: 474 baseLayerFPSRatio = 0.5; 475 break; 476 case ScalabilityMode::L1T3: 477 // Not supported in hw on macOS, but is accepted and errors out when 478 // encoding. Reject the configuration now. 479 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 480 RESULT_DETAIL("macOS only support L1T2 h264 SVC")); 481 default: 482 MOZ_ASSERT_UNREACHABLE("Unhandled value"); 483 } 484 485 status = mgr.Set(kVTCompressionPropertyKey_BaseLayerFrameRateFraction, 486 baseLayerFPSRatio); 487 if (status != noErr) { 488 return MediaResult( 489 NS_ERROR_DOM_MEDIA_FATAL_ERR, 490 RESULT_DETAIL("fail to configure SVC (base ratio: %f). Error: %d", 491 baseLayerFPSRatio, status)); 492 } 493 } else { 494 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 495 "macOS version too old to enable SVC"_ns); 496 } 497 } 498 499 int64_t interval = 500 mConfig.mKeyframeInterval > std::numeric_limits<int64_t>::max() 501 ? std::numeric_limits<int64_t>::max() 502 : AssertedCast<int64_t>(mConfig.mKeyframeInterval); 503 504 status = mgr.Set(kVTCompressionPropertyKey_MaxKeyFrameInterval, interval); 505 if (status != noErr) { 506 return MediaResult( 507 NS_ERROR_DOM_MEDIA_FATAL_ERR, 508 RESULT_DETAIL("fail to configurate keyframe interval: %" PRId64 509 ". Error: %d", 510 interval, status)); 511 } 512 513 if (mConfig.mCodecSpecific.is<H264Specific>()) { 514 const H264Specific& specific = mConfig.mCodecSpecific.as<H264Specific>(); 515 if (!SetProfileLevel(specific.mProfile)) { 516 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 517 RESULT_DETAIL("fail to configurate profile level:%d", 518 int(specific.mProfile))); 519 } 520 } 521 522 MediaResult colorSpaceResult = SetColorSpace(mConfig.mFormat); 523 if (NS_SUCCEEDED(colorSpaceResult.Code())) { 524 LOGD("%s", colorSpaceResult.Description().get()); 525 } else if (colorSpaceResult.Code() == NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR) { 526 // Color space not supported, ignore. 527 LOGW("%s", colorSpaceResult.Description().get()); 528 } else { 529 MOZ_ASSERT(NS_FAILED(colorSpaceResult.Code())); 530 LOGE("%s", colorSpaceResult.Description().get()); 531 return colorSpaceResult; 532 } 533 534 bool isUsingHW = false; 535 status = 536 mgr.Copy(kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder, 537 isUsingHW); 538 mIsHardwareAccelerated = status == noErr && isUsingHW; 539 LOGD("Using hw acceleration: %s", mIsHardwareAccelerated ? "yes" : "no"); 540 541 errorExit.release(); 542 return NS_OK; 543 } 544 545 void AppleVTEncoder::InvalidateSessionIfNeeded() { 546 if (mSession) { 547 VTCompressionSessionInvalidate(mSession); 548 mSession.Reset(); 549 } 550 } 551 552 CFDictionaryRef AppleVTEncoder::BuildSourceImageBufferAttributes( 553 OSType aPixelFormat) { 554 // Source image buffer attributes 555 const void* keys[] = {kCVPixelBufferOpenGLCompatibilityKey, // TODO 556 kCVPixelBufferIOSurfacePropertiesKey, // TODO 557 kCVPixelBufferPixelFormatTypeKey}; 558 559 AutoCFTypeRef<CFDictionaryRef> ioSurfaceProps(CFDictionaryCreate( 560 kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks, 561 &kCFTypeDictionaryValueCallBacks)); 562 AutoCFTypeRef<CFNumberRef> pixelFormat( 563 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &aPixelFormat)); 564 const void* values[] = {kCFBooleanTrue, ioSurfaceProps, pixelFormat}; 565 566 MOZ_ASSERT(std::size(keys) == std::size(values), 567 "Non matching keys/values array size"); 568 569 return CFDictionaryCreate(kCFAllocatorDefault, keys, values, std::size(keys), 570 &kCFTypeDictionaryKeyCallBacks, 571 &kCFTypeDictionaryValueCallBacks); 572 } 573 574 static bool IsKeyframe(CMSampleBufferRef aSample) { 575 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(aSample, 0); 576 if (attachments == nullptr || CFArrayGetCount(attachments) == 0) { 577 return false; 578 } 579 580 return !CFDictionaryContainsKey( 581 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)), 582 kCMSampleAttachmentKey_NotSync); 583 } 584 585 static size_t GetNumParamSets(CMFormatDescriptionRef aDescription) { 586 size_t numParamSets = 0; 587 OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( 588 aDescription, 0, nullptr, nullptr, &numParamSets, nullptr); 589 if (status != noErr) { 590 LOGE("Cannot get number of parameter sets from format description"); 591 } 592 593 return numParamSets; 594 } 595 596 static const uint8_t kNALUStart[4] = {0, 0, 0, 1}; 597 598 static size_t GetParamSet(CMFormatDescriptionRef aDescription, size_t aIndex, 599 const uint8_t** aDataPtr) { 600 size_t length = 0; 601 int headerSize = 0; 602 if (CMVideoFormatDescriptionGetH264ParameterSetAtIndex( 603 aDescription, aIndex, aDataPtr, &length, nullptr, &headerSize) != 604 noErr) { 605 LOGE("failed to get parameter set from format description"); 606 return 0; 607 } 608 MOZ_ASSERT(headerSize == sizeof(kNALUStart), "Only support 4 byte header"); 609 610 return length; 611 } 612 613 static bool WriteSPSPPS(MediaRawData* aDst, 614 CMFormatDescriptionRef aDescription) { 615 // Get SPS/PPS 616 const size_t numParamSets = GetNumParamSets(aDescription); 617 UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter()); 618 for (size_t i = 0; i < numParamSets; i++) { 619 const uint8_t* data = nullptr; 620 size_t length = GetParamSet(aDescription, i, &data); 621 if (length == 0) { 622 return false; 623 } 624 if (!writer->Append(kNALUStart, sizeof(kNALUStart))) { 625 LOGE("Cannot write NAL unit start code"); 626 return false; 627 } 628 if (!writer->Append(data, length)) { 629 LOGE("Cannot write parameter set"); 630 return false; 631 } 632 } 633 return true; 634 } 635 636 static RefPtr<MediaByteBuffer> extractAvcc( 637 CMFormatDescriptionRef aDescription) { 638 CFPropertyListRef list = CMFormatDescriptionGetExtension( 639 aDescription, 640 kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms); 641 if (!list) { 642 LOGE("fail to get atoms"); 643 return nullptr; 644 } 645 CFDataRef avcC = static_cast<CFDataRef>( 646 CFDictionaryGetValue(static_cast<CFDictionaryRef>(list), CFSTR("avcC"))); 647 if (!avcC) { 648 LOGE("fail to extract avcC"); 649 return nullptr; 650 } 651 CFIndex length = CFDataGetLength(avcC); 652 const UInt8* bytes = CFDataGetBytePtr(avcC); 653 if (length <= 0 || !bytes) { 654 LOGE("empty avcC"); 655 return nullptr; 656 } 657 658 RefPtr<MediaByteBuffer> config = new MediaByteBuffer(length); 659 config->AppendElements(bytes, length); 660 return config; 661 } 662 663 bool AppleVTEncoder::WriteExtraData(MediaRawData* aDst, CMSampleBufferRef aSrc, 664 const bool aAsAnnexB) { 665 if (!IsKeyframe(aSrc)) { 666 return true; 667 } 668 669 LOGV("Writing extra data (%s) for keyframe", aAsAnnexB ? "AnnexB" : "AVCC"); 670 671 aDst->mKeyframe = true; 672 CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(aSrc); 673 if (!desc) { 674 LOGE("fail to get format description from sample"); 675 return false; 676 } 677 678 if (aAsAnnexB) { 679 return WriteSPSPPS(aDst, desc); 680 } 681 682 RefPtr<MediaByteBuffer> avcc = extractAvcc(desc); 683 if (!avcc) { 684 LOGE("failed to extract avcc"); 685 return false; 686 } 687 688 if (!mAvcc || !H264::CompareExtraData(avcc, mAvcc)) { 689 LOGV("avcC changed, updating"); 690 mAvcc = avcc; 691 aDst->mExtraData = mAvcc; 692 } 693 694 return true; 695 } 696 697 static bool WriteNALUs(MediaRawData* aDst, CMSampleBufferRef aSrc, 698 bool aAsAnnexB = false) { 699 size_t srcRemaining = CMSampleBufferGetTotalSampleSize(aSrc); 700 CMBlockBufferRef block = CMSampleBufferGetDataBuffer(aSrc); 701 if (!block) { 702 LOGE("Cannot get block buffer frome sample"); 703 return false; 704 } 705 UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter()); 706 size_t writtenLength = aDst->Size(); 707 // Ensure capacity. 708 if (!writer->SetSize(writtenLength + srcRemaining)) { 709 LOGE("Cannot allocate buffer"); 710 return false; 711 } 712 size_t readLength = 0; 713 while (srcRemaining > 0) { 714 // Extract the size of next NAL unit 715 uint8_t unitSizeBytes[4]; 716 MOZ_ASSERT(srcRemaining > sizeof(unitSizeBytes)); 717 if (CMBlockBufferCopyDataBytes(block, readLength, sizeof(unitSizeBytes), 718 reinterpret_cast<uint32_t*>( 719 unitSizeBytes)) != kCMBlockBufferNoErr) { 720 LOGE("Cannot copy unit size bytes"); 721 return false; 722 } 723 size_t unitSize = 724 CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(unitSizeBytes)); 725 726 if (aAsAnnexB) { 727 // Replace unit size bytes with NALU start code. 728 PodCopy(writer->Data() + writtenLength, kNALUStart, sizeof(kNALUStart)); 729 readLength += sizeof(unitSizeBytes); 730 srcRemaining -= sizeof(unitSizeBytes); 731 writtenLength += sizeof(kNALUStart); 732 } else { 733 // Copy unit size bytes + data. 734 unitSize += sizeof(unitSizeBytes); 735 } 736 MOZ_ASSERT(writtenLength + unitSize <= aDst->Size()); 737 // Copy NAL unit data 738 if (CMBlockBufferCopyDataBytes(block, readLength, unitSize, 739 writer->Data() + writtenLength) != 740 kCMBlockBufferNoErr) { 741 LOGE("Cannot copy unit data"); 742 return false; 743 } 744 readLength += unitSize; 745 srcRemaining -= unitSize; 746 writtenLength += unitSize; 747 } 748 MOZ_ASSERT(writtenLength == aDst->Size()); 749 return true; 750 } 751 752 void AppleVTEncoder::OutputFrame(OSStatus aStatus, VTEncodeInfoFlags aFlags, 753 CMSampleBufferRef aBuffer) { 754 LOGV("status: %d, flags: %d, buffer %p", aStatus, aFlags, aBuffer); 755 756 if (aStatus != noErr) { 757 ProcessOutput(nullptr, EncodeResult::EncodeError); 758 return; 759 } 760 761 if (aFlags & kVTEncodeInfo_FrameDropped) { 762 ProcessOutput(nullptr, EncodeResult::FrameDropped); 763 return; 764 } 765 766 if (!aBuffer) { 767 ProcessOutput(nullptr, EncodeResult::EmptyBuffer); 768 return; 769 } 770 771 RefPtr<MediaRawData> output(new MediaRawData()); 772 773 if (__builtin_available(macos 11.3, *)) { 774 if (mConfig.mScalabilityMode != ScalabilityMode::None) { 775 CFDictionaryRef dict = (CFDictionaryRef)(CFArrayGetValueAtIndex( 776 CMSampleBufferGetSampleAttachmentsArray(aBuffer, true), 0)); 777 CFBooleanRef isBaseLayerRef = (CFBooleanRef)CFDictionaryGetValue( 778 dict, (const void*)kCMSampleAttachmentKey_IsDependedOnByOthers); 779 Boolean isBaseLayer = CFBooleanGetValue(isBaseLayerRef); 780 output->mTemporalLayerId.emplace(isBaseLayer ? 0 : 1); 781 } 782 } 783 784 bool forceAvcc = false; 785 if (mConfig.mCodecSpecific.is<H264Specific>()) { 786 forceAvcc = mConfig.mCodecSpecific.as<H264Specific>().mFormat == 787 H264BitStreamFormat::AVC; 788 } 789 bool asAnnexB = !forceAvcc; 790 bool succeeded = WriteExtraData(output, aBuffer, asAnnexB) && 791 WriteNALUs(output, aBuffer, asAnnexB); 792 793 output->mTime = media::TimeUnit::FromSeconds( 794 CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(aBuffer))); 795 output->mDuration = media::TimeUnit::FromSeconds( 796 CMTimeGetSeconds(CMSampleBufferGetOutputDuration(aBuffer))); 797 LOGV("Make a %s output[time: %s, duration: %s]: %s", 798 asAnnexB ? "AnnexB" : "AVCC", output->mTime.ToString().get(), 799 output->mDuration.ToString().get(), succeeded ? "succeed" : "failed"); 800 ProcessOutput(succeeded ? std::move(output) : nullptr, EncodeResult::Success); 801 } 802 803 void AppleVTEncoder::ProcessOutput(RefPtr<MediaRawData>&& aOutput, 804 EncodeResult aResult) { 805 if (!mTaskQueue->IsCurrentThreadIn()) { 806 LOGV("Dispatch ProcessOutput to task queue"); 807 nsresult rv = mTaskQueue->Dispatch( 808 NewRunnableMethod<RefPtr<MediaRawData>, EncodeResult>( 809 "AppleVTEncoder::ProcessOutput", this, 810 &AppleVTEncoder::ProcessOutput, std::move(aOutput), aResult)); 811 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 812 (void)rv; 813 return; 814 } 815 816 if (aResult != EncodeResult::Success) { 817 switch (aResult) { 818 case EncodeResult::EncodeError: 819 mError = 820 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to encode"_ns); 821 break; 822 case EncodeResult::EmptyBuffer: 823 mError = 824 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Buffer is empty"_ns); 825 break; 826 case EncodeResult::FrameDropped: 827 if (mConfig.mUsage == Usage::Realtime) { 828 // Dropping a frame in real-time usage is okay. 829 LOGW("Frame is dropped"); 830 } else { 831 // Some usages like transcoding should not drop a frame. 832 LOGE("Frame is dropped"); 833 mError = 834 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Frame is dropped"_ns); 835 } 836 break; 837 default: 838 MOZ_ASSERT_UNREACHABLE("Unknown EncodeResult"); 839 break; 840 } 841 MaybeResolveOrRejectEncodePromise(); 842 return; 843 } 844 845 LOGV("Got %zu bytes of output", !aOutput.get() ? 0 : aOutput->Size()); 846 847 if (!aOutput) { 848 mError = 849 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "No converted output"_ns); 850 MaybeResolveOrRejectEncodePromise(); 851 return; 852 } 853 854 mEncodedData.AppendElement(std::move(aOutput)); 855 MaybeResolveOrRejectEncodePromise(); 856 } 857 858 RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Encode( 859 const MediaData* aSample) { 860 MOZ_ASSERT(aSample != nullptr); 861 862 RefPtr<const VideoData> sample(aSample->As<const VideoData>()); 863 864 RefPtr<AppleVTEncoder> self = this; 865 return InvokeAsync(mTaskQueue, __func__, [self, this, sample] { 866 MOZ_ASSERT(mEncodePromise.IsEmpty(), 867 "Encode should not be called again before getting results"); 868 RefPtr<EncodePromise> p = mEncodePromise.Ensure(__func__); 869 ProcessEncode(sample); 870 return p; 871 }); 872 } 873 874 // TODO(Bug 1984936): For realtime mode, resolve the promise after the first 875 // sample's result is available, then continue processing remaining samples. 876 // This allows the caller to keep submitting new samples while the encoder 877 // handles pending ones. 878 RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Encode( 879 nsTArray<RefPtr<MediaData>>&& aSamples) { 880 MOZ_ASSERT(!aSamples.IsEmpty()); 881 882 RefPtr<AppleVTEncoder> self = this; 883 return InvokeAsync( 884 mTaskQueue, __func__, [self, samples = std::move(aSamples)]() mutable { 885 MOZ_ASSERT(self->mEncodeBatchPromise.IsEmpty(), 886 "Encode should not be called again before getting results"); 887 RefPtr<EncodePromise> p = self->mEncodeBatchPromise.Ensure(__func__); 888 self->EncodeNextSample(std::move(samples), EncodedData()); 889 return p; 890 }); 891 } 892 893 RefPtr<MediaDataEncoder::ReconfigurationPromise> AppleVTEncoder::Reconfigure( 894 const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) { 895 return InvokeAsync(mTaskQueue, this, __func__, 896 &AppleVTEncoder::ProcessReconfigure, 897 aConfigurationChanges); 898 } 899 900 void AppleVTEncoder::ProcessEncode(const RefPtr<const VideoData>& aSample) { 901 LOGV("::ProcessEncode"); 902 AssertOnTaskQueue(); 903 MOZ_ASSERT(mSession); 904 905 if (NS_FAILED(mError)) { 906 LOGE("Pending error: %s", mError.Description().get()); 907 MaybeResolveOrRejectEncodePromise(); 908 } 909 910 AutoCVBufferRef<CVImageBufferRef> buffer( 911 CreateCVPixelBuffer(aSample->mImage)); 912 if (!buffer) { 913 LOGE("Failed to allocate buffer"); 914 mError = 915 MediaResult(NS_ERROR_OUT_OF_MEMORY, "failed to allocate buffer"_ns); 916 MaybeResolveOrRejectEncodePromise(); 917 return; 918 } 919 920 CFDictionaryRef frameProps = nullptr; 921 if (aSample->mKeyframe) { 922 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame}; 923 CFTypeRef values[] = {kCFBooleanTrue}; 924 MOZ_ASSERT(std::size(keys) == std::size(values)); 925 frameProps = CFDictionaryCreate( 926 kCFAllocatorDefault, keys, values, std::size(keys), 927 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 928 }; 929 930 VTEncodeInfoFlags info; 931 OSStatus status = VTCompressionSessionEncodeFrame( 932 mSession, buffer, 933 CMTimeMake(aSample->mTime.ToMicroseconds(), USECS_PER_S), 934 CMTimeMake(aSample->mDuration.ToMicroseconds(), USECS_PER_S), frameProps, 935 nullptr /* sourceFrameRefcon */, &info); 936 if (status != noErr) { 937 LOGE("VTCompressionSessionEncodeFrame error: %d", status); 938 mError = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 939 "VTCompressionSessionEncodeFrame error"_ns); 940 MaybeResolveOrRejectEncodePromise(); 941 return; 942 } 943 944 if (mConfig.mUsage != Usage::Realtime) { 945 MaybeResolveOrRejectEncodePromise(); 946 return; 947 } 948 949 // The latency between encoding a sample and receiving the encoded output is 950 // critical in real-time usage. To minimize the latency, the output result 951 // should be returned immediately once they are ready, instead of being 952 // returned in the next or later Encode() iterations. 953 LOGV("Encoding in progress"); 954 955 // Workaround for real-time encoding in OS versions < 11. 956 ForceOutputIfNeeded(); 957 } 958 959 RefPtr<MediaDataEncoder::ReconfigurationPromise> 960 AppleVTEncoder::ProcessReconfigure( 961 const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) { 962 AssertOnTaskQueue(); 963 MOZ_ASSERT(mSession); 964 965 bool ok = false; 966 for (const auto& confChange : aConfigurationChanges->mChanges) { 967 // A reconfiguration on the fly succeeds if all changes can be applied 968 // successfuly. In case of failure, the encoder will be drained and 969 // recreated. 970 ok &= confChange.match( 971 // Not supported yet 972 [&](const DimensionsChange& aChange) -> bool { return false; }, 973 [&](const DisplayDimensionsChange& aChange) -> bool { return false; }, 974 [&](const BitrateModeChange& aChange) -> bool { 975 mConfig.mBitrateMode = aChange.get(); 976 return SetBitrateAndMode(mConfig.mBitrateMode, mConfig.mBitrate); 977 }, 978 [&](const BitrateChange& aChange) -> bool { 979 mConfig.mBitrate = aChange.get().refOr(0); 980 // 0 is the default in AppleVTEncoder: the encoder chooses the bitrate 981 // based on the content. 982 return SetBitrateAndMode(mConfig.mBitrateMode, mConfig.mBitrate); 983 }, 984 [&](const FramerateChange& aChange) -> bool { 985 // 0 means default, in VideoToolbox, and is valid, perform some light 986 // sanitation on other values. 987 double fps = aChange.get().refOr(0); 988 if (std::isnan(fps) || fps < 0 || 989 int64_t(fps) > std::numeric_limits<int32_t>::max()) { 990 LOGE("Invalid fps of %lf", fps); 991 return false; 992 } 993 return SetFrameRate(AssertedCast<int64_t>(fps)); 994 }, 995 [&](const UsageChange& aChange) -> bool { 996 mConfig.mUsage = aChange.get(); 997 return SetRealtime(aChange.get() == Usage::Realtime); 998 }, 999 [&](const ContentHintChange& aChange) -> bool { return false; }, 1000 [&](const SampleRateChange& aChange) -> bool { return false; }, 1001 [&](const NumberOfChannelsChange& aChange) -> bool { return false; }); 1002 }; 1003 using P = MediaDataEncoder::ReconfigurationPromise; 1004 if (ok) { 1005 return P::CreateAndResolve(true, __func__); 1006 } 1007 return P::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 1008 } 1009 1010 static size_t NumberOfPlanes(OSType aPixelFormat) { 1011 switch (aPixelFormat) { 1012 case kCVPixelFormatType_32RGBA: 1013 case kCVPixelFormatType_32BGRA: 1014 case kCVPixelFormatType_24RGB: 1015 case kCVPixelFormatType_24BGR: 1016 case kCVPixelFormatType_OneComponent8: 1017 return 1; 1018 case kCVPixelFormatType_444YpCbCr8: 1019 case kCVPixelFormatType_420YpCbCr8PlanarFullRange: 1020 case kCVPixelFormatType_420YpCbCr8Planar: 1021 return 3; 1022 case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: 1023 case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: 1024 return 2; 1025 default: 1026 LOGE("Unsupported input pixel format"); 1027 return 0; 1028 } 1029 } 1030 1031 using namespace layers; 1032 1033 static void ReleaseSurface(void* aReleaseRef, const void* aBaseAddress) { 1034 RefPtr<gfx::DataSourceSurface> released = 1035 dont_AddRef(static_cast<gfx::DataSourceSurface*>(aReleaseRef)); 1036 } 1037 1038 static void ReleaseImage(void* aImageGrip, const void* aDataPtr, 1039 size_t aDataSize, size_t aNumOfPlanes, 1040 const void** aPlanes) { 1041 (static_cast<PlanarYCbCrImage*>(aImageGrip))->Release(); 1042 } 1043 1044 CVPixelBufferRef AppleVTEncoder::CreateCVPixelBuffer(Image* aSource) { 1045 AssertOnTaskQueue(); 1046 1047 auto sfr = EncoderConfig::SampleFormat::FromImage(aSource); 1048 if (sfr.isErr()) { 1049 MediaResult err = sfr.unwrapErr(); 1050 LOGE("%s", err.Description().get()); 1051 return nullptr; 1052 } 1053 const EncoderConfig::SampleFormat sf = sfr.unwrap(); 1054 1055 gfx::ColorRange defaultColorRange = 1056 sf.IsYUV() ? gfx::ColorRange::LIMITED : gfx::ColorRange::FULL; 1057 auto pfr = MapPixelFormat(sf.mPixelFormat, sf.mColorSpace.mRange 1058 ? sf.mColorSpace.mRange.value() 1059 : defaultColorRange); 1060 if (pfr.isErr()) { 1061 MediaResult err = pfr.unwrapErr(); 1062 LOGE("%s", err.Description().get()); 1063 return nullptr; 1064 } 1065 1066 OSType pixelFormat = pfr.unwrap(); 1067 1068 if (sf != mConfig.mFormat) { 1069 LOGV( 1070 "Input image in format %s but encoder configured with format %s. " 1071 "Fingers crossed", 1072 sf.ToString().get(), mConfig.mFormat.ToString().get()); 1073 // Bug 1955153: If the encoder encounters a kVTPixelTransferNotSupportedErr 1074 // error due to an unsupported image format, it must be re-initialized. 1075 // Additionally, any changes to the color space also require re-initializing 1076 // the encoder. 1077 } 1078 1079 if (aSource->GetFormat() == ImageFormat::PLANAR_YCBCR) { 1080 PlanarYCbCrImage* image = aSource->AsPlanarYCbCrImage(); 1081 if (!image || !image->GetData()) { 1082 LOGE("Failed to get PlanarYCbCrImage or its data"); 1083 return nullptr; 1084 } 1085 1086 size_t numPlanes = NumberOfPlanes(pixelFormat); 1087 const PlanarYCbCrImage::Data* yuv = image->GetData(); 1088 1089 auto ySize = yuv->YDataSize(); 1090 auto cbcrSize = yuv->CbCrDataSize(); 1091 void* addresses[3] = {}; 1092 size_t widths[3] = {}; 1093 size_t heights[3] = {}; 1094 size_t strides[3] = {}; 1095 switch (numPlanes) { 1096 case 3: 1097 addresses[2] = yuv->mCrChannel; 1098 widths[2] = cbcrSize.width; 1099 heights[2] = cbcrSize.height; 1100 strides[2] = yuv->mCbCrStride; 1101 [[fallthrough]]; 1102 case 2: 1103 addresses[1] = yuv->mCbChannel; 1104 widths[1] = cbcrSize.width; 1105 heights[1] = cbcrSize.height; 1106 strides[1] = yuv->mCbCrStride; 1107 [[fallthrough]]; 1108 case 1: 1109 addresses[0] = yuv->mYChannel; 1110 widths[0] = ySize.width; 1111 heights[0] = ySize.height; 1112 strides[0] = yuv->mYStride; 1113 break; 1114 default: 1115 LOGE("Unexpected number of planes: %zu", numPlanes); 1116 MOZ_ASSERT_UNREACHABLE("Unexpected number of planes"); 1117 return nullptr; 1118 } 1119 1120 CVPixelBufferRef buffer = nullptr; 1121 image->AddRef(); // Grip input buffers. 1122 CVReturn rv = CVPixelBufferCreateWithPlanarBytes( 1123 kCFAllocatorDefault, yuv->mPictureRect.width, yuv->mPictureRect.height, 1124 pixelFormat, nullptr /* dataPtr */, 0 /* dataSize */, numPlanes, 1125 addresses, widths, heights, strides, ReleaseImage /* releaseCallback */, 1126 image /* releaseRefCon */, nullptr /* pixelBufferAttributes */, 1127 &buffer); 1128 if (rv == kCVReturnSuccess) { 1129 return buffer; 1130 // |image| will be released in |ReleaseImage()|. 1131 } 1132 LOGE("CVPIxelBufferCreateWithPlanarBytes error"); 1133 image->Release(); 1134 return nullptr; 1135 } 1136 1137 RefPtr<gfx::SourceSurface> surface = aSource->GetAsSourceSurface(); 1138 if (!surface) { 1139 LOGE("Failed to get SourceSurface"); 1140 return nullptr; 1141 } 1142 1143 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface(); 1144 if (!dataSurface) { 1145 LOGE("Failed to get DataSurface"); 1146 return nullptr; 1147 } 1148 1149 gfx::DataSourceSurface::ScopedMap map(dataSurface, 1150 gfx::DataSourceSurface::READ); 1151 if (NS_WARN_IF(!map.IsMapped())) { 1152 LOGE("Failed to map DataSurface"); 1153 return nullptr; 1154 } 1155 1156 CVPixelBufferRef buffer = nullptr; 1157 gfx::DataSourceSurface* dss = dataSurface.forget().take(); 1158 CVReturn rv = CVPixelBufferCreateWithBytes( 1159 kCFAllocatorDefault, dss->GetSize().Width(), dss->GetSize().Height(), 1160 pixelFormat, map.GetData(), map.GetStride(), ReleaseSurface, dss, nullptr, 1161 &buffer); 1162 if (rv == kCVReturnSuccess) { 1163 return buffer; 1164 // |dss| will be released in |ReleaseSurface()|. 1165 } 1166 LOGE("CVPIxelBufferCreateWithBytes error: %d", rv); 1167 RefPtr<gfx::DataSourceSurface> released = dont_AddRef(dss); 1168 return nullptr; 1169 } 1170 1171 RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Drain() { 1172 return InvokeAsync(mTaskQueue, this, __func__, &AppleVTEncoder::ProcessDrain); 1173 } 1174 1175 RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::ProcessDrain() { 1176 LOGV("::ProcessDrain"); 1177 AssertOnTaskQueue(); 1178 MOZ_ASSERT(mSession); 1179 1180 OSStatus status = 1181 VTCompressionSessionCompleteFrames(mSession, kCMTimeIndefinite); 1182 if (status != noErr) { 1183 LOGE("VTCompressionSessionCompleteFrames error"); 1184 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1185 __func__); 1186 } 1187 1188 // Resolve the pending encode promise if any. 1189 MaybeResolveOrRejectEncodePromise(); 1190 1191 // VTCompressionSessionCompleteFrames() could have queued multiple tasks with 1192 // the new drained frames. Dispatch a task after them to resolve the promise 1193 // with those frames. 1194 RefPtr<AppleVTEncoder> self = this; 1195 return InvokeAsync(mTaskQueue, __func__, [self]() { 1196 EncodedData pendingFrames(std::move(self->mEncodedData)); 1197 LOGV("Resolve drain promise with %zu encoded outputs", 1198 pendingFrames.Length()); 1199 self->mEncodedData = EncodedData(); 1200 return EncodePromise::CreateAndResolve(std::move(pendingFrames), __func__); 1201 }); 1202 } 1203 1204 RefPtr<ShutdownPromise> AppleVTEncoder::Shutdown() { 1205 return InvokeAsync(mTaskQueue, this, __func__, 1206 &AppleVTEncoder::ProcessShutdown); 1207 } 1208 1209 RefPtr<ShutdownPromise> AppleVTEncoder::ProcessShutdown() { 1210 LOGD("::ProcessShutdown"); 1211 AssertOnTaskQueue(); 1212 InvalidateSessionIfNeeded(); 1213 1214 mIsHardwareAccelerated = false; 1215 mError = MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, "Canceled in shutdown"_ns); 1216 MaybeResolveOrRejectEncodePromise(); 1217 mError = NS_OK; 1218 1219 return ShutdownPromise::CreateAndResolve(true, __func__); 1220 } 1221 1222 RefPtr<GenericPromise> AppleVTEncoder::SetBitrate(uint32_t aBitsPerSec) { 1223 RefPtr<AppleVTEncoder> self = this; 1224 return InvokeAsync(mTaskQueue, __func__, [self, aBitsPerSec]() { 1225 MOZ_ASSERT(self->mSession); 1226 bool rv = self->SetBitrateAndMode(self->mConfig.mBitrateMode, aBitsPerSec); 1227 return rv ? GenericPromise::CreateAndResolve(true, __func__) 1228 : GenericPromise::CreateAndReject( 1229 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__); 1230 }); 1231 } 1232 1233 void AppleVTEncoder::MaybeResolveOrRejectEncodePromise() { 1234 AssertOnTaskQueue(); 1235 1236 if (mEncodePromise.IsEmpty()) { 1237 LOGV( 1238 "No pending promise to resolve(pending outputs: %zu) or reject(err: " 1239 "%s)", 1240 mEncodedData.Length(), mError.Description().get()); 1241 return; 1242 } 1243 1244 if (mTimer) { 1245 mTimer->Cancel(); 1246 mTimer = nullptr; 1247 } 1248 1249 if (NS_FAILED(mError.Code())) { 1250 LOGE("Rejecting encode promise with error: %s", mError.Description().get()); 1251 mEncodePromise.Reject(mError, __func__); 1252 return; 1253 } 1254 1255 LOGV("Resolving with %zu encoded outputs", mEncodedData.Length()); 1256 mEncodePromise.Resolve(std::move(mEncodedData), __func__); 1257 } 1258 1259 void AppleVTEncoder::ForceOutputIfNeeded() { 1260 if (__builtin_available(macos 11.0, *)) { 1261 return; 1262 } 1263 1264 AssertOnTaskQueue(); 1265 1266 // Ideally, OutputFrame (called via FrameCallback) should resolve the encode 1267 // promise. However, sometimes output is produced only after multiple 1268 // inputs. To ensure continuous encoding, we force the encoder to produce a 1269 // potentially empty output if no result is received in 50 ms. 1270 RefPtr<AppleVTEncoder> self = this; 1271 auto r = NS_NewTimerWithCallback( 1272 [self](nsITimer* aTimer) { 1273 if (!self->mSession) { 1274 LOGV("Do nothing since the encoder has been shut down"); 1275 return; 1276 } 1277 1278 LOGV("Resolving the pending promise"); 1279 self->MaybeResolveOrRejectEncodePromise(); 1280 }, 1281 TimeDuration::FromMilliseconds(50), nsITimer::TYPE_ONE_SHOT, 1282 "EncodingProgressChecker"_ns, mTaskQueue); 1283 if (r.isErr()) { 1284 LOGE( 1285 "Failed to set an encoding progress checker. Resolve the pending " 1286 "promise now"); 1287 MaybeResolveOrRejectEncodePromise(); 1288 return; 1289 } 1290 mTimer = r.unwrap(); 1291 } 1292 1293 void AppleVTEncoder::EncodeNextSample( 1294 nsTArray<RefPtr<MediaData>>&& aInputs, 1295 MediaDataEncoder::EncodedData&& aOutputs) { 1296 AssertOnTaskQueue(); 1297 MOZ_ASSERT(!mEncodeBatchPromise.IsEmpty()); 1298 MOZ_ASSERT(!mEncodeBatchRequest.Exists()); 1299 1300 if (aInputs.IsEmpty()) { 1301 LOGV("All samples processed. Resolving the encode promise"); 1302 mEncodeBatchPromise.Resolve(std::move(aOutputs), __func__); 1303 return; 1304 } 1305 1306 LOGV("Processing next sample out of %zu remaining", aInputs.Length()); 1307 Encode(aInputs[0]) 1308 ->Then( 1309 GetCurrentSerialEventTarget(), __func__, 1310 [self = RefPtr{this}, inputs = std::move(aInputs), 1311 outputs = std::move(aOutputs)]( 1312 MediaDataEncoder::EncodedData&& aData) mutable { 1313 self->mEncodeBatchRequest.Complete(); 1314 inputs.RemoveElementAt(0); 1315 outputs.AppendElements(aData); 1316 self->EncodeNextSample(std::move(inputs), std::move(outputs)); 1317 }, 1318 [self = RefPtr{this}](const MediaResult& aError) { 1319 self->mEncodeBatchRequest.Complete(); 1320 LOGE("EncodeNextSample failed: %s", aError.Description().get()); 1321 self->mEncodeBatchPromise.Reject(aError, __func__); 1322 }) 1323 ->Track(mEncodeBatchRequest); 1324 } 1325 1326 #undef LOGE 1327 #undef LOGW 1328 #undef LOGD 1329 #undef LOGV 1330 1331 } // namespace mozilla