/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AppleVTEncoder.h"

#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFByteOrder.h>
#include <CoreFoundation/CFDictionary.h>

#include "ImageContainer.h"
#include "AnnexB.h"
#include "H264.h"

#include "libyuv.h"

#include "AppleUtils.h"

namespace mozilla {
extern LazyLogModule sPEMLog;
#define VTENC_LOGE(fmt, ...)                 \
  MOZ_LOG(sPEMLog, mozilla::LogLevel::Error, \
          ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__))
#define VTENC_LOGD(fmt, ...)                 \
  MOZ_LOG(sPEMLog, mozilla::LogLevel::Debug, \
          ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__))

static CFDictionaryRef BuildEncoderSpec(const bool aHardwareNotAllowed) {
  const void* keys[] = {
      kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder};
  const void* values[] = {aHardwareNotAllowed ? kCFBooleanFalse
                                              : kCFBooleanTrue};

  static_assert(ArrayLength(keys) == ArrayLength(values),
                "Non matching keys/values array size");
  return CFDictionaryCreate(kCFAllocatorDefault, keys, values,
                            ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
                            &kCFTypeDictionaryValueCallBacks);
}

static void FrameCallback(void* aEncoder, void* aFrameRefCon, OSStatus aStatus,
                          VTEncodeInfoFlags aInfoFlags,
                          CMSampleBufferRef aSampleBuffer) {
  if (aStatus != noErr || !aSampleBuffer) {
    VTENC_LOGE("VideoToolbox encoder returned no data status=%d sample=%p",
               aStatus, aSampleBuffer);
    aSampleBuffer = nullptr;
  } else if (aInfoFlags & kVTEncodeInfo_FrameDropped) {
    VTENC_LOGE("frame tagged as dropped");
    return;
  }
  (static_cast<AppleVTEncoder*>(aEncoder))->OutputFrame(aSampleBuffer);
}

static bool SetAverageBitrate(VTCompressionSessionRef& aSession,
                              MediaDataEncoder::Rate aBitsPerSec) {
  int64_t bps(aBitsPerSec);
  AutoCFRelease<CFNumberRef> bitrate(
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &bps));
  return VTSessionSetProperty(aSession,
                              kVTCompressionPropertyKey_AverageBitRate,
                              bitrate) == noErr;
}

static bool SetRealtimeProperties(VTCompressionSessionRef& aSession) {
  return VTSessionSetProperty(aSession, kVTCompressionPropertyKey_RealTime,
                              kCFBooleanTrue) == noErr &&
         VTSessionSetProperty(aSession,
                              kVTCompressionPropertyKey_AllowFrameReordering,
                              kCFBooleanFalse) == noErr;
}

static bool SetProfileLevel(VTCompressionSessionRef& aSession,
                            AppleVTEncoder::H264Specific::ProfileLevel aValue) {
  CFStringRef profileLevel = nullptr;
  switch (aValue) {
    case AppleVTEncoder::H264Specific::ProfileLevel::BaselineAutoLevel:
      profileLevel = kVTProfileLevel_H264_Baseline_AutoLevel;
      break;
    case AppleVTEncoder::H264Specific::ProfileLevel::MainAutoLevel:
      profileLevel = kVTProfileLevel_H264_Main_AutoLevel;
      break;
  }

  return profileLevel ? VTSessionSetProperty(
                            aSession, kVTCompressionPropertyKey_ProfileLevel,
                            profileLevel) == noErr
                      : false;
}

