diff --git a/plugins/win-mf/CMakeLists.txt b/plugins/win-mf/CMakeLists.txt index 41ae06826..6709215d3 100644 --- a/plugins/win-mf/CMakeLists.txt +++ b/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 diff --git a/plugins/win-mf/data/locale/en-US.ini b/plugins/win-mf/data/locale/en-US.ini index 380fad819..3b6ae9ef2 100644 --- a/plugins/win-mf/data/locale/en-US.ini +++ b/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)" diff --git a/plugins/win-mf/mf-common.cpp b/plugins/win-mf/mf-common.cpp new file mode 100644 index 000000000..b0698bb50 --- /dev/null +++ b/plugins/win-mf/mf-common.cpp @@ -0,0 +1,361 @@ +#include "mf-common.hpp" + +#include + +#include +#include +#include + +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(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"<>"); + 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; +} diff --git a/plugins/win-mf/mf-common.hpp b/plugins/win-mf/mf-common.hpp new file mode 100644 index 000000000..b753b934f --- /dev/null +++ b/plugins/win-mf/mf-common.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include +#include +#include + + +#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); + +} diff --git a/plugins/win-mf/mf-encoder-descriptor.cpp b/plugins/win-mf/mf-encoder-descriptor.cpp new file mode 100644 index 000000000..2381bb67a --- /dev/null +++ b/plugins/win-mf/mf-encoder-descriptor.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include + +#include "mf-encoder-descriptor.hpp" + +using namespace MF; + +template 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 &c) = delete; + inline ComHeapPtr(ComHeapPtr &&c) = delete; + inline ~ComHeapPtr() { Kill(); } + + inline void Clear() + { + if (ptr) { + Kill(); + ptr = nullptr; + } + } + + inline ComPtr &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 CreateDescriptor( + ComPtr 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 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 descriptor(new EncoderDescriptor( + activate, entry->name, entry->id, guid, guidString, + isAsync, isHardware, entry->type)); + + return descriptor; +} + +std::vector> EncoderDescriptor::Enumerate() +{ + HRESULT hr; + UINT32 count = 0; + std::vector> descriptors; + + ComHeapPtr 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; +} diff --git a/plugins/win-mf/mf-encoder-descriptor.hpp b/plugins/win-mf/mf-encoder-descriptor.hpp new file mode 100644 index 000000000..6768dd8ca --- /dev/null +++ b/plugins/win-mf/mf-encoder-descriptor.hpp @@ -0,0 +1,69 @@ +#pragma once + +#define WIN32_MEAN_AND_LEAN +#include +#undef WIN32_MEAN_AND_LEAN + +#include +#include + +#include +#include + +#include + +namespace MF { + +enum class EncoderType { + H264_SOFTWARE, + H264_QSV, + H264_NVENC, + H264_VCE, +}; + +class EncoderDescriptor { +public: + static std::vector> EncoderDescriptor::Enumerate(); + +public: + EncoderDescriptor(ComPtr 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 &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 activate; + const char *name; + const char *id; + GUID guid; + std::string guidString; + bool isAsync; + bool isHardware; + EncoderType type; +}; +}; diff --git a/plugins/win-mf/mf-h264-encoder.cpp b/plugins/win-mf/mf-h264-encoder.cpp new file mode 100644 index 000000000..3c929d852 --- /dev/null +++ b/plugins/win-mf/mf-h264-encoder.cpp @@ -0,0 +1,745 @@ +#include +#include + +#include "mf-common.hpp" +#include "mf-h264-encoder.hpp" + +#include +#include + +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 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 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 &i, + ComPtr &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 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 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 &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 &codecApi, GUID guid, + UINT32 value) +{ + VARIANT v; + v.vt = VT_UI4; + v.ulVal = value; + return codecApi->SetValue(&guid, &v); +} + +static HRESULT SetCodecProperty(ComPtr &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 func) +{ + ProfileScope("H264Encoder::Initialize"); + + HRESULT hr; + + ComPtr inputType, outputType; + ComPtr 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 &sample, + ComPtr &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 &sample, DWORD length) +{ + HRESULT hr; + ComPtr buffer; + DWORD currentLength; + + if (!sample) { + HRC(CreateEmptySample(sample, buffer, length)); + } + else { + HRC(sample->GetBufferByIndex(0, &buffer)); + } + + HRC(buffer->GetMaxLength(¤tLength)); + 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 &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 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 sample; + ComPtr 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 sample; + MFT_OUTPUT_STREAM_INFO outputInfo = { 0 }; + + DWORD outputStatus = 0; + MFT_OUTPUT_DATA_BUFFER output = { 0 }; + ComPtr buffer; + BYTE *bufferData; + DWORD bufferLength; + INT64 samplePts; + INT64 sampleDts; + INT64 sampleDur; + std::unique_ptr> data(new std::vector()); + ComPtr type; + std::unique_ptr 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 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; +} diff --git a/plugins/win-mf/mf-h264-encoder.hpp b/plugins/win-mf/mf-h264-encoder.hpp new file mode 100644 index 000000000..fcf8f4872 --- /dev/null +++ b/plugins/win-mf/mf-h264-encoder.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include + +#define WIN32_MEAN_AND_LEAN +#include +#undef WIN32_MEAN_AND_LEAN + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#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> 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> data; + }; + + class H264Encoder { + public: + H264Encoder(const obs_encoder_t *encoder, + std::shared_ptr descriptor, + UINT32 width, + UINT32 height, + UINT32 framerateNum, + UINT32 framerateDen, + H264Profile profile, + UINT32 bitrate); + + ~H264Encoder(); + + bool Initialize(std::function 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 &inputType, + ComPtr &outputType); + HRESULT EnsureCapacity(ComPtr &sample, DWORD length); + HRESULT CreateEmptySample(ComPtr &sample, + ComPtr &buffer, DWORD length); + + HRESULT ProcessInput(ComPtr &sample); + HRESULT ProcessOutput(); + + HRESULT DrainEvent(bool block); + HRESULT DrainEvents(); + private: + const obs_encoder_t *encoder; + std::shared_ptr descriptor; + const UINT32 width; + const UINT32 height; + const UINT32 framerateNum; + const UINT32 framerateDen; + const UINT32 initialBitrate; + const H264Profile profile; + + bool createOutputSample; + ComPtr transform; + ComPtr codecApi; + + std::vector extraData; + + // The frame returned by ProcessOutput + // Valid until the next call to ProcessOutput + std::unique_ptr activeFrame; + + // Queued input samples that the encoder was not ready + // to process + std::queue> inputSamples; + + // Queued output samples that have not been returned from + // ProcessOutput yet + std::queue> encodedFrames; + + ComPtr eventGenerator; + std::atomic inputRequests = 0; + std::atomic outputRequests = 0; + + }; +} diff --git a/plugins/win-mf/mf-h264.cpp b/plugins/win-mf/mf-h264.cpp new file mode 100644 index 000000000..e26f37c2f --- /dev/null +++ b/plugins/win-mf/mf-h264.cpp @@ -0,0 +1,537 @@ +#include +#include + +#include +#include + +#include "mf-h264-encoder.hpp" +#include "mf-encoder-descriptor.hpp" +#include + +using namespace MF; + +struct MFH264_Encoder { + obs_encoder_t *encoder; + std::shared_ptr descriptor; + std::unique_ptr 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 descriptor; + + inline TypeData(std::shared_ptr descriptor_) + : descriptor(descriptor_) + {} +}; + +static const char *MFH264_GetName(void *type_data) +{ + TypeData &typeData = *reinterpret_cast(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( + 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 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(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(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(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(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(type_data); + }; + obs_register_encoder(&info); + } +} diff --git a/plugins/win-mf/mf-plugin.c b/plugins/win-mf/mf-plugin.c deleted file mode 100644 index d55feda2f..000000000 --- a/plugins/win-mf/mf-plugin.c +++ /dev/null @@ -1,12 +0,0 @@ -#include - -extern void RegisterMFAACEncoder(); - -bool obs_module_load(void) -{ - RegisterMFAACEncoder(); - return true; -} - -OBS_DECLARE_MODULE() -OBS_MODULE_USE_DEFAULT_LOCALE("win-mf", "en-US") diff --git a/plugins/win-mf/mf-plugin.cpp b/plugins/win-mf/mf-plugin.cpp new file mode 100644 index 000000000..e36b25b2e --- /dev/null +++ b/plugins/win-mf/mf-plugin.cpp @@ -0,0 +1,28 @@ +#include +#include + +#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")