Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/platforms/wmf/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 60 kB image not shown  

Quelle  MFTEncoder.cpp

  Sprache: C
 

/* 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 "MFTEncoder.h"

#include <comdef.h>

#include "WMFUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/WindowsProcessMitigations.h"
#include "mozilla/dom/WebCodecsUtils.h"
#include "mozilla/mscom/COMWrappers.h"
#include "mozilla/mscom/Utils.h"

using Microsoft::WRL::ComPtr;

// Missing from MinGW.
#ifndef CODECAPI_AVEncAdaptiveMode
#  define STATIC_CODECAPI_AVEncAdaptiveMode \
    0x4419b185, 0xda1f, 0x4f53, 0xbc, 0x76, 0x9, 0x7d, 0xc, 0x1e, 0xfb, 0x1e
DEFINE_CODECAPI_GUID(AVEncAdaptiveMode, "4419b185-da1f-4f53-bc76-097d0c1efb1e",
                     0x4419b185, 0xda1f, 0x4f53, 0xbc, 0x76, 0x9, 0x7d, 0xc,
                     0x1e, 0xfb, 0x1e)
#  define CODECAPI_AVEncAdaptiveMode \
    DEFINE_CODECAPI_GUIDNAMED(AVEncAdaptiveMode)
#endif
#ifndef MF_E_NO_EVENTS_AVAILABLE
#  define MF_E_NO_EVENTS_AVAILABLE _HRESULT_TYPEDEF_(0xC00D3E80L)
#endif

#define MFT_LOG_INTERNAL(level, msg, ...) \
  MOZ_LOG_FMT(mozilla::sPEMLog, LogLevel::level, msg, ##__VA_ARGS__)

#define MFT_ENC_LOG(level, msg, ...)                                    \
  MFT_LOG_INTERNAL(level, "MFTEncoder(0x{})::{}: " msg, fmt::ptr(this), \
                   __func__, ##__VA_ARGS__)
#define MFT_ENC_SLOG(level, msg, ...) \
  MFT_LOG_INTERNAL(level, "MFTEncoder::{}: " msg, __func__, ##__VA_ARGS__)

#define MFT_ENC_LOGD(msg, ...) MFT_ENC_LOG(Debug, msg, ##__VA_ARGS__)
#define MFT_ENC_LOGE(msg, ...) MFT_ENC_LOG(Error, msg, ##__VA_ARGS__)
#define MFT_ENC_LOGW(msg, ...) MFT_ENC_LOG(Warning, msg, ##__VA_ARGS__)
#define MFT_ENC_LOGV(msg, ...) MFT_ENC_LOG(Verbose, msg, ##__VA_ARGS__)

#define MFT_ENC_SLOGD(msg, ...) MFT_ENC_SLOG(Debug, msg, ##__VA_ARGS__)
#define MFT_ENC_SLOGE(msg, ...) MFT_ENC_SLOG(Error, msg, ##__VA_ARGS__)
#define MFT_ENC_SLOGW(msg, ...) MFT_ENC_SLOG(Warning, msg, ##__VA_ARGS__)
#define MFT_ENC_SLOGV(msg, ...) MFT_ENC_SLOG(Verbose, msg, ##__VA_ARGS__)

#undef MFT_RETURN_IF_FAILED_IMPL
#define MFT_RETURN_IF_FAILED_IMPL(x, log_macro)                            \
  do {                                                                     \
    HRESULT rv = x;                                                        \
    if (MOZ_UNLIKELY(FAILED(rv))) {                                        \
      _com_error error(rv);                                                \
      log_macro("(" #x ") failed, rv={:x}({})", static_cast<uint32_t>(rv), \
                NS_ConvertUTF16toUTF8(error.ErrorMessage()).get());        \
      return rv;                                                           \
    }                                                                      \
  } while (false)

#undef MFT_RETURN_IF_FAILED
#define MFT_RETURN_IF_FAILED(x) MFT_RETURN_IF_FAILED_IMPL(x, MFT_ENC_LOGE)

#undef MFT_RETURN_IF_FAILED_S
#define MFT_RETURN_IF_FAILED_S(x) MFT_RETURN_IF_FAILED_IMPL(x, MFT_ENC_SLOGE)

#undef MFT_RETURN_VALUE_IF_FAILED_IMPL
#define MFT_RETURN_VALUE_IF_FAILED_IMPL(x, ret, log_macro)                 \
  do {                                                                     \
    HRESULT rv = x;                                                        \
    if (MOZ_UNLIKELY(FAILED(rv))) {                                        \
      _com_error error(rv);                                                \
      log_macro("(" #x ") failed, rv={:x}({})", static_cast<uint32_t>(rv), \
                NS_ConvertUTF16toUTF8(error.ErrorMessage()).get());        \
      return ret;                                                          \
    }                                                                      \
  } while (false)

#undef MFT_RETURN_VALUE_IF_FAILED
#define MFT_RETURN_VALUE_IF_FAILED(x, r) \
  MFT_RETURN_VALUE_IF_FAILED_IMPL(x, r, MFT_ENC_LOGE)

#undef MFT_RETURN_VALUE_IF_FAILED_S
#define MFT_RETURN_VALUE_IF_FAILED_S(x, r) \
  MFT_RETURN_VALUE_IF_FAILED_IMPL(x, r, MFT_ENC_SLOGE)

#undef MFT_RETURN_ERROR_IF_FAILED_IMPL
#define MFT_RETURN_ERROR_IF_FAILED_IMPL(x, log_macro)                      \
  do {                                                                     \
    HRESULT rv = x;                                                        \
    if (MOZ_UNLIKELY(FAILED(rv))) {                                        \
      _com_error error(rv);                                                \
      log_macro("(" #x ") failed, rv={:x}({})", static_cast<uint32_t>(rv), \
                NS_ConvertUTF16toUTF8(error.ErrorMessage()).get());        \
      return Err(rv);                                                      \
    }                                                                      \
  } while (false)

#undef MFT_RETURN_ERROR_IF_FAILED
#define MFT_RETURN_ERROR_IF_FAILED(x) \
  MFT_RETURN_ERROR_IF_FAILED_IMPL(x, MFT_ENC_LOGE)

#undef MFT_RETURN_ERROR_IF_FAILED_S
#define MFT_RETURN_ERROR_IF_FAILED_S(x) \
  MFT_RETURN_ERROR_IF_FAILED_IMPL(x, MFT_ENC_SLOGE)

#define AUTO_MFTENCODER_MARKER(desc) AUTO_WEBCODECS_MARKER("MFTEncoder", desc);

namespace mozilla {
extern LazyLogModule sPEMLog;

static const char* ErrorStr(HRESULT hr) {
  switch (hr) {
    case S_OK:
      return "OK";
    case MF_E_INVALIDMEDIATYPE:
      return "INVALIDMEDIATYPE";
    case MF_E_INVALIDSTREAMNUMBER:
      return "INVALIDSTREAMNUMBER";
    case MF_E_INVALIDTYPE:
      return "INVALIDTYPE";
    case MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING:
      return "TRANSFORM_PROCESSING";
    case MF_E_TRANSFORM_ASYNC_LOCKED:
      return "TRANSFORM_ASYNC_LOCKED";
    case MF_E_TRANSFORM_NEED_MORE_INPUT:
      return "TRANSFORM_NEED_MORE_INPUT";
    case MF_E_TRANSFORM_STREAM_CHANGE:
      return "TRANSFORM_STREAM_CHANGE";
    case MF_E_TRANSFORM_TYPE_NOT_SET:
      return "TRANSFORM_TYPE_NO_SET";
    case MF_E_UNSUPPORTED_D3D_TYPE:
      return "UNSUPPORTED_D3D_TYPE";
    case E_INVALIDARG:
      return "INVALIDARG";
    case MF_E_MULTIPLE_SUBSCRIBERS:
      return "MULTIPLE_SUBSCRIBERS";
    case MF_E_NO_EVENTS_AVAILABLE:
      return "NO_EVENTS_AVAILABLE";
    case MF_E_NO_SAMPLE_DURATION:
      return "NO_SAMPLE_DURATION";
    case MF_E_NO_SAMPLE_TIMESTAMP:
      return "NO_SAMPLE_TIMESTAMP";
    case MF_E_NOTACCEPTING:
      return "NOTACCEPTING";
    case MF_E_ATTRIBUTENOTFOUND:
      return "NOTFOUND";
    case MF_E_BUFFERTOOSMALL:
      return "BUFFERTOOSMALL";
    case E_NOTIMPL:
      return "NOTIMPL";
    default:
      return "OTHER";
  }
}

static const char* MediaEventTypeStr(MediaEventType aType) {
#define ENUM_TO_STR(enumVal) \
  case enumVal:              \
    return #enumVal
  switch (aType) {
    ENUM_TO_STR(MEUnknown);
    ENUM_TO_STR(METransformUnknown);
    ENUM_TO_STR(METransformNeedInput);
    ENUM_TO_STR(METransformHaveOutput);
    ENUM_TO_STR(METransformDrainComplete);
    ENUM_TO_STR(METransformMarker);
    ENUM_TO_STR(METransformInputStreamStateChanged);
    default:
      break;
  }
  return "Unknown MediaEventType";

#undef ENUM_TO_STR
}

static nsCString ErrorMessage(HRESULT hr) {
  nsCString msg(ErrorStr(hr));
  _com_error err(hr);
  msg.AppendFmt(" ({})", NS_ConvertUTF16toUTF8(err.ErrorMessage()).get());
  return msg;
}

static const char* CodecStr(const GUID& aGUID) {
  if (IsEqualGUID(aGUID, MFVideoFormat_H264)) {
    return "H.264";
  } else if (IsEqualGUID(aGUID, MFVideoFormat_VP80)) {
    return "VP8";
  } else if (IsEqualGUID(aGUID, MFVideoFormat_VP90)) {
    return "VP9";
  } else {
    return "Unsupported codec";
  }
}

static Result<nsCString, HRESULT> GetStringFromAttributes(
    IMFAttributes* aAttributes, REFGUID aGuidKey) {
  UINT32 len = 0;
  MFT_RETURN_ERROR_IF_FAILED_S(aAttributes->GetStringLength(aGuidKey, &len));

  nsCString str;
  if (len > 0) {
    ++len;  // '\0'.
    WCHAR buffer[len];
    MFT_RETURN_ERROR_IF_FAILED_S(
        aAttributes->GetString(aGuidKey, buffer, len, nullptr));
    str.Append(NS_ConvertUTF16toUTF8(buffer));
  }

  return str;
}

static Result<nsCString, HRESULT> GetFriendlyName(IMFActivate* aActivate) {
  return GetStringFromAttributes(aActivate, MFT_FRIENDLY_NAME_Attribute)
      .map([](const nsCString& aName) {
        return aName.IsEmpty() ? "Unknown MFT"_ns : aName;
      });
}

static Result<MFTEncoder::Factory::Provider, HRESULT> GetHardwareVendor(
    IMFActivate* aActivate) {
  nsCString vendor = MOZ_TRY(GetStringFromAttributes(
      aActivate, MFT_ENUM_HARDWARE_VENDOR_ID_Attribute));

  if (vendor == "VEN_1002"_ns) {
    return MFTEncoder::Factory::Provider::HW_AMD;
  } else if (vendor == "VEN_10DE"_ns) {
    return MFTEncoder::Factory::Provider::HW_NVIDIA;
  } else if (vendor == "VEN_8086"_ns) {
    return MFTEncoder::Factory::Provider::HW_Intel;
  } else if (vendor == "VEN_QCOM"_ns) {
    return MFTEncoder::Factory::Provider::HW_Qualcomm;
  }

  MFT_ENC_SLOGD("Undefined hardware vendor id: {}", vendor.get());
  return MFTEncoder::Factory::Provider::HW_Unknown;
}

static Result<nsTArray<ComPtr<IMFActivate>>, HRESULT> EnumMFT(
    GUID aCategory, UINT32 aFlags, const MFT_REGISTER_TYPE_INFO* aInType,
    const MFT_REGISTER_TYPE_INFO* aOutType) {
  nsTArray<ComPtr<IMFActivate>> activates;

  IMFActivate** enumerated;
  UINT32 num = 0;
  MFT_RETURN_ERROR_IF_FAILED_S(
      wmf::MFTEnumEx(aCategory, aFlags, aInType, aOutType, &enumerated, &num));
  for (UINT32 i = 0; i < num; ++i) {
    activates.AppendElement(ComPtr<IMFActivate>(enumerated[i]));
    // MFTEnumEx increments the reference count for each IMFActivate; decrement
    // here so ComPtr manages the lifetime correctly
    enumerated[i]->Release();
  }
  if (enumerated) {
    mscom::wrapped::CoTaskMemFree(enumerated);
  }
  return activates;
}

MFTEncoder::Factory::Factory(Provider aProvider,
                             ComPtr<IMFActivate>&& aActivate)
    : mProvider(aProvider), mActivate(std::move(aActivate)) {
  mName = mozilla::GetFriendlyName(mActivate.Get()).unwrapOr("Unknown"_ns);
}

MFTEncoder::Factory::~Factory() { Shutdown(); }

HRESULT MFTEncoder::Factory::Shutdown() {
  HRESULT hr = S_OK;
  if (mActivate) {
    MFT_ENC_LOGE("Shutdown {} encoder {}",
                 MFTEncoder::Factory::EnumValueToString(mProvider),
                 mName.get());
    // Release MFT resources via activation object.
    hr = mActivate->ShutdownObject();
    if (FAILED(hr)) {
      MFT_ENC_LOGE("Failed to shutdown MFT: {}", ErrorStr(hr));
    }
  }
  mActivate.Reset();
  mName.Truncate();
  return hr;
}

static nsTArray<MFTEncoder::Factory> IntoFactories(
    nsTArray<ComPtr<IMFActivate>>&& aActivates, bool aIsHardware) {
  nsTArray<MFTEncoder::Factory> factories;
  for (auto& activate : aActivates) {
    if (activate) {
      MFTEncoder::Factory::Provider provider =
          aIsHardware ? GetHardwareVendor(activate.Get())
                            .unwrapOr(MFTEncoder::Factory::Provider::HW_Unknown)
                      : MFTEncoder::Factory::Provider::SW;
      factories.AppendElement(
          MFTEncoder::Factory(provider, std::move(activate)));
    }
  }
  return factories;
}

static nsTArray<MFTEncoder::Factory> EnumEncoders(
    const GUID& aSubtype, const MFTEncoder::HWPreference aHWPreference) {
  MFT_REGISTER_TYPE_INFO inType = {.guidMajorType = MFMediaType_Video,
                                   .guidSubtype = MFVideoFormat_NV12};
  MFT_REGISTER_TYPE_INFO outType = {.guidMajorType = MFMediaType_Video,
                                    .guidSubtype = aSubtype};

  auto log = [&](const nsTArray<MFTEncoder::Factory>& aActivates) {
    for (const auto& activate : aActivates) {
      MFT_ENC_SLOGD("Found {} encoders: {}",
                    MFTEncoder::Factory::EnumValueToString(activate.mProvider),
                    activate.mName.get());
    }
  };

  nsTArray<MFTEncoder::Factory> swFactories;
  nsTArray<MFTEncoder::Factory> hwFactories;

  if (aHWPreference != MFTEncoder::HWPreference::SoftwareOnly) {
    // Some HW encoders use DXGI API and crash when locked down.
    // TODO: move HW encoding out of content process (bug 1754531).
    if (IsWin32kLockedDown()) {
      MFT_ENC_SLOGD("Don't use HW encoder when win32k locked down.");
    } else {
      auto r = EnumMFT(MFT_CATEGORY_VIDEO_ENCODER,
                       MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER,
                       &inType, &outType);
      if (r.isErr()) {
        MFT_ENC_SLOGE("enumerate HW encoder for {}: error={}",
                      CodecStr(aSubtype), ErrorMessage(r.unwrapErr()).get());
      } else {
        hwFactories.AppendElements(
            IntoFactories(r.unwrap(), true /* aIsHardware */));
        log(hwFactories);
      }
    }
  }

  if (aHWPreference != MFTEncoder::HWPreference::HardwareOnly) {
    auto r = EnumMFT(MFT_CATEGORY_VIDEO_ENCODER,
                     MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT |
                         MFT_ENUM_FLAG_SORTANDFILTER,
                     &inType, &outType);
    if (r.isErr()) {
      MFT_ENC_SLOGE("enumerate SW encoder for {}: error={}", CodecStr(aSubtype),
                    ErrorMessage(r.unwrapErr()).get());
    } else {
      swFactories.AppendElements(
          IntoFactories(r.unwrap(), false /* aIsHardware */));
      log(swFactories);
    }
  }

  nsTArray<MFTEncoder::Factory> factories;

  switch (aHWPreference) {
    case MFTEncoder::HWPreference::HardwareOnly:
      return hwFactories;
    case MFTEncoder::HWPreference::SoftwareOnly:
      return swFactories;
    case MFTEncoder::HWPreference::PreferHardware:
      factories.AppendElements(std::move(hwFactories));
      factories.AppendElements(std::move(swFactories));
      break;
    case MFTEncoder::HWPreference::PreferSoftware:
      factories.AppendElements(std::move(swFactories));
      factories.AppendElements(std::move(hwFactories));
      break;
  }

  return factories;
}

