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
parent
6285a47726
commit
afa2985f64
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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(¤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<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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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")
|
|
@ -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")
|
Loading…
Reference in New Issue