RefPtr<MediaDataEncoder::InitPromise> AppleVTEncoder::Init() {
  MOZ_ASSERT(!mInited, "Cannot initialize encoder again without shutting down");

  if (mConfig.mSize.width == 0 || mConfig.mSize.height == 0) {
    return InitPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
  }

  AutoCFRelease<CFDictionaryRef> spec(BuildEncoderSpec(mHardwareNotAllowed));
  AutoCFRelease<CFDictionaryRef> srcBufferAttr(
      BuildSourceImageBufferAttributes());
  if (!srcBufferAttr) {
    return InitPromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
                    "fail to create source buffer attributes"),
        __func__);
  }

  OSStatus status = VTCompressionSessionCreate(
      kCFAllocatorDefault, mConfig.mSize.width, mConfig.mSize.height,
      kCMVideoCodecType_H264, spec, srcBufferAttr, kCFAllocatorDefault,
      &FrameCallback, this /* outputCallbackRefCon */, &mSession);

  if (status != noErr) {
    return InitPromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                    "fail to create encoder session"),
        __func__);
  }

  if (!SetAverageBitrate(mSession, mConfig.mBitsPerSec)) {
    return InitPromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                    "fail to configurate average bitrate"),
        __func__);
  }

  if (mConfig.mUsage == Usage::Realtime && !SetRealtimeProperties(mSession)) {
    VTENC_LOGE("fail to configurate realtime properties");
    return InitPromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                    "fail to configurate average bitrate"),
        __func__);
  }

  int64_t interval =
      mConfig.mKeyframeInterval > std::numeric_limits<int64_t>::max()
          ? std::numeric_limits<int64_t>::max()
          : mConfig.mKeyframeInterval;
  AutoCFRelease<CFNumberRef> cf(
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &interval));
  if (VTSessionSetProperty(mSession,
                           kVTCompressionPropertyKey_MaxKeyFrameInterval,
                           cf) != noErr) {
    return InitPromise::CreateAndReject(
        MediaResult(
            NS_ERROR_DOM_MEDIA_FATAL_ERR,
            nsPrintfCString("fail to configurate keyframe interval:%" PRId64,
                            interval)),
        __func__);
  }

  if (mConfig.mCodecSpecific) {
    const H264Specific& specific = mConfig.mCodecSpecific.ref();
    if (!SetProfileLevel(mSession, specific.mProfileLevel)) {
      return InitPromise::CreateAndReject(
          MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                      nsPrintfCString("fail to configurate profile level:%d",
                                      int(specific.mProfileLevel))),
          __func__);
    }
  }

  CFBooleanRef isUsingHW = nullptr;
  status = VTSessionCopyProperty(
      mSession, kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
      kCFAllocatorDefault, &isUsingHW);
  mIsHardwareAccelerated = status == noErr && isUsingHW == kCFBooleanTrue;
  if (isUsingHW) {
    CFRelease(isUsingHW);
  }

  mError = NS_OK;
  return InitPromise::CreateAndResolve(TrackInfo::TrackType::kVideoTrack,
                                       __func__);
}

static Maybe<OSType> MapPixelFormat(MediaDataEncoder::PixelFormat aFormat) {
  switch (aFormat) {
    case MediaDataEncoder::PixelFormat::RGBA32:
    case MediaDataEncoder::PixelFormat::BGRA32:
      return Some(kCVPixelFormatType_32BGRA);
    case MediaDataEncoder::PixelFormat::RGB24:
      return Some(kCVPixelFormatType_24RGB);
    case MediaDataEncoder::PixelFormat::BGR24:
      return Some(kCVPixelFormatType_24BGR);
    case MediaDataEncoder::PixelFormat::GRAY8:
      return Some(kCVPixelFormatType_OneComponent8);
    case MediaDataEncoder::PixelFormat::YUV444P:
      return Some(kCVPixelFormatType_444YpCbCr8);
    case MediaDataEncoder::PixelFormat::YUV420P:
      return Some(kCVPixelFormatType_420YpCbCr8PlanarFullRange);
    case MediaDataEncoder::PixelFormat::YUV420SP_NV12:
      return Some(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
    default:
      return Nothing();
  }
}

CFDictionaryRef AppleVTEncoder::BuildSourceImageBufferAttributes() {
  Maybe<OSType> fmt = MapPixelFormat(mConfig.mSourcePixelFormat);
  if (fmt.isNothing()) {
    VTENC_LOGE("unsupported source pixel format");
    return nullptr;
  }

  // Source image buffer attributes
  const void* keys[] = {kCVPixelBufferOpenGLCompatibilityKey,  // TODO
                        kCVPixelBufferIOSurfacePropertiesKey,  // TODO
                        kCVPixelBufferPixelFormatTypeKey};

  AutoCFRelease<CFDictionaryRef> ioSurfaceProps(CFDictionaryCreate(
      kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
      &kCFTypeDictionaryValueCallBacks));
  AutoCFRelease<CFNumberRef> pixelFormat(
      CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fmt));
  const void* values[] = {kCFBooleanTrue, ioSurfaceProps, pixelFormat};

  MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values),
             "Non matching keys/values array size");

  return CFDictionaryCreate(kCFAllocatorDefault, keys, values,
                            ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
                            &kCFTypeDictionaryValueCallBacks);
}