static void PopulateEncoderInfo(const GUID& aSubtype,
                                nsTArray<MFTEncoder::Info>& aInfos) {
  nsTArray<MFTEncoder::Factory> factories =
      EnumEncoders(aSubtype, MFTEncoder::HWPreference::PreferHardware);
  for (const auto& factory : factories) {
    MFTEncoder::Info info = {.mSubtype = aSubtype, .mName = factory.mName};
    aInfos.AppendElement(info);
    MFT_ENC_SLOGD("<ENC> [{}] {}\n", CodecStr(aSubtype), info.mName.get());
  }
}

Maybe<MFTEncoder::Info> MFTEncoder::GetInfo(const GUID& aSubtype) {
  nsTArray<Info>& infos = Infos();

  for (auto i : infos) {
    if (IsEqualGUID(aSubtype, i.mSubtype)) {
      return Some(i);
    }
  }
  return Nothing();
}

nsCString MFTEncoder::GetFriendlyName(const GUID& aSubtype) {
  Maybe<Info> info = GetInfo(aSubtype);

  return info ? info.ref().mName : "???"_ns;
}

// Called only once by Infos().
nsTArray<MFTEncoder::Info> MFTEncoder::Enumerate() {
  nsTArray<Info> infos;

  if (!wmf::MediaFoundationInitializer::HasInitialized()) {
    MFT_ENC_SLOGE("cannot init Media Foundation");
    return infos;
  }

  PopulateEncoderInfo(MFVideoFormat_H264, infos);
  PopulateEncoderInfo(MFVideoFormat_VP90, infos);
  PopulateEncoderInfo(MFVideoFormat_VP80, infos);

  return infos;
}

