821 lines
24 KiB
C++
821 lines
24 KiB
C++
/* -*- 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/COMWrappers.h"
|
|
#include "mozilla/mscom/Utils.h"
|
|
#include "WMFUtils.h"
|
|
#include <comdef.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;
|
|
}
|
|
mscom::wrapped::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;
|
|
}
|
|
mscom::wrapped::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) {
|
|
MFT_ENC_LOGE("CreateFactory error");
|
|
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))));
|
|
if (FAILED(hr)) {
|
|
_com_error error(hr);
|
|
MFT_ENC_LOGE("MFTEncoder::Create: error = 0x%lX, %ls", hr,
|
|
error.ErrorMessage());
|
|
return 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)) {
|
|
MFT_ENC_LOGE("QueryInterface IID_ICodecAPI error");
|
|
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();
|
|
if (asyncMFT.isErr()) {
|
|
HRESULT hr = asyncMFT.inspectErr();
|
|
_com_error error(hr);
|
|
MFT_ENC_LOGE("AttemptEnableAsync error: %ls", error.ErrorMessage());
|
|
return asyncMFT.inspectErr();
|
|
}
|
|
|
|
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)) {
|
|
MFT_ENC_LOGE("Encoder->GetAttribute error");
|
|
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();
|
|
|
|
if (FAILED(hr)) {
|
|
MFT_ENC_LOGE("Setting async unlock");
|
|
}
|
|
|
|
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(const EncoderConfig& aConfig) {
|
|
MOZ_ASSERT(mscom::IsCurrentThreadMTA());
|
|
MOZ_ASSERT(mConfig);
|
|
|
|
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;
|
|
}
|
|
HRESULT hr = mConfig->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var);
|
|
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
|
|
|
if (aConfig.mBitrate) {
|
|
var.ulVal = aConfig.mBitrate;
|
|
hr = mConfig->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var);
|
|
if (FAILED(hr)) {
|
|
MFT_ENC_LOGE("Couln't set bitrate to %d", aConfig.mBitrate);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
switch (aConfig.mScalabilityMode) {
|
|
case ScalabilityMode::None:
|
|
var.ulVal = 1;
|
|
break;
|
|
case ScalabilityMode::L1T2:
|
|
var.ulVal = 2;
|
|
break;
|
|
case ScalabilityMode::L1T3:
|
|
var.ulVal = 3;
|
|
break;
|
|
}
|
|
|
|
bool isIntel = false; // TODO check this
|
|
if (aConfig.mScalabilityMode != ScalabilityMode::None || isIntel) {
|
|
hr = mConfig->SetValue(&CODECAPI_AVEncVideoTemporalLayerCount, &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 =
|
|
aConfig.mUsage == Usage::Realtime ? VARIANT_TRUE : VARIANT_FALSE;
|
|
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(const InputSample& aInput) {
|
|
MOZ_ASSERT(mscom::IsCurrentThreadMTA());
|
|
MOZ_ASSERT(mEncoder);
|
|
|
|
mPendingInputs.push_back(aInput);
|
|
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.empty()) {
|
|
return S_OK;
|
|
}
|
|
|
|
auto input = mPendingInputs.front();
|
|
mPendingInputs.pop_front();
|
|
|
|
HRESULT hr = mEncoder->ProcessInput(mInputStreamID, input.mSample, 0);
|
|
|
|
if (input.mKeyFrameRequested) {
|
|
VARIANT v = {.vt = VT_UI4, .ulVal = 1};
|
|
mConfig->SetValue(&CODECAPI_AVEncVideoForceKeyFrame, &v);
|
|
}
|
|
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:
|
|
MFT_ENC_LOGD("State is now DrainState::DRAINED");
|
|
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.empty()) {
|
|
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);
|
|
MFT_ENC_LOGD("State is now DrainState::DRAINING");
|
|
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);
|
|
mDrainState = DrainState::DRAINABLE;
|
|
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
|