static bool IsKeyframe(CMSampleBufferRef aSample) {
  CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(aSample, 0);
  if (attachments == nullptr || CFArrayGetCount(attachments) == 0) {
    return false;
  }

  return !CFDictionaryContainsKey(
      static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)),
      kCMSampleAttachmentKey_NotSync);
}

static size_t GetNumParamSets(CMFormatDescriptionRef aDescription) {
  size_t numParamSets = 0;
  OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
      aDescription, 0, nullptr, nullptr, &numParamSets, nullptr);
  if (status != noErr) {
    VTENC_LOGE("Cannot get number of parameter sets from format description");
  }

  return numParamSets;
}

static const uint8_t kNALUStart[4] = {0, 0, 0, 1};

static size_t GetParamSet(CMFormatDescriptionRef aDescription, size_t aIndex,
                          const uint8_t** aDataPtr) {
  size_t length = 0;
  int headerSize = 0;
  if (CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
          aDescription, aIndex, aDataPtr, &length, nullptr, &headerSize) !=
      noErr) {
    VTENC_LOGE("fail to get parameter set from format description");
    return 0;
  }
  MOZ_ASSERT(headerSize == sizeof(kNALUStart), "Only support 4 byte header");

  return length;
}

static bool WriteSPSPPS(MediaRawData* aDst,
                        CMFormatDescriptionRef aDescription) {
  // Get SPS/PPS
  const size_t numParamSets = GetNumParamSets(aDescription);
  UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter());
  for (size_t i = 0; i < numParamSets; i++) {
    const uint8_t* data = nullptr;
    size_t length = GetParamSet(aDescription, i, &data);
    if (length == 0) {
      return false;
    }
    if (!writer->Append(kNALUStart, sizeof(kNALUStart))) {
      VTENC_LOGE("Cannot write NAL unit start code");
      return false;
    }
    if (!writer->Append(data, length)) {
      VTENC_LOGE("Cannot write parameter set");
      return false;
    }
  }
  return true;
}

static RefPtr<MediaByteBuffer> extractAvcc(
    CMFormatDescriptionRef aDescription) {
  CFPropertyListRef list = CMFormatDescriptionGetExtension(
      aDescription,
      kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms);
  if (!list) {
    VTENC_LOGE("fail to get atoms");
    return nullptr;
  }
  CFDataRef avcC = static_cast<CFDataRef>(
      CFDictionaryGetValue(static_cast<CFDictionaryRef>(list), CFSTR("avcC")));
  if (!avcC) {
    VTENC_LOGE("fail to extract avcC");
    return nullptr;
  }
  CFIndex length = CFDataGetLength(avcC);
  const UInt8* bytes = CFDataGetBytePtr(avcC);
  if (length <= 0 || !bytes) {
    VTENC_LOGE("empty avcC");
    return nullptr;
  }

  RefPtr<MediaByteBuffer> config = new MediaByteBuffer(length);
  config->AppendElements(bytes, length);
  return config;
}