nsTArray<MFTEncoder::Info>& MFTEncoder::Infos() {
  static nsTArray<Info> infos = Enumerate();
  return infos;
}

static Result<Ok, nsCString> IsSupported(
    const MFTEncoder::Factory& aFactory, const GUID& aSubtype,
    const gfx::IntSize& aFrameSize,
    const EncoderConfig::CodecSpecific& aCodecSpecific) {
  if (aFactory.mProvider == MFTEncoder::Factory::Provider::SW) {
    return Ok();
  }

  bool isH264HighProfile = IsEqualGUID(aSubtype, MFVideoFormat_H264) &&
                           aCodecSpecific.is<H264Specific>() &&
                           aCodecSpecific.as<H264Specific>().mProfile ==
                               H264_PROFILE::H264_PROFILE_HIGH;
  // This is an empirically safe limit.
  bool isFrameSizeGreaterThan4K =
      aFrameSize.width > 3840 || aFrameSize.height > 2160;

  // For Intel and AMD hardware encoders, initializing the H.264 High profile
  // with large frame sizes such as 7680×4320 may cause SetOutputType to fail or
  // prevent the encoder from producing output.
  if (isH264HighProfile && isFrameSizeGreaterThan4K) {
    return Err(nsFmtCString(
        "{} encoder {} does not support H.264 high profile for 4K+ video",
        MFTEncoder::Factory::EnumValueToString(aFactory.mProvider),
        aFactory.mName.get()));
  }

  // Hardware encoders often don't support frames that are too small.
  if (aFrameSize.width < 128 || aFrameSize.height < 128) {
    return Err(nsFmtCString(
        "{} encoder {} may not support videos smaller than 128x128",
        MFTEncoder::Factory::EnumValueToString(aFactory.mProvider),
        aFactory.mName.get()));
  }

  // TODO: Check the SVC support from different HW encoders.
  return Ok();
}

HRESULT MFTEncoder::Create(const GUID& aSubtype, const gfx::IntSize& aFrameSize,
                           const EncoderConfig::CodecSpecific& aCodecSpecific) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(!mEncoder);

  AUTO_MFTENCODER_MARKER("::Create");

  auto cleanup = MakeScopeExit([&] {
    mEncoder = nullptr;
    mFactory.reset();
    mConfig = nullptr;
  });

  nsTArray<MFTEncoder::Factory> factories =
      EnumEncoders(aSubtype, mHWPreference);
  for (auto& f : factories) {
    MOZ_ASSERT(f);
    if (auto r = IsSupported(f, aSubtype, aFrameSize, aCodecSpecific);
        r.isErr()) {
      nsCString errorMsg = r.unwrapErr();
      MFT_ENC_LOGE("Skip {} encoder {} for {}: {}",
                   MFTEncoder::Factory::EnumValueToString(f.mProvider),
                   f.mName.get(), CodecStr(aSubtype), errorMsg.get());
      continue;
    }

    RefPtr<IMFTransform> encoder;
    // Create the MFT activation object.
    HRESULT hr = f.mActivate->ActivateObject(
        IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(encoder))));
    if (SUCCEEDED(hr) && encoder) {
      MFT_ENC_LOGD("{} for {} is activated", f.mName.get(), CodecStr(aSubtype));
      mFactory.emplace(std::move(f));
      mEncoder = std::move(encoder);
      break;
    }
    _com_error error(hr);
    MFT_ENC_LOGE("ActivateObject {} error = 0x{:X}, {}", f.mName.get(), hr,
                 NS_ConvertUTF16toUTF8(error.ErrorMessage()).get());
  }

  if (!mFactory || !mEncoder) {
    MFT_ENC_LOGE("Failed to create MFT for {}", CodecStr(aSubtype));
    return E_FAIL;
  }

  RefPtr<ICodecAPI> config;
  // Avoid IID_PPV_ARGS() here for MingGW fails to declare UUID for ICodecAPI.
  MFT_RETURN_IF_FAILED(
      mEncoder->QueryInterface(IID_ICodecAPI, getter_AddRefs(config)));
  mConfig = std::move(config);

  SetState(State::Initializing);
  cleanup.release();
  return S_OK;
}

HRESULT
MFTEncoder::Destroy() {
  if (!mEncoder) {
    return S_OK;
  }

  MaybeResolveOrRejectAnyPendingPromise(MediaResult(
      NS_ERROR_DOM_MEDIA_CANCELED, RESULT_DETAIL("Canceled by Destroy")));
  mPendingError = NS_OK;

  mAsyncEventSource = nullptr;
  mEncoder = nullptr;
  mConfig = nullptr;
  HRESULT hr = mFactory ? S_OK : mFactory->Shutdown();
  mFactory.reset();
  // TODO: If Factory::Shutdown() fails and the encoder is not reusable, set the
  // state to error.
  SetState(State::Uninited);

  return hr;
}

HRESULT
MFTEncoder::SetMediaTypes(IMFMediaType* aInputType, IMFMediaType* aOutputType) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(aInputType && aOutputType);
  MOZ_ASSERT(mFactory);
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mState == State::Initializing);

  AUTO_MFTENCODER_MARKER("::SetMediaTypes");

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });

  AsyncMFTResult asyncMFT = AttemptEnableAsync();
  if (asyncMFT.isErr()) {
    HRESULT hr = asyncMFT.inspectErr();
    MFT_ENC_LOGE("AttemptEnableAsync error: {}", ErrorMessage(hr).get());
    return hr;
  }
  bool isAsync = asyncMFT.unwrap();
  MFT_ENC_LOGD("{} encoder {} is {}",
               MFTEncoder::Factory::EnumValueToString(mFactory->mProvider),
               mFactory->mName.get(), isAsync ? "asynchronous" : "synchronous");

  MFT_RETURN_IF_FAILED(GetStreamIDs());

  // Always set encoder output type before input.
  MFT_RETURN_IF_FAILED(
      mEncoder->SetOutputType(mOutputStreamID, aOutputType, 0));

  if (MatchInputSubtype(aInputType) == GUID_NULL) {
    MFT_ENC_LOGE("Input type does not match encoder input subtype");
    return MF_E_INVALIDMEDIATYPE;
  }

  MFT_RETURN_IF_FAILED(mEncoder->SetInputType(mInputStreamID, aInputType, 0));

  MFT_RETURN_IF_FAILED(
      mEncoder->GetInputStreamInfo(mInputStreamID, &mInputStreamInfo));

  MFT_RETURN_IF_FAILED(
      mEncoder->GetOutputStreamInfo(mInputStreamID, &mOutputStreamInfo));

  mOutputStreamProvidesSample =
      IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);

  if (isAsync) {
    MFT_ENC_LOGD("Setting event source w/{} callback", mIsRealtime ? "" : "o");
    RefPtr<IMFMediaEventGenerator> source;
    MFT_RETURN_IF_FAILED(mEncoder->QueryInterface(IID_PPV_ARGS(
        static_cast<IMFMediaEventGenerator**>(getter_AddRefs(source)))));
    // TODO: Consider always using MFTEventSource with callbacks if it does not
    // introduce performance regressions for overall video encoding duration.
    if (mIsRealtime) {
      mAsyncEventSource = MakeRefPtr<MFTEventSource>(this, source.forget());
      mAsyncEventSource->BeginEventListening();
    } else {
      mAsyncEventSource = MakeRefPtr<MFTEventSource>(source.forget());
    }
  }

  MFT_RETURN_IF_FAILED(SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0));

  MFT_RETURN_IF_FAILED(SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0));

  SetState(State::Inited);
  exitWithError.release();
  mNumNeedInput = 0;
  return S_OK;
}

