summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/wmf/MFTEncoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/wmf/MFTEncoder.cpp')
-rw-r--r--dom/media/platforms/wmf/MFTEncoder.cpp754
1 files changed, 754 insertions, 0 deletions
diff --git a/dom/media/platforms/wmf/MFTEncoder.cpp b/dom/media/platforms/wmf/MFTEncoder.cpp
new file mode 100644
index 0000000000..410da2733c
--- /dev/null
+++ b/dom/media/platforms/wmf/MFTEncoder.cpp
@@ -0,0 +1,754 @@
+/* -*- 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 "MFTEncoder.h"
+#include "mozilla/Logging.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/mscom/Utils.h"
+#include "WMFUtils.h"
+
+// 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_ENC_LOGD(arg, ...) \
+ MOZ_LOG(mozilla::sPEMLog, mozilla::LogLevel::Debug, \
+ ("MFTEncoder(0x%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define MFT_ENC_LOGE(arg, ...) \
+ MOZ_LOG(mozilla::sPEMLog, mozilla::LogLevel::Error, \
+ ("MFTEncoder(0x%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define MFT_ENC_SLOGD(arg, ...) \
+ MOZ_LOG(mozilla::sPEMLog, mozilla::LogLevel::Debug, \
+ ("MFTEncoder::%s: " arg, __func__, ##__VA_ARGS__))
+#define MFT_ENC_SLOGE(arg, ...) \
+ MOZ_LOG(mozilla::sPEMLog, mozilla::LogLevel::Error, \
+ ("MFTEncoder::%s: " arg, __func__, ##__VA_ARGS__))
+
+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_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_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* 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 UINT32 EnumEncoders(const GUID& aSubtype, IMFActivate**& aActivates,
+ const bool aUseHW = true) {
+ UINT32 num = 0;
+ MFT_REGISTER_TYPE_INFO inType = {.guidMajorType = MFMediaType_Video,
+ .guidSubtype = MFVideoFormat_NV12};
+ MFT_REGISTER_TYPE_INFO outType = {.guidMajorType = MFMediaType_Video,
+ .guidSubtype = aSubtype};
+ HRESULT hr = S_OK;
+ if (aUseHW) {
+ if (IsWin32kLockedDown()) {
+ // Some HW encoders use DXGI API and crash when locked down.
+ // TODO: move HW encoding out of content process (bug 1754531).
+ MFT_ENC_SLOGD("Don't use HW encoder when win32k locked down.");
+ return 0;
+ }
+
+ hr = wmf::MFTEnumEx(MFT_CATEGORY_VIDEO_ENCODER,
+ MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER,
+ &inType, &outType, &aActivates, &num);
+ if (FAILED(hr)) {
+ MFT_ENC_SLOGE("enumerate HW encoder for %s: error=%s", CodecStr(aSubtype),
+ ErrorStr(hr));
+ return 0;
+ }
+ if (num > 0) {
+ return num;
+ }
+ }
+
+ // Try software MFTs.
+ hr = wmf::MFTEnumEx(MFT_CATEGORY_VIDEO_ENCODER,
+ MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT |
+ MFT_ENUM_FLAG_SORTANDFILTER,
+ &inType, &outType, &aActivates, &num);
+ if (FAILED(hr)) {
+ MFT_ENC_SLOGE("enumerate SW encoder for %s: error=%s", CodecStr(aSubtype),
+ ErrorStr(hr));
+ return 0;
+ }
+ if (num == 0) {
+ MFT_ENC_SLOGD("cannot find encoder for %s", CodecStr(aSubtype));
+ }
+ return num;
+}
+
+static HRESULT GetFriendlyName(IMFActivate* aAttributes, nsCString& aName) {
+ UINT32 len = 0;
+ HRESULT hr = aAttributes->GetStringLength(MFT_FRIENDLY_NAME_Attribute, &len);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ if (len > 0) {
+ ++len; // '\0'.
+ WCHAR name[len];
+ if (SUCCEEDED(aAttributes->GetString(MFT_FRIENDLY_NAME_Attribute, name, len,
+ nullptr))) {
+ aName.Append(NS_ConvertUTF16toUTF8(name));
+ }
+ }
+
+ if (aName.Length() == 0) {
+ aName.Append("Unknown MFT");
+ }
+
+ return S_OK;
+}
+
+static void PopulateEncoderInfo(const GUID& aSubtype,
+ nsTArray<MFTEncoder::Info>& aInfos) {
+ IMFActivate** activates = nullptr;
+ UINT32 num = EnumEncoders(aSubtype, activates);
+ for (UINT32 i = 0; i < num; ++i) {
+ MFTEncoder::Info info = {.mSubtype = aSubtype};
+ GetFriendlyName(activates[i], info.mName);
+ aInfos.AppendElement(info);
+ MFT_ENC_SLOGD("<ENC> [%s] %s\n", CodecStr(aSubtype), info.mName.Data());
+ activates[i]->Release();
+ activates[i] = nullptr;
+ }
+ CoTaskMemFree(activates);
+}
+
+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;
+}
+
+already_AddRefed<IMFActivate> MFTEncoder::CreateFactory(const GUID& aSubtype) {
+ IMFActivate** activates = nullptr;
+ UINT32 num = EnumEncoders(aSubtype, activates, !mHardwareNotAllowed);
+ if (num == 0) {
+ return nullptr;
+ }
+
+ // Keep the first and throw out others, if there is any.
+ RefPtr<IMFActivate> factory = activates[0];
+ activates[0] = nullptr;
+ for (UINT32 i = 1; i < num; ++i) {
+ activates[i]->Release();
+ activates[i] = nullptr;
+ }
+ CoTaskMemFree(activates);
+
+ return factory.forget();
+}
+
+HRESULT MFTEncoder::Create(const GUID& aSubtype) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(!mEncoder);
+
+ RefPtr<IMFActivate> factory = CreateFactory(aSubtype);
+ if (!factory) {
+ return E_FAIL;
+ }
+
+ // Create MFT via the activation object.
+ RefPtr<IMFTransform> encoder;
+ HRESULT hr = factory->ActivateObject(
+ IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(encoder))));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<ICodecAPI> config;
+ // Avoid IID_PPV_ARGS() here for MingGW fails to declare UUID for ICodecAPI.
+ hr = encoder->QueryInterface(IID_ICodecAPI, getter_AddRefs(config));
+ if (FAILED(hr)) {
+ encoder = nullptr;
+ factory->ShutdownObject();
+ return hr;
+ }
+
+ mFactory = std::move(factory);
+ mEncoder = std::move(encoder);
+ mConfig = std::move(config);
+ return S_OK;
+}
+
+HRESULT
+MFTEncoder::Destroy() {
+ if (!mEncoder) {
+ return S_OK;
+ }
+
+ mEncoder = nullptr;
+ mConfig = nullptr;
+ // Release MFT resources via activation object.
+ HRESULT hr = mFactory->ShutdownObject();
+ mFactory = nullptr;
+
+ return hr;
+}
+
+HRESULT
+MFTEncoder::SetMediaTypes(IMFMediaType* aInputType, IMFMediaType* aOutputType) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(aInputType && aOutputType);
+
+ AsyncMFTResult asyncMFT = AttemptEnableAsync();
+ NS_ENSURE_TRUE(asyncMFT.isOk(), asyncMFT.unwrapErr());
+
+ HRESULT hr = GetStreamIDs();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ // Always set encoder output type before input.
+ hr = mEncoder->SetOutputType(mOutputStreamID, aOutputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ NS_ENSURE_TRUE(MatchInputSubtype(aInputType) != GUID_NULL,
+ MF_E_INVALIDMEDIATYPE);
+
+ hr = mEncoder->SetInputType(mInputStreamID, aInputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mEncoder->GetInputStreamInfo(mInputStreamID, &mInputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mEncoder->GetOutputStreamInfo(mInputStreamID, &mOutputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ mOutputStreamProvidesSample =
+ IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (asyncMFT.unwrap()) {
+ RefPtr<IMFMediaEventGenerator> source;
+ hr = mEncoder->QueryInterface(IID_PPV_ARGS(
+ static_cast<IMFMediaEventGenerator**>(getter_AddRefs(source))));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ mEventSource.SetAsyncEventGenerator(source.forget());
+ } else {
+ mEventSource.InitSyncMFTEventQueue();
+ }
+
+ 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() {
+ IMFAttributes* pAttributes = nullptr;
+ HRESULT hr = mEncoder->GetAttributes(&pAttributes);
+ if (FAILED(hr)) {
+ return AsyncMFTResult(hr);
+ }
+
+ bool async =
+ MFGetAttributeUINT32(pAttributes, MF_TRANSFORM_ASYNC, FALSE) == TRUE;
+ if (async) {
+ hr = pAttributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
+ } else {
+ hr = S_OK;
+ }
+ pAttributes->Release();
+
+ return SUCCEEDED(hr) ? AsyncMFTResult(async) : AsyncMFTResult(hr);
+}
+
+HRESULT MFTEncoder::GetStreamIDs() {
+ DWORD numIns;
+ DWORD numOuts;
+ HRESULT hr = mEncoder->GetStreamCount(&numIns, &numOuts);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ if (numIns < 1 || numOuts < 1) {
+ MFT_ENC_LOGE("stream count error");
+ return MF_E_INVALIDSTREAMNUMBER;
+ }
+
+ DWORD inIDs[numIns];
+ DWORD outIDs[numOuts];
+ 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");
+ return hr;
+ }
+ return S_OK;
+}
+
+GUID MFTEncoder::MatchInputSubtype(IMFMediaType* aInputType) {
+ MOZ_ASSERT(mEncoder);
+ MOZ_ASSERT(aInputType);
+
+ GUID desired = GUID_NULL;
+ HRESULT hr = aInputType->GetGUID(MF_MT_SUBTYPE, &desired);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), GUID_NULL);
+ MOZ_ASSERT(desired != GUID_NULL);
+
+ DWORD i = 0;
+ IMFMediaType* inputType = nullptr;
+ GUID preferred = GUID_NULL;
+ while (true) {
+ hr = mEncoder->GetInputAvailableType(mInputStreamID, i, &inputType);
+ if (hr == MF_E_NO_MORE_TYPES) {
+ break;
+ }
+ NS_ENSURE_TRUE(SUCCEEDED(hr), GUID_NULL);
+
+ GUID sub = GUID_NULL;
+ hr = inputType->GetGUID(MF_MT_SUBTYPE, &sub);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), 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(UINT32 aBitsPerSec) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mConfig);
+
+ VARIANT var;
+ var.vt = VT_UI4;
+ var.ulVal = eAVEncCommonRateControlMode_CBR;
+ HRESULT hr = mConfig->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ var.ulVal = aBitsPerSec;
+ hr = mConfig->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (SUCCEEDED(mConfig->IsModifiable(&CODECAPI_AVEncAdaptiveMode))) {
+ var.ulVal = eAVEncAdaptiveMode_Resolution;
+ hr = mConfig->SetValue(&CODECAPI_AVEncAdaptiveMode, &var);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+
+ if (SUCCEEDED(mConfig->IsModifiable(&CODECAPI_AVLowLatencyMode))) {
+ var.vt = VT_BOOL;
+ var.boolVal = VARIANT_TRUE;
+ hr = mConfig->SetValue(&CODECAPI_AVLowLatencyMode, &var);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+
+ 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);
+}
+
+static HRESULT CreateSample(RefPtr<IMFSample>* aOutSample, DWORD aSize,
+ DWORD aAlignment) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ HRESULT hr;
+ RefPtr<IMFSample> sample;
+ hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ hr = wmf::MFCreateAlignedMemoryBuffer(aSize, aAlignment,
+ getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ *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);
+}
+
+HRESULT
+MFTEncoder::PushInput(RefPtr<IMFSample>&& aInput) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+ MOZ_ASSERT(aInput);
+
+ mPendingInputs.Push(aInput.forget());
+ if (mEventSource.IsSync() && mNumNeedInput == 0) {
+ // To step 2 in
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
+ mNumNeedInput++;
+ }
+
+ HRESULT hr = ProcessInput();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return ProcessEvents();
+}
+
+HRESULT MFTEncoder::ProcessInput() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+
+ if (mNumNeedInput == 0 || mPendingInputs.GetSize() == 0) {
+ return S_OK;
+ }
+
+ RefPtr<IMFSample> input = mPendingInputs.PopFront();
+ HRESULT hr = mEncoder->ProcessInput(mInputStreamID, input, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ --mNumNeedInput;
+
+ if (!mEventSource.IsSync()) {
+ return S_OK;
+ }
+ // For sync MFT: Step 3 in
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
+ DWORD flags = 0;
+ hr = mEncoder->GetOutputStatus(&flags);
+ MediaEventType evType = MEUnknown;
+ switch (hr) {
+ case S_OK:
+ evType = flags == MFT_OUTPUT_STATUS_SAMPLE_READY
+ ? METransformHaveOutput // To step 4: ProcessOutput().
+ : METransformNeedInput; // To step 2: ProcessInput().
+ break;
+ case E_NOTIMPL:
+ evType = METransformHaveOutput; // To step 4: ProcessOutput().
+ break;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("undefined output status");
+ return hr;
+ }
+ return mEventSource.QueueSyncMFTEvent(evType);
+}
+
+HRESULT MFTEncoder::ProcessEvents() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+
+ HRESULT hr = E_FAIL;
+ while (true) {
+ Event event = mEventSource.GetEvent();
+ if (event.isErr()) {
+ hr = event.unwrapErr();
+ break;
+ }
+
+ MediaEventType evType = event.unwrap();
+ switch (evType) {
+ case METransformNeedInput:
+ ++mNumNeedInput;
+ hr = ProcessInput();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ break;
+ case METransformHaveOutput:
+ hr = ProcessOutput();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ break;
+ case METransformDrainComplete:
+ mDrainState = DrainState::DRAINED;
+ break;
+ default:
+ MFT_ENC_LOGE("unsupported event: %lx", evType);
+ }
+ }
+
+ switch (hr) {
+ case MF_E_NO_EVENTS_AVAILABLE:
+ return S_OK;
+ case MF_E_MULTIPLE_SUBSCRIBERS:
+ default:
+ MFT_ENC_LOGE("failed to get event: %s", ErrorStr(hr));
+ return hr;
+ }
+}
+
+HRESULT MFTEncoder::ProcessOutput() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+
+ MFT_OUTPUT_DATA_BUFFER output = {.dwStreamID = mOutputStreamID,
+ .pSample = nullptr,
+ .dwStatus = 0,
+ .pEvents = nullptr};
+ RefPtr<IMFSample> sample;
+ HRESULT hr = E_FAIL;
+ if (!mOutputStreamProvidesSample) {
+ hr = CreateSample(&sample, mOutputStreamInfo.cbSize,
+ mOutputStreamInfo.cbAlignment > 1
+ ? mOutputStreamInfo.cbAlignment - 1
+ : 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ output.pSample = sample;
+ }
+
+ DWORD status = 0;
+ hr = mEncoder->ProcessOutput(0, 1, &output, &status);
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ MFT_ENC_LOGD("output stream change");
+ if (output.dwStatus & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE) {
+ // Follow the instructions in Microsoft doc:
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/handling-stream-changes#output-type
+ IMFMediaType* outputType = nullptr;
+ hr = mEncoder->GetOutputAvailableType(mOutputStreamID, 0, &outputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ hr = mEncoder->SetOutputType(mOutputStreamID, outputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+ return MF_E_TRANSFORM_STREAM_CHANGE;
+ }
+
+ // Step 8 in
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ MOZ_ASSERT(mEventSource.IsSync());
+ MOZ_ASSERT(mDrainState == DrainState::DRAINING);
+
+ mEventSource.QueueSyncMFTEvent(METransformDrainComplete);
+ return S_OK;
+ }
+
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mOutputs.AppendElement(output.pSample);
+ if (mOutputStreamProvidesSample) {
+ // Release MFT provided sample.
+ output.pSample->Release();
+ output.pSample = nullptr;
+ }
+
+ return S_OK;
+}
+
+HRESULT MFTEncoder::TakeOutput(nsTArray<RefPtr<IMFSample>>& aOutput) {
+ MOZ_ASSERT(aOutput.Length() == 0);
+ aOutput.SwapElements(mOutputs);
+ return S_OK;
+}
+
+HRESULT MFTEncoder::Drain(nsTArray<RefPtr<IMFSample>>& aOutput) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+ MOZ_ASSERT(aOutput.Length() == 0);
+
+ switch (mDrainState) {
+ case DrainState::DRAINABLE:
+ // Exhaust pending inputs.
+ while (mPendingInputs.GetSize() > 0) {
+ if (mEventSource.IsSync()) {
+ // Step 5 in
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
+ mEventSource.QueueSyncMFTEvent(METransformNeedInput);
+ }
+ HRESULT hr = ProcessEvents();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+ SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+ mDrainState = DrainState::DRAINING;
+ [[fallthrough]]; // To collect and return outputs.
+ case DrainState::DRAINING:
+ // Collect remaining outputs.
+ while (mOutputs.Length() == 0 && mDrainState != DrainState::DRAINED) {
+ if (mEventSource.IsSync()) {
+ // Step 8 in
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/basic-mft-processing-model#process-data
+ mEventSource.QueueSyncMFTEvent(METransformHaveOutput);
+ }
+ HRESULT hr = ProcessEvents();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ }
+ [[fallthrough]]; // To return outputs.
+ case DrainState::DRAINED:
+ aOutput.SwapElements(mOutputs);
+ return S_OK;
+ }
+}
+
+HRESULT MFTEncoder::GetMPEGSequenceHeader(nsTArray<UINT8>& aHeader) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mEncoder);
+ MOZ_ASSERT(aHeader.Length() == 0);
+
+ RefPtr<IMFMediaType> outputType;
+ HRESULT hr = mEncoder->GetOutputCurrentType(mOutputStreamID,
+ getter_AddRefs(outputType));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ UINT32 length = 0;
+ hr = outputType->GetBlobSize(MF_MT_MPEG_SEQUENCE_HEADER, &length);
+ if (hr == MF_E_ATTRIBUTENOTFOUND || length == 0) {
+ return S_OK;
+ }
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ aHeader.SetCapacity(length);
+ hr = outputType->GetBlob(MF_MT_MPEG_SEQUENCE_HEADER, aHeader.Elements(),
+ length, nullptr);
+ aHeader.SetLength(SUCCEEDED(hr) ? length : 0);
+
+ return hr;
+}
+
+MFTEncoder::Event MFTEncoder::EventSource::GetEvent() {
+ if (IsSync()) {
+ return GetSyncMFTEvent();
+ }
+
+ RefPtr<IMFMediaEvent> event;
+ HRESULT hr = mImpl.as<RefPtr<IMFMediaEventGenerator>>()->GetEvent(
+ MF_EVENT_FLAG_NO_WAIT, getter_AddRefs(event));
+ MediaEventType type = MEUnknown;
+ if (SUCCEEDED(hr)) {
+ hr = event->GetType(&type);
+ }
+ return SUCCEEDED(hr) ? Event{type} : Event{hr};
+}
+
+HRESULT MFTEncoder::EventSource::QueueSyncMFTEvent(MediaEventType aEventType) {
+ MOZ_ASSERT(IsSync());
+ MOZ_ASSERT(IsOnCurrentThread());
+
+ auto q = mImpl.as<UniquePtr<EventQueue>>().get();
+ q->push(aEventType);
+ return S_OK;
+}
+
+MFTEncoder::Event MFTEncoder::EventSource::GetSyncMFTEvent() {
+ MOZ_ASSERT(IsOnCurrentThread());
+
+ auto q = mImpl.as<UniquePtr<EventQueue>>().get();
+ if (q->empty()) {
+ return Event{MF_E_NO_EVENTS_AVAILABLE};
+ }
+
+ MediaEventType type = q->front();
+ q->pop();
+ return Event{type};
+}
+
+#ifdef DEBUG
+bool MFTEncoder::EventSource::IsOnCurrentThread() {
+ if (!mThread) {
+ mThread = GetCurrentSerialEventTarget();
+ }
+ return mThread->IsOnCurrentThread();
+}
+#endif
+
+} // namespace mozilla
+
+#undef MFT_ENC_SLOGE
+#undef MFT_ENC_SLOGD
+#undef MFT_ENC_LOGE
+#undef MFT_ENC_LOGD