bool AppleVTEncoder::WriteExtraData(MediaRawData* aDst, CMSampleBufferRef aSrc,
                                    const bool aAsAnnexB) {
  if (!IsKeyframe(aSrc)) {
    return true;
  }

  aDst->mKeyframe = true;
  CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(aSrc);
  if (!desc) {
    VTENC_LOGE("fail to get format description from sample");
    return false;
  }

  if (aAsAnnexB) {
    return WriteSPSPPS(aDst, desc);
  }

  RefPtr<MediaByteBuffer> avcc = extractAvcc(desc);
  if (!avcc) {
    return false;
  }

  if (!mAvcc || !H264::CompareExtraData(avcc, mAvcc)) {
    mAvcc = avcc;
    aDst->mExtraData = mAvcc;
  }

  return avcc != nullptr;
}

static bool WriteNALUs(MediaRawData* aDst, CMSampleBufferRef aSrc,
                       bool aAsAnnexB = false) {
  size_t srcRemaining = CMSampleBufferGetTotalSampleSize(aSrc);
  CMBlockBufferRef block = CMSampleBufferGetDataBuffer(aSrc);
  if (!block) {
    VTENC_LOGE("Cannot get block buffer frome sample");
    return false;
  }
  UniquePtr<MediaRawDataWriter> writer(aDst->CreateWriter());
  size_t writtenLength = aDst->Size();
  // Ensure capacity.
  if (!writer->SetSize(writtenLength + srcRemaining)) {
    VTENC_LOGE("Cannot allocate buffer");
    return false;
  }
  size_t readLength = 0;
  while (srcRemaining > 0) {
    // Extract the size of next NAL unit
    uint8_t unitSizeBytes[4];
    MOZ_ASSERT(srcRemaining > sizeof(unitSizeBytes));
    if (CMBlockBufferCopyDataBytes(block, readLength, sizeof(unitSizeBytes),
                                   reinterpret_cast<uint32_t*>(
                                       unitSizeBytes)) != kCMBlockBufferNoErr) {
      VTENC_LOGE("Cannot copy unit size bytes");
      return false;
    }
    size_t unitSize =
        CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(unitSizeBytes));

    if (aAsAnnexB) {
      // Replace unit size bytes with NALU start code.
      PodCopy(writer->Data() + writtenLength, kNALUStart, sizeof(kNALUStart));
      readLength += sizeof(unitSizeBytes);
      srcRemaining -= sizeof(unitSizeBytes);
      writtenLength += sizeof(kNALUStart);
    } else {
      // Copy unit size bytes + data.
      unitSize += sizeof(unitSizeBytes);
    }
    MOZ_ASSERT(writtenLength + unitSize <= aDst->Size());
    // Copy NAL unit data
    if (CMBlockBufferCopyDataBytes(block, readLength, unitSize,
                                   writer->Data() + writtenLength) !=
        kCMBlockBufferNoErr) {
      VTENC_LOGE("Cannot copy unit data");
      return false;
    }
    readLength += unitSize;
    srcRemaining -= unitSize;
    writtenLength += unitSize;
  }
  MOZ_ASSERT(writtenLength == aDst->Size());
  return true;
}

void AppleVTEncoder::OutputFrame(CMSampleBufferRef aBuffer) {
  RefPtr<MediaRawData> output(new MediaRawData());

  bool asAnnexB = mConfig.mUsage == Usage::Realtime;
  bool succeeded = WriteExtraData(output, aBuffer, asAnnexB) &&
                   WriteNALUs(output, aBuffer, asAnnexB);

  output->mTime = media::TimeUnit::FromSeconds(
      CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(aBuffer)));
  output->mDuration = media::TimeUnit::FromSeconds(
      CMTimeGetSeconds(CMSampleBufferGetOutputDuration(aBuffer)));
  ProcessOutput(succeeded ? std::move(output) : nullptr);
}