// Async MFT won't work without unlocking. See
// https://docs.microsoft.com/en-us/windows/win32/medfound/asynchronous-mfts#unlocking-asynchronous-mfts
MFTEncoder::AsyncMFTResult MFTEncoder::AttemptEnableAsync() {
  ComPtr<IMFAttributes> attributes = nullptr;
  HRESULT hr = mEncoder->GetAttributes(&attributes);
  if (FAILED(hr)) {
    MFT_ENC_LOGE("Encoder->GetAttribute error");
    return AsyncMFTResult(hr);
  }

  // Retrieve `MF_TRANSFORM_ASYNC` using `MFGetAttributeUINT32` rather than
  // `attributes->GetUINT32`, since `MF_TRANSFORM_ASYNC` may not be present in
  // the attributes.
  bool async =
      MFGetAttributeUINT32(attributes.Get(), MF_TRANSFORM_ASYNC, FALSE) == TRUE;
  if (!async) {
    MFT_ENC_LOGD("Encoder is not async");
    return AsyncMFTResult(false);
  }

  hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
  if (FAILED(hr)) {
    MFT_ENC_LOGE("SetUINT32 async unlock error");
    return AsyncMFTResult(hr);
  }

  return AsyncMFTResult(true);
}

HRESULT MFTEncoder::GetStreamIDs() {
  DWORD numIns;
  DWORD numOuts;
  MFT_RETURN_IF_FAILED(mEncoder->GetStreamCount(&numIns, &numOuts));
  MFT_ENC_LOGD("input stream count: {}, output stream count: {}", numIns,
               numOuts);
  if (numIns < 1 || numOuts < 1) {
    MFT_ENC_LOGE("stream count error");
    return MF_E_INVALIDSTREAMNUMBER;
  }

  DWORD inIDs[numIns];
  DWORD outIDs[numOuts];
  HRESULT hr = mEncoder->GetStreamIDs(numIns, inIDs, numOuts, outIDs);
  if (SUCCEEDED(hr)) {
    mInputStreamID = inIDs[0];
    mOutputStreamID = outIDs[0];
  } else if (hr == E_NOTIMPL) {
    mInputStreamID = 0;
    mOutputStreamID = 0;
  } else {
    MFT_ENC_LOGE("failed to get stream IDs: {}", ErrorMessage(hr).get());
    return hr;
  }
  MFT_ENC_LOGD("input stream ID: {}, output stream ID: {}", mInputStreamID,
               mOutputStreamID);
  return S_OK;
}

GUID MFTEncoder::MatchInputSubtype(IMFMediaType* aInputType) {
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(aInputType);

  GUID desired = GUID_NULL;
  MFT_RETURN_VALUE_IF_FAILED(aInputType->GetGUID(MF_MT_SUBTYPE, &desired),
                             GUID_NULL);
  MOZ_ASSERT(desired != GUID_NULL);

  DWORD i = 0;
  RefPtr<IMFMediaType> inputType;
  GUID preferred = GUID_NULL;
  while (true) {
    HRESULT hr = mEncoder->GetInputAvailableType(mInputStreamID, i,
                                                 getter_AddRefs(inputType));
    if (hr == MF_E_NO_MORE_TYPES) {
      break;
    }
    if (FAILED(hr)) {
      MFT_ENC_LOGE("GetInputAvailableType error: {}", ErrorMessage(hr).get());
      return GUID_NULL;
    }

    GUID sub = GUID_NULL;
    MFT_RETURN_VALUE_IF_FAILED(inputType->GetGUID(MF_MT_SUBTYPE, &sub),
                               GUID_NULL);

    if (IsEqualGUID(desired, sub)) {
      preferred = desired;
      break;
    }
    ++i;
  }

  return IsEqualGUID(preferred, desired) ? preferred : GUID_NULL;
}

HRESULT
MFTEncoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, ULONG_PTR aData) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  return mEncoder->ProcessMessage(aMsg, aData);
}

HRESULT MFTEncoder::SetModes(const EncoderConfig& aConfig) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mConfig);
  MOZ_ASSERT(mState == State::Initializing);

  AUTO_MFTENCODER_MARKER("::SetModes");

  VARIANT var;
  var.vt = VT_UI4;
  switch (aConfig.mBitrateMode) {
    case BitrateMode::Constant:
      var.ulVal = eAVEncCommonRateControlMode_CBR;
      break;
    case BitrateMode::Variable:
      if (aConfig.mCodec == CodecType::VP8 ||
          aConfig.mCodec == CodecType::VP9) {
        MFT_ENC_LOGE(
            "Overriding requested VRB bitrate mode, forcing CBR for VP8/VP9 "
            "encoding.");
        var.ulVal = eAVEncCommonRateControlMode_CBR;
      } else {
        var.ulVal = eAVEncCommonRateControlMode_PeakConstrainedVBR;
      }
      break;
  }
  MFT_RETURN_IF_FAILED(
      mConfig->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var));

  if (aConfig.mBitrate) {
    var.ulVal = aConfig.mBitrate;
    MFT_RETURN_IF_FAILED(
        mConfig->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var));
  }

  switch (aConfig.mScalabilityMode) {
    case ScalabilityMode::None:
      var.ulVal = 1;
      break;
    case ScalabilityMode::L1T2:
      var.ulVal = 2;
      break;
    case ScalabilityMode::L1T3:
      var.ulVal = 3;
      break;
  }

  // TODO check this and replace it with mFactory->mProvider
  bool isIntel = false;
  if (aConfig.mScalabilityMode != ScalabilityMode::None || isIntel) {
    MFT_RETURN_IF_FAILED(
        mConfig->SetValue(&CODECAPI_AVEncVideoTemporalLayerCount, &var));
  }

  if (SUCCEEDED(mConfig->IsModifiable(&CODECAPI_AVEncAdaptiveMode))) {
    var.ulVal = eAVEncAdaptiveMode_Resolution;
    MFT_RETURN_IF_FAILED(mConfig->SetValue(&CODECAPI_AVEncAdaptiveMode, &var));
  }

  if (SUCCEEDED(mConfig->IsModifiable(&CODECAPI_AVLowLatencyMode))) {
    var.vt = VT_BOOL;
    var.boolVal =
        aConfig.mUsage == Usage::Realtime ? VARIANT_TRUE : VARIANT_FALSE;
    MFT_RETURN_IF_FAILED(mConfig->SetValue(&CODECAPI_AVLowLatencyMode, &var));
  }

  uint32_t interval = SaturatingCast<uint32_t>(aConfig.mKeyframeInterval);
  if (interval != 0) {
    var.vt = VT_UI4;
    var.ulVal = interval;
    if (SUCCEEDED(mConfig->IsModifiable(&CODECAPI_AVEncMPVGOPSize))) {
      MFT_RETURN_IF_FAILED(mConfig->SetValue(&CODECAPI_AVEncMPVGOPSize, &var));
      MFT_ENC_LOGD("Set GOPSize to {}", var.ulVal);
    }
    // Set keyframe distance through both media type and codec API for better
    // compatibility. Some encoders may only support one of these methods.
    // `MF_MT_MAX_KEYFRAME_SPACING` is set in `CreateOutputType`.
    if (SUCCEEDED(
            mConfig->IsModifiable(&CODECAPI_AVEncVideoMaxKeyframeDistance))) {
      MFT_RETURN_IF_FAILED(
          mConfig->SetValue(&CODECAPI_AVEncVideoMaxKeyframeDistance, &var));
      MFT_ENC_LOGD("Set MaxKeyframeDistance to {}", var.ulVal);
    }
  }

  mIsRealtime = aConfig.mUsage == Usage::Realtime;

  return S_OK;
}

HRESULT
MFTEncoder::SetBitrate(UINT32 aBitsPerSec) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mConfig);

  VARIANT var = {.vt = VT_UI4, .ulVal = aBitsPerSec};
  return mConfig->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var);
}

bool MFTEncoder::IsHardwareAccelerated() const {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  return mFactory && mFactory->mProvider != MFTEncoder::Factory::Provider::SW;
}

template <typename T, typename E, bool IsExclusive = true>
static auto ResultToPromise(Result<T, E>&& aResult) {
  if (aResult.isErr()) {
    return MozPromise<T, E, IsExclusive>::CreateAndReject(aResult.unwrapErr(),
                                                          __func__);
  }
  return MozPromise<T, E, IsExclusive>::CreateAndResolve(aResult.unwrap(),
                                                         __func__);
};

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::Encode(
    nsTArray<InputSample>&& aInputs) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (!IsAsync()) {
    return ResultToPromise(EncodeSync(std::move(aInputs)));
  }
  if (!mIsRealtime) {
    return ResultToPromise(EncodeAsync(std::move(aInputs)));
  }
  return EncodeWithAsyncCallback(std::move(aInputs));
}

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::Drain() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (!IsAsync()) {
    return ResultToPromise(DrainSync());
  }
  if (!mIsRealtime) {
    return ResultToPromise(DrainAsync());
  }
  return DrainWithAsyncCallback();
}

