Преглед изворни кода

win-mf: Add media foundation h264 encoder

Implements hardware encoders through the Media Foundation interface
provided by Microsoft.

Supports:
- Quicksync (Intel)
- VCE (AMD)
- NVENC (NVIDIA, might only be supported through MF on Windows 10)

Notes:
- NVENC and VCE do not appear to have proper CBR implementations.  This
  isn't a fault of our code, but the Media Foundation libraries.
  Quicksync however appears to be fine.
jp9000 пре 10 година
родитељ
комит
afa2985f64

+ 12 - 3
plugins/win-mf/CMakeLists.txt

@@ -1,18 +1,27 @@
 project(win-mf)
 
 set(win-mf_SOURCES
-	mf-plugin.c
+	mf-plugin.cpp
 	mf-aac.cpp
-	mf-aac-encoder.cpp)
+	mf-aac-encoder.cpp
+	mf-common.cpp
+	mf-encoder-descriptor.cpp
+	mf-h264.cpp
+	mf-h264-encoder.cpp)
 
 set(win-mf_HEADERS
-	mf-aac-encoder.hpp)
+	mf-common.hpp
+	mf-encoder-descriptor.hpp
+	mf-aac-encoder.hpp
+	mf-h264-encoder.hpp)
 
 add_library(win-mf MODULE
 	${win-mf_SOURCES}
 	${win-mf_HEADERS})
 
 target_link_libraries(win-mf
+	d3d9
+	dxva2
 	uuid
 	mfplat
 	mfuuid

+ 27 - 0
plugins/win-mf/data/locale/en-US.ini

@@ -1,2 +1,29 @@
 MFAACEnc="Media Foundation AAC Encoder"
 Bitrate="Bitrate"
+
+MF.H264.EncoderName="Media Foundation H264 Encoder"
+MF.H264.Encoder="Encoder Name"
+MF.H264.LowLatency="Low Latency (Disable frame re-ordering)"
+MF.H264.BFrames="Consecutive B-Frame count"
+MF.H264.CustomBufsize="Use Custom Buffer Size"
+MF.H264.BufferSize="Buffer Size"
+MF.H264.CustomMaxBitrate="Use Custom Max Bitrate"
+MF.H264.Bitrate="Bitrate"
+MF.H264.MaxBitrate="Max Bitrate"
+MF.H264.KeyframeIntervalSec="Keyframe Interval (seconds, 0=auto)"
+MF.H264.RateControl="Rate Control"
+MF.H264.CBR="CBR (Constant Bitrate)"
+MF.H264.VBR="VBR (Variable Bitrate)"
+MF.H264.CQP="CQP (Constant Quality)"
+MF.H264.MinQP="Minimum QP"
+MF.H264.MaxQP="Maximum QP"
+MF.H264.QPI="QP I-Frame"
+MF.H264.QPP="QP P-Frame"
+MF.H264.QPB="QP B-Frame"
+MF.H264.Profile="Profile"
+MF.H264.Advanced="Advanced"
+
+MF.H264.EncoderSWMicrosoft="Microsoft Software H.264 Encoder"
+MF.H264.EncoderHWAMD="AMD Video Coding Engine H.264 Encoder (Media Foundation)"
+MF.H264.EncoderHWIntel="Intel Quick Sync H.264 Encoder (Media Foundation)"
+MF.H264.EncoderHWNVIDIA="NVIDIA NVENC H.264 Encoder (Media Foundation)"

+ 361 - 0
plugins/win-mf/mf-common.cpp

@@ -0,0 +1,361 @@
+#include "mf-common.hpp"
+
+#include <util/platform.h>
+
+#include <Mferror.h>
+#include <strsafe.h>
+#include <wrl/client.h>
+
+static void DBGMSG(PCWSTR format, ...)
+{
+	va_list args;
+	va_start(args, format);
+
+	WCHAR msg[MAX_PATH];
+
+	if (SUCCEEDED(StringCbVPrintf(msg, sizeof(msg), format, args)))
+	{
+		char *cmsg;
+		os_wcs_to_utf8_ptr(msg, 0, &cmsg);
+		MF_LOG(LOG_INFO, "%s", cmsg);
+		bfree(cmsg);
+	}
+}
+
+#ifndef IF_EQUAL_RETURN
+#define IF_EQUAL_RETURN(param, val) if(val == param) return L#val
+#endif
+
+static LPCWSTR GetGUIDNameConst(const GUID& guid)
+{
+	IF_EQUAL_RETURN(guid, MF_MT_MAJOR_TYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_MAJOR_TYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_SUBTYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_ALL_SAMPLES_INDEPENDENT);
+	IF_EQUAL_RETURN(guid, MF_MT_FIXED_SIZE_SAMPLES);
+	IF_EQUAL_RETURN(guid, MF_MT_COMPRESSED);
+	IF_EQUAL_RETURN(guid, MF_MT_SAMPLE_SIZE);
+	IF_EQUAL_RETURN(guid, MF_MT_WRAPPED_TYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_NUM_CHANNELS);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_SAMPLES_PER_SECOND);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_FLOAT_SAMPLES_PER_SECOND);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_AVG_BYTES_PER_SECOND);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_BLOCK_ALIGNMENT);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_BITS_PER_SAMPLE);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_VALID_BITS_PER_SAMPLE);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_SAMPLES_PER_BLOCK);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_CHANNEL_MASK);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_FOLDDOWN_MATRIX);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_WMADRC_PEAKREF);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_WMADRC_PEAKTARGET);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_WMADRC_AVGREF);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_WMADRC_AVGTARGET);
+	IF_EQUAL_RETURN(guid, MF_MT_AUDIO_PREFER_WAVEFORMATEX);
+	IF_EQUAL_RETURN(guid, MF_MT_AAC_PAYLOAD_TYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION);
+	IF_EQUAL_RETURN(guid, MF_MT_FRAME_SIZE);
+	IF_EQUAL_RETURN(guid, MF_MT_FRAME_RATE);
+	IF_EQUAL_RETURN(guid, MF_MT_FRAME_RATE_RANGE_MAX);
+	IF_EQUAL_RETURN(guid, MF_MT_FRAME_RATE_RANGE_MIN);
+	IF_EQUAL_RETURN(guid, MF_MT_PIXEL_ASPECT_RATIO);
+	IF_EQUAL_RETURN(guid, MF_MT_DRM_FLAGS);
+	IF_EQUAL_RETURN(guid, MF_MT_PAD_CONTROL_FLAGS);
+	IF_EQUAL_RETURN(guid, MF_MT_SOURCE_CONTENT_HINT);
+	IF_EQUAL_RETURN(guid, MF_MT_VIDEO_CHROMA_SITING);
+	IF_EQUAL_RETURN(guid, MF_MT_INTERLACE_MODE);
+	IF_EQUAL_RETURN(guid, MF_MT_TRANSFER_FUNCTION);
+	IF_EQUAL_RETURN(guid, MF_MT_VIDEO_PRIMARIES);
+	IF_EQUAL_RETURN(guid, MF_MT_CUSTOM_VIDEO_PRIMARIES);
+	IF_EQUAL_RETURN(guid, MF_MT_YUV_MATRIX);
+	IF_EQUAL_RETURN(guid, MF_MT_VIDEO_LIGHTING);
+	IF_EQUAL_RETURN(guid, MF_MT_VIDEO_NOMINAL_RANGE);
+	IF_EQUAL_RETURN(guid, MF_MT_GEOMETRIC_APERTURE);
+	IF_EQUAL_RETURN(guid, MF_MT_MINIMUM_DISPLAY_APERTURE);
+	IF_EQUAL_RETURN(guid, MF_MT_PAN_SCAN_APERTURE);
+	IF_EQUAL_RETURN(guid, MF_MT_PAN_SCAN_ENABLED);
+	IF_EQUAL_RETURN(guid, MF_MT_AVG_BITRATE);
+	IF_EQUAL_RETURN(guid, MF_MT_AVG_BIT_ERROR_RATE);
+	IF_EQUAL_RETURN(guid, MF_MT_MAX_KEYFRAME_SPACING);
+	IF_EQUAL_RETURN(guid, MF_MT_DEFAULT_STRIDE);
+	IF_EQUAL_RETURN(guid, MF_MT_PALETTE);
+	IF_EQUAL_RETURN(guid, MF_MT_USER_DATA);
+	IF_EQUAL_RETURN(guid, MF_MT_AM_FORMAT_TYPE);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG_START_TIME_CODE);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG2_PROFILE);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG2_LEVEL);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG2_FLAGS);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG_SEQUENCE_HEADER);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_AAUX_SRC_PACK_0);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_AAUX_CTRL_PACK_0);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_AAUX_SRC_PACK_1);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_AAUX_CTRL_PACK_1);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_VAUX_SRC_PACK);
+	IF_EQUAL_RETURN(guid, MF_MT_DV_VAUX_CTRL_PACK);
+	IF_EQUAL_RETURN(guid, MF_MT_ARBITRARY_HEADER);
+	IF_EQUAL_RETURN(guid, MF_MT_ARBITRARY_FORMAT);
+	IF_EQUAL_RETURN(guid, MF_MT_IMAGE_LOSS_TOLERANT);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG4_SAMPLE_DESCRIPTION);
+	IF_EQUAL_RETURN(guid, MF_MT_MPEG4_CURRENT_SAMPLE_ENTRY);
+	IF_EQUAL_RETURN(guid, MF_MT_ORIGINAL_4CC);
+	IF_EQUAL_RETURN(guid, MF_MT_ORIGINAL_WAVE_FORMAT_TAG);
+
+	// Media types
+
+	IF_EQUAL_RETURN(guid, MFMediaType_Audio);
+	IF_EQUAL_RETURN(guid, MFMediaType_Video);
+	IF_EQUAL_RETURN(guid, MFMediaType_Protected);
+	IF_EQUAL_RETURN(guid, MFMediaType_SAMI);
+	IF_EQUAL_RETURN(guid, MFMediaType_Script);
+	IF_EQUAL_RETURN(guid, MFMediaType_Image);
+	IF_EQUAL_RETURN(guid, MFMediaType_HTML);
+	IF_EQUAL_RETURN(guid, MFMediaType_Binary);
+	IF_EQUAL_RETURN(guid, MFMediaType_FileTransfer);
+
+	IF_EQUAL_RETURN(guid, MFVideoFormat_AI44); //     FCC('AI44')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_ARGB32); //   D3DFMT_A8R8G8B8
+	IF_EQUAL_RETURN(guid, MFVideoFormat_AYUV); //     FCC('AYUV')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_DV25); //     FCC('dv25')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_DV50); //     FCC('dv50')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_DVH1); //     FCC('dvh1')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_DVSD); //     FCC('dvsd')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_DVSL); //     FCC('dvsl')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_H264); //     FCC('H264')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_I420); //     FCC('I420')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_IYUV); //     FCC('IYUV')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_M4S2); //     FCC('M4S2')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MJPG);
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MP43); //     FCC('MP43')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MP4S); //     FCC('MP4S')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MP4V); //     FCC('MP4V')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MPG1); //     FCC('MPG1')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MSS1); //     FCC('MSS1')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_MSS2); //     FCC('MSS2')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_NV11); //     FCC('NV11')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_NV12); //     FCC('NV12')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_P010); //     FCC('P010')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_P016); //     FCC('P016')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_P210); //     FCC('P210')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_P216); //     FCC('P216')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_RGB24); //    D3DFMT_R8G8B8
+	IF_EQUAL_RETURN(guid, MFVideoFormat_RGB32); //    D3DFMT_X8R8G8B8
+	IF_EQUAL_RETURN(guid, MFVideoFormat_RGB555); //   D3DFMT_X1R5G5B5
+	IF_EQUAL_RETURN(guid, MFVideoFormat_RGB565); //   D3DFMT_R5G6B5
+	IF_EQUAL_RETURN(guid, MFVideoFormat_RGB8);
+	IF_EQUAL_RETURN(guid, MFVideoFormat_UYVY); //     FCC('UYVY')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_v210); //     FCC('v210')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_v410); //     FCC('v410')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_WMV1); //     FCC('WMV1')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_WMV2); //     FCC('WMV2')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_WMV3); //     FCC('WMV3')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_WVC1); //     FCC('WVC1')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y210); //     FCC('Y210')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y216); //     FCC('Y216')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y410); //     FCC('Y410')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y416); //     FCC('Y416')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y41P);
+	IF_EQUAL_RETURN(guid, MFVideoFormat_Y41T);
+	IF_EQUAL_RETURN(guid, MFVideoFormat_YUY2); //     FCC('YUY2')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_YV12); //     FCC('YV12')
+	IF_EQUAL_RETURN(guid, MFVideoFormat_YVYU);
+
+	IF_EQUAL_RETURN(guid, MFAudioFormat_PCM); //              WAVE_FORMAT_PCM
+	IF_EQUAL_RETURN(guid, MFAudioFormat_Float); //            WAVE_FORMAT_IEEE_FLOAT
+	IF_EQUAL_RETURN(guid, MFAudioFormat_DTS); //              WAVE_FORMAT_DTS
+	IF_EQUAL_RETURN(guid, MFAudioFormat_Dolby_AC3_SPDIF); //  WAVE_FORMAT_DOLBY_AC3_SPDIF
+	IF_EQUAL_RETURN(guid, MFAudioFormat_DRM); //              WAVE_FORMAT_DRM
+	IF_EQUAL_RETURN(guid, MFAudioFormat_WMAudioV8); //        WAVE_FORMAT_WMAUDIO2
+	IF_EQUAL_RETURN(guid, MFAudioFormat_WMAudioV9); //        WAVE_FORMAT_WMAUDIO3
+	IF_EQUAL_RETURN(guid, MFAudioFormat_WMAudio_Lossless); // WAVE_FORMAT_WMAUDIO_LOSSLESS
+	IF_EQUAL_RETURN(guid, MFAudioFormat_WMASPDIF); //         WAVE_FORMAT_WMASPDIF
+	IF_EQUAL_RETURN(guid, MFAudioFormat_MSP1); //             WAVE_FORMAT_WMAVOICE9
+	IF_EQUAL_RETURN(guid, MFAudioFormat_MP3); //              WAVE_FORMAT_MPEGLAYER3
+	IF_EQUAL_RETURN(guid, MFAudioFormat_MPEG); //             WAVE_FORMAT_MPEG
+	IF_EQUAL_RETURN(guid, MFAudioFormat_AAC); //              WAVE_FORMAT_MPEG_HEAAC
+	IF_EQUAL_RETURN(guid, MFAudioFormat_ADTS); //             WAVE_FORMAT_MPEG_ADTS_AAC
+
+	return NULL;
+}
+
+static float OffsetToFloat(const MFOffset& offset)
+{
+	return offset.value + (static_cast<float>(offset.fract) / 65536.0f);
+}
+
+static HRESULT LogVideoArea(const PROPVARIANT& var)
+{
+	if (var.caub.cElems < sizeof(MFVideoArea)) {
+		return MF_E_BUFFERTOOSMALL;
+	}
+
+	MFVideoArea *pArea = (MFVideoArea*)var.caub.pElems;
+
+	DBGMSG(L"(%f,%f) (%d,%d)", OffsetToFloat(pArea->OffsetX), OffsetToFloat(pArea->OffsetY),
+		pArea->Area.cx, pArea->Area.cy);
+	return S_OK;
+}
+
+
+static HRESULT GetGUIDName(const GUID& guid, WCHAR **ppwsz)
+{
+	HRESULT hr = S_OK;
+	WCHAR *pName = NULL;
+
+	LPCWSTR pcwsz = GetGUIDNameConst(guid);
+	if (pcwsz) {
+		size_t cchLength = 0;
+
+		hr = StringCchLength(pcwsz, STRSAFE_MAX_CCH, &cchLength);
+		if (FAILED(hr)) {
+			goto done;
+		}
+
+		pName = (WCHAR*)CoTaskMemAlloc((cchLength + 1) * sizeof(WCHAR));
+
+		if (pName == NULL) {
+			hr = E_OUTOFMEMORY;
+			goto done;
+		}
+
+		hr = StringCchCopy(pName, cchLength + 1, pcwsz);
+		if (FAILED(hr)) {
+			goto done;
+		}
+	} else {
+		hr = StringFromCLSID(guid, &pName);
+	}
+
+done:
+	if (FAILED(hr)) {
+		*ppwsz = NULL;
+		CoTaskMemFree(pName);
+	} else {
+		*ppwsz = pName;
+	}
+	return hr;
+}
+
+static void LogUINT32AsUINT64(const PROPVARIANT& var)
+{
+	UINT32 uHigh = 0, uLow = 0;
+	Unpack2UINT32AsUINT64(var.uhVal.QuadPart, &uHigh, &uLow);
+	DBGMSG(L"%d x %d", uHigh, uLow);
+}
+
+
+// Handle certain known special cases.
+static HRESULT SpecialCaseAttributeValue(GUID guid, const PROPVARIANT& var)
+{
+	if ((guid == MF_MT_FRAME_RATE) || (guid == MF_MT_FRAME_RATE_RANGE_MAX) ||
+		(guid == MF_MT_FRAME_RATE_RANGE_MIN) || (guid == MF_MT_FRAME_SIZE) ||
+		(guid == MF_MT_PIXEL_ASPECT_RATIO)) {
+
+		// Attributes that contain two packed 32-bit values.
+		LogUINT32AsUINT64(var);
+
+	} else if ((guid == MF_MT_GEOMETRIC_APERTURE) ||
+		(guid == MF_MT_MINIMUM_DISPLAY_APERTURE) ||
+		(guid == MF_MT_PAN_SCAN_APERTURE)) {
+
+		// Attributes that an MFVideoArea structure.
+		return LogVideoArea(var);
+
+	} else {
+		return S_FALSE;
+	}
+	return S_OK;
+}
+
+static HRESULT LogAttributeValueByIndex(IMFAttributes *pAttr, DWORD index)
+{
+	WCHAR *pGuidName = NULL;
+	WCHAR *pGuidValName = NULL;
+
+	GUID guid = { 0 };
+
+	PROPVARIANT var;
+	PropVariantInit(&var);
+
+	HRESULT hr = pAttr->GetItemByIndex(index, &guid, &var);
+	if (FAILED(hr)) {
+		goto done;
+	}
+
+	hr = GetGUIDName(guid, &pGuidName);
+	if (FAILED(hr)) {
+		goto done;
+	}
+
+	DBGMSG(L"%s", pGuidName);
+
+	hr = SpecialCaseAttributeValue(guid, var);
+	if (FAILED(hr)) {
+		goto done;
+	}
+	if (hr == S_FALSE) {
+		switch (var.vt) {
+		case VT_UI4:
+			DBGMSG(L"%d", var.ulVal);
+			break;
+
+		case VT_UI8:
+			DBGMSG(L"%I64d", var.uhVal);
+			break;
+
+		case VT_R8:
+			DBGMSG(L"%f", var.dblVal);
+			break;
+
+		case VT_CLSID:
+			hr = GetGUIDName(*var.puuid, &pGuidValName);
+			if (SUCCEEDED(hr))
+			{
+				DBGMSG(pGuidValName);
+			}
+			break;
+
+		case VT_LPWSTR:
+			DBGMSG(var.pwszVal);
+			break;
+
+		case VT_VECTOR | VT_UI1:
+			DBGMSG(L"<<byte array>>");
+			break;
+
+		case VT_UNKNOWN:
+			DBGMSG(L"IUnknown");
+			break;
+
+		default:
+			DBGMSG(L"Unexpected attribute type (vt = %d)", var.vt);
+			break;
+		}
+	}
+
+done:
+	CoTaskMemFree(pGuidName);
+	CoTaskMemFree(pGuidValName);
+	PropVariantClear(&var);
+	return hr;
+}
+
+bool MF::LogMediaType(IMFMediaType *pType)
+{
+	UINT32 count = 0;
+
+	HRESULT hr = pType->GetCount(&count);
+	if (FAILED(hr)) {
+		return false;
+	}
+
+	if (count == 0) {
+		DBGMSG(L"Empty media type.");
+	}
+
+	for (UINT32 i = 0; i < count; i++) {
+		hr = LogAttributeValueByIndex(pType, i);
+		if (FAILED(hr)) {
+			return false;
+		}
+	}
+	return true;
+}

