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.
master
jp9000 2015-09-15 11:45:37 -07:00
parent 6285a47726
commit afa2985f64
11 changed files with 2209 additions and 15 deletions

View File

@ -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

View File

@ -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)"

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
};
};

View File

@ -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;
}

View File

@ -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
plugins/win-mf/mf-h264.cpp Normal file
View File

@ -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);
}
}

View File

@ -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")

View File

@ -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")