static HRESULT CreateSample(RefPtr<IMFSample>* aOutSample, DWORD aSize,
                            DWORD aAlignment) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  RefPtr<IMFSample> sample;
  MFT_RETURN_IF_FAILED_S(wmf::MFCreateSample(getter_AddRefs(sample)));

  RefPtr<IMFMediaBuffer> buffer;
  MFT_RETURN_IF_FAILED_S(wmf::MFCreateAlignedMemoryBuffer(
      aSize, aAlignment, getter_AddRefs(buffer)));

  MFT_RETURN_IF_FAILED_S(sample->AddBuffer(buffer));

  *aOutSample = sample.forget();

  return S_OK;
}

HRESULT
MFTEncoder::CreateInputSample(RefPtr<IMFSample>* aSample, size_t aSize) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  return CreateSample(
      aSample, aSize,
      mInputStreamInfo.cbAlignment > 0 ? mInputStreamInfo.cbAlignment - 1 : 0);
}

Result<MFTEncoder::EncodedData, MediaResult> MFTEncoder::EncodeSync(
    nsTArray<InputSample>&& aInputs) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(State::Encoding);

  EncodedData outputs;

  // Follow steps in
  // https://learn.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
  for (auto& input : aInputs) {
    HRESULT hr = ProcessInput(std::move(input));
    if (FAILED(hr)) {
      return Err(MediaResult(
          NS_ERROR_DOM_MEDIA_FATAL_ERR,
          RESULT_DETAIL("ProcessInput error: %s", ErrorMessage(hr).get())));
    }

    DWORD flags = 0;
    hr = mEncoder->GetOutputStatus(&flags);
    if (FAILED(hr) && hr != E_NOTIMPL) {
      return Err(MediaResult(
          NS_ERROR_DOM_MEDIA_FATAL_ERR,
          RESULT_DETAIL("GetOutputStatus error: %s", ErrorMessage(hr).get())));
    }

    if (hr == E_NOTIMPL ||
        (hr == S_OK && (flags & MFT_OUTPUT_STATUS_SAMPLE_READY))) {
      outputs.AppendElements(MOZ_TRY(PullOutputs().mapErr([](HRESULT e) {
        return MediaResult(
            NS_ERROR_DOM_MEDIA_FATAL_ERR,
            RESULT_DETAIL("PullOutputs error: %s", ErrorMessage(e).get()));
      })));
    }
  }

  exitWithError.release();
  SetState(State::Inited);
  return outputs;
}

Result<MFTEncoder::EncodedData, MediaResult> MFTEncoder::DrainSync() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(State::Draining);

  // Follow step 7 in
  // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
  if (FAILED(hr)) {
    return Err(MediaResult(
        NS_ERROR_DOM_MEDIA_FATAL_ERR,
        RESULT_DETAIL("SendMFTMessage MFT_MESSAGE_COMMAND_DRAIN error: %s",
                      ErrorMessage(hr).get())));
  }

  EncodedData outputs = MOZ_TRY(PullOutputs().mapErr([](HRESULT e) {
    return MediaResult(
        NS_ERROR_DOM_MEDIA_FATAL_ERR,
        RESULT_DETAIL("PullOutputs error: %s", ErrorMessage(e).get()));
  }));
  exitWithError.release();
  SetState(State::Inited);
  return outputs;
}

Result<MFTEncoder::EncodedData, HRESULT> MFTEncoder::PullOutputs() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  EncodedData outputs;
  MPEGHeader header;
  while (true) {
    auto r = GetOutputOrNewHeader();
    if (r.isErr()) {
      HRESULT e = r.unwrapErr();
      if (e == MF_E_TRANSFORM_NEED_MORE_INPUT) {
        MFT_ENC_LOGD("Need more inputs");
        // Step 4 or 8 in
        // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
        break;
      }
      MFT_ENC_LOGE("GetOutputOrNewHeader failed: {}", ErrorMessage(e).get());
      return Err(e);
    }

    OutputResult result = r.unwrap();
    if (result.IsHeader()) {
      header = result.TakeHeader();
      MFT_ENC_LOGD(
          "Obtained new MPEG header, attempting to retrieve output again");
      continue;
    }

    MOZ_ASSERT(result.IsSample());
    outputs.AppendElement(OutputSample{.mSample = result.TakeSample()});
    if (!header.IsEmpty()) {
      outputs.LastElement().mHeader = std::move(header);
    }
  }

  MFT_ENC_LOGV("{} outputs pulled", outputs.Length());
  return outputs;
}

Result<MFTEncoder::EncodedData, MediaResult> MFTEncoder::EncodeAsync(
    nsTArray<InputSample>&& aInputs) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(State::Encoding);

  size_t inputCounts = aInputs.Length();
  for (auto& input : aInputs) {
    mPendingInputs.push_back(std::move(input));
  }

  MOZ_TRY(ProcessPendingInputs().mapErr([](HRESULT hr) {
    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                       RESULT_DETAIL("ProcessPendingInputs error: %s",
                                     ErrorMessage(hr).get()));
  }));
  MFT_ENC_LOGV("{} inputs processed, {} inputs remain, inputs needed: {}",
               inputCounts - mPendingInputs.size(), mPendingInputs.size(),
               mNumNeedInput);

  // If the underlying system signaled that more input is needed, continue
  // processing inputs until either no more input is required or there are no
  // pending inputs left.
  MOZ_TRY(ProcessPendingEvents().mapErr([](HRESULT hr) {
    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                       RESULT_DETAIL("ProcessPendingEvents error: %s",
                                     ErrorMessage(hr).get()));
  }));
  MOZ_ASSERT(mNumNeedInput == 0 || mPendingInputs.empty());

  exitWithError.release();
  SetState(State::Inited);
  EncodedData outputs = std::move(mOutputs);
  return outputs;
}

Result<MFTEncoder::EncodedData, MediaResult> MFTEncoder::DrainAsync() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(mPendingInputs.empty() ? State::Draining : State::PreDraining);

  // Ensure all pending inputs are processed before initiating the drain. If any
  // pending inputs remain, the input-needed count must be zero; otherwise, they
  // would have been processed in Encode().
  MOZ_ASSERT_IF(!mPendingInputs.empty(), mNumNeedInput == 0);
  while (!mPendingInputs.empty()) {
    MFT_ENC_LOGV("Pending inputs: {}, inputs needed: {}", mPendingInputs.size(),
                 mNumNeedInput);
    // Prompt the MFT to process pending inputs or collect any pending outputs,
    // which may allow more inputs to be accepted.
    MOZ_TRY(ProcessPendingEvents().mapErr([](HRESULT hr) {
      return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                         RESULT_DETAIL("ProcessPendingEvents error: %s",
                                       ErrorMessage(hr).get()));
    }));
  }

  if (mState == State::PreDraining) {
    SetState(State::Draining);
  }

  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
  if (FAILED(hr)) {
    return Err(MediaResult(
        NS_ERROR_DOM_MEDIA_FATAL_ERR,
        RESULT_DETAIL("SendMFTMessage MFT_MESSAGE_COMMAND_DRAIN error: %s",
                      ErrorMessage(hr).get())));
  }

  ProcessedResults results;
  do {
    results = MOZ_TRY(ProcessPendingEvents().mapErr([](HRESULT hr) {
      return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                         RESULT_DETAIL("ProcessPendingEvents error: %s",
                                       ErrorMessage(hr).get()));
    }));
  } while (!results.contains(ProcessedResult::DrainComplete));

  exitWithError.release();
  SetState(State::Inited);
  EncodedData outputs = std::move(mOutputs);
  return outputs;
}

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::EncodeWithAsyncCallback(
    nsTArray<InputSample>&& aInputs) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mEncodePromise.IsEmpty());
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(State::Encoding);

  size_t inputCounts = aInputs.Length();
  for (auto& input : aInputs) {
    mPendingInputs.push_back(std::move(input));
  }

  auto inputsProcessed = ProcessPendingInputs();
  if (inputsProcessed.isErr()) {
    return EncodePromise::CreateAndReject(
        MediaResult(
            NS_ERROR_DOM_MEDIA_FATAL_ERR,
            RESULT_DETAIL("ProcessPendingInputs error: %s",
                          ErrorMessage(inputsProcessed.unwrapErr()).get())),
        __func__);
  }
  MFT_ENC_LOGV("{} inputs processed, {} inputs remain, inputs needed: {}",
               inputCounts - mPendingInputs.size(), mPendingInputs.size(),
               mNumNeedInput);

  RefPtr<MFTEncoder::EncodePromise> p = mEncodePromise.Ensure(__func__);
  exitWithError.release();

  // TODO: Calculate time duration based on frame rate instead of a fixed value.
  auto timerResult = NS_NewTimerWithCallback(
      [self = RefPtr{this}](nsITimer* aTimer) {
        if (!self->mEncoder) {
          MFT_ENC_SLOGW(
              "Timer callback aborted: encoder has already been shut down");
          return;
        }

        MFT_ENC_SLOGV("Timer callback: resolving pending encode promise");
        self->MaybeResolveOrRejectEncodePromise();
      },
      TimeDuration::FromMilliseconds(20), nsITimer::TYPE_ONE_SHOT,
      "EncodingProgressChecker"_ns, GetCurrentSerialEventTarget());
  if (timerResult.isErr()) {
    MFT_ENC_LOGE(
        "Failed to set an encoding progress checker. Resolve encode promise "
        "directly");
    MaybeResolveOrRejectEncodePromise();
    return p;
  }

  mTimer = timerResult.unwrap();
  return p;
}

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::DrainWithAsyncCallback() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  return PrepareForDrain()->Then(
      GetCurrentSerialEventTarget(), __func__,
      [self = RefPtr{this}](MFTEncoder::EncodedData&& aOutput) {
        MFT_ENC_SLOGV("All pending inputs are processed, now starts draining");
        self->mOutputs.AppendElements(std::move(aOutput));
        return self->StartDraining();
      },
      [self = RefPtr{this}](const MediaResult& aError) {
        MFT_ENC_SLOGE("PrepareForDrain failed: {}", aError.Description().get());
        return EncodePromise::CreateAndReject(aError, __func__);
      });
}

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::PrepareForDrain() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mPreDrainPromise.IsEmpty());
  MOZ_ASSERT(mState == State::Inited);

  SetState(State::PreDraining);
  MFT_ENC_LOGV("Pending inputs: {}, inputs needed: {}", mPendingInputs.size(),
               mNumNeedInput);

  if (mPendingInputs.empty()) {
    MFT_ENC_LOGV("No pending inputs, leave {} state immediately",
                 EnumValueToString(mState));
    SetState(State::Inited);
    return EncodePromise::CreateAndResolve(std::move(mOutputs), __func__);
  }

  MOZ_ASSERT(mNumNeedInput == 0);
  MFT_ENC_LOGV("Waiting for {} pending inputs to be processed",
               mPendingInputs.size());

  return mPreDrainPromise.Ensure(__func__);
}