+ 61 - 0
plugins/win-mf/mf-common.hpp

@@ -0,0 +1,61 @@
+#pragma once
+
+#include <obs-module.h>
+
+#include <mfapi.h>
+#include <functional>
+#include <comdef.h>
+#include <chrono>
+
+
+#ifndef MF_LOG
+#define MF_LOG(level, format, ...) \
+	blog(level, "[Media Foundation encoder]: " format, ##__VA_ARGS__)
+#endif
+#ifndef MF_LOG_ENCODER
+#define MF_LOG_ENCODER(format_name, encoder, level, format, ...) \
+	blog(level, "[Media Foundation %s: '%s']: " format, \
+			format_name, obs_encoder_get_name(encoder), \
+			##__VA_ARGS__)
+#endif
+
+#ifndef MF_LOG_COM
+#define MF_LOG_COM(level, msg, hr) MF_LOG(level, \
+		msg " failed,  %S (0x%08lx)", \
+		_com_error(hr).ErrorMessage(), hr)
+#endif
+
+#ifndef HRC
+#define HRC(r) \
+	if(FAILED(hr = (r))) { \
+		MF_LOG_COM(LOG_ERROR, #r, hr); \
+		goto fail; \
+	}
+#endif
+
+#ifndef HR_CHECK
+#define HR_CHECK(level, r) \
+	if(FAILED(hr = (r))) { \
+		MF_LOG_COM(level, #r, hr); \
+		goto fail; \
+		}
+#endif
+
+#ifndef HRL
+#define HRL(r) \
+	if(FAILED(hr = (r))) \
+		MF_LOG_COM(LOG_WARNING, #r, hr);
+#endif
+
+namespace MF {
+
+enum Status {
+	FAILURE,
+	SUCCESS,
+	NOT_ACCEPTING,
+	NEED_MORE_INPUT
+};
+
+bool LogMediaType(IMFMediaType *mediaType);
+
+}

+ 194 - 0
plugins/win-mf/mf-encoder-descriptor.cpp

@@ -0,0 +1,194 @@
+#include <obs-module.h>
+#include <util/platform.h>
+#include <memory>
+#include <algorithm>
+
+#include "mf-encoder-descriptor.hpp"
+
+using namespace MF;
+
+template<class T> class ComHeapPtr {
+
+protected:
+	T *ptr;
+
+	inline void Kill()
+	{
+		if (ptr)
+			CoTaskMemFree(ptr);
+	}
+
+	inline void Replace(T *p)
+	{
+		if (ptr != p) {
+			if (ptr) ptr->Kill();
+			ptr = p;
+		}
+	}
+
+public:
+	inline ComHeapPtr() : ptr(nullptr)                 {}
+	inline ComHeapPtr(T *p) : ptr(p)                   {}
+	inline ComHeapPtr(const ComHeapPtr<T> &c)          = delete;
+	inline ComHeapPtr(ComHeapPtr<T> &&c)               = delete;
+	inline ~ComHeapPtr()                               { Kill(); }
+
+	inline void Clear()
+	{
+		if (ptr) {
+			Kill();
+			ptr = nullptr;
+		}
+	}
+
+	inline ComPtr<T> &operator=(T *p)
+	{
+		Replace(p);
+		return *this;
+	}
+
+	inline T *Detach()
+	{
+		T *out = ptr;
+		ptr = nullptr;
+		return out;
+	}
+
+	inline T **Assign()                { Clear(); return &ptr; }
+	inline void Set(T *p)              { Kill(); ptr = p; }
+
+	inline T *Get() const              { return ptr; }
+
+	inline T **operator&()             { return Assign(); }
+
+	inline    operator T*() const      { return ptr; }
+	inline T *operator->() const       { return ptr; }
+
+	inline bool operator==(T *p) const { return ptr == p; }
+	inline bool operator!=(T *p) const { return ptr != p; }
+
+	inline bool operator!() const      { return !ptr; }
+};
+
+struct EncoderEntry {
+	const char *guid;
+	const char *name;
+	const char *id;
+	EncoderType type;
+
+} guidNameMap[] = {
+	{
+		"{6CA50344-051A-4DED-9779-A43305165E35}",
+		"MF.H264.EncoderSWMicrosoft",
+		"mf_h264_software",
+		EncoderType::H264_SOFTWARE
+	},
+	{
+		"{ADC9BC80-0F41-46C6-AB75-D693D793597D}",
+		"MF.H264.EncoderHWAMD",
+		"mf_h264_vce",
+		EncoderType::H264_VCE,
+	},
+	{
+		"{4BE8D3C0-0515-4A37-AD55-E4BAE19AF471}",
+		"MF.H264.EncoderHWIntel",
+		"mf_h264_qsv",
+		EncoderType::H264_QSV
+	},
+	{
+		"{60F44560-5A20-4857-BFEF-D29773CB8040}",
+		"MF.H264.EncoderHWNVIDIA",
+		"mf_h264_nvenc",
+		EncoderType::H264_NVENC
+	}
+};
+
+static std::string MBSToString(wchar_t *mbs)
+{
+	char *cstr;
+	os_wcs_to_utf8_ptr(mbs, 0, &cstr);
+	std::string str = cstr;
+	bfree(cstr);
+	return str;
+}
+
+static std::unique_ptr<EncoderDescriptor> CreateDescriptor(
+		ComPtr<IMFActivate> activate)
+{
+	UINT32 flags;
+	if (FAILED(activate->GetUINT32(MF_TRANSFORM_FLAGS_Attribute, &flags)))
+		return nullptr;
+
+	bool isAsync = !(flags & MFT_ENUM_FLAG_SYNCMFT);
+	isAsync |= !!(flags & MFT_ENUM_FLAG_ASYNCMFT);
+	bool isHardware = !!(flags & MFT_ENUM_FLAG_HARDWARE);
+
+	GUID guid = {0};
+
+	if (FAILED(activate->GetGUID(MFT_TRANSFORM_CLSID_Attribute, &guid)))
+		return nullptr;
+
+	ComHeapPtr<WCHAR> guidW;
+	StringFromIID(guid, &guidW);
+	std::string guidString = MBSToString(guidW);
+
+	auto pred = [guidString](const EncoderEntry &name) {
+		return guidString == name.guid;
+	};
+
+	EncoderEntry *entry = std::find_if(std::begin(guidNameMap),
+			std::end(guidNameMap), pred);
+
+	std::unique_ptr<EncoderDescriptor> descriptor(new EncoderDescriptor(
+			activate, entry->name, entry->id, guid, guidString,
+			isAsync, isHardware, entry->type));
+
+	return descriptor;
+}
+
+std::vector<std::shared_ptr<EncoderDescriptor>> EncoderDescriptor::Enumerate()
+{
+	HRESULT hr;
+	UINT32 count = 0;
+	std::vector<std::shared_ptr<EncoderDescriptor>> descriptors;
+
+	ComHeapPtr<IMFActivate *> ppActivate;
+
+	MFT_REGISTER_TYPE_INFO info = { MFMediaType_Video, MFVideoFormat_H264 };
+
+	UINT32 unFlags = 0;
+
+	unFlags |= MFT_ENUM_FLAG_LOCALMFT;
+	unFlags |= MFT_ENUM_FLAG_TRANSCODE_ONLY;
+
+	unFlags |= MFT_ENUM_FLAG_SYNCMFT;
+	unFlags |= MFT_ENUM_FLAG_ASYNCMFT;
+	unFlags |= MFT_ENUM_FLAG_HARDWARE;
+
+	unFlags |= MFT_ENUM_FLAG_SORTANDFILTER;
+
+	hr = MFTEnumEx(MFT_CATEGORY_VIDEO_ENCODER,
+		unFlags,
+		NULL,
+		&info,
+		&ppActivate,
+		&count);
+
+	if (SUCCEEDED(hr) && count == 0) {
+		return descriptors;
+	}
+
+	if (SUCCEEDED(hr)) {
+		for (decltype(count) i = 0; i < count; i++) {
+			auto p = std::move(CreateDescriptor(ppActivate[i]));
+			if (p)
+				descriptors.emplace_back(std::move(p));
+		}
+	}
+
+	for (UINT32 i = 0; i < count; i++) {
+		ppActivate[i]->Release();
+	}
+
+	return descriptors;
+}

+ 69 - 0
plugins/win-mf/mf-encoder-descriptor.hpp

@@ -0,0 +1,69 @@
+#pragma once
+
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+#undef WIN32_MEAN_AND_LEAN
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include <stdint.h>
+#include <vector>
+
+#include <util/windows/ComPtr.hpp>
+
+namespace MF {
+
+enum class EncoderType {
+	H264_SOFTWARE,
+	H264_QSV,
+	H264_NVENC,
+	H264_VCE,
+};
+
+class EncoderDescriptor {
+public:
+	static std::vector<std::shared_ptr<EncoderDescriptor>> EncoderDescriptor::Enumerate();
+
+public:
+	EncoderDescriptor(ComPtr<IMFActivate> activate_,
+			const char *name_,
+			const char *id_,
+			GUID &guid_,
+			const std::string &guidString_,
+			bool isAsync_,
+			bool isHardware_,
+			EncoderType type_)
+		: activate             (activate_),
+		  name                 (name_),
+		  id                   (id_),
+		  guid                 (guid_),
+		  guidString           (guidString_),
+		  isAsync              (isAsync_),
+		  isHardware           (isHardware_),
+		  type                 (type_)
+	{}
+
+	EncoderDescriptor(const EncoderDescriptor &) = delete;
+
+public:
+	const char *Name() const { return name; }
+	const char *Id() const { return id; }
+	ComPtr<IMFActivate> &Activator() { return activate; }
+	GUID &Guid() { return guid; }
+	std::string GuidString() const { return guidString; }
+	bool Async() const { return isAsync; }
+	bool Hardware() const { return isHardware; }
+	EncoderType Type() const { return type; }
+
+private:
+	ComPtr<IMFActivate> activate;
+	const char *name;
+	const char *id;
+	GUID guid;
+	std::string guidString;
+	bool isAsync;
+	bool isHardware;
+	EncoderType type;
+};
+};

+ 745 - 0
plugins/win-mf/mf-h264-encoder.cpp

@@ -0,0 +1,745 @@
+#include <obs-module.h>
+#include <util/profiler.hpp>
+
+#include "mf-common.hpp"
+#include "mf-h264-encoder.hpp"
+
+#include <codecapi.h>
+#include <mferror.h>
+
+using namespace MF;
+
+static eAVEncH264VProfile MapProfile(H264Profile profile)
+{
+	switch (profile) {
+	case H264ProfileBaseline: return eAVEncH264VProfile_Base;
+	case H264ProfileMain:     return eAVEncH264VProfile_Main;
+	case H264ProfileHigh:     return eAVEncH264VProfile_High;
+	default:                  return eAVEncH264VProfile_Base;
+	}
+}
+
+static eAVEncCommonRateControlMode MapRateControl(H264RateControl rc)
+{
+	switch (rc) {
+	case H264RateControlCBR:
+		return eAVEncCommonRateControlMode_CBR;
+	case H264RateControlConstrainedVBR:
+		return eAVEncCommonRateControlMode_PeakConstrainedVBR;
+	case H264RateControlVBR:
+		return eAVEncCommonRateControlMode_UnconstrainedVBR;
+	case H264RateControlCQP:
+		return eAVEncCommonRateControlMode_Quality;
+	default:
+		return eAVEncCommonRateControlMode_CBR;
+	}
+}
+
+static UINT32 MapQpToQuality(H264QP &qp)
+{
+	return 100 - (UINT32)floor(100.0 / 51.0 * qp.defaultQp + 0.5f);
+}
+
+static bool ProcessNV12(std::function<void(UINT32 height, INT32 plane)> func,
+	UINT32 height)
+{
+	INT32 plane = 0;
+
+	func(height, plane++);
+	func(height / 2, plane);
+
+	return true;
+}
+
+H264Encoder::H264Encoder(const obs_encoder_t *encoder,
+	std::shared_ptr<EncoderDescriptor> descriptor,
+	UINT32 width,
+	UINT32 height,
+	UINT32 framerateNum,
+	UINT32 framerateDen,
+	H264Profile profile,
+	UINT32 bitrate)
+	: encoder(encoder),
+	descriptor(descriptor),
+	width(width),
+	height(height),
+	framerateNum(framerateNum),
+	framerateDen(framerateDen),
+	initialBitrate(bitrate),
+	profile(profile)
+{}
+
+H264Encoder::~H264Encoder()
+{}
+
+HRESULT H264Encoder::CreateMediaTypes(ComPtr<IMFMediaType> &i,
+		ComPtr<IMFMediaType> &o)
+{
+	HRESULT hr;
+	HRC(MFCreateMediaType(&i));
+	HRC(MFCreateMediaType(&o));
+
+	HRC(i->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
+	HRC(i->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12));
+	HRC(MFSetAttributeSize(i, MF_MT_FRAME_SIZE, width, height));
+	HRC(MFSetAttributeRatio(i, MF_MT_FRAME_RATE, framerateNum,
+			framerateDen));
+	HRC(i->SetUINT32(MF_MT_INTERLACE_MODE,
+			MFVideoInterlaceMode::MFVideoInterlace_Progressive));
+	HRC(MFSetAttributeRatio(i, MF_MT_PIXEL_ASPECT_RATIO, 1, 1));
+
+	HRC(o->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
+	HRC(o->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264));
+	HRC(MFSetAttributeSize(o, MF_MT_FRAME_SIZE, width, height));
+	HRC(MFSetAttributeRatio(o, MF_MT_FRAME_RATE, framerateNum,
+			framerateDen));
+	HRC(o->SetUINT32(MF_MT_AVG_BITRATE, initialBitrate * 1000));
+	HRC(o->SetUINT32(MF_MT_INTERLACE_MODE,
+			MFVideoInterlaceMode::MFVideoInterlace_Progressive));
+	HRC(MFSetAttributeRatio(o, MF_MT_PIXEL_ASPECT_RATIO, 1, 1));
+	HRC(o->SetUINT32(MF_MT_MPEG2_LEVEL, (UINT32)-1));
+	HRC(o->SetUINT32(MF_MT_MPEG2_PROFILE, MapProfile(profile)));
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT H264Encoder::DrainEvents()
+{
+	HRESULT hr;
+	while ((hr = DrainEvent(false)) == S_OK);
+	if (hr == MF_E_NO_EVENTS_AVAILABLE)
+		hr = S_OK;
+	return hr;
+}
+
+HRESULT H264Encoder::DrainEvent(bool block)
+{
+	HRESULT hr, eventStatus;
+	ComPtr<IMFMediaEvent> event;
+	MediaEventType type;
+
+	hr = eventGenerator->GetEvent(
+		block ? 0 : MF_EVENT_FLAG_NO_WAIT, &event);
+
+	if (hr != MF_E_NO_EVENTS_AVAILABLE && FAILED(hr))
+		goto fail;
+	if (hr == MF_E_NO_EVENTS_AVAILABLE)
+		return hr;
+
+	HRC(event->GetType(&type));
+	HRC(event->GetStatus(&eventStatus));
+
+	if (SUCCEEDED(eventStatus)) {
+		if (type == METransformNeedInput) {
+			inputRequests++;
+		}
+		else if (type == METransformHaveOutput) {
+			outputRequests++;
+		}
+	}
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT H264Encoder::InitializeEventGenerator()
+{
+	HRESULT hr;
+
+	HRC(transform->QueryInterface(&eventGenerator));
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT H264Encoder::InitializeExtraData()
+{
+	HRESULT hr;
+	ComPtr<IMFMediaType> inputType;
+	UINT32 headerSize;
+
+	extraData.clear();
+
+	HRC(transform->GetOutputCurrentType(0, &inputType));
+
+	HRC(inputType->GetBlobSize(MF_MT_MPEG_SEQUENCE_HEADER, &headerSize));
+
+	extraData.resize(headerSize);
+
+	HRC(inputType->GetBlob(MF_MT_MPEG_SEQUENCE_HEADER, extraData.data(),
+			headerSize, NULL));
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+static HRESULT SetCodecProperty(ComPtr<ICodecAPI> &codecApi, GUID guid,
+	bool value)
+{
+	VARIANT v;
+	v.vt = VT_BOOL;
+	v.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE;
+	return codecApi->SetValue(&guid, &v);
+}
+
+static HRESULT SetCodecProperty(ComPtr<ICodecAPI> &codecApi, GUID guid,
+	UINT32 value)
+{
+	VARIANT v;
+	v.vt = VT_UI4;
+	v.ulVal = value;
+	return codecApi->SetValue(&guid, &v);
+}
+
+static HRESULT SetCodecProperty(ComPtr<ICodecAPI> &codecApi, GUID guid,
+	UINT64 value)
+{
+	VARIANT v;
+	v.vt = VT_UI8;
+	v.ullVal = value;
+	return codecApi->SetValue(&guid, &v);
+}
+
+bool H264Encoder::SetBitrate(UINT32 bitrate)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncCommonMeanBitRate,
+				UINT32(bitrate * 1000)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetQP(H264QP &qp)
+{
+	HRESULT hr;
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncCommonQuality,
+				UINT32(MapQpToQuality(qp))));
+		HRL(SetCodecProperty(codecApi,
+				CODECAPI_AVEncVideoEncodeQP,
+				UINT64(qp.Pack(true))));
+		HRL(SetCodecProperty(codecApi,
+				CODECAPI_AVEncVideoEncodeFrameTypeQP,
+				UINT64(qp.Pack(false))));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetMinQP(UINT32 minQp)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncVideoMinQP,
+				UINT32(minQp)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetMaxQP(UINT32 maxQp)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncVideoMaxQP,
+				UINT32(maxQp)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetRateControl(H264RateControl rateControl)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncCommonRateControlMode,
+				UINT32(MapRateControl(rateControl))));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetKeyframeInterval(UINT32 seconds)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		float gopSize = float(framerateNum) / framerateDen * seconds;
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncMPVGOPSize,
+				UINT32(gopSize)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetMaxBitrate(UINT32 maxBitrate)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+			CODECAPI_AVEncCommonMaxBitRate,
+			UINT32(maxBitrate * 1000)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetLowLatency(bool lowLatency)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+			CODECAPI_AVEncCommonLowLatency,
+			lowLatency));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetBufferSize(UINT32 bufferSize)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+			CODECAPI_AVEncCommonBufferSize,
+			UINT32(bufferSize * 1000)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetBFrameCount(UINT32 bFrames)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+			CODECAPI_AVEncMPVDefaultBPictureCount,
+			UINT32(bFrames)));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::SetEntropyEncoding(H264EntropyEncoding entropyEncoding)
+{
+	HRESULT hr;
+
+	if (codecApi) {
+		HR_CHECK(LOG_WARNING, SetCodecProperty(codecApi,
+				CODECAPI_AVEncH264CABACEnable,
+				entropyEncoding == H264EntropyEncodingCABAC));
+	}
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::Initialize(std::function<bool(void)> func)
+{
+	ProfileScope("H264Encoder::Initialize");
+
+	HRESULT hr;
+
+	ComPtr<IMFMediaType> inputType, outputType;
+	ComPtr<IMFAttributes> transformAttributes;
+	MFT_OUTPUT_STREAM_INFO streamInfo = {0};
+
+	HRC(CoCreateInstance(descriptor->Guid(), NULL, CLSCTX_INPROC_SERVER,
+			IID_PPV_ARGS(&transform)));
+
+	HRC(CreateMediaTypes(inputType, outputType));
+
+	if (descriptor->Async()) {
+		HRC(transform->GetAttributes(&transformAttributes));
+		HRC(transformAttributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK,
+				TRUE));
+	}
+
+	HRC(transform->QueryInterface(&codecApi));
+
+	if (func && !func()) {
+		MF_LOG(LOG_ERROR, "Failed setting custom properties");
+		goto fail;
+	}
+
+	MF_LOG(LOG_INFO, "Setting output type to transform");
+	LogMediaType(outputType.Get());
+	HRC(transform->SetOutputType(0, outputType.Get(), 0));
+
+	MF_LOG(LOG_INFO, "Setting input type to transform");
+	LogMediaType(inputType.Get());
+	HRC(transform->SetInputType(0, inputType.Get(), 0));
+
+	HRC(transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING,
+		NULL));
+
+	HRC(transform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM,
+		NULL));
+
+	if (descriptor->Async())
+		HRC(InitializeEventGenerator());
+
+	HRC(transform->GetOutputStreamInfo(0, &streamInfo));
+	createOutputSample = !(streamInfo.dwFlags &
+			       (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES |
+			        MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES));
+
+	return true;
+
+fail:
+	return false;
+}
+
+bool H264Encoder::ExtraData(UINT8 **data, UINT32 *dataLength)
+{
+	if (extraData.empty())
+		return false;
+
+	*data = extraData.data();
+	*dataLength = (UINT32)extraData.size();
+
+	return true;
+}
+
+HRESULT H264Encoder::CreateEmptySample(ComPtr<IMFSample> &sample,
+	ComPtr<IMFMediaBuffer> &buffer, DWORD length)
+{
+	HRESULT hr;
+
+	HRC(MFCreateSample(&sample));
+	HRC(MFCreateMemoryBuffer(length, &buffer));
+	HRC(sample->AddBuffer(buffer.Get()));
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT H264Encoder::EnsureCapacity(ComPtr<IMFSample> &sample, DWORD length)
+{
+	HRESULT hr;
+	ComPtr<IMFMediaBuffer> buffer;
+	DWORD currentLength;
+
+	if (!sample) {
+		HRC(CreateEmptySample(sample, buffer, length));
+	}
+	else {
+		HRC(sample->GetBufferByIndex(0, &buffer));
+	}
+
+	HRC(buffer->GetMaxLength(&currentLength));
+	if (currentLength < length) {
+		HRC(sample->RemoveAllBuffers());
+		HRC(MFCreateMemoryBuffer(length, &buffer));
+		HRC(sample->AddBuffer(buffer));
+	}
+	else {
+		buffer->SetCurrentLength(0);
+	}
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+HRESULT H264Encoder::ProcessInput(ComPtr<IMFSample> &sample)
+{
+	ProfileScope("H264Encoder::ProcessInput(sample)");
+
+	HRESULT hr = S_OK;
+	if (descriptor->Async()) {
+		if (inputRequests == 1 && inputSamples.empty()) {
+			inputRequests--;
+			return transform->ProcessInput(0, sample, 0);
+		}
+
+		inputSamples.push(sample);
+
+		while (inputRequests > 0) {
+			if (inputSamples.empty())
+				return hr;
+			ComPtr<IMFSample> queuedSample = inputSamples.front();
+			inputSamples.pop();
+			inputRequests--;
+			HRC(transform->ProcessInput(0, queuedSample, 0));
+		}
+	} else {
+		return transform->ProcessInput(0, sample, 0);
+	}
+
+fail:
+	return hr;
+}
+
+bool H264Encoder::ProcessInput(UINT8 **data, UINT32 *linesize, UINT64 pts,
+		Status *status)
+{
+	ProfileScope("H264Encoder::ProcessInput");
+
+	HRESULT hr;
+	ComPtr<IMFSample> sample;
+	ComPtr<IMFMediaBuffer> buffer;
+	BYTE *bufferData;
+	UINT64 sampleDur;
+	UINT32 imageSize;
+
+	HRC(MFCalculateImageSize(MFVideoFormat_NV12, width, height, &imageSize));
+
+	HRC(CreateEmptySample(sample, buffer, imageSize));
+
+	{
+		ProfileScope("H264EncoderCopyInputSample");
+
+		HRC(buffer->Lock(&bufferData, NULL, NULL));
+
+		ProcessNV12([&, this](DWORD height, int plane) {
+			MFCopyImage(bufferData, width, data[plane],
+					linesize[plane], width, height);
+			bufferData += width * height;
+		}, height);
+	}
+
+	HRC(buffer->Unlock());
+	HRC(buffer->SetCurrentLength(imageSize));
+
+	MFFrameRateToAverageTimePerFrame(framerateNum, framerateDen, &sampleDur);
+
+	HRC(sample->SetSampleTime(pts * sampleDur));
+	HRC(sample->SetSampleDuration(sampleDur));
+
+	if (descriptor->Async()) {
+		HRC(DrainEvents());
+
+		while (outputRequests > 0 && (hr = ProcessOutput()) == S_OK);
+
+		if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
+			MF_LOG_COM(LOG_ERROR, "ProcessOutput()", hr);
+			goto fail;
+		}
+
+
+		while (inputRequests == 0) {
+			hr = DrainEvent(false);
+			if (hr == MF_E_NO_EVENTS_AVAILABLE) {
+				Sleep(1);
+				continue;
+			}
+			if (FAILED(hr)) {
+				MF_LOG_COM(LOG_ERROR, "DrainEvent()", hr);
+				goto fail;
+			}
+			if (outputRequests > 0) {
+				hr = ProcessOutput();
+				if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT &&
+				    FAILED(hr))
+					goto fail;
+			}
+		}
+	}
+
+	HRC(ProcessInput(sample));
+
+	*status = SUCCESS;
+	return true;
+
+fail:
+	*status = FAILURE;
+	return false;
+}
+
+HRESULT H264Encoder::ProcessOutput()
+{
+	HRESULT hr;
+	ComPtr<IMFSample> sample;
+	MFT_OUTPUT_STREAM_INFO outputInfo = { 0 };
+
+	DWORD outputStatus = 0;
+	MFT_OUTPUT_DATA_BUFFER output = { 0 };
+	ComPtr<IMFMediaBuffer> buffer;
+	BYTE *bufferData;
+	DWORD bufferLength;
+	INT64 samplePts;
+	INT64 sampleDts;
+	INT64 sampleDur;
+	std::unique_ptr<std::vector<BYTE>> data(new std::vector<BYTE>());
+	ComPtr<IMFMediaType> type;
+	std::unique_ptr<H264Frame> frame;
+
+	if (descriptor->Async()) {
+		HRC(DrainEvents());
+
+		if (outputRequests == 0)
+			return S_OK;
+
+		outputRequests--;
+	}
+
+	if (createOutputSample) {
+		HRC(transform->GetOutputStreamInfo(0, &outputInfo));
+		HRC(CreateEmptySample(sample, buffer, outputInfo.cbSize));
+		output.pSample = sample;
+	} else {
+		output.pSample = NULL;
+	}
+
+	while (true) {
+		hr = transform->ProcessOutput(0, 1, &output,
+				&outputStatus);
+		ComPtr<IMFCollection> events(output.pEvents);
+
+		if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
+			return hr;
+
+		if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+			HRC(transform->GetOutputAvailableType(0, 0, &type));
+			HRC(transform->SetOutputType(0, type, 0));
+			MF_LOG(LOG_INFO, "Updating output type to transform");
+			LogMediaType(type);
+			if (descriptor->Async() && outputRequests > 0) {
+				outputRequests--;
+				continue;
+			} else {
+				return MF_E_TRANSFORM_NEED_MORE_INPUT;
+			}
+		}
+
+		if (hr != S_OK) {
+			MF_LOG_COM(LOG_ERROR, "transform->ProcessOutput()",
+					hr);
+			return hr;
+		}
+
+		break;
+	}
+
+	if (!createOutputSample)
+		sample.Set(output.pSample);
+
+
+	HRC(sample->GetBufferByIndex(0, &buffer));
+
+	bool keyframe = !!MFGetAttributeUINT32(sample,
+			MFSampleExtension_CleanPoint, FALSE);
+
+	HRC(buffer->Lock(&bufferData, NULL, &bufferLength));
+
+	if (keyframe && extraData.empty())
+		HRC(InitializeExtraData());
+
+	data->reserve(bufferLength + extraData.size());
+
+	if (keyframe)
+		data->insert(data->end(), extraData.begin(), extraData.end());
+
+	data->insert(data->end(), &bufferData[0], &bufferData[bufferLength]);
+	HRC(buffer->Unlock());
+
+	HRC(sample->GetSampleDuration(&sampleDur));
+	HRC(sample->GetSampleTime(&samplePts));
+
+	sampleDts = MFGetAttributeUINT64(sample,
+			MFSampleExtension_DecodeTimestamp, samplePts);
+
+	frame.reset(new H264Frame(keyframe,
+			samplePts / sampleDur,
+			sampleDts / sampleDur,
+			std::move(data)));
+
+	encodedFrames.push(std::move(frame));
+
+	return S_OK;
+
+fail:
+	return hr;
+}
+
+bool H264Encoder::ProcessOutput(UINT8 **data, UINT32 *dataLength,
+		UINT64 *pts, UINT64 *dts, bool *keyframe, Status *status)
+{
+	ProfileScope("H264Encoder::ProcessOutput");
+
+	HRESULT hr;
+
+	hr = ProcessOutput();
+
+	if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || encodedFrames.empty()) {
+		*status = NEED_MORE_INPUT;
+		return true;
+	}
+
+	if (FAILED(hr) && encodedFrames.empty()) {
+		*status = FAILURE;
+		return false;
+	}
+
+	activeFrame = std::move(encodedFrames.front());
+	encodedFrames.pop();
+
+	*data = activeFrame.get()->Data();
+	*dataLength = activeFrame.get()->DataLength();
+	*pts = activeFrame.get()->Pts();
+	*dts = activeFrame.get()->Dts();
+	*keyframe = activeFrame.get()->Keyframe();
+	*status = SUCCESS;
+
+	return true;
+}

