tor-browser

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

FFmpegDataEncoder.cpp (19702B)


      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 "FFmpegDataEncoder.h"
      8 
      9 #include <utility>
     10 
     11 #include "FFmpegLog.h"
     12 #include "FFmpegUtils.h"
     13 #include "PlatformEncoderModule.h"
     14 #include "libavutil/error.h"
     15 #include "mozilla/StaticMutex.h"
     16 #include "mozilla/StaticPrefs_media.h"
     17 
     18 namespace mozilla {
     19 
     20 template <>
     21 AVCodecID GetFFmpegEncoderCodecId<LIBAV_VER>(CodecType aCodec) {
     22 #if LIBAVCODEC_VERSION_MAJOR >= 58
     23  if (XRE_IsParentProcess() || XRE_IsContentProcess() ||
     24      StaticPrefs::media_use_remote_encoder_video()) {
     25    if (aCodec == CodecType::VP8) {
     26      return AV_CODEC_ID_VP8;
     27    }
     28 
     29    if (aCodec == CodecType::VP9) {
     30      return AV_CODEC_ID_VP9;
     31    }
     32 
     33    if (aCodec == CodecType::H264) {
     34      return AV_CODEC_ID_H264;
     35    }
     36 
     37    if (aCodec == CodecType::H265) {
     38      return AV_CODEC_ID_HEVC;
     39    }
     40 
     41    if (aCodec == CodecType::AV1) {
     42      return AV_CODEC_ID_AV1;
     43    }
     44  }
     45 
     46  if (XRE_IsParentProcess() || XRE_IsContentProcess() ||
     47      StaticPrefs::media_use_remote_encoder_audio()) {
     48    if (aCodec == CodecType::Opus) {
     49      return AV_CODEC_ID_OPUS;
     50    }
     51 
     52    if (aCodec == CodecType::Vorbis) {
     53      return AV_CODEC_ID_VORBIS;
     54    }
     55  }
     56 #endif
     57  return AV_CODEC_ID_NONE;
     58 }
     59 
     60 /* static */
     61 AVCodec* FFmpegDataEncoder<LIBAV_VER>::FindSoftwareEncoder(
     62    const FFmpegLibWrapper* aLib, AVCodecID aCodecId) {
     63  MOZ_ASSERT(aLib);
     64 
     65  // We use this instead of MOZ_USE_HWDECODE because it is possible to disable
     66  // support for hardware encoding in Firefox, while the system ffmpeg library
     67  // still exposes the hardware codecs.
     68 #if LIBAVCODEC_VERSION_MAJOR >= 58
     69  AVCodec* fallbackCodec = nullptr;
     70  void* opaque = nullptr;
     71  while (AVCodec* codec = aLib->av_codec_iterate(&opaque)) {
     72    if (codec->id != aCodecId || !aLib->av_codec_is_encoder(codec)) {
     73      continue;
     74    }
     75 
     76    if (codec->capabilities & AV_CODEC_CAP_HARDWARE) {
     77      continue;
     78    }
     79 
     80    // Prioritize libx264 for now since it's the only h264 codec we tested.
     81    // Once libopenh264 is supported, we can simply use the first one we find.
     82    if (aCodecId == AV_CODEC_ID_H264 && strcmp(codec->name, "libx264") != 0) {
     83      if (!fallbackCodec) {
     84        fallbackCodec = codec;
     85      }
     86      continue;
     87    }
     88 
     89    if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) {
     90      if (!fallbackCodec) {
     91        fallbackCodec = codec;
     92      }
     93      continue;
     94    }
     95 
     96    FFMPEGV_LOG("Using preferred software codec %s", codec->name);
     97    return codec;
     98  }
     99 
    100  if (fallbackCodec) {
    101    FFMPEGV_LOG("Using fallback software codec %s", fallbackCodec->name);
    102  }
    103  return fallbackCodec;
    104 #else
    105  // Prioritize libx264 for now since it's the only h264 codec we tested. Once
    106  // libopenh264 is supported, we can simply use `avcodec_find_encoder` and
    107  // rename this function.
    108  if (aCodecId == AV_CODEC_ID_H264) {
    109    AVCodec* codec = aLib->avcodec_find_encoder_by_name("libx264");
    110    if (codec) {
    111      FFMPEGV_LOG("Prefer libx264 for h264 codec");
    112      return codec;
    113    }
    114    FFMPEGV_LOG("Fallback to other h264 library. Fingers crossed");
    115  }
    116 
    117  return aLib->avcodec_find_encoder(aCodecId);
    118 #endif
    119 }
    120 
    121 #ifdef MOZ_USE_HWDECODE
    122 /* static */
    123 AVCodec* FFmpegDataEncoder<LIBAV_VER>::FindHardwareEncoder(
    124    const FFmpegLibWrapper* aLib, AVCodecID aCodecId) {
    125  MOZ_ASSERT(aLib);
    126 
    127  AVCodec* fallbackCodec = nullptr;
    128  void* opaque = nullptr;
    129  while (AVCodec* codec = aLib->av_codec_iterate(&opaque)) {
    130    if (codec->id != aCodecId || !aLib->av_codec_is_encoder(codec)) {
    131      continue;
    132    }
    133 
    134    bool hasHwConfig = codec->capabilities & AV_CODEC_CAP_HARDWARE;
    135    if (!hasHwConfig) {
    136      for (int i = 0; const AVCodecHWConfig* config =
    137                          aLib->avcodec_get_hw_config(codec, i);
    138           ++i) {
    139        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
    140          hasHwConfig = true;
    141          break;
    142        }
    143      }
    144    }
    145 
    146    if (!hasHwConfig) {
    147      continue;
    148    }
    149 
    150    if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) {
    151      if (!fallbackCodec) {
    152        fallbackCodec = codec;
    153      }
    154      continue;
    155    }
    156 
    157    FFMPEGV_LOG("Using preferred hardware codec %s", codec->name);
    158    return codec;
    159  }
    160 
    161  if (fallbackCodec) {
    162    FFMPEGV_LOG("Using fallback hardware codec %s", fallbackCodec->name);
    163  }
    164  return fallbackCodec;
    165 }
    166 #endif
    167 
    168 /* static */
    169 Result<RefPtr<MediaRawData>, MediaResult>
    170 FFmpegDataEncoder<LIBAV_VER>::CreateMediaRawData(AVPacket* aPacket) {
    171  MOZ_ASSERT(aPacket);
    172 
    173  // Copy frame data from AVPacket.
    174  auto data = MakeRefPtr<MediaRawData>();
    175  UniquePtr<MediaRawDataWriter> writer(data->CreateWriter());
    176  if (!writer->Append(aPacket->data, static_cast<size_t>(aPacket->size))) {
    177    return Err(MediaResult(NS_ERROR_OUT_OF_MEMORY,
    178                           "fail to allocate MediaRawData buffer"_ns));
    179  }
    180  return data;
    181 }
    182 
    183 StaticMutex FFmpegDataEncoder<LIBAV_VER>::sMutex;
    184 
    185 FFmpegDataEncoder<LIBAV_VER>::FFmpegDataEncoder(
    186    const FFmpegLibWrapper* aLib, AVCodecID aCodecID,
    187    const RefPtr<TaskQueue>& aTaskQueue, const EncoderConfig& aConfig)
    188    : mLib(aLib),
    189      mCodecID(aCodecID),
    190      mTaskQueue(aTaskQueue),
    191      mConfig(aConfig),
    192      mCodecName(EmptyCString()),
    193      mCodecContext(nullptr),
    194      mFrame(nullptr),
    195      mVideoCodec(IsVideoCodec(aCodecID)) {
    196  MOZ_ASSERT(mLib);
    197  MOZ_ASSERT(mTaskQueue);
    198 #if LIBAVCODEC_VERSION_MAJOR < 58
    199  MOZ_CRASH("FFmpegDataEncoder needs ffmpeg 58 at least.");
    200 #endif
    201 };
    202 
    203 RefPtr<MediaDataEncoder::EncodePromise> FFmpegDataEncoder<LIBAV_VER>::Encode(
    204    const MediaData* aSample) {
    205  MOZ_ASSERT(aSample != nullptr);
    206 
    207  FFMPEG_LOG("Encode");
    208  return InvokeAsync(
    209      mTaskQueue, __func__,
    210      [self = RefPtr<FFmpegDataEncoder<LIBAV_VER>>(this),
    211       sample = RefPtr<MediaData>(const_cast<MediaData*>(aSample))]() {
    212        return self->ProcessEncode({sample});
    213      });
    214 }
    215 
    216 RefPtr<MediaDataEncoder::EncodePromise> FFmpegDataEncoder<LIBAV_VER>::Encode(
    217    nsTArray<RefPtr<MediaData>>&& aSamples) {
    218  MOZ_ASSERT(!aSamples.IsEmpty());
    219 
    220  FFMPEG_LOG("Encode: %zu samples", aSamples.Length());
    221  return InvokeAsync(mTaskQueue, __func__,
    222                     [self = RefPtr<FFmpegDataEncoder<LIBAV_VER>>(this),
    223                      samples = std::move(aSamples)]() mutable {
    224                       return self->ProcessEncode(std::move(samples));
    225                     });
    226 }
    227 
    228 RefPtr<MediaDataEncoder::ReconfigurationPromise>
    229 FFmpegDataEncoder<LIBAV_VER>::Reconfigure(
    230    const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) {
    231  return InvokeAsync(mTaskQueue, this, __func__,
    232                     &FFmpegDataEncoder<LIBAV_VER>::ProcessReconfigure,
    233                     aConfigurationChanges);
    234 }
    235 
    236 RefPtr<MediaDataEncoder::EncodePromise> FFmpegDataEncoder<LIBAV_VER>::Drain() {
    237  FFMPEG_LOG("Drain");
    238  return InvokeAsync(mTaskQueue, this, __func__,
    239                     &FFmpegDataEncoder::ProcessDrain);
    240 }
    241 
    242 RefPtr<ShutdownPromise> FFmpegDataEncoder<LIBAV_VER>::Shutdown() {
    243  FFMPEG_LOG("Shutdown");
    244  return InvokeAsync(mTaskQueue, this, __func__,
    245                     &FFmpegDataEncoder::ProcessShutdown);
    246 }
    247 
    248 RefPtr<GenericPromise> FFmpegDataEncoder<LIBAV_VER>::SetBitrate(
    249    uint32_t aBitrate) {
    250  FFMPEG_LOG("SetBitrate");
    251  return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
    252 }
    253 
    254 RefPtr<MediaDataEncoder::EncodePromise> FFmpegDataEncoder<
    255    LIBAV_VER>::ProcessEncode(nsTArray<RefPtr<MediaData>>&& aSamples) {
    256  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    257 
    258  FFMPEG_LOG("ProcessEncode: %zu samples", aSamples.Length());
    259 
    260 #if LIBAVCODEC_VERSION_MAJOR < 58
    261  // TODO(Bug 1868253): implement encode with avcodec_encode_video2().
    262  MOZ_CRASH("FFmpegDataEncoder needs ffmpeg 58 at least.");
    263  return EncodePromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
    264 #else
    265  EncodedData output;
    266  for (auto& sample : aSamples) {
    267    auto rv = EncodeInputWithModernAPIs(sample);
    268    if (rv.isErr()) {
    269      MediaResult e = rv.unwrapErr();
    270      FFMPEG_LOG("%s", e.Description().get());
    271      return EncodePromise::CreateAndReject(e, __func__);
    272    }
    273    output.AppendElements(rv.unwrap());
    274  }
    275  return EncodePromise::CreateAndResolve(std::move(output), __func__);
    276 #endif
    277 }
    278 
    279 RefPtr<MediaDataEncoder::ReconfigurationPromise>
    280 FFmpegDataEncoder<LIBAV_VER>::ProcessReconfigure(
    281    const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) {
    282  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    283 
    284  FFMPEG_LOG("ProcessReconfigure");
    285 
    286  bool ok = false;
    287  for (const auto& confChange : aConfigurationChanges->mChanges) {
    288    // A reconfiguration on the fly succeeds if all changes can be applied
    289    // successfuly. In case of failure, the encoder will be drained and
    290    // recreated.
    291    ok &= confChange.match(
    292        // Not supported yet
    293        [&](const DimensionsChange& aChange) -> bool { return false; },
    294        [&](const DisplayDimensionsChange& aChange) -> bool { return false; },
    295        [&](const BitrateModeChange& aChange) -> bool { return false; },
    296        [&](const BitrateChange& aChange) -> bool {
    297          // Verified on x264
    298          if (!strcmp(mCodecContext->codec->name, "libx264")) {
    299            MOZ_ASSERT(aChange.get().ref() != 0);
    300            mConfig.mBitrate = aChange.get().ref();
    301            mCodecContext->bit_rate =
    302                static_cast<FFmpegBitRate>(mConfig.mBitrate);
    303            return true;
    304          }
    305          return false;
    306        },
    307        [&](const FramerateChange& aChange) -> bool { return false; },
    308        [&](const UsageChange& aChange) -> bool { return false; },
    309        [&](const ContentHintChange& aChange) -> bool { return false; },
    310        [&](const SampleRateChange& aChange) -> bool { return false; },
    311        [&](const NumberOfChannelsChange& aChange) -> bool { return false; });
    312  };
    313  using P = MediaDataEncoder::ReconfigurationPromise;
    314  if (ok) {
    315    return P::CreateAndResolve(true, __func__);
    316  }
    317  return P::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
    318 }
    319 
    320 RefPtr<MediaDataEncoder::EncodePromise>
    321 FFmpegDataEncoder<LIBAV_VER>::ProcessDrain() {
    322  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    323 
    324  FFMPEG_LOG("ProcessDrain");
    325 
    326 #if LIBAVCODEC_VERSION_MAJOR < 58
    327  MOZ_CRASH("FFmpegDataEncoder needs ffmpeg 58 at least.");
    328  return EncodePromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
    329 #else
    330  auto rv = DrainWithModernAPIs();
    331  if (rv.isErr()) {
    332    MediaResult e = rv.unwrapErr();
    333    FFMPEG_LOG("%s", e.Description().get());
    334    return EncodePromise::CreateAndReject(rv.inspectErr(), __func__);
    335  }
    336  return EncodePromise::CreateAndResolve(rv.unwrap(), __func__);
    337 #endif
    338 }
    339 
    340 RefPtr<ShutdownPromise> FFmpegDataEncoder<LIBAV_VER>::ProcessShutdown() {
    341  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    342 
    343  FFMPEG_LOG("ProcessShutdown");
    344 
    345  ShutdownInternal();
    346 
    347  // Don't shut mTaskQueue down since it's owned by others.
    348  return ShutdownPromise::CreateAndResolve(true, __func__);
    349 }
    350 
    351 void FFmpegDataEncoder<LIBAV_VER>::SetContextBitrate() {
    352  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    353  MOZ_ASSERT(mCodecContext);
    354 
    355  if (mConfig.mBitrateMode == BitrateMode::Constant) {
    356    mCodecContext->rc_max_rate = static_cast<FFmpegBitRate>(mConfig.mBitrate);
    357    mCodecContext->rc_min_rate = static_cast<FFmpegBitRate>(mConfig.mBitrate);
    358    mCodecContext->bit_rate = static_cast<FFmpegBitRate>(mConfig.mBitrate);
    359    FFMPEG_LOG("Encoding in CBR: %d", mConfig.mBitrate);
    360  } else {
    361    mCodecContext->rc_max_rate = static_cast<FFmpegBitRate>(mConfig.mBitrate);
    362    mCodecContext->rc_min_rate = 0;
    363    mCodecContext->bit_rate = static_cast<FFmpegBitRate>(mConfig.mBitrate);
    364    FFMPEG_LOG("Encoding in VBR: [%d;%d]", (int)mCodecContext->rc_min_rate,
    365               (int)mCodecContext->rc_max_rate);
    366  }
    367 }
    368 
    369 void FFmpegDataEncoder<LIBAV_VER>::ShutdownInternal() {
    370  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    371 
    372  FFMPEG_LOG("ShutdownInternal");
    373 
    374  DestroyFrame();
    375 
    376  if (mCodecContext) {
    377    ReleaseCodecContext();
    378    mCodecContext = nullptr;
    379  }
    380 }
    381 
    382 Result<AVCodecContext*, MediaResult>
    383 FFmpegDataEncoder<LIBAV_VER>::AllocateCodecContext(bool aHardware) {
    384  AVCodec* codec = nullptr;
    385  if (aHardware) {
    386 #ifdef MOZ_USE_HWDECODE
    387    codec = FindHardwareEncoder(mLib, mCodecID);
    388 #endif
    389  } else {
    390    codec = FindSoftwareEncoder(mLib, mCodecID);
    391  }
    392  if (!codec) {
    393    return Err(MediaResult(
    394        NS_ERROR_DOM_MEDIA_FATAL_ERR,
    395        RESULT_DETAIL("failed to find ffmpeg encoder for codec id %d",
    396                      mCodecID)));
    397  }
    398 
    399  AVCodecContext* ctx = mLib->avcodec_alloc_context3(codec);
    400  if (!ctx) {
    401    return Err(MediaResult(
    402        NS_ERROR_OUT_OF_MEMORY,
    403        RESULT_DETAIL("failed to allocate ffmpeg context for codec %s",
    404                      codec->name)));
    405  }
    406 
    407  MOZ_ASSERT(ctx->codec == codec);
    408 
    409  return ctx;
    410 }
    411 
    412 int FFmpegDataEncoder<LIBAV_VER>::OpenCodecContext(const AVCodec* aCodec,
    413                                                   AVDictionary** aOptions) {
    414  MOZ_ASSERT(mCodecContext);
    415 
    416  StaticMutexAutoLock mon(sMutex);
    417  return mLib->avcodec_open2(mCodecContext, aCodec, aOptions);
    418 }
    419 
    420 void FFmpegDataEncoder<LIBAV_VER>::ReleaseCodecContext() {
    421  StaticMutexAutoLock mon(sMutex);
    422  if (!mCodecContext) {
    423    return;
    424  }
    425 
    426 #if LIBAVCODEC_VERSION_MAJOR < 57
    427  mLib->avcodec_close(mCodecContext);
    428  mLib->av_freep(&mCodecContext);
    429 #else
    430  mLib->avcodec_free_context(&mCodecContext);
    431 #endif
    432 }
    433 
    434 bool FFmpegDataEncoder<LIBAV_VER>::PrepareFrame() {
    435  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    436 
    437  // TODO: Merge the duplicate part with FFmpegDataDecoder's PrepareFrame.
    438 #if LIBAVCODEC_VERSION_MAJOR >= 55
    439  if (mFrame) {
    440    mLib->av_frame_unref(mFrame);
    441  } else {
    442    mFrame = mLib->av_frame_alloc();
    443  }
    444 #elif LIBAVCODEC_VERSION_MAJOR == 54
    445  if (mFrame) {
    446    mLib->avcodec_get_frame_defaults(mFrame);
    447  } else {
    448    mFrame = mLib->avcodec_alloc_frame();
    449  }
    450 #else
    451  mLib->av_freep(&mFrame);
    452  mFrame = mLib->avcodec_alloc_frame();
    453 #endif
    454  return !!mFrame;
    455 }
    456 
    457 void FFmpegDataEncoder<LIBAV_VER>::DestroyFrame() {
    458  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    459  if (mFrame) {
    460 #if LIBAVCODEC_VERSION_MAJOR >= 55
    461    mLib->av_frame_unref(mFrame);
    462    mLib->av_frame_free(&mFrame);
    463 #elif LIBAVCODEC_VERSION_MAJOR == 54
    464    mLib->avcodec_free_frame(&mFrame);
    465 #else
    466    mLib->av_freep(&mFrame);
    467 #endif
    468    mFrame = nullptr;
    469  }
    470 }
    471 
    472 // avcodec_send_frame and avcodec_receive_packet were introduced in version 58.
    473 #if LIBAVCODEC_VERSION_MAJOR >= 58
    474 Result<MediaDataEncoder::EncodedData, MediaResult>
    475 FFmpegDataEncoder<LIBAV_VER>::EncodeWithModernAPIs() {
    476  // Initialize AVPacket.
    477  AVPacket* pkt = mLib->av_packet_alloc();
    478 
    479  if (!pkt) {
    480    return Err(
    481        MediaResult(NS_ERROR_OUT_OF_MEMORY, "failed to allocate packet"_ns));
    482  }
    483 
    484  auto freePacket = MakeScopeExit([this, &pkt] { mLib->av_packet_free(&pkt); });
    485 
    486  // Send frame and receive packets.
    487  if (int ret = mLib->avcodec_send_frame(mCodecContext, mFrame); ret < 0) {
    488    // In theory, avcodec_send_frame could sent -EAGAIN to signal its internal
    489    // buffers is full. In practice this can't happen as we only feed one frame
    490    // at a time, and we immediately call avcodec_receive_packet right after.
    491    // TODO: Create a NS_ERROR_DOM_MEDIA_ENCODE_ERR in ErrorList.py?
    492    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    493                           RESULT_DETAIL("avcodec_send_frame error: %s",
    494                                         MakeErrorString(mLib, ret).get())));
    495  }
    496 
    497  EncodedData output;
    498  while (true) {
    499    int ret = mLib->avcodec_receive_packet(mCodecContext, pkt);
    500    if (ret == AVERROR(EAGAIN)) {
    501      // The encoder is asking for more inputs.
    502      FFMPEG_LOG("encoder is asking for more input!");
    503      break;
    504    }
    505 
    506    if (ret < 0) {
    507      // AVERROR_EOF is returned when the encoder has been fully flushed, but it
    508      // shouldn't happen here.
    509      return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    510                             RESULT_DETAIL("avcodec_receive_packet error: %s",
    511                                           MakeErrorString(mLib, ret).get())));
    512    }
    513 
    514    auto r = ToMediaRawData(pkt);
    515    mLib->av_packet_unref(pkt);
    516    if (r.isErr()) {
    517      MediaResult e = r.unwrapErr();
    518      FFMPEG_LOG("%s", e.Description().get());
    519      return Err(e);
    520    }
    521 
    522    RefPtr<MediaRawData> d = r.unwrap();
    523    if (!d) {
    524      // This can happen if e.g. DTX is enabled
    525      FFMPEG_LOG("No encoded packet output");
    526      continue;
    527    }
    528    output.AppendElement(std::move(d));
    529  }
    530 
    531  FFMPEG_LOG("Got %zu encoded data", output.Length());
    532  return std::move(output);
    533 }
    534 
    535 Result<MediaDataEncoder::EncodedData, MediaResult>
    536 FFmpegDataEncoder<LIBAV_VER>::DrainWithModernAPIs() {
    537  MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
    538  MOZ_ASSERT(mCodecContext);
    539 
    540  // TODO: Create a common utility to merge the duplicate code below with
    541  // EncodeWithModernAPIs above.
    542 
    543  // Initialize AVPacket.
    544  AVPacket* pkt = mLib->av_packet_alloc();
    545  if (!pkt) {
    546    return Err(
    547        MediaResult(NS_ERROR_OUT_OF_MEMORY, "failed to allocate packet"_ns));
    548  }
    549  auto freePacket = MakeScopeExit([this, &pkt] { mLib->av_packet_free(&pkt); });
    550 
    551  // Enter draining mode by sending NULL to the avcodec_send_frame(). Note that
    552  // this can leave the encoder in a permanent EOF state after draining. As a
    553  // result, the encoder is unable to continue encoding. A new
    554  // AVCodecContext/encoder creation is required if users need to encode after
    555  // draining.
    556  //
    557  // TODO: Use `avcodec_flush_buffers` to drain the pending packets if
    558  // AV_CODEC_CAP_ENCODER_FLUSH is set in mCodecContext->codec->capabilities.
    559  if (int ret = mLib->avcodec_send_frame(mCodecContext, nullptr); ret < 0) {
    560    if (ret == AVERROR_EOF) {
    561      // The encoder has been flushed. Drain can be called multiple time.
    562      FFMPEG_LOG("encoder has been flushed!");
    563      return EncodedData();
    564    }
    565 
    566    return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    567                           RESULT_DETAIL("avcodec_send_frame error: %s",
    568                                         MakeErrorString(mLib, ret).get())));
    569  }
    570 
    571  EncodedData output;
    572  while (true) {
    573    int ret = mLib->avcodec_receive_packet(mCodecContext, pkt);
    574    if (ret == AVERROR_EOF) {
    575      FFMPEG_LOG("encoder has no more output packet!");
    576      break;
    577    }
    578 
    579    if (ret < 0) {
    580      // avcodec_receive_packet should not result in a -EAGAIN once it's in
    581      // draining mode.
    582      return Err(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    583                             RESULT_DETAIL("avcodec_receive_packet error: %s",
    584                                           MakeErrorString(mLib, ret).get())));
    585    }
    586 
    587    auto r = ToMediaRawData(pkt);
    588    mLib->av_packet_unref(pkt);
    589    if (r.isErr()) {
    590      MediaResult e = r.unwrapErr();
    591      FFMPEG_LOG("%s", e.Description().get());
    592      return Err(e);
    593    }
    594 
    595    RefPtr<MediaRawData> d = r.unwrap();
    596    if (!d) {
    597      return Err(
    598          MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    599                      "failed to create a MediaRawData from the AVPacket"_ns));
    600    }
    601    output.AppendElement(std::move(d));
    602  }
    603 
    604  FFMPEG_LOG("Encoding successful, %zu packets", output.Length());
    605 
    606  // TODO: Evaluate a better solution (Bug 1869466)
    607  // TODO: Only re-create AVCodecContext when avcodec_flush_buffers is
    608  // unavailable.
    609  ShutdownInternal();
    610  MediaResult r = InitEncoder();
    611  if (NS_FAILED(r.Code())) {
    612    FFMPEG_LOG("%s", r.Description().get());
    613    return Err(r);
    614  }
    615 
    616  return std::move(output);
    617 }
    618 #endif  // LIBAVCODEC_VERSION_MAJOR >= 58
    619 
    620 }  // namespace mozilla