RefPtr<MFTEncoder::EncodePromise> MFTEncoder::StartDraining() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mDrainPromise.IsEmpty());
  MOZ_ASSERT(mPendingInputs.empty());
  MOZ_ASSERT(mState == State::Inited);

  auto exitWithError = MakeScopeExit([&] { SetState(State::Error); });
  SetState(State::Draining);

  HRESULT r = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
  if (FAILED(r)) {
    return EncodePromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                    RESULT_DETAIL("SendMFTMessage COMMAND_DRAIN failed: %s",
                                  ErrorMessage(r).get())),
        __func__);
  }

  RefPtr<MFTEncoder::EncodePromise> p = mDrainPromise.Ensure(__func__);
  exitWithError.release();
  return p;
}

void MFTEncoder::EventHandler(MediaEventType aEventType, HRESULT aStatus) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  MFT_ENC_LOGV("[state: {}] Get event: {}, status: {}",
               EnumValueToString(mState), MediaEventTypeStr(aEventType),
               ErrorMessage(aStatus).get());

  if (!mAsyncEventSource) {
    MFT_ENC_LOGW("Async event source is not initialized or destroyed");
    return;
  }

  MOZ_ASSERT(mState != State::Uninited);

  auto errorHandler = [&](MediaResult&& aError) {
    MFT_ENC_LOGE("{}", aError.Message().get());
    mPendingError = aError;
    switch (mState) {
      case State::Encoding:
        MaybeResolveOrRejectEncodePromise();
        break;
      case State::Draining:
        MaybeResolveOrRejectDrainPromise();
        break;
      case State::PreDraining:
        MaybeResolveOrRejectPreDrainPromise();
        break;
      default:
        MFT_ENC_LOGW("Received error in state {}", EnumValueToString(mState));
    }
  };

  if (FAILED(aStatus)) {
    errorHandler(
        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                    RESULT_DETAIL("Received error status: %s for event %s",
                                  ErrorMessage(aStatus).get(),
                                  MediaEventTypeStr(aEventType))));
    return;
  }

  auto processed = ProcessEvent(aEventType);
  if (processed.isErr()) {
    HRESULT hr = processed.unwrapErr();
    errorHandler(MediaResult(
        NS_ERROR_DOM_MEDIA_FATAL_ERR,
        RESULT_DETAIL("ProcessEvent error: %s for event %s",
                      ErrorMessage(hr).get(), MediaEventTypeStr(aEventType))));
    return;
  }

  const bool waitForOutput =
      StaticPrefs::media_wmf_encoder_realtime_wait_for_output();

  ProcessedResult result = processed.unwrap();
  MFT_ENC_LOGV(
      "{} processed: {}\n\tpending inputs: {}\n\tinput needed: {}\n\tpending "
      "outputs: {} (waitForOutput={})",
      MediaEventTypeStr(aEventType), MFTEncoder::EnumValueToString(result),
      mPendingInputs.size(), mNumNeedInput, mOutputs.Length(),
      waitForOutput ? "yes" : "no");
  switch (result) {
    case ProcessedResult::AllAvailableInputsProcessed:
      // Since mNumNeedInput was incremented in ProcessEvent(), before calling
      // ProcessInput(), a result indicating no input was processed means there
      // were not enough pending inputs in the queue.
      MOZ_ASSERT(mPendingInputs.empty());
      // If EventHandler is in the PreDraining state here, it means there were
      // pending inputs to process before draining started. Processing those
      // inputs should have produced InputProcessed results, and the state
      // should have transitioned out of PreDraining. Therefore, we should not
      // still be in PreDraining at this point.
      MOZ_ASSERT(mState != State::PreDraining);
      [[fallthrough]];
    case ProcessedResult::InputProcessed:
      if (mState == State::Encoding) {
        // In realtime mode, we could resolve the encode promise only upon
        // receiving an output. However, since the performance gain is minor,
        // unless the wait-for-output setting is enabled, it's better to prevent
        // the encode promise from being resolved by the timer callback if no
        // output is produced in time.
        if (!waitForOutput) {
          MaybeResolveOrRejectEncodePromise();
        }
      } else if (mState == State::PreDraining) {
        if (mPendingInputs.empty()) {
          MaybeResolveOrRejectPreDrainPromise();
        }
      }
      break;
    case ProcessedResult::OutputHeaderYielded:
      if (mState == State::Encoding) {
        if (!waitForOutput) {
          MaybeResolveOrRejectEncodePromise();
        }
      }
      break;
    case ProcessedResult::OutputDataYielded:
      if (mState == State::Encoding) {
        MaybeResolveOrRejectEncodePromise();
      }
      break;
    case ProcessedResult::DrainComplete:
      MOZ_ASSERT(mState == State::Draining);
      MaybeResolveOrRejectDrainPromise();
      break;
    default:
      MOZ_ASSERT_UNREACHABLE(
          "Unexpected ProcessedResult value in EventHandler");
  }

  mAsyncEventSource->BeginEventListening();
}

void MFTEncoder::MaybeResolveOrRejectEncodePromise() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (mEncodePromise.IsEmpty()) {
    MFT_ENC_LOGV("[{}] No encode promise to resolve or reject",
                 EnumValueToString(mState));
    return;
  }

  MOZ_ASSERT(mState == State::Encoding);

  MFT_ENC_LOGV("Resolving ({} outputs ) or rejecting encode promise ({})",
               mOutputs.Length(),
               NS_FAILED(mPendingError.Code())
                   ? mPendingError.Description().get()
                   : "no error");

  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
    MFT_ENC_LOGV("Encode timer cancelled");
  }

  if (NS_FAILED(mPendingError.Code())) {
    SetState(State::Error);
    mEncodePromise.Reject(mPendingError, __func__);
    mPendingError = NS_OK;
    return;
  }

  mEncodePromise.Resolve(std::move(mOutputs), __func__);
  SetState(State::Inited);
}

void MFTEncoder::MaybeResolveOrRejectDrainPromise() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (mDrainPromise.IsEmpty()) {
    MFT_ENC_LOGV("[{}] No drain promise to resolve or reject",
                 EnumValueToString(mState));
    return;
  }

  MOZ_ASSERT(mState == State::Draining);

  MFT_ENC_LOGV("Resolving ({} outputs ) or rejecting drain promise ({})",
               mOutputs.Length(),
               NS_FAILED(mPendingError.Code())
                   ? mPendingError.Description().get()
                   : "no error");

  if (NS_FAILED(mPendingError.Code())) {
    SetState(State::Error);
    mDrainPromise.Reject(mPendingError, __func__);
    mPendingError = NS_OK;
    return;
  }

  mDrainPromise.Resolve(std::move(mOutputs), __func__);
  SetState(State::Inited);
}