void AppleVTEncoder::ProcessOutput(RefPtr<MediaRawData>&& aOutput) {
  if (!mTaskQueue->IsCurrentThreadIn()) {
    nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
        "AppleVTEncoder::ProcessOutput", this, &AppleVTEncoder::ProcessOutput,
        std::move(aOutput)));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
    return;
  }
  AssertOnTaskQueue();

  if (aOutput) {
    mEncodedData.AppendElement(std::move(aOutput));
  } else {
    mError = NS_ERROR_DOM_MEDIA_FATAL_ERR;
  }
}

RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Encode(
    const MediaData* aSample) {
  MOZ_ASSERT(aSample != nullptr);
  RefPtr<const VideoData> sample(aSample->As<const VideoData>());

  return InvokeAsync<RefPtr<const VideoData>>(mTaskQueue, this, __func__,
                                              &AppleVTEncoder::ProcessEncode,
                                              std::move(sample));
}

RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::ProcessEncode(
    RefPtr<const VideoData> aSample) {
  AssertOnTaskQueue();
  MOZ_ASSERT(mSession);

  if (NS_FAILED(mError)) {
    return EncodePromise::CreateAndReject(mError, __func__);
  }

  AutoCVBufferRelease<CVImageBufferRef> buffer(
      CreateCVPixelBuffer(aSample->mImage));
  if (!buffer) {
    return EncodePromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
  }

  CFDictionaryRef frameProps = nullptr;
  if (aSample->mKeyframe) {
    CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
    CFTypeRef values[] = {kCFBooleanTrue};
    MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values));
    frameProps = CFDictionaryCreate(
        kCFAllocatorDefault, keys, values, ArrayLength(keys),
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  };

  VTEncodeInfoFlags info;
  OSStatus status = VTCompressionSessionEncodeFrame(
      mSession, buffer,
      CMTimeMake(aSample->mTime.ToMicroseconds(), USECS_PER_S),
      CMTimeMake(aSample->mDuration.ToMicroseconds(), USECS_PER_S), frameProps,
      nullptr /* sourceFrameRefcon */, &info);
  if (status != noErr) {
    return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                          __func__);
  }

  return EncodePromise::CreateAndResolve(std::move(mEncodedData), __func__);
}

static size_t NumberOfPlanes(MediaDataEncoder::PixelFormat aPixelFormat) {
  switch (aPixelFormat) {
    case MediaDataEncoder::PixelFormat::RGBA32:
    case MediaDataEncoder::PixelFormat::BGRA32:
    case MediaDataEncoder::PixelFormat::RGB24:
    case MediaDataEncoder::PixelFormat::BGR24:
    case MediaDataEncoder::PixelFormat::GRAY8:
      return 1;
    case MediaDataEncoder::PixelFormat::YUV444P:
    case MediaDataEncoder::PixelFormat::YUV420P:
      return 3;
    case MediaDataEncoder::PixelFormat::YUV420SP_NV12:
      return 2;
    default:
      VTENC_LOGE("Unsupported input pixel format");
      return 0;
  }
}

using namespace layers;

static void ReleaseImage(void* aImageGrip, const void* aDataPtr,
                         size_t aDataSize, size_t aNumOfPlanes,
                         const void** aPlanes) {
  (static_cast<PlanarYCbCrImage*>(aImageGrip))->Release();
}