+ 175 - 0
plugins/win-mf/mf-h264-encoder.hpp

@@ -0,0 +1,175 @@
+#pragma once
+
+#include <obs-module.h>
+
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+#undef WIN32_MEAN_AND_LEAN
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include <wmcodecdsp.h>
+
+#include <vector>
+#include <queue>
+#include <memory>
+#include <atomic>
+
+#include <util/windows/ComPtr.hpp>
+
+#include "mf-encoder-descriptor.hpp"
+#include "mf-common.hpp"
+
+namespace MF {
+	enum H264Profile {
+		H264ProfileBaseline,
+		H264ProfileMain,
+		H264ProfileHigh
+	};
+
+	enum H264RateControl {
+		H264RateControlCBR,
+		H264RateControlConstrainedVBR,
+		H264RateControlVBR,
+		H264RateControlCQP
+	};
+
+	struct H264QP {
+		UINT16 defaultQp;
+		UINT16 i;
+		UINT16 p;
+		UINT16 b;
+
+		UINT64 Pack(bool packDefault) {
+			int shift = packDefault ? 0 : 16;
+			UINT64 packedQp;
+			if (packDefault)
+				packedQp = defaultQp;
+
+			packedQp |= i << shift;
+			shift += 16;
+			packedQp |= p << shift;
+			shift += 16;
+			packedQp |= b << shift;
+
+			return packedQp;
+		}
+	};
+
+	enum H264EntropyEncoding {
+		H264EntropyEncodingCABLC,
+		H264EntropyEncodingCABAC
+	};
+
+	struct H264Frame {
+	public:
+		H264Frame(bool keyframe, UINT64 pts, UINT64 dts,
+				std::unique_ptr<std::vector<uint8_t>> data)
+			: keyframe(keyframe), pts(pts), dts(dts),
+			  data(std::move(data))
+		{}
+		bool Keyframe() { return keyframe; }
+		BYTE *Data() { return data.get()->data(); }
+		DWORD DataLength() { return (DWORD)data.get()->size(); }
+		INT64 Pts() { return pts; }
+		INT64 Dts() { return dts; }
+
+	private:
+		H264Frame(H264Frame const&) = delete;
+		H264Frame& operator=(H264Frame const&) = delete;
+	private:
+		bool keyframe;
+		INT64 pts;
+		INT64 dts;
+		std::unique_ptr<std::vector<uint8_t>> data;
+	};
+
+	class H264Encoder {
+	public:
+		H264Encoder(const obs_encoder_t *encoder,
+			std::shared_ptr<EncoderDescriptor> descriptor,
+			UINT32 width,
+			UINT32 height,
+			UINT32 framerateNum,
+			UINT32 framerateDen,
+			H264Profile profile,
+			UINT32 bitrate);
+
+		~H264Encoder();
+
+		bool Initialize(std::function<bool(void)> func);
+		bool ProcessInput(UINT8 **data, UINT32 *linesize, UINT64 pts,
+			Status *status);
+		bool ProcessOutput(UINT8 **data, UINT32 *dataLength,
+			UINT64 *pts, UINT64 *dts, bool *keyframe,
+			Status *status);
+		bool ExtraData(UINT8 **data, UINT32 *dataLength);
+
+		const obs_encoder_t *ObsEncoder() { return encoder; }
+
+	public:
+		bool SetBitrate(UINT32 bitrate);
+		bool SetQP(H264QP &qp);
+		bool SetMaxBitrate(UINT32 maxBitrate);
+		bool SetRateControl(H264RateControl rateControl);
+		bool SetKeyframeInterval(UINT32 seconds);
+		bool SetLowLatency(bool lowLatency);
+		bool SetBufferSize(UINT32 bufferSize);
+		bool SetBFrameCount(UINT32 bFrames);
+		bool SetEntropyEncoding(H264EntropyEncoding entropyEncoding);
+		bool SetMinQP(UINT32 minQp);
+		bool SetMaxQP(UINT32 maxQp);
+
+	private:
+		H264Encoder(H264Encoder const&) = delete;
+		H264Encoder& operator=(H264Encoder const&) = delete;
+
+	private:
+		HRESULT InitializeEventGenerator();
+		HRESULT InitializeExtraData();
+		HRESULT CreateMediaTypes(ComPtr<IMFMediaType> &inputType,
+			ComPtr<IMFMediaType> &outputType);
+		HRESULT EnsureCapacity(ComPtr<IMFSample> &sample, DWORD length);
+		HRESULT CreateEmptySample(ComPtr<IMFSample> &sample,
+			ComPtr<IMFMediaBuffer> &buffer, DWORD length);
+
+		HRESULT ProcessInput(ComPtr<IMFSample> &sample);
+		HRESULT ProcessOutput();
+
+		HRESULT DrainEvent(bool block);
+		HRESULT DrainEvents();
+	private:
+		const obs_encoder_t *encoder;
+		std::shared_ptr<EncoderDescriptor> descriptor;
+		const UINT32 width;
+		const UINT32 height;
+		const UINT32 framerateNum;
+		const UINT32 framerateDen;
+		const UINT32 initialBitrate;
+		const H264Profile profile;
+
+		bool createOutputSample;
+		ComPtr<IMFTransform> transform;
+		ComPtr<ICodecAPI> codecApi;
+
+		std::vector<BYTE> extraData;
+
+		// The frame returned by ProcessOutput
+		// Valid until the next call to ProcessOutput
+		std::unique_ptr<H264Frame> activeFrame;
+
+		// Queued input samples that the encoder was not ready
+		// to process
+		std::queue<ComPtr<IMFSample>> inputSamples;
+
+		// Queued output samples that have not been returned from
+		// ProcessOutput yet
+		std::queue<std::unique_ptr<H264Frame>> encodedFrames;
+
+		ComPtr<IMFMediaEventGenerator> eventGenerator;
+		std::atomic<UINT32> inputRequests = 0;
+		std::atomic<UINT32> outputRequests = 0;
+
+	};
+}