void MFTEncoder::MaybeResolveOrRejectPreDrainPromise() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (mPreDrainPromise.IsEmpty()) {
    MFT_ENC_LOGV("[{}] No pre-drain promise to resolve or reject",
                 EnumValueToString(mState));
    return;
  }

  MOZ_ASSERT(mState == State::PreDraining);

  MFT_ENC_LOGV("Resolving pre-drain promise ({} outputs ) or rejecting ({})",
               mOutputs.Length(),
               NS_FAILED(mPendingError.Code())
                   ? mPendingError.Description().get()
                   : "no error");

  if (NS_FAILED(mPendingError.Code())) {
    SetState(State::Error);
    mPreDrainPromise.Reject(mPendingError, __func__);
    mPendingError = NS_OK;
    return;
  }

  MOZ_ASSERT(mPendingInputs.empty());
  mPreDrainPromise.Resolve(std::move(mOutputs), __func__);
  SetState(State::Inited);
}

void MFTEncoder::MaybeResolveOrRejectAnyPendingPromise(
    const MediaResult& aResult) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  if (NS_FAILED(aResult.Code())) {
    MFT_ENC_LOGW(
        "[{}] Rejecting pending promises with error: {} (previous pending "
        "error: {})",
        EnumValueToString(mState), aResult.Description().get(),
        mPendingError.Description().get());
    mPendingError = aResult;
  }

  MaybeResolveOrRejectEncodePromise();
  MaybeResolveOrRejectPreDrainPromise();
  MaybeResolveOrRejectDrainPromise();
}

Result<MFTEncoder::ProcessedResults, HRESULT>
MFTEncoder::ProcessPendingEvents() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mAsyncEventSource);

  ProcessedResults results;
  while (true) {
    auto got = GetPendingEvent();
    if (got.isErr()) {
      HRESULT hr = got.unwrapErr();
      if (hr == MF_E_NO_EVENTS_AVAILABLE) {
        MFT_ENC_LOGV("No more pending events");
        break;
      }
      MFT_ENC_LOGE("GetPendingEvent error: {}", ErrorMessage(hr).get());
      return Err(hr);
    }

    MediaEventType event = got.unwrap();
    MFT_ENC_LOGV("Processing pending event: {}", MediaEventTypeStr(event));
    ProcessedResult result = MOZ_TRY(ProcessEvent(event));
    MFT_ENC_LOGV("event processed: {}", MFTEncoder::EnumValueToString(result));
    results += result;
  }

  return results;
}

Result<MFTEncoder::ProcessedResult, HRESULT> MFTEncoder::ProcessEvent(
    MediaEventType aType) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  switch (aType) {
    case METransformNeedInput:
      ++mNumNeedInput;
      return ProcessInput();
    case METransformHaveOutput:
      return ProcessOutput();
    case METransformDrainComplete:
      return ProcessDrainComplete();
    default:
      MFT_ENC_LOGE("Unsupported event type: {}", MediaEventTypeStr(aType));
      break;
  }
  return Err(E_UNEXPECTED);
}

Result<MFTEncoder::ProcessedResult, HRESULT> MFTEncoder::ProcessInput() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  MFT_ENC_LOGV("Inputs needed: {}, pending inputs: {}", mNumNeedInput,
               mPendingInputs.size());
  if (mNumNeedInput == 0 || mPendingInputs.empty()) {
    return ProcessedResult::AllAvailableInputsProcessed;
  }

  auto input = mPendingInputs.front();
  mPendingInputs.pop_front();
  MFT_RETURN_ERROR_IF_FAILED(ProcessInput(std::move(input)));
  --mNumNeedInput;

  return ProcessedResult::InputProcessed;
}

Result<MFTEncoder::ProcessedResult, HRESULT> MFTEncoder::ProcessOutput() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  OutputResult result = MOZ_TRY(GetOutputOrNewHeader());
  if (result.IsHeader()) {
    mOutputHeader = result.TakeHeader();
    MFT_ENC_LOGD("Got new MPEG header, size: {}", mOutputHeader.Length());
    return ProcessedResult::OutputHeaderYielded;
  }

  MOZ_ASSERT(result.IsSample());
  mOutputs.AppendElement(OutputSample{.mSample = result.TakeSample()});
  if (!mOutputHeader.IsEmpty()) {
    mOutputs.LastElement().mHeader = std::move(mOutputHeader);
  }
  return ProcessedResult::OutputDataYielded;
}

Result<MFTEncoder::ProcessedResult, HRESULT>
MFTEncoder::ProcessDrainComplete() {
  // After draining is complete, the MFT will not emit another
  // METransformNeedInput event until it receives an
  // MFT_MESSAGE_NOTIFY_START_OF_STREAM message.
  MFT_RETURN_ERROR_IF_FAILED(
      SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0));
  MFT_ENC_LOGV("Drain complete, resetting inputs needed({}) to 0",
               mNumNeedInput);
  mNumNeedInput = 0;
  return ProcessedResult::DrainComplete;
}

Result<MFTEncoder::ProcessedResult, HRESULT>
MFTEncoder::ProcessPendingInputs() {
  while (!mPendingInputs.empty()) {
    auto r = MOZ_TRY(ProcessInput());
    if (r == ProcessedResult::AllAvailableInputsProcessed) {
      break;
    }
  }
  return ProcessedResult::AllAvailableInputsProcessed;
}

Result<MediaEventType, HRESULT> MFTEncoder::GetPendingEvent() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  MOZ_ASSERT(mAsyncEventSource);
  MOZ_ASSERT(!mIsRealtime);
  return mAsyncEventSource->GetEvent(MF_EVENT_FLAG_NO_WAIT);
}

Result<MFTEncoder::OutputResult, HRESULT> MFTEncoder::GetOutputOrNewHeader() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  RefPtr<IMFSample> sample;
  DWORD status = 0;
  DWORD bufStatus = 0;

  HRESULT hr = ProcessOutput(sample, status, bufStatus);
  MFT_ENC_LOGV(
      "output processed: {}, status: 0x{:x}, output buffer status: 0x{:x}",
      ErrorMessage(hr).get(), status, bufStatus);

  if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
    if (bufStatus & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE) {
      MFT_ENC_LOGW("output buffer format changed, updating output type");
      MFT_RETURN_ERROR_IF_FAILED(UpdateOutputType());
      return OutputResult(MOZ_TRY(GetMPEGSequenceHeader()));
    }
    // TODO: We should query for updated stream identifiers here. For now,
    // handle this as an error.
    MFT_ENC_LOGE("Stream identifiers changed");
    return Err(hr);
  }

  if (FAILED(hr)) {
    return Err(hr);
  }

  MOZ_ASSERT(sample);
  return OutputResult(sample.forget());
}

HRESULT MFTEncoder::UpdateOutputType() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);
  // Per Microsoft's documentation:
  // https://docs.microsoft.com/en-us/windows/win32/medfound/handling-stream-changes#output-type
  RefPtr<IMFMediaType> outputType;
  MFT_RETURN_IF_FAILED(mEncoder->GetOutputAvailableType(
      mOutputStreamID, 0, getter_AddRefs(outputType)));
  MFT_RETURN_IF_FAILED(mEncoder->SetOutputType(mOutputStreamID, outputType, 0));
  MFT_ENC_LOGW("stream format has been renegotiated for output stream {}",
               mOutputStreamID);
  return S_OK;
}

HRESULT MFTEncoder::ProcessOutput(RefPtr<IMFSample>& aSample,
                                  DWORD& aOutputStatus, DWORD& aBufferStatus) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  MFT_OUTPUT_DATA_BUFFER output = {.dwStreamID = mOutputStreamID,
                                   .pSample = nullptr,
                                   .dwStatus = 0,
                                   .pEvents = nullptr};
  RefPtr<IMFSample> sample;
  if (!mOutputStreamProvidesSample) {
    MFT_RETURN_IF_FAILED(CreateSample(&sample, mOutputStreamInfo.cbSize,
                                      mOutputStreamInfo.cbAlignment > 1
                                          ? mOutputStreamInfo.cbAlignment - 1
                                          : 0));
    output.pSample = sample;
  }

  HRESULT hr = mEncoder->ProcessOutput(01, &output, &aOutputStatus);
  aBufferStatus = output.dwStatus;
  if (output.pEvents) {
    MFT_ENC_LOGW("Discarding events from ProcessOutput");
    output.pEvents->Release();
    output.pEvents = nullptr;
  }

  if (FAILED(hr)) {
    return hr;
  }

  aSample = output.pSample;
  if (mOutputStreamProvidesSample) {
    // Release MFT provided sample.
    output.pSample->Release();
    output.pSample = nullptr;
  }

  return hr;
}

HRESULT MFTEncoder::ProcessInput(InputSample&& aInput) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  if (aInput.mKeyFrameRequested) {
    VARIANT v = {.vt = VT_UI4, .ulVal = 1};
    mConfig->SetValue(&CODECAPI_AVEncVideoForceKeyFrame, &v);
  }
  MFT_RETURN_IF_FAILED(
      mEncoder->ProcessInput(mInputStreamID, aInput.mSample, 0));
  return S_OK;
}

