summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/wmf/MFTDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/wmf/MFTDecoder.cpp')
-rw-r--r--dom/media/platforms/wmf/MFTDecoder.cpp430
1 files changed, 430 insertions, 0 deletions
diff --git a/dom/media/platforms/wmf/MFTDecoder.cpp b/dom/media/platforms/wmf/MFTDecoder.cpp
new file mode 100644
index 0000000000..6b66a9e399
--- /dev/null
+++ b/dom/media/platforms/wmf/MFTDecoder.cpp
@@ -0,0 +1,430 @@
+/* -*- 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 "MFTDecoder.h"
+#include "WMFUtils.h"
+#include "mozilla/Logging.h"
+#include "nsThreadUtils.h"
+#include "mozilla/mscom/COMWrappers.h"
+#include "mozilla/mscom/Utils.h"
+#include "PlatformDecoderModule.h"
+
+#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+MFTDecoder::MFTDecoder() {
+ memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+ memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+MFTDecoder::~MFTDecoder() {
+ if (mActivate) {
+ // Releases all internal references to the created IMFTransform.
+ // https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfactivate-shutdownobject
+ mActivate->ShutdownObject();
+ }
+}
+
+HRESULT MFTDecoder::Create(const GUID& aCLSID) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ HRESULT hr = mscom::wrapped::CoCreateInstance(
+ aCLSID, nullptr, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(mDecoder))));
+ NS_WARNING_ASSERTION(SUCCEEDED(hr), "Failed to create MFT by CLSID");
+ return hr;
+}
+
+HRESULT
+MFTDecoder::Create(const GUID& aCategory, const GUID& aInSubtype,
+ const GUID& aOutSubtype) {
+ // Note: IMFTransform is documented to only be safe on MTA threads.
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ // Use video by default, but select audio if necessary.
+ const GUID major = aCategory == MFT_CATEGORY_AUDIO_DECODER
+ ? MFMediaType_Audio
+ : MFMediaType_Video;
+
+ // Ignore null GUIDs to allow searching for all decoders supporting
+ // just one input or output type.
+ auto createInfo = [&major](const GUID& subtype) -> MFT_REGISTER_TYPE_INFO* {
+ if (IsEqualGUID(subtype, GUID_NULL)) {
+ return nullptr;
+ }
+
+ MFT_REGISTER_TYPE_INFO* info = new MFT_REGISTER_TYPE_INFO();
+ info->guidMajorType = major;
+ info->guidSubtype = subtype;
+ return info;
+ };
+ const MFT_REGISTER_TYPE_INFO* inInfo = createInfo(aInSubtype);
+ const MFT_REGISTER_TYPE_INFO* outInfo = createInfo(aOutSubtype);
+
+ // Request a decoder from the Windows API.
+ HRESULT hr;
+ IMFActivate** acts = nullptr;
+ UINT32 actsNum = 0;
+
+ hr = wmf::MFTEnumEx(aCategory, MFT_ENUM_FLAG_SORTANDFILTER, inInfo, outInfo,
+ &acts, &actsNum);
+ delete inInfo;
+ delete outInfo;
+ if (FAILED(hr)) {
+ NS_WARNING(nsPrintfCString("MFTEnumEx failed with code %lx", hr).get());
+ return hr;
+ }
+ if (actsNum == 0) {
+ NS_WARNING("MFTEnumEx returned no IMFActivate instances");
+ return WINCODEC_ERR_COMPONENTNOTFOUND;
+ }
+ auto guard = MakeScopeExit([&] {
+ // Start from index 1, acts[0] will be stored as a RefPtr to release later.
+ for (UINT32 i = 1; i < actsNum; i++) {
+ acts[i]->Release();
+ }
+ CoTaskMemFree(acts);
+ });
+
+ // Create the IMFTransform to do the decoding.
+ // Note: Ideally we would cache the IMFActivate and call
+ // IMFActivate::DetachObject, but doing so causes the MFTs to fail on
+ // MFT_MESSAGE_SET_D3D_MANAGER.
+ mActivate = RefPtr<IMFActivate>(acts[0]);
+ hr = mActivate->ActivateObject(
+ IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(mDecoder))));
+ NS_WARNING_ASSERTION(
+ SUCCEEDED(hr),
+ nsPrintfCString("IMFActivate::ActivateObject failed with code %lx", hr)
+ .get());
+ return hr;
+}
+
+HRESULT
+MFTDecoder::SetMediaTypes(IMFMediaType* aInputType, IMFMediaType* aOutputType,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ // Set the input type to the one the caller gave us...
+ HRESULT hr = mDecoder->SetInputType(0, aInputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ GUID currentSubtype = {0};
+ hr = aOutputType->GetGUID(MF_MT_SUBTYPE, &currentSubtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderOutputType(currentSubtype, aOutputType, std::move(aCallback));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ 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);
+
+ return S_OK;
+}
+
+already_AddRefed<IMFAttributes> MFTDecoder::GetAttributes() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ RefPtr<IMFAttributes> attr;
+ HRESULT hr = mDecoder->GetAttributes(getter_AddRefs(attr));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ return attr.forget();
+}
+
+already_AddRefed<IMFAttributes> MFTDecoder::GetOutputStreamAttributes() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ RefPtr<IMFAttributes> attr;
+ HRESULT hr = mDecoder->GetOutputStreamAttributes(0, getter_AddRefs(attr));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ return attr.forget();
+}
+
+HRESULT
+MFTDecoder::FindDecoderOutputType() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ MOZ_ASSERT(mOutputType, "SetDecoderTypes must have been called once");
+
+ return FindDecoderOutputTypeWithSubtype(mOutputSubType);
+}
+
+HRESULT
+MFTDecoder::FindDecoderOutputTypeWithSubtype(const GUID& aSubType) {
+ return SetDecoderOutputType(aSubType, nullptr,
+ [](IMFMediaType*) { return S_OK; });
+}
+
+HRESULT
+MFTDecoder::SetDecoderOutputType(
+ const GUID& aSubType, IMFMediaType* aTypeToUse,
+ std::function<HRESULT(IMFMediaType*)>&& aCallback) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ if (!aTypeToUse) {
+ aTypeToUse = mOutputType;
+ }
+
+ // Iterate the enumerate the output types, until we find one compatible
+ // with what we need.
+ RefPtr<IMFMediaType> outputType;
+ UINT32 typeIndex = 0;
+ while (SUCCEEDED(mDecoder->GetOutputAvailableType(
+ 0, typeIndex++, getter_AddRefs(outputType)))) {
+ GUID outSubtype = {0};
+ HRESULT hr = outputType->GetGUID(MF_MT_SUBTYPE, &outSubtype);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (aSubType == outSubtype) {
+ hr = aCallback(outputType);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->SetOutputType(0, outputType, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mMFTProvidesOutputSamples = IsFlagSet(mOutputStreamInfo.dwFlags,
+ MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);
+
+ mOutputType = outputType;
+ mOutputSubType = outSubtype;
+
+ return S_OK;
+ }
+ outputType = nullptr;
+ }
+ return E_FAIL;
+}
+
+HRESULT
+MFTDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, ULONG_PTR aData) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+ LOG("Send message '%s'", MFTMessageTypeToStr(aMsg));
+ HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::CreateInputSample(const uint8_t* aData, uint32_t aDataSize,
+ int64_t aTimestamp, int64_t aDuration,
+ RefPtr<IMFSample>* aOutSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+ RefPtr<IMFSample> sample;
+ hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize =
+ std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+ UINT32 alignment =
+ (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+ hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment,
+ getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ DWORD maxLength = 0;
+ DWORD currentLength = 0;
+ BYTE* dst = nullptr;
+ hr = buffer->Lock(&dst, &maxLength, &currentLength);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ // Copy data into sample's buffer.
+ memcpy(dst, aData, aDataSize);
+
+ hr = buffer->Unlock();
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = buffer->SetCurrentLength(aDataSize);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (aDuration == 0) {
+ // If the sample duration is 0, the decoder will try and estimate the
+ // duration. In practice this can lead to some wildly incorrect durations,
+ // as in bug 1560440. The Microsoft docs seem conflicting here with
+ // `IMFSample::SetSampleDuration` stating 'The duration can also be zero.
+ // This might be valid for some types of data.' However,
+ // `IMFSample::GetSampleDuration method` states 'If the retrieved duration
+ // is zero, or if the method returns MF_E_NO_SAMPLE_DURATION, the duration
+ // is unknown. In that case, it might be possible to calculate the duration
+ // from the media type--for example, by using the video frame rate or the
+ // audio sampling rate.' The latter of those seems to be how the decoder
+ // handles 0 duration, hence why it estimates.
+ //
+ // Since our demuxing pipeline can create 0 duration samples, and since the
+ // decoder will override them to something positive anyway, setting them to
+ // have a trivial duration seems like the lesser of evils.
+ aDuration = 1;
+ }
+ hr = sample->SetSampleDuration(UsecsToHNs(aDuration));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.forget();
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::CreateOutputSample(RefPtr<IMFSample>* aOutSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+ RefPtr<IMFSample> sample;
+ hr = wmf::MFCreateSample(getter_AddRefs(sample));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize = mOutputStreamInfo.cbSize;
+ UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1)
+ ? mOutputStreamInfo.cbAlignment - 1
+ : 0;
+ hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment,
+ 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
+MFTDecoder::Output(RefPtr<IMFSample>* aOutput) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ HRESULT hr;
+
+ MFT_OUTPUT_DATA_BUFFER output = {0};
+
+ bool providedSample = false;
+ RefPtr<IMFSample> sample;
+ if (*aOutput) {
+ output.pSample = *aOutput;
+ providedSample = true;
+ } else if (!mMFTProvidesOutputSamples) {
+ hr = CreateOutputSample(&sample);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ output.pSample = sample;
+ }
+
+ DWORD status = 0;
+ hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+ if (output.pEvents) {
+ // We must release this, as per the IMFTransform::ProcessOutput()
+ // MSDN documentation.
+ output.pEvents->Release();
+ output.pEvents = nullptr;
+ }
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ return MF_E_TRANSFORM_STREAM_CHANGE;
+ }
+
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ // Not enough input to produce output. This is an expected failure,
+ // so don't warn on encountering it.
+ return hr;
+ }
+ // Treat other errors as unexpected, and warn.
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ if (!output.pSample) {
+ return S_OK;
+ }
+
+ if (mDiscontinuity) {
+ output.pSample->SetUINT32(MFSampleExtension_Discontinuity, TRUE);
+ mDiscontinuity = false;
+ }
+
+ *aOutput = output.pSample; // AddRefs
+ if (mMFTProvidesOutputSamples && !providedSample) {
+ // If the MFT is providing samples, we must release the sample here.
+ // Typically only the H.264 MFT provides samples when using DXVA,
+ // and it always re-uses the same sample, so if we don't release it
+ // MFT::ProcessOutput() deadlocks waiting for the sample to be released.
+ output.pSample->Release();
+ output.pSample = nullptr;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::Input(const uint8_t* aData, uint32_t aDataSize, int64_t aTimestamp,
+ int64_t aDuration) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER);
+
+ RefPtr<IMFSample> input;
+ HRESULT hr =
+ CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
+ NS_ENSURE_TRUE(SUCCEEDED(hr) && input != nullptr, hr);
+
+ return Input(input);
+}
+
+HRESULT
+MFTDecoder::Input(IMFSample* aSample) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ HRESULT hr = mDecoder->ProcessInput(0, aSample, 0);
+ if (hr == MF_E_NOTACCEPTING) {
+ // MFT *already* has enough data to produce a sample. Retrieve it.
+ return MF_E_NOTACCEPTING;
+ }
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::Flush() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mDiscontinuity = true;
+
+ return S_OK;
+}
+
+HRESULT
+MFTDecoder::GetInputMediaType(RefPtr<IMFMediaType>& aMediaType) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder, E_POINTER);
+ return mDecoder->GetInputCurrentType(0, getter_AddRefs(aMediaType));
+}
+
+HRESULT
+MFTDecoder::GetOutputMediaType(RefPtr<IMFMediaType>& aMediaType) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ NS_ENSURE_TRUE(mDecoder, E_POINTER);
+ return mDecoder->GetOutputCurrentType(0, getter_AddRefs(aMediaType));
+}
+
+#undef LOG
+
+} // namespace mozilla