0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 13:08:50 +02:00
obs-studio/plugins/win-mf/mf-h264-encoder.cpp
jp9000 88996aef73 win-mf: Fix more issues on encoder shutdown
The previous commit (672378d20) was supposed to fix issues with the
encoder releasing while data was still being processed, but did not
account for when the encoder has never started up.  That was my fault.

Furthermore, the way in which it was waiting to drain events was
incorrect.  The encoder may still be active even though there aren't any
events queued.  The proper way to wait for an async encoder to finish up
is to process output samples until it requests more input samples.
2015-09-17 13:42:43 -07:00

771 lines
16 KiB
C++

#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 hr;
if (!descriptor->Async() || !eventGenerator || !pendingRequests)
return;
// Make sure all events have finished before releasing, and drain
// all output requests until it makes an input request.
// If you do not do this, you risk it releasing while there's still
// encoder activity, which can cause a crash with certain interfaces.
while (inputRequests == 0) {
hr = ProcessOutput();
if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
MF_LOG_COM(LOG_ERROR, "H264Encoder::~H264Encoder: "
"ProcessOutput()", hr);
break;
}
if (inputRequests == 0)
Sleep(1);
}
}
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));
pendingRequests++;
*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;
pendingRequests--;
return true;
}