Result<nsTArray<UINT8>, HRESULT> MFTEncoder::GetMPEGSequenceHeader() {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());
  MOZ_ASSERT(mEncoder);

  RefPtr<IMFMediaType> outputType;
  MFT_RETURN_ERROR_IF_FAILED(mEncoder->GetOutputCurrentType(
      mOutputStreamID, getter_AddRefs(outputType)));
  UINT32 length = 0;
  HRESULT hr = outputType->GetBlobSize(MF_MT_MPEG_SEQUENCE_HEADER, &length);
  if (hr == MF_E_ATTRIBUTENOTFOUND) {
    MFT_ENC_LOGW("GetBlobSize MF_MT_MPEG_SEQUENCE_HEADER: not found");
    return nsTArray<UINT8>();
  }
  if (FAILED(hr)) {
    MFT_ENC_LOGE("GetBlobSize MF_MT_MPEG_SEQUENCE_HEADER error: {}",
                 ErrorMessage(hr).get());
    return Err(hr);
  }
  if (length == 0) {
    MFT_ENC_LOGW("GetBlobSize MF_MT_MPEG_SEQUENCE_HEADER: no header");
    return nsTArray<UINT8>();
  }
  MFT_ENC_LOGD("GetBlobSize MF_MT_MPEG_SEQUENCE_HEADER: {}", length);

  nsTArray<UINT8> header;
  header.SetCapacity(length);
  hr = outputType->GetBlob(MF_MT_MPEG_SEQUENCE_HEADER, header.Elements(),
                           length, nullptr);
  header.SetLength(SUCCEEDED(hr) ? length : 0);

  return header;
}

void MFTEncoder::SetState(State aState) {
  MOZ_ASSERT(mscom::IsCurrentThreadMTA());

  MFT_ENC_LOGD("SetState: {} -> {}", EnumValueToString(mState),
               EnumValueToString(aState));
  mState = aState;
}

#define MFT_EVTSRC_LOG(level, msg, ...)                                     \
  MFT_LOG_INTERNAL(level, "MFTEventSource(0x{})::{}: " msg, fmt::ptr(this), \
                   __func__, ##__VA_ARGS__)
#define MFT_EVTSRC_SLOG(level, msg, ...) \
  MFT_LOG_INTERNAL(level, "MFTEventSource::{}: " msg, __func__, ##__VA_ARGS__)

#define MFT_EVTSRC_LOGD(msg, ...) MFT_EVTSRC_LOG(Debug, msg, ##__VA_ARGS__)
#define MFT_EVTSRC_LOGE(msg, ...) MFT_EVTSRC_LOG(Error, msg, ##__VA_ARGS__)
#define MFT_EVTSRC_LOGW(msg, ...) MFT_EVTSRC_LOG(Warning, msg, ##__VA_ARGS__)
#define MFT_EVTSRC_LOGV(msg, ...) MFT_EVTSRC_LOG(Verbose, msg, ##__VA_ARGS__)

#define MFT_EVTSRC_SLOGW(msg, ...) MFT_EVTSRC_SLOG(Warning, msg, ##__VA_ARGS__)

#define MFT_EVTSRC_RETURN_IF_FAILED(x) \
  MFT_RETURN_IF_FAILED_IMPL(x, MFT_EVTSRC_LOGE)
#define MFT_EVTSRC_RETURN_ERROR_IF_FAILED(x) \
  MFT_RETURN_ERROR_IF_FAILED_IMPL(x, MFT_EVTSRC_LOGE)

MFTEventSource::MFTEventSource(
    nsISerialEventTarget* aEncoderThread, MFTEncoder* aEncoder,
    already_AddRefed<IMFMediaEventGenerator> aEventGenerator)
    : mId(GenerateId()),
      mEncoderThread(aEncoderThread),
      mEncoder(aEncoder),
      mEventGenerator(aEventGenerator, "MFTEventSource::mEventGenerator") {
  MOZ_ASSERT(mEncoderThread);
  auto g = mEventGenerator.Lock();
  MOZ_ASSERT(!!g.ref());
  MFT_EVTSRC_LOGD("(id {}) created", mId);
}

MFTEventSource::~MFTEventSource() {
  MFT_EVTSRC_LOGD("(id {}) destroyed", mId);
  auto g = mEventGenerator.Lock();
  *g = nullptr;
}

Result<MediaEventType, HRESULT> MFTEventSource::GetEvent(DWORD aFlags) {
  MOZ_ASSERT(mEncoderThread->IsOnCurrentThread());
  MOZ_ASSERT(!CanForwardEvents());

  HRESULT hr = S_OK;
  RefPtr<IMFMediaEvent> event;
  {
    auto g = mEventGenerator.Lock();
    hr = g.ref()->GetEvent(aFlags, getter_AddRefs(event));
  }
  if (FAILED(hr)) {
    if (hr == MF_E_NO_EVENTS_AVAILABLE) {
      MFT_EVTSRC_LOGV("GetEvent: {}", ErrorMessage(hr).get());
    } else {
      MFT_EVTSRC_LOGE("GetEvent error: {}", ErrorMessage(hr).get());
    }
    return Err(hr);
  }
  MediaEventType type = MEUnknown;
  MFT_EVTSRC_RETURN_ERROR_IF_FAILED(event->GetType(&type));
  return type;
}

HRESULT MFTEventSource::BeginEventListening() {
  MOZ_ASSERT(mEncoderThread->IsOnCurrentThread());
  MOZ_ASSERT(CanForwardEvents());

  MFT_EVTSRC_LOGV("(id {}) starts waiting for event", mId);
  HRESULT hr = S_OK;
  {
    auto g = mEventGenerator.Lock();
    hr = g.ref()->BeginGetEvent(this, nullptr);
  }
  return hr;
}

STDMETHODIMP MFTEventSource::GetParameters(DWORD* aFlags, DWORD* aQueue) {
  MOZ_ASSERT(aFlags);
  MOZ_ASSERT(aQueue);
  *aFlags = MFASYNC_FAST_IO_PROCESSING_CALLBACK;
  *aQueue = MFASYNC_CALLBACK_QUEUE_TIMER;
  return S_OK;
}

STDMETHODIMP MFTEventSource::Invoke(IMFAsyncResult* aResult) {
  RefPtr<IMFMediaEvent> event;
  {
    auto g = mEventGenerator.Lock();
    MFT_EVTSRC_RETURN_IF_FAILED(
        g.ref()->EndGetEvent(aResult, getter_AddRefs(event)));
  }

  MediaEventType type = MEUnknown;
  MFT_EVTSRC_RETURN_IF_FAILED(event->GetType(&type));

  MFT_EVTSRC_LOGV("(id {}) received event: {}", mId, MediaEventTypeStr(type));

  HRESULT status = S_OK;
  MFT_EVTSRC_RETURN_IF_FAILED(event->GetStatus(&status));

  mEncoderThread->Dispatch(
      NS_NewRunnableFunction(__func__,
                             [type, status, id = mId, encoder = mEncoder]() {
                               if (!encoder->mAsyncEventSource ||
                                   encoder->mAsyncEventSource->mId != id) {
                                 MFT_EVTSRC_SLOGW(
                                     "Event {} from source {} is stale",
                                     MediaEventTypeStr(type), id);
                                 return;
                               }
                               encoder->EventHandler(type, status);
                             }),
      NS_DISPATCH_NORMAL);

  return status;
}

STDMETHODIMP MFTEventSource::QueryInterface(REFIID aIID, void** aPPV) {
  const IID IID_IMFAsyncCallback = __uuidof(IMFAsyncCallback);
  if (aIID == IID_IUnknown || aIID == IID_IMFAsyncCallback) {
    *aPPV = static_cast<IMFAsyncCallback*>(this);
    AddRef();
    return S_OK;
  }

  return E_NOINTERFACE;
}

#undef MFT_EVTSRC_LOG
#undef MFT_EVTSRC_SLOG
#undef MFT_EVTSRC_LOGE
#undef MFT_EVTSRC_RETURN_IF_FAILED
#undef MFT_EVTSRC_RETURN_ERROR_IF_FAILED

}  // namespace mozilla

#undef MFT_ENC_SLOGE
#undef MFT_ENC_SLOGD
#undef MFT_ENC_LOGE
#undef MFT_ENC_LOGW
#undef MFT_ENC_LOGV
#undef MFT_ENC_LOGD
#undef MFT_RETURN_IF_FAILED
#undef MFT_RETURN_IF_FAILED_S
#undef MFT_RETURN_VALUE_IF_FAILED
#undef MFT_RETURN_VALUE_IF_FAILED_S
#undef MFT_RETURN_ERROR_IF_FAILED
#undef MFT_RETURN_ERROR_IF_FAILED_S
#undef MFT_RETURN_IF_FAILED_IMPL
#undef MFT_RETURN_VALUE_IF_FAILED_IMPL
#undef MFT_RETURN_ERROR_IF_FAILED_IMPL
#undef MFT_ENC_LOG
#undef MFT_ENC_SLOG
#undef MFT_LOG_INTERNAL
#undef AUTO_MFTENCODER_MARKER

Messung V0.5 in Prozent
C=95 H=93 G=93

¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.