ChromiumCDMParent.cpp (52228B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "ChromiumCDMParent.h" 7 8 #include "AnnexB.h" 9 #include "ChromiumCDMCallback.h" 10 #include "ChromiumCDMCallbackProxy.h" 11 #include "ChromiumCDMProxy.h" 12 #include "GMPContentChild.h" 13 #include "GMPContentParent.h" 14 #include "GMPLog.h" 15 #include "GMPService.h" 16 #include "GMPUtils.h" 17 #include "H264.h" 18 #include "VideoUtils.h" 19 #include "content_decryption_module.h" 20 #include "mozilla/ScopeExit.h" 21 #include "mozilla/StaticPrefs_media.h" 22 #include "mozilla/dom/MediaKeyMessageEventBinding.h" 23 #include "mozilla/gmp/GMPTypes.h" 24 25 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead 26 27 namespace mozilla::gmp { 28 29 using namespace eme; 30 31 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent, 32 uint32_t aPluginId) 33 : mPluginId(aPluginId), 34 mContentParent(aContentParent), 35 mVideoShmemLimit(StaticPrefs::media_eme_chromium_api_video_shmems()) 36 #ifdef DEBUG 37 , 38 mGMPThread(aContentParent->GMPEventTarget()) 39 #endif 40 { 41 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 42 GMP_LOG_DEBUG( 43 "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, " 44 "id=%" PRIu32 ")", 45 this, aContentParent, aPluginId); 46 } 47 48 RefPtr<ChromiumCDMParent::InitPromise> ChromiumCDMParent::Init( 49 ChromiumCDMCallback* aCDMCallback, bool aAllowDistinctiveIdentifier, 50 bool aAllowPersistentState, nsIEventTarget* aMainThread) { 51 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 52 GMP_LOG_DEBUG( 53 "ChromiumCDMParent::Init(this=%p) shutdown=%s abormalShutdown=%s " 54 "actorDestroyed=%s", 55 this, mIsShutdown ? "true" : "false", 56 mAbnormalShutdown ? "true" : "false", mActorDestroyed ? "true" : "false"); 57 if (!aCDMCallback || !aMainThread) { 58 GMP_LOG_DEBUG( 59 "ChromiumCDMParent::Init(this=%p) failed " 60 "nullCallback=%s nullMainThread=%s", 61 this, !aCDMCallback ? "true" : "false", 62 !aMainThread ? "true" : "false"); 63 64 return ChromiumCDMParent::InitPromise::CreateAndReject( 65 MediaResult(NS_ERROR_FAILURE, 66 nsPrintfCString("ChromiumCDMParent::Init() failed " 67 "nullCallback=%s nullMainThread=%s", 68 !aCDMCallback ? "true" : "false", 69 !aMainThread ? "true" : "false")), 70 __func__); 71 } 72 73 RefPtr<ChromiumCDMParent::InitPromise> promise = 74 mInitPromise.Ensure(__func__); 75 RefPtr<ChromiumCDMParent> self = this; 76 SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState) 77 ->Then( 78 GetCurrentSerialEventTarget(), __func__, 79 [self, aCDMCallback](bool aSuccess) { 80 if (!aSuccess) { 81 GMP_LOG_DEBUG( 82 "ChromiumCDMParent::Init() failed with callback from " 83 "child indicating CDM failed init"); 84 self->mInitPromise.RejectIfExists( 85 MediaResult(NS_ERROR_FAILURE, 86 "ChromiumCDMParent::Init() failed with callback " 87 "from child indicating CDM failed init"), 88 __func__); 89 return; 90 } 91 GMP_LOG_DEBUG( 92 "ChromiumCDMParent::Init() succeeded with callback from child"); 93 self->mCDMCallback = aCDMCallback; 94 self->mInitPromise.ResolveIfExists(true /* unused */, __func__); 95 }, 96 [self](ResponseRejectReason&& aReason) { 97 RefPtr<gmp::GeckoMediaPluginService> service = 98 gmp::GeckoMediaPluginService::GetGeckoMediaPluginService(); 99 bool xpcomWillShutdown = 100 service && service->XPCOMWillShutdownReceived(); 101 GMP_LOG_DEBUG( 102 "ChromiumCDMParent::Init(this=%p) failed " 103 "shutdown=%s cdmCrash=%s actorDestroyed=%s " 104 "browserShutdown=%s promiseRejectReason=%d", 105 self.get(), self->mIsShutdown ? "true" : "false", 106 self->mAbnormalShutdown ? "true" : "false", 107 self->mActorDestroyed ? "true" : "false", 108 xpcomWillShutdown ? "true" : "false", 109 static_cast<int>(aReason)); 110 self->mInitPromise.RejectIfExists( 111 MediaResult( 112 NS_ERROR_FAILURE, 113 nsPrintfCString("ChromiumCDMParent::Init() failed " 114 "shutdown=%s cdmCrash=%s actorDestroyed=%s " 115 "browserShutdown=%s promiseRejectReason=%d", 116 self->mIsShutdown ? "true" : "false", 117 self->mAbnormalShutdown ? "true" : "false", 118 self->mActorDestroyed ? "true" : "false", 119 xpcomWillShutdown ? "true" : "false", 120 static_cast<int>(aReason))), 121 __func__); 122 }); 123 return promise; 124 } 125 126 void ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken, 127 uint32_t aSessionType, 128 uint32_t aInitDataType, 129 uint32_t aPromiseId, 130 const nsTArray<uint8_t>& aInitData) { 131 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 132 GMP_LOG_DEBUG("ChromiumCDMParent::CreateSession(this=%p)", this); 133 if (mIsShutdown) { 134 RejectPromiseShutdown(aPromiseId); 135 return; 136 } 137 if (!SendCreateSessionAndGenerateRequest(aPromiseId, aSessionType, 138 aInitDataType, aInitData)) { 139 RejectPromiseWithStateError( 140 aPromiseId, "Failed to send generateRequest to CDM process."_ns); 141 return; 142 } 143 mPromiseToCreateSessionToken.InsertOrUpdate(aPromiseId, aCreateSessionToken); 144 } 145 146 void ChromiumCDMParent::LoadSession(uint32_t aPromiseId, uint32_t aSessionType, 147 nsString aSessionId) { 148 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 149 GMP_LOG_DEBUG("ChromiumCDMParent::LoadSession(this=%p, pid=%" PRIu32 150 ", type=%" PRIu32 ", sid=%s)", 151 this, aPromiseId, aSessionType, 152 NS_ConvertUTF16toUTF8(aSessionId).get()); 153 if (mIsShutdown) { 154 RejectPromiseShutdown(aPromiseId); 155 return; 156 } 157 if (!SendLoadSession(aPromiseId, aSessionType, 158 NS_ConvertUTF16toUTF8(aSessionId))) { 159 RejectPromiseWithStateError( 160 aPromiseId, "Failed to send loadSession to CDM process."_ns); 161 return; 162 } 163 } 164 165 void ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId, 166 const nsTArray<uint8_t>& aCert) { 167 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 168 GMP_LOG_DEBUG("ChromiumCDMParent::SetServerCertificate(this=%p)", this); 169 if (mIsShutdown) { 170 RejectPromiseShutdown(aPromiseId); 171 return; 172 } 173 if (!SendSetServerCertificate(aPromiseId, aCert)) { 174 RejectPromiseWithStateError( 175 aPromiseId, "Failed to send setServerCertificate to CDM process"_ns); 176 } 177 } 178 179 void ChromiumCDMParent::UpdateSession(const nsCString& aSessionId, 180 uint32_t aPromiseId, 181 const nsTArray<uint8_t>& aResponse) { 182 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 183 GMP_LOG_DEBUG("ChromiumCDMParent::UpdateSession(this=%p)", this); 184 if (mIsShutdown) { 185 RejectPromiseShutdown(aPromiseId); 186 return; 187 } 188 if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) { 189 RejectPromiseWithStateError( 190 aPromiseId, "Failed to send updateSession to CDM process"_ns); 191 } 192 } 193 194 void ChromiumCDMParent::CloseSession(const nsCString& aSessionId, 195 uint32_t aPromiseId) { 196 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 197 GMP_LOG_DEBUG("ChromiumCDMParent::CloseSession(this=%p)", this); 198 if (mIsShutdown) { 199 RejectPromiseShutdown(aPromiseId); 200 return; 201 } 202 if (!SendCloseSession(aPromiseId, aSessionId)) { 203 RejectPromiseWithStateError( 204 aPromiseId, "Failed to send closeSession to CDM process"_ns); 205 } 206 } 207 208 void ChromiumCDMParent::RemoveSession(const nsCString& aSessionId, 209 uint32_t aPromiseId) { 210 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 211 GMP_LOG_DEBUG("ChromiumCDMParent::RemoveSession(this=%p)", this); 212 if (mIsShutdown) { 213 RejectPromiseShutdown(aPromiseId); 214 return; 215 } 216 if (!SendRemoveSession(aPromiseId, aSessionId)) { 217 RejectPromiseWithStateError( 218 aPromiseId, "Failed to send removeSession to CDM process"_ns); 219 } 220 } 221 222 void ChromiumCDMParent::NotifyOutputProtectionStatus(bool aSuccess, 223 uint32_t aLinkMask, 224 uint32_t aProtectionMask) { 225 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 226 GMP_LOG_DEBUG("ChromiumCDMParent::NotifyOutputProtectionStatus(this=%p)", 227 this); 228 if (mIsShutdown) { 229 return; 230 } 231 const bool haveCachedValue = mOutputProtectionLinkMask.isSome(); 232 if (mAwaitingOutputProtectionInformation && !aSuccess) { 233 MOZ_DIAGNOSTIC_ASSERT( 234 !haveCachedValue, 235 "Should not have a cached value if we're still awaiting infomation"); 236 // We're awaiting info and don't yet have a cached value, and the check 237 // failed, don't cache the result, just forward the failure. 238 CompleteQueryOutputProtectionStatus(false, aLinkMask, aProtectionMask); 239 return; 240 } 241 if (!mAwaitingOutputProtectionInformation && haveCachedValue && !aSuccess) { 242 // We're not awaiting info, already have a cached value, and the check 243 // failed. Ignore this, we'll update our info from any future successful 244 // checks. 245 return; 246 } 247 MOZ_ASSERT(aSuccess, "Failed checks should be handled by this point"); 248 // Update our protection information. 249 mOutputProtectionLinkMask = Some(aLinkMask); 250 251 if (mAwaitingOutputProtectionInformation) { 252 // If we have an outstanding query, service that query with this 253 // information. 254 mAwaitingOutputProtectionInformation = false; 255 MOZ_ASSERT(!haveCachedValue, 256 "If we were waiting on information, we shouldn't have yet " 257 "cached a value"); 258 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(), 259 aProtectionMask); 260 } 261 } 262 263 void ChromiumCDMParent::CompleteQueryOutputProtectionStatus( 264 bool aSuccess, uint32_t aLinkMask, uint32_t aProtectionMask) { 265 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 266 GMP_LOG_DEBUG( 267 "ChromiumCDMParent::CompleteQueryOutputProtectionStatus(this=%p) " 268 "mIsShutdown=%s aSuccess=%s aLinkMask=%" PRIu32, 269 this, mIsShutdown ? "true" : "false", aSuccess ? "true" : "false", 270 aLinkMask); 271 if (mIsShutdown) { 272 return; 273 } 274 (void)SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask, 275 aProtectionMask); 276 } 277 278 static cdm::HdcpVersion ToCDMHdcpVersion( 279 const dom::HDCPVersion& aMinHdcpVersion) { 280 switch (aMinHdcpVersion) { 281 case dom::HDCPVersion::_1_0: 282 return cdm::HdcpVersion::kHdcpVersion1_0; 283 case dom::HDCPVersion::_1_1: 284 return cdm::HdcpVersion::kHdcpVersion1_1; 285 case dom::HDCPVersion::_1_2: 286 return cdm::HdcpVersion::kHdcpVersion1_2; 287 case dom::HDCPVersion::_1_3: 288 return cdm::HdcpVersion::kHdcpVersion1_3; 289 case dom::HDCPVersion::_1_4: 290 return cdm::HdcpVersion::kHdcpVersion1_4; 291 case dom::HDCPVersion::_2_0: 292 return cdm::HdcpVersion::kHdcpVersion2_0; 293 case dom::HDCPVersion::_2_1: 294 return cdm::HdcpVersion::kHdcpVersion2_1; 295 case dom::HDCPVersion::_2_2: 296 return cdm::HdcpVersion::kHdcpVersion2_2; 297 case dom::HDCPVersion::_2_3: 298 return cdm::HdcpVersion::kHdcpVersion2_3; 299 // When adding another version remember to update GMPMessageUtils so that we 300 // can serialize it correctly and have correct bounds on the enum! 301 default: 302 MOZ_ASSERT_UNREACHABLE("Unexpected HDCP version!"); 303 return cdm::HdcpVersion::kHdcpVersionNone; 304 } 305 } 306 307 void ChromiumCDMParent::GetStatusForPolicy( 308 uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) { 309 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 310 GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this); 311 if (mIsShutdown) { 312 RejectPromiseShutdown(aPromiseId); 313 return; 314 } 315 if (!SendGetStatusForPolicy(aPromiseId, ToCDMHdcpVersion(aMinHdcpVersion))) { 316 RejectPromiseWithStateError( 317 aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns); 318 } 319 } 320 321 bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, 322 MediaRawData* aSample) { 323 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 324 const CryptoSample& crypto = aSample->mCrypto; 325 if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) { 326 GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match"); 327 return false; 328 } 329 330 Shmem shmem; 331 if (!AllocShmem(aSample->Size(), &shmem)) { 332 return false; 333 } 334 memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size()); 335 cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted; 336 switch (crypto.mCryptoScheme) { 337 case CryptoScheme::None: 338 break; // Default to none 339 case CryptoScheme::Cenc: 340 encryptionScheme = cdm::EncryptionScheme::kCenc; 341 break; 342 case CryptoScheme::Cbcs: 343 case CryptoScheme::Cbcs_1_9: 344 encryptionScheme = cdm::EncryptionScheme::kCbcs; 345 break; 346 default: 347 GMP_LOG_DEBUG( 348 "InitCDMInputBuffer got unexpected encryption scheme with " 349 "value of %" PRIu8 ". Treating as no encryption.", 350 static_cast<uint8_t>(crypto.mCryptoScheme)); 351 MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type"); 352 break; 353 } 354 355 const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs 356 ? crypto.mIV 357 : crypto.mConstantIV; 358 aBuffer = gmp::CDMInputBuffer( 359 shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(), 360 aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes, 361 crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock, 362 encryptionScheme); 363 return true; 364 } 365 366 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) { 367 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 368 GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32, 369 aSizeInBytes); 370 Shmem shmem; 371 if (!AllocShmem(aSizeInBytes, &shmem)) { 372 return false; 373 } 374 if (!SendGiveBuffer(std::move(shmem))) { 375 DeallocShmem(shmem); 376 return false; 377 } 378 return true; 379 } 380 381 RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) { 382 if (mIsShutdown) { 383 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 384 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample), 385 __func__); 386 } 387 CDMInputBuffer buffer; 388 if (!InitCDMInputBuffer(buffer, aSample)) { 389 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample), 390 __func__); 391 } 392 // Send a buffer to the CDM to store the output. The CDM will either fill 393 // it with the decrypted sample and return it, or deallocate it on failure. 394 if (!SendBufferToCDM(aSample->Size())) { 395 DeallocShmem(buffer.mData()); 396 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample), 397 __func__); 398 } 399 400 RefPtr<DecryptJob> job = new DecryptJob(aSample); 401 if (!SendDecrypt(job->mId, buffer)) { 402 GMP_LOG_DEBUG( 403 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message", 404 this); 405 DeallocShmem(buffer.mData()); 406 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample), 407 __func__); 408 } 409 RefPtr<DecryptPromise> promise = job->Ensure(); 410 mDecrypts.AppendElement(job); 411 return promise; 412 } 413 414 ipc::IPCResult ChromiumCDMParent::Recv__delete__() { 415 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 416 MOZ_ASSERT(mIsShutdown); 417 GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this); 418 if (mContentParent) { 419 mContentParent->ChromiumCDMDestroyed(this); 420 mContentParent = nullptr; 421 } 422 return IPC_OK(); 423 } 424 425 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus( 426 const uint32_t& aPromiseId, const uint32_t& aKeyStatus) { 427 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 428 GMP_LOG_DEBUG( 429 "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, " 430 "pid=%" PRIu32 ", keystatus=%" PRIu32 ")", 431 this, aPromiseId, aKeyStatus); 432 if (!mCDMCallback || mIsShutdown) { 433 return IPC_OK(); 434 } 435 436 mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus); 437 438 return IPC_OK(); 439 } 440 441 ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise( 442 const uint32_t& aPromiseId, const nsCString& aSessionId) { 443 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 444 GMP_LOG_DEBUG( 445 "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32 446 ", sid=%s)", 447 this, aPromiseId, aSessionId.get()); 448 if (!mCDMCallback || mIsShutdown) { 449 return IPC_OK(); 450 } 451 452 Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId); 453 if (token.isNothing()) { 454 RejectPromiseWithStateError(aPromiseId, 455 "Lost session token for new session."_ns); 456 return IPC_OK(); 457 } 458 459 mCDMCallback->SetSessionId(token.value(), aSessionId); 460 461 ResolvePromise(aPromiseId); 462 463 return IPC_OK(); 464 } 465 466 ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise( 467 const uint32_t& aPromiseId, const bool& aSuccessful) { 468 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 469 GMP_LOG_DEBUG( 470 "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32 471 ", successful=%d)", 472 this, aPromiseId, aSuccessful); 473 if (!mCDMCallback || mIsShutdown) { 474 return IPC_OK(); 475 } 476 477 mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful); 478 479 return IPC_OK(); 480 } 481 482 void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) { 483 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 484 GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")", 485 this, aPromiseId); 486 487 // Note: The MediaKeys rejects all pending DOM promises when it 488 // initiates shutdown. 489 if (!mCDMCallback || mIsShutdown) { 490 return; 491 } 492 493 mCDMCallback->ResolvePromise(aPromiseId); 494 } 495 496 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise( 497 const uint32_t& aPromiseId) { 498 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 499 ResolvePromise(aPromiseId); 500 return IPC_OK(); 501 } 502 503 void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId, 504 ErrorResult&& aException, 505 const nsCString& aErrorMessage) { 506 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 507 GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")", 508 this, aPromiseId); 509 // Note: The MediaKeys rejects all pending DOM promises when it 510 // initiates shutdown. 511 if (!mCDMCallback || mIsShutdown) { 512 // Suppress the exception as it will not be explicitly handled due to the 513 // early return. 514 aException.SuppressException(); 515 return; 516 } 517 518 mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage); 519 } 520 521 void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) { 522 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 523 RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns); 524 } 525 526 void ChromiumCDMParent::RejectPromiseWithStateError( 527 uint32_t aPromiseId, const nsCString& aErrorMessage) { 528 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 529 ErrorResult rv; 530 rv.ThrowInvalidStateError(aErrorMessage); 531 RejectPromise(aPromiseId, std::move(rv), aErrorMessage); 532 } 533 534 static ErrorResult ToErrorResult(uint32_t aException, 535 const nsCString& aErrorMessage) { 536 // XXXbz could we have a CopyableErrorResult sent to us with a better error 537 // message? 538 ErrorResult rv; 539 switch (static_cast<cdm::Exception>(aException)) { 540 case cdm::Exception::kExceptionNotSupportedError: 541 rv.ThrowNotSupportedError(aErrorMessage); 542 break; 543 case cdm::Exception::kExceptionInvalidStateError: 544 rv.ThrowInvalidStateError(aErrorMessage); 545 break; 546 case cdm::Exception::kExceptionTypeError: 547 rv.ThrowTypeError(aErrorMessage); 548 break; 549 case cdm::Exception::kExceptionQuotaExceededError: 550 rv.ThrowQuotaExceededError(aErrorMessage); 551 break; 552 default: 553 MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value."); 554 // Note: Unique placeholder. 555 rv.ThrowTimeoutError(aErrorMessage); 556 }; 557 return rv; 558 } 559 560 ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise( 561 const uint32_t& aPromiseId, const uint32_t& aException, 562 const uint32_t& aSystemCode, const nsCString& aErrorMessage) { 563 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 564 RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage), 565 aErrorMessage); 566 return IPC_OK(); 567 } 568 569 ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage( 570 const nsCString& aSessionId, const uint32_t& aMessageType, 571 nsTArray<uint8_t>&& aMessage) { 572 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 573 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)", 574 this, aSessionId.get()); 575 if (!mCDMCallback || mIsShutdown) { 576 return IPC_OK(); 577 } 578 579 mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage)); 580 return IPC_OK(); 581 } 582 583 ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange( 584 const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) { 585 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 586 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this); 587 if (!mCDMCallback || mIsShutdown) { 588 return IPC_OK(); 589 } 590 591 mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo)); 592 return IPC_OK(); 593 } 594 595 ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange( 596 const nsCString& aSessionId, const double& aSecondsSinceEpoch) { 597 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 598 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf", 599 this, aSecondsSinceEpoch); 600 if (!mCDMCallback || mIsShutdown) { 601 return IPC_OK(); 602 } 603 604 mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch); 605 return IPC_OK(); 606 } 607 608 ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed( 609 const nsCString& aSessionId) { 610 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 611 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this); 612 if (!mCDMCallback || mIsShutdown) { 613 return IPC_OK(); 614 } 615 616 mCDMCallback->SessionClosed(aSessionId); 617 return IPC_OK(); 618 } 619 620 ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() { 621 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 622 GMP_LOG_DEBUG( 623 "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) " 624 "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s", 625 this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false", 626 mAwaitingOutputProtectionInformation ? "true" : "false"); 627 if (mIsShutdown) { 628 // We're shutdown, don't try to service the query. 629 return IPC_OK(); 630 } 631 if (!mCDMCallback) { 632 // We don't have a callback. We're not yet outputting anything so can report 633 // we're safe. 634 CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{}); 635 return IPC_OK(); 636 } 637 638 if (mOutputProtectionLinkMask.isSome()) { 639 MOZ_DIAGNOSTIC_ASSERT( 640 !mAwaitingOutputProtectionInformation, 641 "If we have a cached value we should not be awaiting information"); 642 // We have a cached value, use that. 643 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(), 644 uint32_t{}); 645 return IPC_OK(); 646 } 647 648 // We need to call up the stack to get the info. The CDM proxy will finish 649 // the request via `NotifyOutputProtectionStatus`. 650 mAwaitingOutputProtectionInformation = true; 651 mCDMCallback->QueryOutputProtectionStatus(); 652 return IPC_OK(); 653 } 654 655 DecryptStatus ToDecryptStatus(uint32_t aStatus) { 656 switch (static_cast<cdm::Status>(aStatus)) { 657 case cdm::kSuccess: 658 return DecryptStatus::Ok; 659 case cdm::kNoKey: 660 return DecryptStatus::NoKeyErr; 661 default: 662 return DecryptStatus::GenericErr; 663 } 664 } 665 666 ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId, 667 const uint32_t& aStatus) { 668 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 669 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32 670 ", status=%" PRIu32 ")", 671 this, aId, aStatus); 672 673 if (mIsShutdown) { 674 MOZ_ASSERT(mDecrypts.IsEmpty()); 675 return IPC_OK(); 676 } 677 678 for (size_t i = 0; i < mDecrypts.Length(); i++) { 679 if (mDecrypts[i]->mId == aId) { 680 mDecrypts[i]->PostResult(ToDecryptStatus(aStatus)); 681 mDecrypts.RemoveElementAt(i); 682 break; 683 } 684 } 685 return IPC_OK(); 686 } 687 688 ipc::IPCResult ChromiumCDMParent::RecvDecryptedShmem(const uint32_t& aId, 689 const uint32_t& aStatus, 690 ipc::Shmem&& aShmem) { 691 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 692 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedShmem(this=%p, id=%" PRIu32 693 ", status=%" PRIu32 ")", 694 this, aId, aStatus); 695 696 // We must deallocate the shmem once we've copied the result out of it 697 // in PostResult below. 698 auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); }); 699 700 if (mIsShutdown) { 701 MOZ_ASSERT(mDecrypts.IsEmpty()); 702 return IPC_OK(); 703 } 704 for (size_t i = 0; i < mDecrypts.Length(); i++) { 705 if (mDecrypts[i]->mId == aId) { 706 mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), 707 aShmem.IsReadable() 708 ? Span<const uint8_t>(aShmem.get<uint8_t>(), 709 aShmem.Size<uint8_t>()) 710 : Span<const uint8_t>()); 711 mDecrypts.RemoveElementAt(i); 712 break; 713 } 714 } 715 return IPC_OK(); 716 } 717 718 ipc::IPCResult ChromiumCDMParent::RecvDecryptedData(const uint32_t& aId, 719 const uint32_t& aStatus, 720 nsTArray<uint8_t>&& aData) { 721 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 722 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedData(this=%p, id=%" PRIu32 723 ", status=%" PRIu32 ")", 724 this, aId, aStatus); 725 726 if (mIsShutdown) { 727 MOZ_ASSERT(mDecrypts.IsEmpty()); 728 return IPC_OK(); 729 } 730 for (size_t i = 0; i < mDecrypts.Length(); i++) { 731 if (mDecrypts[i]->mId == aId) { 732 mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData); 733 mDecrypts.RemoveElementAt(i); 734 break; 735 } 736 } 737 return IPC_OK(); 738 } 739 740 ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() { 741 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 742 GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this, 743 mVideoShmemLimit, mVideoShmemsActive); 744 745 // Put an upper limit on the number of shmems we tolerate the CDM asking 746 // for, to prevent a memory blow-out. In practice, we expect the CDM to 747 // need less than 5, but some encodings require more. 748 // We'd expect CDMs to not have video frames larger than 720p-1080p 749 // (due to DRM robustness requirements), which is about 1.5MB-3MB per 750 // frame. 751 if (mVideoShmemLimit > 50) { 752 mDecodePromise.RejectIfExists( 753 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 754 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")), 755 __func__); 756 Shutdown(); 757 return IPC_OK(); 758 } 759 mVideoShmemLimit++; 760 761 EnsureSufficientShmems(mVideoFrameBufferSize); 762 763 return IPC_OK(); 764 } 765 766 bool ChromiumCDMParent::PurgeShmems() { 767 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 768 GMP_LOG_DEBUG( 769 "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu" 770 " limit=%" PRIu32 " active=%" PRIu32, 771 this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive); 772 773 if (mVideoShmemsActive == 0) { 774 // We haven't allocated any shmems, nothing to do here. 775 return true; 776 } 777 if (!SendPurgeShmems()) { 778 return false; 779 } 780 mVideoShmemsActive = 0; 781 return true; 782 } 783 784 bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) { 785 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 786 GMP_LOG_DEBUG( 787 "ChromiumCDMParent::EnsureSufficientShmems(this=%p) " 788 "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32, 789 this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit, 790 mVideoShmemsActive); 791 792 // The Chromium CDM API requires us to implement a synchronous 793 // interface to supply buffers to the CDM for it to write decrypted samples 794 // into. We want our buffers to be backed by shmems, in order to reduce 795 // the overhead of transferring decoded frames. However due to sandboxing 796 // restrictions, the CDM process cannot allocate shmems itself. 797 // We don't want to be doing synchronous IPC to request shmems from the 798 // content process, nor do we want to have to do intr IPC or make async 799 // IPC conform to the sync allocation interface. So instead we have the 800 // content process pre-allocate a set of shmems and give them to the CDM 801 // process in advance of them being needed. 802 // 803 // When the CDM needs to allocate a buffer for storing a decoded video 804 // frame, the CDM host gives it one of these shmems' buffers. When this 805 // is sent back to the content process, we upload it to a GPU surface, 806 // and send the shmem back to the CDM process so it can reuse it. 807 // 808 // Normally the CDM won't allocate more than one buffer at once, but 809 // we've seen cases where it allocates multiple buffers, returns one and 810 // holds onto the rest. So we need to ensure we have several extra 811 // shmems pre-allocated for the CDM. This threshold is set by the pref 812 // media.eme.chromium-api.video-shmems. 813 // 814 // We also have a failure recovery mechanism; if the CDM asks for more 815 // buffers than we have shmem's available, ChromiumCDMChild gives the 816 // CDM a non-shared memory buffer, and returns the frame to the parent 817 // in an nsTArray<uint8_t> instead of a shmem. The child then sends a 818 // message to the parent asking it to increase the number of shmems in 819 // the pool. Via this mechanism we should recover from incorrectly 820 // predicting how many shmems to pre-allocate. 821 // 822 // At decoder start up, we guess how big the shmems need to be based on 823 // the video frame dimensions. If we guess wrong, the CDM will follow 824 // the non-shmem path, and we'll re-create the shmems of the correct size. 825 // This meanns we can recover from guessing the shmem size wrong. 826 // We must re-take this path after every decoder de-init/re-init, as the 827 // frame sizes should change every time we switch video stream. 828 829 if (mVideoFrameBufferSize < aVideoFrameSize) { 830 if (!PurgeShmems()) { 831 return false; 832 } 833 mVideoFrameBufferSize = aVideoFrameSize; 834 } 835 836 while (mVideoShmemsActive < mVideoShmemLimit) { 837 if (!SendBufferToCDM(mVideoFrameBufferSize)) { 838 return false; 839 } 840 mVideoShmemsActive++; 841 } 842 843 return true; 844 } 845 846 ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame, 847 nsTArray<uint8_t>&& aData) { 848 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 849 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64, 850 this, aFrame.mTimestamp()); 851 852 if (mIsShutdown || mDecodePromise.IsEmpty()) { 853 return IPC_OK(); 854 } 855 856 if (!EnsureSufficientShmems(aData.Length())) { 857 mDecodePromise.RejectIfExists( 858 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 859 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")), 860 __func__); 861 return IPC_OK(); 862 } 863 864 RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData); 865 if (!v) { 866 mDecodePromise.RejectIfExists( 867 MediaResult(NS_ERROR_OUT_OF_MEMORY, 868 RESULT_DETAIL("Can't create VideoData")), 869 __func__); 870 return IPC_OK(); 871 } 872 873 ReorderAndReturnOutput(std::move(v)); 874 875 return IPC_OK(); 876 } 877 878 ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame, 879 ipc::Shmem&& aShmem) { 880 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 881 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64 882 " duration=%" PRId64, 883 this, aFrame.mTimestamp(), aFrame.mDuration()); 884 885 // On failure we need to deallocate the shmem we're to return to the 886 // CDM. On success we return it to the CDM to be reused. 887 auto autoDeallocateShmem = 888 MakeScopeExit([&, this] { this->DeallocShmem(aShmem); }); 889 890 if (mIsShutdown || mDecodePromise.IsEmpty()) { 891 return IPC_OK(); 892 } 893 894 RefPtr<VideoData> v = CreateVideoFrame( 895 aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>())); 896 if (!v) { 897 mDecodePromise.RejectIfExists( 898 MediaResult(NS_ERROR_OUT_OF_MEMORY, 899 RESULT_DETAIL("Can't create VideoData")), 900 __func__); 901 return IPC_OK(); 902 } 903 904 // Return the shmem to the CDM so the shmem can be reused to send us 905 // another frame. 906 if (!SendGiveBuffer(std::move(aShmem))) { 907 mDecodePromise.RejectIfExists( 908 MediaResult(NS_ERROR_OUT_OF_MEMORY, 909 RESULT_DETAIL("Can't return shmem to CDM process")), 910 __func__); 911 return IPC_OK(); 912 } 913 914 // Don't need to deallocate the shmem since the CDM process is responsible 915 // for it again. 916 autoDeallocateShmem.release(); 917 918 ReorderAndReturnOutput(std::move(v)); 919 920 return IPC_OK(); 921 } 922 923 void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) { 924 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 925 if (mMaxRefFrames == 0) { 926 mDecodePromise.ResolveIfExists( 927 MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__); 928 return; 929 } 930 mReorderQueue.Push(std::move(aFrame)); 931 MediaDataDecoder::DecodedData results; 932 while (mReorderQueue.Length() > mMaxRefFrames) { 933 results.AppendElement(mReorderQueue.Pop()); 934 } 935 mDecodePromise.Resolve(std::move(results), __func__); 936 } 937 938 already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame( 939 const CDMVideoFrame& aFrame, Span<uint8_t> aData) { 940 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 941 MOZ_ASSERT(aData.Length() > 0); 942 GMP_LOG_DEBUG( 943 "ChromiumCDMParent::CreateVideoFrame(this=%p aFrame.mFormat=%" PRIu32 ")", 944 this, aFrame.mFormat()); 945 946 if (aFrame.mFormat() == cdm::VideoFormat::kUnknownVideoFormat) { 947 GMP_LOG_DEBUG( 948 "ChromiumCDMParent::CreateVideoFrame(this=%p) Got kUnknownVideoFormat, " 949 "bailing.", 950 this); 951 return nullptr; 952 } 953 954 if (aFrame.mFormat() == cdm::VideoFormat::kYUV420P9 || 955 aFrame.mFormat() == cdm::VideoFormat::kYUV422P9 || 956 aFrame.mFormat() == cdm::VideoFormat::kYUV444P9) { 957 // If we ever hit this we can reconsider support, but 9 bit formats 958 // should be so rare as to be non-existent. 959 GMP_LOG_DEBUG( 960 "ChromiumCDMParent::CreateVideoFrame(this=%p) Got a 9 bit depth pixel " 961 "format. We don't support these, bailing.", 962 this); 963 return nullptr; 964 } 965 966 VideoData::YCbCrBuffer b; 967 968 // Determine the dimensions of our chroma planes, color depth and chroma 969 // subsampling. 970 uint32_t chromaWidth = (aFrame.mImageWidth() + 1) / 2; 971 uint32_t chromaHeight = (aFrame.mImageHeight() + 1) / 2; 972 gfx::ColorDepth colorDepth = gfx::ColorDepth::COLOR_8; 973 gfx::ChromaSubsampling chromaSubsampling = 974 gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; 975 switch (aFrame.mFormat()) { 976 case cdm::VideoFormat::kYv12: 977 case cdm::VideoFormat::kI420: 978 break; 979 case cdm::VideoFormat::kYUV420P10: 980 colorDepth = gfx::ColorDepth::COLOR_10; 981 break; 982 case cdm::VideoFormat::kYUV422P10: 983 chromaHeight = aFrame.mImageHeight(); 984 colorDepth = gfx::ColorDepth::COLOR_10; 985 chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH; 986 break; 987 case cdm::VideoFormat::kYUV444P10: 988 chromaWidth = aFrame.mImageWidth(); 989 chromaHeight = aFrame.mImageHeight(); 990 colorDepth = gfx::ColorDepth::COLOR_10; 991 chromaSubsampling = gfx::ChromaSubsampling::FULL; 992 break; 993 case cdm::VideoFormat::kYUV420P12: 994 colorDepth = gfx::ColorDepth::COLOR_12; 995 break; 996 case cdm::VideoFormat::kYUV422P12: 997 chromaHeight = aFrame.mImageHeight(); 998 colorDepth = gfx::ColorDepth::COLOR_12; 999 chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH; 1000 break; 1001 case cdm::VideoFormat::kYUV444P12: 1002 chromaWidth = aFrame.mImageWidth(); 1003 chromaHeight = aFrame.mImageHeight(); 1004 colorDepth = gfx::ColorDepth::COLOR_12; 1005 chromaSubsampling = gfx::ChromaSubsampling::FULL; 1006 break; 1007 default: 1008 MOZ_ASSERT_UNREACHABLE("Should handle all formats"); 1009 return nullptr; 1010 } 1011 1012 // Since we store each plane separately we can just roll the offset 1013 // into our pointer to that plane and store that. 1014 b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset(); 1015 b.mPlanes[0].mWidth = aFrame.mImageWidth(); 1016 b.mPlanes[0].mHeight = aFrame.mImageHeight(); 1017 b.mPlanes[0].mStride = aFrame.mYPlane().mStride(); 1018 b.mPlanes[0].mSkip = 0; 1019 1020 b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset(); 1021 b.mPlanes[1].mWidth = chromaWidth; 1022 b.mPlanes[1].mHeight = chromaHeight; 1023 b.mPlanes[1].mStride = aFrame.mUPlane().mStride(); 1024 b.mPlanes[1].mSkip = 0; 1025 1026 b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset(); 1027 b.mPlanes[2].mWidth = chromaWidth; 1028 b.mPlanes[2].mHeight = chromaHeight; 1029 b.mPlanes[2].mStride = aFrame.mVPlane().mStride(); 1030 b.mPlanes[2].mSkip = 0; 1031 1032 b.mColorDepth = colorDepth; 1033 b.mChromaSubsampling = chromaSubsampling; 1034 1035 // Ensure that each plane fits within the buffer bounds. 1036 auto bpp = BytesPerPixel(SurfaceFormatForColorDepth(colorDepth)); 1037 for (const auto& plane : b.mPlanes) { 1038 auto rowSize = CheckedInt<uint32_t>(plane.mWidth) * bpp; 1039 if (NS_WARN_IF(!rowSize.isValid() || rowSize.value() > plane.mStride)) { 1040 GMP_LOG_DEBUG( 1041 "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane width %u stride " 1042 "%u bpp %d mismatch, bailing.", 1043 this, plane.mWidth, plane.mStride, bpp); 1044 return nullptr; 1045 } 1046 auto size = CheckedInt<uint32_t>(plane.mStride) * plane.mHeight; 1047 if (NS_WARN_IF(!size.isValid())) { 1048 GMP_LOG_DEBUG( 1049 "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride " 1050 "%u integer overflow, bailing.", 1051 this, plane.mHeight, plane.mStride); 1052 return nullptr; 1053 } 1054 auto offset = plane.mData - aData.Elements(); 1055 auto required = size + offset; 1056 if (NS_WARN_IF(!required.isValid() || required.value() > aData.Length())) { 1057 GMP_LOG_DEBUG( 1058 "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride " 1059 "%u offset %u buffer length %zu overflow, bailing.", 1060 this, plane.mHeight, plane.mStride, static_cast<uint32_t>(offset), 1061 aData.Length()); 1062 return nullptr; 1063 } 1064 } 1065 1066 // We unfortunately can't know which colorspace the video is using at this 1067 // stage. 1068 b.mYUVColorSpace = 1069 DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()}); 1070 1071 gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight()); 1072 1073 mozilla::Result<already_AddRefed<VideoData>, MediaResult> r = 1074 VideoData::CreateAndCopyData( 1075 mVideoInfo, mImageContainer, mLastStreamOffset, 1076 media::TimeUnit::FromMicroseconds( 1077 CheckedInt64(aFrame.mTimestamp()).value()), 1078 media::TimeUnit::FromMicroseconds( 1079 CheckedInt64(aFrame.mDuration()).value()), 1080 b, false, media::TimeUnit::FromMicroseconds(-1), pictureRegion, 1081 mKnowsCompositor); 1082 RefPtr<VideoData> v = r.unwrapOr(nullptr); 1083 1084 if (!v || !v->mImage) { 1085 NS_WARNING("Failed to decode video frame."); 1086 return v.forget(); 1087 } 1088 1089 // This is a DRM image. 1090 v->mImage->SetIsDRM(true); 1091 1092 return v.forget(); 1093 } 1094 1095 ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) { 1096 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1097 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32 1098 ")", 1099 this, aStatus); 1100 if (mIsShutdown) { 1101 MOZ_ASSERT(mDecodePromise.IsEmpty()); 1102 return IPC_OK(); 1103 } 1104 1105 if (aStatus == cdm::kNeedMoreData) { 1106 mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__); 1107 return IPC_OK(); 1108 } 1109 1110 mDecodePromise.RejectIfExists( 1111 MediaResult( 1112 NS_ERROR_DOM_MEDIA_FATAL_ERR, 1113 RESULT_DETAIL( 1114 "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32 1115 ")", 1116 cdm::EnumValueToString(cdm::Status(aStatus)), aStatus)), 1117 __func__); 1118 return IPC_OK(); 1119 } 1120 1121 ipc::IPCResult ChromiumCDMParent::RecvShutdown() { 1122 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1123 GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this); 1124 Shutdown(); 1125 return IPC_OK(); 1126 } 1127 1128 void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) { 1129 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1130 GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this, 1131 aWhy); 1132 MOZ_ASSERT(!mActorDestroyed); 1133 mActorDestroyed = true; 1134 // Shutdown() will clear mCDMCallback, so let's keep a reference for later 1135 // use. 1136 auto callback = mCDMCallback; 1137 if (!mIsShutdown) { 1138 // Plugin crash. 1139 MOZ_ASSERT(aWhy == AbnormalShutdown); 1140 Shutdown(); 1141 } 1142 MOZ_ASSERT(mIsShutdown); 1143 RefPtr<ChromiumCDMParent> kungFuDeathGrip(this); 1144 if (mContentParent) { 1145 mContentParent->ChromiumCDMDestroyed(this); 1146 mContentParent = nullptr; 1147 } 1148 mAbnormalShutdown = (aWhy == AbnormalShutdown); 1149 if (mAbnormalShutdown && callback) { 1150 callback->Terminated(); 1151 } 1152 MaybeDisconnect(mAbnormalShutdown); 1153 } 1154 1155 RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder( 1156 const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo, 1157 RefPtr<layers::ImageContainer> aImageContainer, 1158 RefPtr<layers::KnowsCompositor> aKnowsCompositor) { 1159 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1160 if (mIsShutdown) { 1161 return MediaDataDecoder::InitPromise::CreateAndReject( 1162 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1163 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1164 __func__); 1165 } 1166 1167 // The Widevine CDM version 1.4.8.970 and above contain a video decoder that 1168 // does not optimally allocate video frames; it requests buffers much larger 1169 // than required. The exact formula the CDM uses to calculate their frame 1170 // sizes isn't obvious, but they normally request around or slightly more 1171 // than 1.5X the optimal amount. So pad the size of buffers we allocate so 1172 // that we're likely to have buffers big enough to accomodate the CDM's weird 1173 // frame size calculation. 1174 const size_t bufferSize = 1175 1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height); 1176 if (bufferSize <= 0) { 1177 return MediaDataDecoder::InitPromise::CreateAndReject( 1178 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1179 RESULT_DETAIL("Video frame buffer size is invalid.")), 1180 __func__); 1181 } 1182 1183 if (!EnsureSufficientShmems(bufferSize)) { 1184 return MediaDataDecoder::InitPromise::CreateAndReject( 1185 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1186 RESULT_DETAIL("Failed to init shmems for video decoder")), 1187 __func__); 1188 } 1189 1190 if (!SendInitializeVideoDecoder(aConfig)) { 1191 return MediaDataDecoder::InitPromise::CreateAndReject( 1192 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1193 RESULT_DETAIL("Failed to send init video decoder to CDM")), 1194 __func__); 1195 } 1196 1197 mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264) 1198 ? H264::HasSPS(aInfo.mExtraData) 1199 ? H264::ComputeMaxRefFrames(aInfo.mExtraData) 1200 : 16 1201 : 0; 1202 1203 mVideoDecoderInitialized = true; 1204 mImageContainer = aImageContainer; 1205 mKnowsCompositor = aKnowsCompositor; 1206 mVideoInfo = aInfo; 1207 mVideoFrameBufferSize = bufferSize; 1208 1209 return mInitVideoDecoderPromise.Ensure(__func__); 1210 } 1211 1212 ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone( 1213 const uint32_t& aStatus) { 1214 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1215 GMP_LOG_DEBUG( 1216 "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")", 1217 this, aStatus); 1218 if (mIsShutdown) { 1219 MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty()); 1220 return IPC_OK(); 1221 } 1222 if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) { 1223 mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__); 1224 } else { 1225 mVideoDecoderInitialized = false; 1226 mInitVideoDecoderPromise.RejectIfExists( 1227 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1228 RESULT_DETAIL( 1229 "CDM init decode failed with status %s (%" PRIu32 ")", 1230 cdm::EnumValueToString(cdm::Status(aStatus)), aStatus)), 1231 __func__); 1232 } 1233 return IPC_OK(); 1234 } 1235 1236 RefPtr<MediaDataDecoder::DecodePromise> 1237 ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) { 1238 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1239 if (mIsShutdown) { 1240 return MediaDataDecoder::DecodePromise::CreateAndReject( 1241 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1242 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1243 __func__); 1244 } 1245 1246 GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64, 1247 aSample->mTime.ToMicroseconds()); 1248 1249 CDMInputBuffer buffer; 1250 1251 if (!InitCDMInputBuffer(buffer, aSample)) { 1252 return MediaDataDecoder::DecodePromise::CreateAndReject( 1253 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."), 1254 __func__); 1255 } 1256 1257 mLastStreamOffset = aSample->mOffset; 1258 1259 if (!SendDecryptAndDecodeFrame(buffer)) { 1260 GMP_LOG_DEBUG( 1261 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.", 1262 this); 1263 DeallocShmem(buffer.mData()); 1264 return MediaDataDecoder::DecodePromise::CreateAndReject( 1265 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1266 "Failed to send decrypt to CDM process."), 1267 __func__); 1268 } 1269 1270 return mDecodePromise.Ensure(__func__); 1271 } 1272 1273 RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() { 1274 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1275 if (mIsShutdown) { 1276 MOZ_ASSERT(mReorderQueue.IsEmpty()); 1277 return MediaDataDecoder::FlushPromise::CreateAndReject( 1278 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1279 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1280 __func__); 1281 } 1282 1283 mReorderQueue.Clear(); 1284 1285 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); 1286 if (!SendResetVideoDecoder()) { 1287 return MediaDataDecoder::FlushPromise::CreateAndReject( 1288 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1289 "Failed to send flush to CDM."), 1290 __func__); 1291 } 1292 return mFlushDecoderPromise.Ensure(__func__); 1293 } 1294 1295 ipc::IPCResult ChromiumCDMParent::RecvResetVideoDecoderComplete() { 1296 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1297 MOZ_ASSERT(mReorderQueue.IsEmpty()); 1298 if (mIsShutdown) { 1299 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty()); 1300 return IPC_OK(); 1301 } 1302 mFlushDecoderPromise.ResolveIfExists(true, __func__); 1303 return IPC_OK(); 1304 } 1305 1306 RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() { 1307 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1308 MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete"); 1309 if (mIsShutdown) { 1310 return MediaDataDecoder::DecodePromise::CreateAndReject( 1311 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1312 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1313 __func__); 1314 } 1315 1316 RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__); 1317 if (!SendDrain()) { 1318 mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__); 1319 } 1320 return p; 1321 } 1322 1323 ipc::IPCResult ChromiumCDMParent::RecvDrainComplete() { 1324 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1325 if (mIsShutdown) { 1326 MOZ_ASSERT(mDecodePromise.IsEmpty()); 1327 return IPC_OK(); 1328 } 1329 1330 MediaDataDecoder::DecodedData samples; 1331 while (!mReorderQueue.IsEmpty()) { 1332 samples.AppendElement(mReorderQueue.Pop()); 1333 } 1334 1335 mDecodePromise.ResolveIfExists(std::move(samples), __func__); 1336 return IPC_OK(); 1337 } 1338 RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() { 1339 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1340 if (mIsShutdown || !mVideoDecoderInitialized) { 1341 return ShutdownPromise::CreateAndResolve(true, __func__); 1342 } 1343 mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, 1344 __func__); 1345 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); 1346 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty()); 1347 if (!SendDeinitializeVideoDecoder()) { 1348 return ShutdownPromise::CreateAndResolve(true, __func__); 1349 } 1350 mVideoDecoderInitialized = false; 1351 1352 GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this); 1353 1354 // The ChromiumCDMChild will purge its shmems, so if the decoder is 1355 // reinitialized the shmems need to be re-allocated, and they may need 1356 // to be a different size. 1357 mVideoShmemsActive = 0; 1358 mVideoFrameBufferSize = 0; 1359 return ShutdownPromise::CreateAndResolve(true, __func__); 1360 } 1361 1362 void ChromiumCDMParent::Shutdown() { 1363 MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); 1364 GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this); 1365 1366 if (mIsShutdown) { 1367 return; 1368 } 1369 mIsShutdown = true; 1370 1371 // If we're shutting down due to the plugin shutting down due to application 1372 // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the 1373 // proxy will shutdown when the owning MediaKeys is destroyed during cycle 1374 // collection, and that will not shut down cleanly as the GMP thread will be 1375 // shutdown by then. 1376 if (mCDMCallback) { 1377 mCDMCallback->Shutdown(); 1378 } 1379 1380 // We may be called from a task holding the last reference to the CDM 1381 // callback, so let's clear our local weak pointer to ensure it will not be 1382 // used afterward (including from an already-queued task, e.g.: ActorDestroy). 1383 mCDMCallback = nullptr; 1384 1385 mReorderQueue.Clear(); 1386 1387 for (RefPtr<DecryptJob>& decrypt : mDecrypts) { 1388 decrypt->PostResult(eme::AbortedErr); 1389 } 1390 mDecrypts.Clear(); 1391 1392 if (mVideoDecoderInitialized && !mActorDestroyed) { 1393 (void)SendDeinitializeVideoDecoder(); 1394 mVideoDecoderInitialized = false; 1395 } 1396 1397 // Note: MediaKeys rejects all outstanding promises when it initiates 1398 // shutdown. 1399 mPromiseToCreateSessionToken.Clear(); 1400 1401 mInitPromise.RejectIfExists( 1402 MediaResult(NS_ERROR_DOM_ABORT_ERR, 1403 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1404 __func__); 1405 1406 mInitVideoDecoderPromise.RejectIfExists( 1407 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1408 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1409 __func__); 1410 mDecodePromise.RejectIfExists( 1411 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1412 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1413 __func__); 1414 mFlushDecoderPromise.RejectIfExists( 1415 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 1416 RESULT_DETAIL("ChromiumCDMParent is shutdown")), 1417 __func__); 1418 1419 if (!mActorDestroyed) { 1420 (void)SendDestroy(); 1421 } 1422 } 1423 1424 } // namespace mozilla::gmp 1425 1426 #undef NS_DispatchToMainThread