CVPixelBufferRef AppleVTEncoder::CreateCVPixelBuffer(const Image* aSource) {
  AssertOnTaskQueue();

  // TODO: support types other than YUV
  PlanarYCbCrImage* image = const_cast<Image*>(aSource)->AsPlanarYCbCrImage();
  if (!image || !image->GetData()) {
    return nullptr;
  }

  OSType format = MapPixelFormat(mConfig.mSourcePixelFormat).ref();
  size_t numPlanes = NumberOfPlanes(mConfig.mSourcePixelFormat);
  const PlanarYCbCrImage::Data* yuv = image->GetData();
  if (!yuv) {
    return nullptr;
  }
  auto ySize = yuv->YDataSize();
  auto cbcrSize = yuv->CbCrDataSize();
  void* addresses[3] = {};
  size_t widths[3] = {};
  size_t heights[3] = {};
  size_t strides[3] = {};
  switch (numPlanes) {
    case 3:
      addresses[2] = yuv->mCrChannel;
      widths[2] = cbcrSize.width;
      heights[2] = cbcrSize.height;
      strides[2] = yuv->mCbCrStride;
      [[fallthrough]];
    case 2:
      addresses[1] = yuv->mCbChannel;
      widths[1] = cbcrSize.width;
      heights[1] = cbcrSize.height;
      strides[1] = yuv->mCbCrStride;
      [[fallthrough]];
    case 1:
      addresses[0] = yuv->mYChannel;
      widths[0] = ySize.width;
      heights[0] = ySize.height;
      strides[0] = yuv->mYStride;
      break;
    default:
      return nullptr;
  }

  CVPixelBufferRef buffer = nullptr;
  image->AddRef();  // Grip input buffers.
  CVReturn rv = CVPixelBufferCreateWithPlanarBytes(
      kCFAllocatorDefault, yuv->mPictureRect.width, yuv->mPictureRect.height,
      format, nullptr /* dataPtr */, 0 /* dataSize */, numPlanes, addresses,
      widths, heights, strides, ReleaseImage /* releaseCallback */,
      image /* releaseRefCon */, nullptr /* pixelBufferAttributes */, &buffer);
  if (rv == kCVReturnSuccess) {
    return buffer;
    // |image| will be released in |ReleaseImage()|.
  } else {
    image->Release();
    return nullptr;
  }
}

RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::Drain() {
  return InvokeAsync(mTaskQueue, this, __func__, &AppleVTEncoder::ProcessDrain);
}

RefPtr<MediaDataEncoder::EncodePromise> AppleVTEncoder::ProcessDrain() {
  AssertOnTaskQueue();
  MOZ_ASSERT(mSession);

  if (mFramesCompleted) {
    MOZ_DIAGNOSTIC_ASSERT(mEncodedData.IsEmpty());
    return EncodePromise::CreateAndResolve(EncodedData(), __func__);
  }

  OSStatus status =
      VTCompressionSessionCompleteFrames(mSession, kCMTimeIndefinite);
  if (status != noErr) {
    return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                          __func__);
  }
  mFramesCompleted = true;
  // VTCompressionSessionCompleteFrames() could have queued multiple tasks with
  // the new drained frames. Dispatch a task after them to resolve the promise
  // with those frames.
  RefPtr<AppleVTEncoder> self = this;
  return InvokeAsync(mTaskQueue, __func__, [self]() {
    EncodedData pendingFrames(std::move(self->mEncodedData));
    self->mEncodedData = EncodedData();
    return EncodePromise::CreateAndResolve(std::move(pendingFrames), __func__);
  });
}

RefPtr<ShutdownPromise> AppleVTEncoder::Shutdown() {
  return InvokeAsync(mTaskQueue, this, __func__,
                     &AppleVTEncoder::ProcessShutdown);
}

RefPtr<ShutdownPromise> AppleVTEncoder::ProcessShutdown() {
  if (mSession) {
    VTCompressionSessionInvalidate(mSession);
    CFRelease(mSession);
    mSession = nullptr;
    mInited = false;
  }
  return ShutdownPromise::CreateAndResolve(true, __func__);
}

RefPtr<GenericPromise> AppleVTEncoder::SetBitrate(
    MediaDataEncoder::Rate aBitsPerSec) {
  RefPtr<AppleVTEncoder> self = this;
  return InvokeAsync(mTaskQueue, __func__, [self, aBitsPerSec]() {
    MOZ_ASSERT(self->mSession);
    return SetAverageBitrate(self->mSession, aBitsPerSec)
               ? GenericPromise::CreateAndResolve(true, __func__)
               : GenericPromise::CreateAndReject(
                     NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__);
  });
}

}  // namespace mozilla
