tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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