+ 537 - 0
plugins/win-mf/mf-h264.cpp

@@ -0,0 +1,537 @@
+#include <obs-module.h>
+#include <util/profiler.hpp>
+
+#include <memory>
+#include <chrono>
+
+#include "mf-h264-encoder.hpp"
+#include "mf-encoder-descriptor.hpp"
+#include <VersionHelpers.h>
+
+using namespace MF;
+
+struct MFH264_Encoder {
+	obs_encoder_t *encoder;
+	std::shared_ptr<EncoderDescriptor> descriptor;
+	std::unique_ptr<H264Encoder> h264Encoder;
+	uint32_t width;
+	uint32_t height;
+	uint32_t framerateNum;
+	uint32_t framerateDen;
+	uint32_t keyint;
+	bool advanced;
+	uint32_t bitrate;
+	uint32_t maxBitrate;
+	bool useMaxBitrate;
+	uint32_t bufferSize;
+	bool useBufferSize;
+	H264Profile profile;
+	H264RateControl rateControl;
+	H264QP qp;
+	uint32_t minQp;
+	uint32_t maxQp;
+	bool lowLatency;
+	uint32_t bFrames;
+
+	const char *profiler_encode = nullptr;
+};
+
+#define MFTEXT(x) obs_module_text("MF.H264." x)
+#define TEXT_ADVANCED        MFTEXT("Advanced")
+#define TEXT_LOW_LAT         MFTEXT("LowLatency")
+#define TEXT_B_FRAMES        MFTEXT("BFrames")
+#define TEXT_BITRATE         MFTEXT("Bitrate")
+#define TEXT_CUSTOM_BUF      MFTEXT("CustomBufsize")
+#define TEXT_BUF_SIZE        MFTEXT("BufferSize")
+#define TEXT_USE_MAX_BITRATE MFTEXT("CustomMaxBitrate")
+#define TEXT_MAX_BITRATE     MFTEXT("MaxBitrate")
+#define TEXT_KEYINT_SEC      MFTEXT("KeyframeIntervalSec")
+#define TEXT_RATE_CONTROL    MFTEXT("RateControl")
+#define TEXT_MIN_QP          MFTEXT("MinQP")
+#define TEXT_MAX_QP          MFTEXT("MaxQP")
+#define TEXT_QPI             MFTEXT("QPI")
+#define TEXT_QPP             MFTEXT("QPP")
+#define TEXT_QPB             MFTEXT("QPB")
+#define TEXT_PROFILE         MFTEXT("Profile")
+#define TEXT_CBR             MFTEXT("CBR")
+#define TEXT_VBR             MFTEXT("VBR")
+#define TEXT_CQP             MFTEXT("CQP")
+
+#define MFP(x) "mf_h264_" ## x
+#define MFP_USE_ADVANCED     MFP("use_advanced")
+#define MFP_USE_LOWLAT       MFP("use_low_latency")
+#define MFP_B_FRAMES         MFP("b_frames")
+#define MFP_BITRATE          MFP("bitrate")
+#define MFP_USE_BUF_SIZE     MFP("use_buf_size")
+#define MFP_BUF_SIZE         MFP("buf_size")
+#define MFP_USE_MAX_BITRATE  MFP("use_max_bitrate")
+#define MFP_MAX_BITRATE      MFP("max_bitrate")
+#define MFP_KEY_INT          MFP("key_int")
+#define MFP_RATE_CONTROL     MFP("rate_control")
+#define MFP_MIN_QP           MFP("min_qp")
+#define MFP_MAX_QP           MFP("max_qp")
+#define MFP_QP_I             MFP("qp_i")
+#define MFP_QP_P             MFP("qp_p")
+#define MFP_QP_B             MFP("qp_b")
+#define MFP_PROFILE          MFP("profile")
+
+struct TypeData {
+	std::shared_ptr<EncoderDescriptor> descriptor;
+
+	inline TypeData(std::shared_ptr<EncoderDescriptor> descriptor_)
+		: descriptor(descriptor_)
+	{}
+};
+
+static const char *MFH264_GetName(void *type_data)
+{
+	TypeData &typeData = *reinterpret_cast<TypeData*>(type_data);
+	return obs_module_text(typeData.descriptor->Name());
+}
+
+static void set_visible(obs_properties_t *ppts, const char *name, bool visible)
+{
+	obs_property_t *p = obs_properties_get(ppts, name);
+	obs_property_set_visible(p, visible);
+}
+
+static bool use_bufsize_modified(obs_properties_t *ppts, obs_property_t *p,
+	obs_data_t *settings)
+{
+	UNUSED_PARAMETER(p);
+
+	bool use_bufsize = obs_data_get_bool(settings, MFP_USE_BUF_SIZE);
+
+	set_visible(ppts, MFP_BUF_SIZE, use_bufsize);
+
+	return true;
+}
+
+static bool use_max_bitrate_modified(obs_properties_t *ppts, obs_property_t *p,
+	obs_data_t *settings)
+{
+	UNUSED_PARAMETER(p);
+
+	bool advanced        = obs_data_get_bool(settings, MFP_USE_ADVANCED);
+	bool use_max_bitrate = obs_data_get_bool(settings, MFP_USE_MAX_BITRATE);
+
+	set_visible(ppts, MFP_MAX_BITRATE, advanced && use_max_bitrate);
+
+	return true;
+}
+
+static bool use_advanced_modified(obs_properties_t *ppts, obs_property_t *p,
+	obs_data_t *settings)
+{
+	UNUSED_PARAMETER(p);
+
+	bool advanced = obs_data_get_bool(settings, MFP_USE_ADVANCED);
+
+	set_visible(ppts, MFP_MIN_QP,       advanced);
+	set_visible(ppts, MFP_MAX_QP,       advanced);
+	set_visible(ppts, MFP_USE_LOWLAT,   advanced);
+	set_visible(ppts, MFP_B_FRAMES,     advanced);
+
+	H264RateControl rateControl = (H264RateControl)obs_data_get_int(
+		settings, MFP_RATE_CONTROL);
+
+	if (rateControl == H264RateControlCBR ||
+	    rateControl == H264RateControlVBR) {
+		set_visible(ppts, MFP_USE_MAX_BITRATE, advanced);
+		use_max_bitrate_modified(ppts, NULL, settings);
+	}
+
+	return true;
+}
+
+static bool rate_control_modified(obs_properties_t *ppts, obs_property_t *p,
+	obs_data_t *settings)
+{
+	UNUSED_PARAMETER(p);
+
+	H264RateControl rateControl = (H264RateControl)obs_data_get_int(
+		settings, MFP_RATE_CONTROL);
+
+	bool advanced = obs_data_get_bool(settings, MFP_USE_ADVANCED);
+
+	set_visible(ppts, MFP_BITRATE,         false);
+	set_visible(ppts, MFP_USE_BUF_SIZE,    false);
+	set_visible(ppts, MFP_BUF_SIZE,        false);
+	set_visible(ppts, MFP_USE_MAX_BITRATE, false);
+	set_visible(ppts, MFP_MAX_BITRATE,     false);
+	set_visible(ppts, MFP_QP_I,            false);
+	set_visible(ppts, MFP_QP_P,            false);
+	set_visible(ppts, MFP_QP_B,            false);
+
+	switch (rateControl) {
+	case H264RateControlCBR:
+		use_bufsize_modified(ppts,     NULL, settings);
+		use_max_bitrate_modified(ppts, NULL, settings);
+
+		set_visible(ppts, MFP_BITRATE,         true);
+		set_visible(ppts, MFP_USE_BUF_SIZE,    true);
+		set_visible(ppts, MFP_USE_MAX_BITRATE, advanced);
+
+		break;
+	case H264RateControlVBR:
+		use_bufsize_modified(ppts,     NULL, settings);
+		use_max_bitrate_modified(ppts, NULL, settings);
+
+		set_visible(ppts, MFP_BITRATE,         true);
+		set_visible(ppts, MFP_USE_BUF_SIZE,    true);
+		set_visible(ppts, MFP_USE_MAX_BITRATE, advanced);
+
+		break;
+	case H264RateControlCQP:
+		set_visible(ppts, MFP_QP_I,            true);
+		set_visible(ppts, MFP_QP_P,            true);
+		set_visible(ppts, MFP_QP_B,            true);
+
+		break;
+	default: break;
+	}
+
+	return true;
+}
+
+static obs_properties_t *MFH264_GetProperties(void *)
+{
+	obs_properties_t *props = obs_properties_create();
+	obs_property_t *p;
+
+	obs_property_t *list = obs_properties_add_list(props, MFP_PROFILE,
+		TEXT_PROFILE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+
+	obs_property_list_add_int(list, "baseline", H264ProfileBaseline);
+	obs_property_list_add_int(list, "main",     H264ProfileMain);
+	obs_property_list_add_int(list, "high",     H264ProfileHigh);
+
+	obs_properties_add_int(props, MFP_KEY_INT, TEXT_KEYINT_SEC, 0, 20, 1);
+
+	list = obs_properties_add_list(props, MFP_RATE_CONTROL,
+		TEXT_RATE_CONTROL, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+
+	obs_property_list_add_int(list, TEXT_CBR, H264RateControlCBR);
+	obs_property_list_add_int(list, TEXT_VBR, H264RateControlVBR);
+	obs_property_list_add_int(list, TEXT_CQP, H264RateControlCQP);
+
+	obs_property_set_modified_callback(list, rate_control_modified);
+
+	obs_properties_add_int(props, MFP_BITRATE, TEXT_BITRATE, 50, 10000000,
+			1);
+
+	p = obs_properties_add_bool(props, MFP_USE_BUF_SIZE, TEXT_CUSTOM_BUF);
+	obs_property_set_modified_callback(p, use_bufsize_modified);
+	obs_properties_add_int(props, MFP_BUF_SIZE, TEXT_BUF_SIZE, 0,
+			10000000, 1);
+
+	obs_properties_add_int(props, MFP_QP_I, TEXT_QPI, 0, 51, 1);
+	obs_properties_add_int(props, MFP_QP_P, TEXT_QPP, 0, 51, 1);
+	obs_properties_add_int(props, MFP_QP_B, TEXT_QPB, 0, 51, 1);
+
+	p = obs_properties_add_bool(props, MFP_USE_ADVANCED, TEXT_ADVANCED);
+	obs_property_set_modified_callback(p, use_advanced_modified);
+
+	p = obs_properties_add_bool(props, MFP_USE_MAX_BITRATE,
+		TEXT_USE_MAX_BITRATE);
+	obs_property_set_modified_callback(p, use_max_bitrate_modified);
+	obs_properties_add_int(props, MFP_MAX_BITRATE, TEXT_MAX_BITRATE, 50,
+		10000000, 1);
+
+	obs_properties_add_bool(props, MFP_USE_LOWLAT, TEXT_LOW_LAT);
+	obs_properties_add_int(props,  MFP_B_FRAMES,   TEXT_B_FRAMES, 0, 16, 1);
+	obs_properties_add_int(props,  MFP_MIN_QP,     TEXT_MIN_QP,   1, 51, 1);
+	obs_properties_add_int(props,  MFP_MAX_QP,     TEXT_MAX_QP,   1, 51, 1);
+	return props;
+}
+
+static void MFH264_GetDefaults(obs_data_t *settings)
+{
+#define PROP_DEF(x, y, z) obs_data_set_default_ ## x(settings, y, z)
+	PROP_DEF(int,    MFP_BITRATE,         2500);
+	PROP_DEF(bool,   MFP_USE_LOWLAT,      true);
+	PROP_DEF(int,    MFP_B_FRAMES,        2);
+	PROP_DEF(bool,   MFP_USE_BUF_SIZE,    false);
+	PROP_DEF(int,    MFP_BUF_SIZE,        2500);
+	PROP_DEF(bool,   MFP_USE_MAX_BITRATE, false);
+	PROP_DEF(int,    MFP_MAX_BITRATE,     2500);
+	PROP_DEF(int,    MFP_KEY_INT,         2);
+	PROP_DEF(int,    MFP_RATE_CONTROL,    H264RateControlCBR);
+	PROP_DEF(int,    MFP_PROFILE,         H264ProfileMain);
+	PROP_DEF(int,    MFP_MIN_QP,          1);
+	PROP_DEF(int,    MFP_MAX_QP,          51);
+	PROP_DEF(int,    MFP_QP_I,            26);
+	PROP_DEF(int,    MFP_QP_B,            26);
+	PROP_DEF(int,    MFP_QP_P,            26);
+	PROP_DEF(bool,   MFP_USE_ADVANCED,    false);
+#undef DEF
+}
+
+static void UpdateParams(MFH264_Encoder *enc, obs_data_t *settings)
+{
+	video_t *video = obs_encoder_video(enc->encoder);
+	const struct video_output_info *voi = video_output_get_info(video);
+	TypeData &typeData = *reinterpret_cast<TypeData*>(
+			obs_encoder_get_type_data(enc->encoder));
+
+	enc->width = (uint32_t)obs_encoder_get_width(enc->encoder);
+	enc->height = (uint32_t)obs_encoder_get_height(enc->encoder);
+	enc->framerateNum = voi->fps_num;
+	enc->framerateDen = voi->fps_den;
+
+	enc->descriptor = typeData.descriptor;
+
+#define PROP_GET(x, y, z) (z)obs_data_get_ ## x(settings, y)
+	enc->profile       = PROP_GET(int,  MFP_PROFILE,      H264Profile);
+	enc->rateControl   = PROP_GET(int,  MFP_RATE_CONTROL, H264RateControl);
+	enc->keyint        = PROP_GET(int,  MFP_KEY_INT,      uint32_t);
+	enc->bitrate       = PROP_GET(int,  MFP_BITRATE,      uint32_t);
+	enc->useBufferSize = PROP_GET(bool, MFP_USE_BUF_SIZE, bool);
+	enc->bufferSize    = PROP_GET(int,  MFP_BUF_SIZE,     uint32_t);
+	enc->useMaxBitrate = PROP_GET(bool, MFP_USE_MAX_BITRATE, uint32_t);
+	enc->maxBitrate    = PROP_GET(int,  MFP_MAX_BITRATE,  uint32_t);
+	enc->minQp         = PROP_GET(int,  MFP_MIN_QP,       uint16_t);
+	enc->maxQp         = PROP_GET(int,  MFP_MAX_QP,       uint16_t);
+	enc->qp.defaultQp  = PROP_GET(int,  MFP_QP_I,         uint16_t);
+	enc->qp.i          = PROP_GET(int,  MFP_QP_I,         uint16_t);
+	enc->qp.p          = PROP_GET(int,  MFP_QP_P,         uint16_t);
+	enc->qp.b          = PROP_GET(int,  MFP_QP_B,         uint16_t);
+	enc->lowLatency    = PROP_GET(bool, MFP_USE_LOWLAT,   bool);
+	enc->bFrames       = PROP_GET(int,  MFP_B_FRAMES,     uint32_t);
+	enc->advanced      = PROP_GET(bool, MFP_USE_ADVANCED, bool);
+#undef PROP_GET
+}
+
+#undef MFTEXT
+#undef MFP
+
+static bool ApplyCBR(MFH264_Encoder *enc)
+{
+	enc->h264Encoder->SetBitrate(enc->bitrate);
+
+	if (enc->useMaxBitrate)
+		enc->h264Encoder->SetMaxBitrate(enc->maxBitrate);
+	else
+		enc->h264Encoder->SetMaxBitrate(enc->bitrate);
+
+	if (enc->useBufferSize)
+		enc->h264Encoder->SetBufferSize(enc->bufferSize);
+
+	return true;
+}
+
+static bool ApplyCVBR(MFH264_Encoder *enc)
+{
+	enc->h264Encoder->SetBitrate(enc->bitrate);
+
+	if (enc->advanced && enc->useMaxBitrate)
+		enc->h264Encoder->SetMaxBitrate(enc->maxBitrate);
+	else
+		enc->h264Encoder->SetMaxBitrate(enc->bitrate);
+
+	if (enc->useBufferSize)
+		enc->h264Encoder->SetBufferSize(enc->bufferSize);
+
+	return true;
+}
+
+static bool ApplyVBR(MFH264_Encoder *enc)
+{
+	enc->h264Encoder->SetBitrate(enc->bitrate);
+
+	if (enc->useBufferSize)
+		enc->h264Encoder->SetBufferSize(enc->bufferSize);
+
+	return true;
+}
+
+static bool ApplyCQP(MFH264_Encoder *enc)
+{
+	enc->h264Encoder->SetQP(enc->qp);
+
+	return true;
+}
+
+static void *MFH264_Create(obs_data_t *settings, obs_encoder_t *encoder)
+{
+	ProfileScope("MFH264_Create");
+
+	std::unique_ptr<MFH264_Encoder> enc(new MFH264_Encoder());
+	enc->encoder = encoder;
+
+	UpdateParams(enc.get(), settings);
+
+	ProfileScope(enc->descriptor->Name());
+
+	enc->h264Encoder.reset(new H264Encoder(encoder,
+			enc->descriptor,
+			enc->width,
+			enc->height,
+			enc->framerateNum,
+			enc->framerateDen,
+			enc->profile,
+			enc->bitrate));
+
+	auto applySettings = [&]() {
+		enc.get()->h264Encoder->SetRateControl(enc->rateControl);
+		enc.get()->h264Encoder->SetKeyframeInterval(enc->keyint);
+
+		enc.get()->h264Encoder->SetEntropyEncoding(
+				H264EntropyEncodingCABAC);
+
+		if (enc->advanced) {
+			enc.get()->h264Encoder->SetLowLatency(enc->lowLatency);
+			enc.get()->h264Encoder->SetBFrameCount(enc->bFrames);
+
+			enc.get()->h264Encoder->SetMinQP(enc->minQp);
+			enc.get()->h264Encoder->SetMaxQP(enc->maxQp);
+		}
+
+		if (enc->rateControl == H264RateControlVBR &&
+		    enc->advanced &&
+		    enc->useMaxBitrate)
+			enc->rateControl = H264RateControlConstrainedVBR;
+
+
+		switch (enc->rateControl) {
+		case H264RateControlCBR:
+			return ApplyCBR(enc.get());
+		case H264RateControlConstrainedVBR:
+			return ApplyCVBR(enc.get());
+		case H264RateControlVBR:
+			return ApplyVBR(enc.get());
+		case H264RateControlCQP:
+			return ApplyCQP(enc.get());
+		default: return false;
+		}
+	};
+
+	if (!enc->h264Encoder->Initialize(applySettings))
+		return nullptr;
+
+	return enc.release();
+}
+
+static void MFH264_Destroy(void *data)
+{
+	MFH264_Encoder *enc = static_cast<MFH264_Encoder *>(data);
+	delete enc;
+}
+
+static bool MFH264_Encode(void *data, struct encoder_frame *frame,
+		struct encoder_packet *packet, bool *received_packet)
+{
+	MFH264_Encoder *enc = static_cast<MFH264_Encoder *>(data);
+	Status status;
+
+	if (!enc->profiler_encode)
+		 enc->profiler_encode = profile_store_name(
+				obs_get_profiler_name_store(),
+				"MFH264_Encode(%s)", enc->descriptor->Name());
+
+	ProfileScope(enc->profiler_encode);
+
+	*received_packet = false;
+
+	if (!enc->h264Encoder->ProcessInput(frame->data, frame->linesize,
+			frame->pts, &status))
+		return false;
+
+	UINT8 *outputData;
+	UINT32 outputDataLength;
+	UINT64 outputPts;
+	UINT64 outputDts;
+	bool keyframe;
+
+	if (!enc->h264Encoder->ProcessOutput(&outputData, &outputDataLength,
+			&outputPts, &outputDts, &keyframe, &status))
+		return false;
+
+	// Needs more input, not a failure case
+	if (status == NEED_MORE_INPUT)
+		return true;
+
+	packet->type = OBS_ENCODER_VIDEO;
+	packet->pts = outputPts;
+	packet->dts = outputPts;
+	packet->data = outputData;
+	packet->size = outputDataLength;
+	packet->keyframe = keyframe;
+
+	*received_packet = true;
+	return true;
+}
+
+static bool MFH264_GetExtraData(void *data, uint8_t **extra_data, size_t *size)
+{
+	MFH264_Encoder *enc = static_cast<MFH264_Encoder *>(data);
+
+	uint8_t *extraData;
+	UINT32 extraDataLength;
+
+	if (!enc->h264Encoder->ExtraData(&extraData, &extraDataLength))
+		return false;
+
+	*extra_data = extraData;
+	*size = extraDataLength;
+
+	return true;
+}
+
+static bool MFH264_GetSEIData(void *data, uint8_t **sei_data, size_t *size)
+{
+	UNUSED_PARAMETER(data);
+	UNUSED_PARAMETER(sei_data);
+	UNUSED_PARAMETER(size);
+
+	return false;
+}
+
+static void MFH264_GetVideoInfo(void *, struct video_scale_info *info)
+{
+	info->format = VIDEO_FORMAT_NV12;
+}
+
+static bool MFH264_Update(void *data, obs_data_t *settings)
+{
+	MFH264_Encoder *enc = static_cast<MFH264_Encoder *>(data);
+
+	UpdateParams(enc, settings);
+
+	enc->h264Encoder->SetBitrate(enc->bitrate);
+	enc->h264Encoder->SetQP(enc->qp);
+
+	return true;
+}
+
+void RegisterMFH264Encoders()
+{
+	obs_encoder_info info = { 0 };
+	info.type = OBS_ENCODER_VIDEO;
+	info.get_name = MFH264_GetName;
+	info.create = MFH264_Create;
+	info.destroy = MFH264_Destroy;
+	info.encode = MFH264_Encode;
+	info.update = MFH264_Update;
+	info.get_properties = MFH264_GetProperties;
+	info.get_defaults = MFH264_GetDefaults;
+	info.get_extra_data = MFH264_GetExtraData;
+	info.get_sei_data = MFH264_GetSEIData;
+	info.get_video_info = MFH264_GetVideoInfo;
+	info.codec = "h264";
+
+	auto encoders = EncoderDescriptor::Enumerate();
+	for (auto e : encoders) {
+		/* ignore the software encoder due to the fact that we already
+		 * have an objectively superior software encoder available */
+		if (e->Type() == EncoderType::H264_SOFTWARE)
+			continue;
+
+		info.id = e->Id();
+		info.type_data = new TypeData(e);
+		info.free_type_data = [] (void *type_data) {
+			delete reinterpret_cast<TypeData*>(type_data);
+		};
+		obs_register_encoder(&info);
+	}
+}

+ 0 - 12
plugins/win-mf/mf-plugin.c

@@ -1,12 +0,0 @@
-#include <obs-module.h>
-
-extern void RegisterMFAACEncoder();
-
-bool obs_module_load(void)
-{
-	RegisterMFAACEncoder();
-	return true;
-}
-
-OBS_DECLARE_MODULE()
-OBS_MODULE_USE_DEFAULT_LOCALE("win-mf", "en-US")

+ 28 - 0
plugins/win-mf/mf-plugin.cpp

@@ -0,0 +1,28 @@
+#include <obs-module.h>
+#include <util/profiler.h>
+
+#include "mf-common.hpp"
+
+extern "C" extern void RegisterMFAACEncoder();
+extern void RegisterMFH264Encoders();
+
+
+extern "C" bool obs_module_load(void)
+{
+	CoInitializeEx(0, COINIT_MULTITHREADED);
+	MFStartup(MF_VERSION, MFSTARTUP_FULL);
+
+	RegisterMFAACEncoder();
+	RegisterMFH264Encoders();
+
+	return true;
+}
+
+extern "C" void obs_module_unload(void)
+{
+	MFShutdown();
+	CoUninitialize();
+}
+
+OBS_DECLARE_MODULE()
+OBS_MODULE_USE_DEFAULT_LOCALE("win-mf", "en-US")