GMPVideoEncoder.cpp (19446B)
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 "GMPVideoEncoder.h" 8 9 #include "AnnexB.h" 10 #include "ErrorList.h" 11 #include "GMPLog.h" 12 #include "GMPService.h" 13 #include "GMPUtils.h" 14 #include "GMPVideoHost.h" 15 #include "H264.h" 16 #include "ImageContainer.h" 17 #include "ImageConversion.h" 18 #include "mozilla/StaticPrefs_media.h" 19 #include "nsServiceManagerUtils.h" 20 #include "prsystem.h" 21 22 namespace mozilla { 23 24 static GMPVideoCodecMode ToGMPVideoCodecMode(Usage aUsage) { 25 switch (aUsage) { 26 case Usage::Realtime: 27 return kGMPRealtimeVideo; 28 case Usage::Record: 29 default: 30 // ParamValidationExt in OpenH264 rejects all other codec modes besides 31 // realtime and screensharing. 32 return kGMPScreensharing; 33 } 34 } 35 36 static GMPProfile ToGMPProfile(H264_PROFILE aProfile) { 37 switch (aProfile) { 38 case H264_PROFILE_MAIN: 39 return kGMPH264ProfileMain; 40 case H264_PROFILE_EXTENDED: 41 return kGMPH264ProfileExtended; 42 case H264_PROFILE_HIGH: 43 return kGMPH264ProfileHigh; 44 case H264_PROFILE_UNKNOWN: 45 default: 46 return kGMPH264ProfileUnknown; 47 } 48 } 49 50 static GMPLevel ToGMPLevel(H264_LEVEL aLevel) { 51 switch (aLevel) { 52 case H264_LEVEL::H264_LEVEL_1: 53 return kGMPH264Level1_0; 54 case H264_LEVEL::H264_LEVEL_1_1: 55 // H264_LEVEL_1_b has the same value as H264_LEVEL_1_1, while 56 // kGMPH264Level1_B and kGMPH264Level1_1 differ. Since we can't tell the 57 // difference, we just ignore the 1_b case. 58 return kGMPH264Level1_1; 59 case H264_LEVEL::H264_LEVEL_1_2: 60 return kGMPH264Level1_2; 61 case H264_LEVEL::H264_LEVEL_1_3: 62 return kGMPH264Level1_3; 63 case H264_LEVEL::H264_LEVEL_2: 64 return kGMPH264Level2_0; 65 case H264_LEVEL::H264_LEVEL_2_1: 66 return kGMPH264Level2_1; 67 case H264_LEVEL::H264_LEVEL_2_2: 68 return kGMPH264Level2_2; 69 case H264_LEVEL::H264_LEVEL_3: 70 return kGMPH264Level3_0; 71 case H264_LEVEL::H264_LEVEL_3_1: 72 return kGMPH264Level3_1; 73 case H264_LEVEL::H264_LEVEL_3_2: 74 return kGMPH264Level3_2; 75 case H264_LEVEL::H264_LEVEL_4: 76 return kGMPH264Level4_0; 77 case H264_LEVEL::H264_LEVEL_4_1: 78 return kGMPH264Level4_1; 79 case H264_LEVEL::H264_LEVEL_4_2: 80 return kGMPH264Level4_2; 81 case H264_LEVEL::H264_LEVEL_5: 82 return kGMPH264Level5_0; 83 case H264_LEVEL::H264_LEVEL_5_1: 84 return kGMPH264Level5_1; 85 case H264_LEVEL::H264_LEVEL_5_2: 86 return kGMPH264Level5_2; 87 // 6.0 and above isn't supported. 88 default: 89 return kGMPH264LevelUnknown; 90 } 91 } 92 93 RefPtr<MediaDataEncoder::InitPromise> GMPVideoEncoder::Init() { 94 MOZ_ASSERT(IsOnGMPThread()); 95 MOZ_ASSERT(mInitPromise.IsEmpty()); 96 97 mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); 98 MOZ_ASSERT(mMPS); 99 100 RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__)); 101 102 nsTArray<nsCString> tags(1); 103 tags.AppendElement("h264"_ns); 104 105 UniquePtr<GetGMPVideoEncoderCallback> callback(new InitDoneCallback(this)); 106 if (NS_FAILED(mMPS->GetGMPVideoEncoder(nullptr, &tags, ""_ns, 107 std::move(callback)))) { 108 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Init -- failed to request encoder", 109 this); 110 mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 111 } 112 113 return promise; 114 } 115 116 void GMPVideoEncoder::InitComplete(GMPVideoEncoderProxy* aGMP, 117 GMPVideoHost* aHost) { 118 MOZ_ASSERT(IsOnGMPThread()); 119 120 mGMP = aGMP; 121 mHost = aHost; 122 123 if (NS_WARN_IF(!mGMP) || NS_WARN_IF(!mHost) || 124 NS_WARN_IF(mInitPromise.IsEmpty())) { 125 GMP_LOG_ERROR( 126 "[%p] GMPVideoEncoder::InitComplete -- failed to create proxy/host", 127 this); 128 Teardown(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "No proxy/host"), 129 __func__); 130 return; 131 } 132 133 GMPVideoCodec codec{}; 134 135 codec.mGMPApiVersion = kGMPVersion36; 136 codec.mCodecType = kGMPVideoCodecH264; 137 codec.mMode = ToGMPVideoCodecMode(mConfig.mUsage); 138 codec.mWidth = mConfig.mSize.width; 139 codec.mHeight = mConfig.mSize.height; 140 141 // A bitrate need to be set here, attempt to make an educated guess if none is 142 // provided. 143 if (mConfig.mBitrate) { 144 codec.mStartBitrate = mConfig.mBitrate / 1000; 145 } else { 146 int32_t longDimension = std::max(mConfig.mSize.width, mConfig.mSize.height); 147 if (longDimension < 720) { 148 codec.mStartBitrate = 2000; 149 } else if (longDimension < 1080) { 150 codec.mStartBitrate = 4000; 151 } else { 152 codec.mStartBitrate = 8000; 153 } 154 } 155 156 codec.mMinBitrate = mConfig.mMinBitrate / 1000; 157 codec.mMaxBitrate = mConfig.mMaxBitrate ? mConfig.mMaxBitrate / 1000 158 : codec.mStartBitrate * 2; 159 codec.mMaxFramerate = mConfig.mFramerate; 160 codec.mUseThreadedEncode = StaticPrefs::media_gmp_encoder_multithreaded(); 161 codec.mLogLevel = GetGMPLibraryLogLevel(); 162 163 switch (mConfig.mScalabilityMode) { 164 case ScalabilityMode::L1T2: 165 codec.mTemporalLayerNum = 2; 166 break; 167 case ScalabilityMode::L1T3: 168 codec.mTemporalLayerNum = 3; 169 break; 170 default: 171 MOZ_FALLTHROUGH_ASSERT("Unhandled scalability mode!"); 172 case ScalabilityMode::None: 173 codec.mTemporalLayerNum = 1; 174 break; 175 } 176 177 if (mConfig.mCodecSpecific.is<H264Specific>()) { 178 const H264Specific& specific = mConfig.mCodecSpecific.as<H264Specific>(); 179 codec.mProfile = ToGMPProfile(specific.mProfile); 180 codec.mLevel = ToGMPLevel(specific.mLevel); 181 } 182 183 nsTArray<uint8_t> codecSpecific; 184 GMPErr err = mGMP->InitEncode(codec, codecSpecific, this, 185 PR_GetNumberOfProcessors(), 0); 186 if (NS_WARN_IF(err != GMPNoErr)) { 187 GMP_LOG_ERROR("[%p] GMPVideoEncoder::InitComplete -- failed to init proxy", 188 this); 189 Teardown(ToMediaResult(err, "InitEncode failed"_ns), __func__); 190 return; 191 } 192 193 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::InitComplete -- encoder initialized", 194 this); 195 mInitPromise.Resolve(true, __func__); 196 } 197 198 RefPtr<MediaDataEncoder::EncodePromise> GMPVideoEncoder::Encode( 199 const MediaData* aSample) { 200 MOZ_ASSERT(aSample != nullptr); 201 MOZ_ASSERT(IsOnGMPThread()); 202 203 if (NS_WARN_IF(!IsInitialized())) { 204 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 205 __func__); 206 } 207 208 GMPVideoFrame* ftmp = nullptr; 209 GMPErr err = mHost->CreateFrame(kGMPI420VideoFrame, &ftmp); 210 if (NS_WARN_IF(err != GMPNoErr)) { 211 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Encode -- failed to create frame", 212 this); 213 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 214 __func__); 215 } 216 217 GMPUniquePtr<GMPVideoi420Frame> frame(static_cast<GMPVideoi420Frame*>(ftmp)); 218 const VideoData* sample(aSample->As<const VideoData>()); 219 const uint64_t timestamp = sample->mTime.ToMicroseconds(); 220 221 const gfx::IntSize ySize = mConfig.mSize; 222 const gfx::IntSize cbCrSize = 223 gfx::ChromaSize(ySize, gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT); 224 const int32_t yStride = ySize.width; 225 const int32_t cbCrStride = cbCrSize.width; 226 227 GMP_LOG_DEBUG( 228 "[%p] GMPVideoEncoder::Encode -- request encode of frame @ %" PRIu64 229 " y %dx%d stride=%d cbCr %dx%d stride=%d", 230 this, timestamp, ySize.width, ySize.height, yStride, cbCrSize.width, 231 cbCrSize.height, cbCrStride); 232 233 err = frame->CreateEmptyFrame(ySize.width, ySize.height, yStride, cbCrStride, 234 cbCrStride); 235 if (NS_WARN_IF(err != GMPNoErr)) { 236 GMP_LOG_ERROR( 237 "[%p] GMPVideoEncoder::Encode -- failed to allocate frame data", this); 238 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 239 __func__); 240 } 241 242 uint8_t* yDest = frame->Buffer(GMPPlaneType::kGMPYPlane); 243 uint8_t* uDest = frame->Buffer(GMPPlaneType::kGMPUPlane); 244 uint8_t* vDest = frame->Buffer(GMPPlaneType::kGMPVPlane); 245 246 nsresult rv = ConvertToI420(sample->mImage, yDest, yStride, uDest, cbCrStride, 247 vDest, cbCrStride, ySize); 248 if (NS_WARN_IF(NS_FAILED(rv))) { 249 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Encode -- failed to convert to I420", 250 this); 251 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 252 __func__); 253 } 254 255 frame->SetTimestamp(timestamp); 256 257 AutoTArray<GMPVideoFrameType, 1> frameType; 258 frameType.AppendElement(sample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame); 259 260 nsTArray<uint8_t> codecSpecific; 261 err = mGMP->Encode(std::move(frame), codecSpecific, frameType); 262 if (NS_WARN_IF(err != GMPNoErr)) { 263 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Encode -- failed to queue frame", 264 this); 265 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 266 __func__); 267 } 268 269 RefPtr<EncodePromise::Private> promise = new EncodePromise::Private(__func__); 270 mPendingEncodes.InsertOrUpdate(timestamp, promise); 271 return promise.forget(); 272 } 273 274 // TODO(Bug 1984936): For realtime mode, resolve the promise after the first 275 // sample's result is available, then continue processing remaining samples. 276 // This allows the caller to keep submitting new samples while the encoder 277 // handles pending ones. 278 RefPtr<MediaDataEncoder::EncodePromise> GMPVideoEncoder::Encode( 279 nsTArray<RefPtr<MediaData>>&& aSamples) { 280 MOZ_ASSERT(!aSamples.IsEmpty()); 281 MOZ_ASSERT(IsOnGMPThread()); 282 283 if (NS_WARN_IF(!IsInitialized())) { 284 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 285 __func__); 286 } 287 288 RefPtr<EncodePromise> promise = mEncodeBatchPromise.Ensure(__func__); 289 EncodeNextSample(std::move(aSamples), EncodedData()); 290 return promise; 291 } 292 293 RefPtr<MediaDataEncoder::ReconfigurationPromise> GMPVideoEncoder::Reconfigure( 294 const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) { 295 // General reconfiguration interface not implemented right now 296 return MediaDataEncoder::ReconfigurationPromise::CreateAndReject( 297 NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 298 } 299 300 RefPtr<MediaDataEncoder::EncodePromise> GMPVideoEncoder::Drain() { 301 MOZ_ASSERT(IsOnGMPThread()); 302 MOZ_ASSERT(mDrainPromise.IsEmpty()); 303 304 if (NS_WARN_IF(!IsInitialized())) { 305 return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 306 __func__); 307 } 308 309 if (mPendingEncodes.IsEmpty()) { 310 return EncodePromise::CreateAndResolve(EncodedData(), __func__); 311 } 312 313 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Drain -- waiting for queue to clear", 314 this); 315 return mDrainPromise.Ensure(__func__); 316 } 317 318 RefPtr<ShutdownPromise> GMPVideoEncoder::Shutdown() { 319 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Shutdown", this); 320 MOZ_ASSERT(IsOnGMPThread()); 321 322 Teardown(MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, "Shutdown"_ns), __func__); 323 return ShutdownPromise::CreateAndResolve(true, __func__); 324 } 325 326 RefPtr<GenericPromise> GMPVideoEncoder::SetBitrate(uint32_t aBitsPerSec) { 327 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::SetBitrate -- %u", this, aBitsPerSec); 328 MOZ_ASSERT(IsOnGMPThread()); 329 330 if (NS_WARN_IF(!IsInitialized())) { 331 return GenericPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 332 __func__); 333 } 334 335 GMPErr err = mGMP->SetRates(aBitsPerSec / 1000, mConfig.mFramerate); 336 if (NS_WARN_IF(err != GMPNoErr)) { 337 return GenericPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 338 __func__); 339 } 340 341 return GenericPromise::CreateAndResolve(true, __func__); 342 } 343 344 void GMPVideoEncoder::Encoded(GMPVideoEncodedFrame* aEncodedFrame, 345 const nsTArray<uint8_t>& aCodecSpecificInfo) { 346 MOZ_ASSERT(IsOnGMPThread()); 347 MOZ_ASSERT(aEncodedFrame); 348 349 uint64_t timestamp = aEncodedFrame->TimeStamp(); 350 351 RefPtr<EncodePromise::Private> promise; 352 if (!mPendingEncodes.Remove(timestamp, getter_AddRefs(promise))) { 353 GMP_LOG_WARNING( 354 "[%p] GMPVideoEncoder::Encoded -- no frame matching timestamp %" PRIu64, 355 this, timestamp); 356 return; 357 } 358 359 uint8_t* encodedData = aEncodedFrame->Buffer(); 360 uint32_t encodedSize = aEncodedFrame->Size(); 361 362 if (NS_WARN_IF(encodedSize == 0) || NS_WARN_IF(!encodedData) || 363 NS_WARN_IF(aEncodedFrame->BufferType() != GMP_BufferLength32)) { 364 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Encoded -- bad/empty frame", this); 365 promise->Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 366 Teardown(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Bad/empty frame"_ns), 367 __func__); 368 return; 369 } 370 371 // This code was copied from WebrtcGmpVideoEncoder::Encoded in order to 372 // massage/correct issues with OpenH264 and WebRTC. This allows us to use the 373 // PlatformEncoderModule framework with WebRTC, fallback to this encoder and 374 // actually render the video. 375 if (NS_WARN_IF(!AdjustOpenH264NALUSequence(aEncodedFrame))) { 376 promise->Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 377 Teardown(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Bad frame data"_ns), 378 __func__); 379 return; 380 } 381 382 auto output = MakeRefPtr<MediaRawData>(); 383 384 UniquePtr<MediaRawDataWriter> writer(output->CreateWriter()); 385 if (NS_WARN_IF(!writer->SetSize(encodedSize))) { 386 GMP_LOG_ERROR( 387 "[%p] GMPVideoEncoder::Encoded -- failed to allocate %u buffer", this, 388 encodedSize); 389 promise->Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 390 Teardown(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Init writer failed"_ns), 391 __func__); 392 return; 393 } 394 395 memcpy(writer->Data(), encodedData, encodedSize); 396 397 output->mTime = 398 media::TimeUnit::FromMicroseconds(static_cast<int64_t>(timestamp)); 399 output->mKeyframe = aEncodedFrame->FrameType() == kGMPKeyFrame; 400 401 int32_t maybeTemporalLayerId = aEncodedFrame->GetTemporalLayerId(); 402 auto temporalLayerId = CheckedUint8(maybeTemporalLayerId); 403 if (temporalLayerId.isValid()) { 404 output->mTemporalLayerId = Some(temporalLayerId.value()); 405 } 406 407 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Encoded -- %sframe @ timestamp %" PRIu64 408 ", temporal layer %d", 409 this, output->mKeyframe ? "key" : "", timestamp, 410 maybeTemporalLayerId); 411 412 if (mConfig.mCodecSpecific.is<H264Specific>()) { 413 const H264Specific& specific = mConfig.mCodecSpecific.as<H264Specific>(); 414 if (specific.mFormat == H264BitStreamFormat::AVC) { 415 const uint8_t kExtraData[] = { 416 1 /* version */, 417 static_cast<uint8_t>(specific.mProfile), 418 0 /* profile compat (0) */, 419 static_cast<uint8_t>(specific.mLevel), 420 0xfc | 3 /* nal size - 1 */, 421 0xe0 /* num SPS (0) */, 422 0 /* num PPS (0) */ 423 }; 424 425 auto extraData = MakeRefPtr<MediaByteBuffer>(); 426 extraData->AppendElements(kExtraData, std::size(kExtraData)); 427 428 if (NS_WARN_IF(!AnnexB::ConvertSampleToAVCC(output, extraData))) { 429 GMP_LOG_ERROR( 430 "[%p] GMPVideoEncoder::Encoded -- failed to convert to AVCC", this); 431 promise->Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); 432 Teardown( 433 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Convert AVCC failed"_ns), 434 __func__); 435 return; 436 } 437 } 438 } 439 440 EncodedData encodedDataSet(1); 441 encodedDataSet.AppendElement(std::move(output)); 442 promise->Resolve(std::move(encodedDataSet), __func__); 443 444 if (mPendingEncodes.IsEmpty()) { 445 mDrainPromise.ResolveIfExists(EncodedData(), __func__); 446 } 447 } 448 449 void GMPVideoEncoder::Dropped(uint64_t aTimestamp) { 450 MOZ_ASSERT(IsOnGMPThread()); 451 452 RefPtr<EncodePromise::Private> promise; 453 if (!mPendingEncodes.Remove(aTimestamp, getter_AddRefs(promise))) { 454 GMP_LOG_WARNING( 455 "[%p] GMPVideoEncoder::Dropped -- no frame matching timestamp %" PRIu64, 456 this, aTimestamp); 457 return; 458 } 459 460 promise->Reject(NS_ERROR_DOM_MEDIA_DROPPED_BY_ENCODER_ERR, __func__); 461 } 462 463 void GMPVideoEncoder::Teardown(const MediaResult& aResult, 464 StaticString aCallSite) { 465 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Teardown", this); 466 MOZ_ASSERT(IsOnGMPThread()); 467 468 // Ensure we are kept alive at least until we return. 469 RefPtr<GMPVideoEncoder> self(this); 470 471 mEncodeBatchPromise.RejectIfExists(aResult, aCallSite); 472 mEncodeBatchRequest.DisconnectIfExists(); 473 474 PendingEncodePromises pendingEncodes = std::move(mPendingEncodes); 475 for (auto i = pendingEncodes.Iter(); !i.Done(); i.Next()) { 476 i.Data()->Reject(aResult, aCallSite); 477 } 478 479 mInitPromise.RejectIfExists(aResult, aCallSite); 480 mDrainPromise.RejectIfExists(aResult, aCallSite); 481 482 if (mGMP) { 483 mGMP->Close(); 484 mGMP = nullptr; 485 } 486 487 mHost = nullptr; 488 } 489 490 void GMPVideoEncoder::Error(GMPErr aError) { 491 GMP_LOG_ERROR("[%p] GMPVideoEncoder::Error -- GMPErr(%u)", this, 492 uint32_t(aError)); 493 MOZ_ASSERT(IsOnGMPThread()); 494 Teardown(ToMediaResult(aError, "Error GMP callback"_ns), __func__); 495 } 496 497 void GMPVideoEncoder::Terminated() { 498 GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Terminated", this); 499 MOZ_ASSERT(IsOnGMPThread()); 500 Teardown( 501 MediaResult(NS_ERROR_DOM_MEDIA_ABORT_ERR, "Terminated GMP callback"_ns), 502 __func__); 503 } 504 505 void GMPVideoEncoder::EncodeNextSample( 506 nsTArray<RefPtr<MediaData>>&& aInputs, 507 MediaDataEncoder::EncodedData&& aOutputs) { 508 MOZ_ASSERT(IsOnGMPThread()); 509 MOZ_ASSERT(IsInitialized()); 510 MOZ_ASSERT(!mEncodeBatchPromise.IsEmpty()); 511 MOZ_ASSERT(!mEncodeBatchRequest.Exists()); 512 513 if (aInputs.IsEmpty()) { 514 GMP_LOG_VERBOSE("[%p] All samples processed. Resolving the encode promise", 515 this); 516 mEncodeBatchPromise.Resolve(std::move(aOutputs), __func__); 517 return; 518 } 519 520 GMP_LOG_VERBOSE("[%p] Processing next sample out of %zu remaining", this, 521 aInputs.Length()); 522 Encode(aInputs[0]) 523 ->Then( 524 GetCurrentSerialEventTarget(), __func__, 525 [self = RefPtr{this}, inputs = std::move(aInputs), 526 outputs = std::move(aOutputs)]( 527 EncodePromise::ResolveOrRejectValue&& aValue) mutable { 528 self->mEncodeBatchRequest.Complete(); 529 if (aValue.IsReject() && 530 aValue.RejectValue().Code() != 531 NS_ERROR_DOM_MEDIA_DROPPED_BY_ENCODER_ERR) { 532 auto& error = aValue.RejectValue(); 533 GMP_LOG_ERROR( 534 "[%p] GMPVideoEncoder::EncodeNextSample -- failed to encode: " 535 "%s", 536 self.get(), error.Description().get()); 537 self->mEncodeBatchPromise.Reject(error, __func__); 538 return; 539 } 540 inputs.RemoveElementAt(0); 541 if (aValue.IsResolve()) { 542 outputs.AppendElements(aValue.ResolveValue()); 543 } else { 544 GMP_LOG_WARNING( 545 "[%p] GMPVideoEncoder::EncodeNextSample -- dropped by " 546 "encoder: %s. Continuing.", 547 self.get(), aValue.RejectValue().Description().get()); 548 } 549 self->EncodeNextSample(std::move(inputs), std::move(outputs)); 550 }) 551 ->Track(mEncodeBatchRequest); 552 } 553 554 } // namespace mozilla