Files
UnrealEngineUWP/Engine/Source/Runtime/AVEncoder/Private/Microsoft/Windows/AmfVideoEncoder.cpp
Marc Audy 8f73cd7fa9 Merge UE5/Release-Engine-Staging @ 15630841 to UE5/Main
This represents UE4/Main @ 15601601

[CL 15631170 by Marc Audy in ue5-main branch]
2021-03-05 19:27:14 -04:00

879 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AmfVideoEncoder.h"
#include "Microsoft/AVEncoderMicrosoftCommon.h"
#include "CommonRenderResources.h"
#if AVENCODER_SUPPORTED_MICROSOFT_PLATFORM
THIRD_PARTY_INCLUDES_START
#include "ThirdParty/AmdAmf/core/Result.h"
#include "ThirdParty/AmdAmf/core/Factory.h"
#include "ThirdParty/AmdAmf/components/VideoEncoderVCE.h"
#include "ThirdParty/AmdAmf/core/Compute.h"
#include "ThirdParty/AmdAmf/core/Plane.h"
THIRD_PARTY_INCLUDES_END
DECLARE_STATS_GROUP(TEXT("AmfVideoEncoder"), STATGROUP_AmfVideoEncoder, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("AmfEncoder->QueryOutput"), STAT_Amf_QueryOutput, STATGROUP_AmfVideoEncoder);
DECLARE_CYCLE_STAT(TEXT("StreamEncodedFrame"), STAT_Amf_StreamEncodedFrame, STATGROUP_AmfVideoEncoder);
DECLARE_CYCLE_STAT(TEXT("OnEncodedVideoFrameCallback"), STAT_Amf_OnEncodedVideoFrameCallback, STATGROUP_AmfVideoEncoder);
DECLARE_CYCLE_STAT(TEXT("SubmitFrameToEncoder"), STAT_Amf_SubmitFrameToEncoder, STATGROUP_AmfVideoEncoder);
DECLARE_CYCLE_STAT(TEXT("AmfEncoder->SubmitInput"), STAT_Amf_SubmitInput, STATGROUP_AmfVideoEncoder);
// NOTE: This only exists in a more recent version of the AMF SDK. Adding it here so I don't need to update the SDK yet.
#ifdef AMF_VIDEO_ENCODER_LOWLATENCY_MODE
// If you get this error, it means the AMF SDK was updated and already contains the property, so you can remove this check and the property that was added explicitly.
#error "AMF_VIDEO_ENCODER_LOWLATENCY_MODE already exists. Please remove duplicate"
#endif
#define AMF_VIDEO_ENCODER_LOWLATENCY_MODE L"LowLatencyInternal" // bool; default = false, enables low latency mode and POC mode 2 in the encoder
#define CHECK_AMF_RET(AMF_call)\
{\
AMF_RESULT Res = AMF_call;\
if (!(Res== AMF_OK || Res==AMF_ALREADY_INITIALIZED))\
{\
UE_LOG(LogAVEncoder, Error, TEXT("`" #AMF_call "` failed with error code: %d"), Res);\
/*check(false);*/\
return false;\
}\
}
#define CHECK_AMF_NORET(AMF_call)\
{\
AMF_RESULT Res = AMF_call;\
if (Res != AMF_OK)\
{\
UE_LOG(LogAVEncoder, Error, TEXT("`" #AMF_call "` failed with error code: %d"), Res);\
}\
}
namespace AVEncoder
{
namespace {
// enumerates all available properties of AMFPropertyStorage interface and logs their name,
// current and default values and other info
inline bool LogAmfPropertyStorage(amf::AMFPropertyStorageEx* PropertyStorage)
{
SIZE_T NumProps = PropertyStorage->GetPropertiesInfoCount();
for (int i = 0; i != NumProps; ++i)
{
const amf::AMFPropertyInfo* Info;
CHECK_AMF_RET(PropertyStorage->GetPropertyInfo(i, &Info));
if (Info->accessType != amf::AMF_PROPERTY_ACCESS_PRIVATE)
{
amf::AMFVariant Value;
CHECK_AMF_RET(PropertyStorage->GetProperty(Info->name, &Value));
FString EnumDesc;
if (Info->pEnumDescription)
{
int j = 0;
for (; /*j != Value.ToInt32()*/; ++j)
{
if (Info->pEnumDescription[j].value == Value.ToInt32())
{
break;
}
}
EnumDesc = TEXT(" ") + FString(Info->pEnumDescription[j].name);
}
UE_LOG(LogAVEncoder, Log, TEXT("Prop %s (%s): value: %s%s, default value: %s (%s - %s), access: %d"),
Info->name,
Info->desc,
Value.ToWString().c_str(),
*EnumDesc,
amf::AMFVariant{ Info->defaultValue }.ToWString().c_str(),
amf::AMFVariant{ Info->minValue }.ToWString().c_str(),
amf::AMFVariant{ Info->maxValue }.ToWString().c_str(),
static_cast<int>(Info->accessType));
}
else
{
UE_LOG(LogAVEncoder, VeryVerbose, TEXT("Prop: %s (%s) - PRIVATE"), Info->name, Info->desc);
}
}
return true;
}
const TCHAR* ToString(AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_ENUM PicType)
{
switch (PicType)
{
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_IDR:
return TEXT("AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_IDR");
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_I:
return TEXT("AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_I");
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_P:
return TEXT("AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_P");
case AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_B:
return TEXT("AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_B");
default:
checkNoEntry();
return TEXT("Unknown");
}
}
AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_ENUM ToAmfRcMode(FH264Settings::ERateControlMode RcMode)
{
switch(RcMode)
{
case FH264Settings::ERateControlMode::ConstQP:
return AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP;
case FH264Settings::ERateControlMode::VBR:
return AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR;
case FH264Settings::ERateControlMode::CBR:
return AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR;
default:
UE_LOG(LogAVEncoder, Error, TEXT("Invalid rate control mode (%d) for nvenc"), (int)RcMode);
return AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR;
}
};
} // anonymous namespace
class FAmfVideoEncoder : public FVideoEncoder
{
public:
FAmfVideoEncoder();
~FAmfVideoEncoder();
private:
//
// FVideoEncoder interface
//
const TCHAR* GetName() const override;
const TCHAR* GetType() const override;
bool Initialize(const FVideoEncoderConfig& InConfig) override;
void Shutdown() override;
bool CopyTexture(FTexture2DRHIRef Texture, FTimespan CaptureTs, FTimespan Duration, FBufferId& OutBufferId, FIntPoint Resolution = {0, 0}) override;
void Drop(FBufferId BufferId) override;
void Encode(FBufferId BufferId, bool bForceKeyFrame, uint32 Bitrate = 0, TUniquePtr<AVEncoder::FEncoderVideoFrameCookie> Cookie = nullptr) override;
FVideoEncoderConfig GetConfig() const override;
bool SetBitrate(uint32 Bitrate) override;
bool SetFramerate(uint32 Framerate) override;
bool SetParameter(const FString& Parameter, const FString& Value) override;
struct FInputFrame
{
FInputFrame() {}
UE_NONCOPYABLE(FInputFrame);
FTexture2DRHIRef Texture;
FTimespan CaptureTs;
FTimespan Duration;
bool bForceKeyFrame = false;
};
struct FOutputFrame
{
FOutputFrame() {}
UE_NONCOPYABLE(FOutputFrame);
amf::AMFDataPtr EncodedData;
TUniquePtr<FEncoderVideoFrameCookie> Cookie;
};
enum class EFrameState
{
Free,
Capturing,
Captured,
EncoderFailed,
Encoding
};
struct FFrame
{
const FBufferId Id = 0;
TAtomic<EFrameState> State = { EFrameState::Free };
FInputFrame InputFrame;
FOutputFrame OutputFrame;
uint64 FrameIdx = 0;
// Some timestamps to track how long a frame spends in each step
FTimespan CopyBufferStartTs;
FTimespan CopyBufferFinishTs;
FTimespan EncodingStartTs;
FTimespan EncodingFinishTs;
};
bool bInitialized = false;
amf_handle DllHandle = nullptr;
amf::AMFFactory* AmfFactory = nullptr;
amf::AMFContextPtr AmfContext;
amf::AMFComponentPtr AmfEncoder;
FVideoEncoderConfig Config;
FH264Settings ConfigH264;
uint32 CapturedFrameCount = 0; // of captured, not encoded frames
static const uint32 NumBufferedFrames = 3;
FFrame BufferedFrames[NumBufferedFrames];
TQueue<FFrame*> EncodingQueue;
// Used to make sure we don't have a race condition trying to access a deleted "this" captured
// in the render command lambda sent to the render thread from EncoderCheckLoop
static FThreadSafeCounter ImplCounter;
FRHICOMMAND_MACRO(FRHISubmitFrameToEncoder)
{
FAmfVideoEncoder* Encoder;
FFrame* Frame;
FRHISubmitFrameToEncoder(FAmfVideoEncoder* InEncoder, FFrame* InFrame)
: Encoder(InEncoder), Frame(InFrame)
{
}
void Execute(FRHICommandListBase& CmdList)
{
Encoder->SubmitFrameToEncoder(*Frame);
}
};
void ResetFrameInputBuffer(FFrame& Frame, FIntPoint Resolution);
bool ProcessOutput();
void UpdateRes(FFrame& Frame, FIntPoint Resolution);
void EncodeFrameInRenderingThread(FFrame& Frame, uint32 Bitrate);
bool SubmitFrameToEncoder(FFrame& Frame);
void UpdateEncoderConfig(FIntPoint Resolution, uint32 Bitrate);
void HandleDroppedFrame(FFrame& Frame);
void HandleEncodedFrame(FFrame& Frame);
};
//////////////////////////////////////////////////////////////////////////
// FAmfVideoEncoder
//////////////////////////////////////////////////////////////////////////
FThreadSafeCounter FAmfVideoEncoder::ImplCounter(0);
FAmfVideoEncoder::FAmfVideoEncoder()
{
}
FAmfVideoEncoder::~FAmfVideoEncoder()
{
if (DllHandle)
{
UE_LOG(LogAVEncoder, Fatal, TEXT("FAmfVideoEncoder Shutdown not called before destruction."));
}
}
const TCHAR* FAmfVideoEncoder::GetName() const
{
return TEXT("h264.amf");
}
const TCHAR* FAmfVideoEncoder::GetType() const
{
return TEXT("h264");
}
bool FAmfVideoEncoder::Initialize(const FVideoEncoderConfig& InConfig)
{
check(!bInitialized);
Config = InConfig;
ConfigH264 = {};
ReadH264Settings(Config.Options, ConfigH264);
UE_LOG(LogAVEncoder, Log, TEXT("FPixelStreamingAmfVideoEncoder initialization with %dx%d, %d FPS")
, Config.Width, Config.Height, Config.Framerate);
DllHandle = FPlatformProcess::GetDllHandle(AMF_DLL_NAME);
if (DllHandle == nullptr)
{
return false;
}
AMFInit_Fn AmfInitFn = (AMFInit_Fn)FPlatformProcess::GetDllExport((HMODULE)DllHandle, TEXT(AMF_INIT_FUNCTION_NAME));
if (AmfInitFn == nullptr)
{
return false;
}
CHECK_AMF_RET(AmfInitFn(AMF_FULL_VERSION, &AmfFactory));
AMFQueryVersion_Fn AmfVersionFun = (AMFQueryVersion_Fn)FPlatformProcess::GetDllExport((HMODULE)DllHandle, TEXT(AMF_QUERY_VERSION_FUNCTION_NAME));
if (AmfVersionFun == nullptr)
{
return false;
}
uint64 AmfVersion = 0;
AmfVersionFun(&AmfVersion);
FString RHIName = GDynamicRHI->GetName();
if (RHIName != TEXT("D3D11"))
{
UE_LOG(LogAVEncoder, Fatal, TEXT("AMF not supported with a %s renderer"), *RHIName);
}
TRefCountPtr<ID3D11Device> DxDevice(static_cast<ID3D11Device*>(GDynamicRHI->RHIGetNativeDevice()));
// Work-around for the EOS overlay to get an unwrapped, raw ID3D11Device
{
TRefCountPtr<ID3D11DeviceContext> DeviceContext;
DxDevice->GetImmediateContext(DeviceContext.GetInitReference());
TRefCountPtr<ID3D11Device> UnwrappedDevice;
DeviceContext->GetDevice(UnwrappedDevice.GetInitReference());
DxDevice = MoveTemp(UnwrappedDevice);
}
CHECK_AMF_RET(AmfFactory->CreateContext(&AmfContext));
checkf(DxDevice != nullptr, TEXT("Cannot initialize NvEnc with invalid device"));
CHECK_AMF_RET(AmfContext->InitDX11(DxDevice.GetReference()));
CHECK_AMF_RET(AmfFactory->CreateComponent(AmfContext, AMFVideoEncoderVCE_AVC, &AmfEncoder));
if (Config.Preset == FVideoEncoderConfig::EPreset::LowLatency)
{
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_USAGE, AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_LOWLATENCY_MODE, true));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_PROFILE, AMF_VIDEO_ENCODER_PROFILE_BASELINE));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QUALITY_PRESET, AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED));
}
else
{
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_USAGE, AMF_VIDEO_ENCODER_USAGE_TRANSCONDING));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_PROFILE, AMF_VIDEO_ENCODER_PROFILE_MAIN));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QUALITY_PRESET, AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY));
}
//CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_B_PIC_PATTERN, 0));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, Config.Bitrate));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_FRAMESIZE, ::AMFConstructSize(Config.Width, Config.Height)));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_ASPECT_RATIO, ::AMFConstructRatio(Config.Width, Config.Height)));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_FRAMERATE, ::AMFConstructRate(Config.Framerate, 1)));
// generate key-frames every second, useful for seeking in resulting .mp4 and keeping recording ring buffer
// of second-precise duration
uint64 IdrPeriod = Config.Framerate;
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_IDR_PERIOD, IdrPeriod));
// insert SPS/PPS before every key-frame. .mp4 file video stream must start from SPS/PPS. Their size is
// negligible so having them before every key-frame is not an issue but they presence simplifies
// implementation significantly. Otherwise we would extract SPS/PPS from the first key-frame and store
// them manually at the beginning of resulting .mp4 file
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_HEADER_INSERTION_SPACING, IdrPeriod));
CHECK_AMF_RET(AmfEncoder->Init(amf::AMF_SURFACE_RGBA, Config.Width, Config.Height));
// #AVENCODER : This doesn't seem to be working. It fails with code 3 (AMF_ACCESS_DENIED) or gives a warning that has bee initialized already,
// depending if it's called before the Init (gives error 3), or after the Init (giveswarning).
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD, ToAmfRcMode(ConfigH264.RcMode)));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_MIN_QP, ConfigH264.QP));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_I, ConfigH264.QP));
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_P, ConfigH264.QP));
// #AVENCODER : This doesn't seem to be working. It fails with code 3 (AMF_ACCESS_DENIED) or gives a warning that has bee initialized already,
// depending if it's called before the Init (gives error 3), or after the Init (giveswarning).
CHECK_AMF_RET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_B, ConfigH264.QP));
//LogAmfPropertyStorage(AmfEncoder);
FBufferId Id = 0;
for (FFrame& Frame : BufferedFrames)
{
// We keep `Id` as const, because it not supposed to change at all once initialized
*(const_cast<FBufferId*>(&Frame.Id)) = Id++;
ResetFrameInputBuffer(Frame, { (int)Config.Width, (int)Config.Height });
}
UE_LOG(LogAVEncoder, Log, TEXT("AMF H.264 encoder initialised, v.0x%X"), AmfVersion);
bInitialized = true;
return true;
}
void FAmfVideoEncoder::Shutdown()
{
// BufferedFrames keep references to AMFData, we need to release them before destroying AMF
for (FFrame& Frame : BufferedFrames)
{
Frame.OutputFrame.EncodedData = nullptr;
}
// Cleanup in this order
if (AmfEncoder)
{
AmfEncoder->Terminate();
AmfEncoder = nullptr;
}
if (AmfContext)
{
AmfContext->Terminate();
AmfContext = nullptr;
}
AmfFactory = nullptr;
if (DllHandle)
{
FPlatformProcess::FreeDllHandle(DllHandle);
DllHandle = nullptr;
}
}
bool FAmfVideoEncoder::CopyTexture(FTexture2DRHIRef Texture, FTimespan CaptureTs, FTimespan Duration, FBufferId& OutBufferId, FIntPoint Resolution)
{
check(IsInRenderingThread());
// First process output, to free slots we can use for the new request
if (!ProcessOutput())
{
return false;
}
// Find a free slot we can use
FFrame* Frame = nullptr;
for (FFrame& Slot : BufferedFrames)
{
if (Slot.State.Load() == EFrameState::Free)
{
Frame = &Slot;
OutBufferId = Slot.Id;
break;
}
}
if (!Frame)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Frame dropped because Amf queue is full"));
return false;
}
Frame->FrameIdx = CapturedFrameCount++;
Frame->InputFrame.CaptureTs = CaptureTs;
Frame->InputFrame.Duration = Duration;
Frame->CopyBufferStartTs = FTimespan::FromSeconds(FPlatformTime::Seconds());
{
UpdateRes(*Frame, Resolution.Size() ? Resolution : Texture->GetSizeXY());
CopyTextureImpl(Texture, Frame->InputFrame.Texture, nullptr);
}
UE_LOG(LogAVEncoder, Verbose, TEXT("Buffer #%d (%d) captured"), Frame->FrameIdx, OutBufferId);
Frame->State = EFrameState::Capturing;
return true;
}
void FAmfVideoEncoder::Drop(FBufferId BufferId)
{
FFrame& Frame = BufferedFrames[BufferId];
{
EFrameState State = Frame.State.Load();
checkf(State == EFrameState::Capturing, TEXT("Buffer %d: Expected state %d, found %d")
, BufferId, (int)EFrameState::Capturing, (int)State);
}
Frame.State = EFrameState::Free;
UE_LOG(LogAVEncoder, Log, TEXT("Buffer #%d (%d) dropped"), BufferedFrames[BufferId].FrameIdx, BufferId);
}
void FAmfVideoEncoder::Encode(FBufferId BufferId, bool bForceKeyFrame, uint32 Bitrate, TUniquePtr<AVEncoder::FEncoderVideoFrameCookie> Cookie)
{
FFrame& Frame = BufferedFrames[BufferId];
{
EFrameState State = Frame.State.Load();
checkf(State == EFrameState::Capturing, TEXT("Buffer %d : Expected state %d, but found %d"), BufferId, (int)EFrameState::Captured, (int)State);
}
Frame.State = EFrameState::Captured;
Frame.CopyBufferFinishTs = FTimespan::FromSeconds(FPlatformTime::Seconds());
Frame.InputFrame.bForceKeyFrame = bForceKeyFrame;
Frame.OutputFrame.Cookie = MoveTemp(Cookie);
int32 CurrImplCounter = ImplCounter.GetValue();
ENQUEUE_RENDER_COMMAND(AmfEncEncodeFrame)(
[this, &Frame, BufferId, Bitrate, CurrImplCounter](FRHICommandListImmediate& RHICmdList)
{
if (CurrImplCounter != ImplCounter.GetValue()) // Check if the "this" we captured is still valid
return;
EncodeFrameInRenderingThread(Frame, Bitrate);
UE_LOG(LogAVEncoder, VeryVerbose, TEXT("Buffer #%d (%d), ts %lld started encoding"), Frame.FrameIdx, BufferId, Frame.InputFrame.CaptureTs.GetTicks());
});
}
FVideoEncoderConfig FAmfVideoEncoder::GetConfig() const
{
return Config;
}
bool FAmfVideoEncoder::SetBitrate(uint32 Bitrate)
{
Config.Bitrate = Bitrate;
return true;
}
bool FAmfVideoEncoder::SetFramerate(uint32 Framerate)
{
Config.Framerate = Framerate;
return true;
}
bool FAmfVideoEncoder::SetParameter(const FString& Parameter, const FString& Value)
{
return ReadH264Setting(Parameter, Value, ConfigH264);
}
void FAmfVideoEncoder::ResetFrameInputBuffer(FFrame& Frame, FIntPoint Resolution)
{
Frame.InputFrame.Texture.SafeRelease();
// Make sure format used here is compatible with AMF_SURFACE_FORMAT specified in encoder Init() function.
FRHIResourceCreateInfo CreateInfo(TEXT("FrameInputBuffer"));
Frame.InputFrame.Texture = RHICreateTexture2D(Resolution.X, Resolution.Y, EPixelFormat::PF_R8G8B8A8, 1, 1, TexCreate_RenderTargetable, CreateInfo);
}
bool FAmfVideoEncoder::ProcessOutput()
{
check(IsInRenderingThread());
while(true)
{
// Drop any failed submits
// If for some reason this frame failed to be submitted to AMF (e.g: call to AmfEncoder->SubmitInput failed due to AMF_INPUT_FULL), then we need to tell webrtc to drop it
while(!EncodingQueue.IsEmpty() && (*EncodingQueue.Peek())->State == EFrameState::EncoderFailed)
{
FFrame* Frame = nullptr;
EncodingQueue.Dequeue(Frame);
HandleDroppedFrame(*Frame);
}
amf::AMFDataPtr EncodedData;
AMF_RESULT Ret;
{
SCOPE_CYCLE_COUNTER(STAT_Amf_QueryOutput);
Ret = AmfEncoder->QueryOutput(&EncodedData);
}
// If AMF says a frame finished encoding, then we must have one in EncodingQueue by design
if (Ret == AMF_OK && EncodedData != nullptr)
{
FFrame* Frame;
verify(EncodingQueue.Dequeue(Frame));
Frame->OutputFrame.EncodedData = EncodedData;
HandleEncodedFrame(*Frame);
}
else if (Ret == AMF_REPEAT)
{
break; // not ready yet
}
else
{
UE_LOG(LogAVEncoder, Error, TEXT("Failed to query AMF H.264 Encoder output: %d, %p"), Ret, EncodedData.GetPtr());
return false;
}
}
return true;
}
void FAmfVideoEncoder::HandleDroppedFrame(FFrame& Frame)
{
check(Frame.State==EFrameState::EncoderFailed);
FOutputFrame& OutputFrame = Frame.OutputFrame;
FAVPacket Packet(EPacketType::Video);
Frame.EncodingFinishTs = FTimespan::FromSeconds(FPlatformTime::Seconds());
Packet.Timestamp = Frame.InputFrame.CaptureTs;
Packet.Duration = Frame.InputFrame.Duration;
Packet.Timings.EncodeStartTs = Frame.EncodingStartTs;
Packet.Timings.EncodeFinishTs = Frame.EncodingFinishTs;
UE_LOG(LogAVEncoder, VeryVerbose, TEXT("dropping frame with ts %lld due to encoder failure") , Packet.Timestamp.GetTicks());
{
SCOPE_CYCLE_COUNTER(STAT_Amf_OnEncodedVideoFrameCallback);
OnEncodedVideoFrame(Packet, MoveTemp(Frame.OutputFrame.Cookie));
}
Frame.State = EFrameState::Free;
}
void FAmfVideoEncoder::HandleEncodedFrame(FFrame& Frame)
{
check(Frame.State==EFrameState::Encoding);
FOutputFrame& OutputFrame = Frame.OutputFrame;
FAVPacket Packet(EPacketType::Video);
//
// Query for buffer interface
//
amf::AMFBufferPtr EncodedBuffer(Frame.OutputFrame.EncodedData);
void* EncodedBufferPtr = EncodedBuffer->GetNative();
size_t EncodedBufferSize = EncodedBuffer->GetSize();
//
// Check if it's a keyframe
//
amf_int64 PicType;
CHECK_AMF_NORET(EncodedBuffer->GetProperty(AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE, &PicType));
bool bKeyFrame = PicType == AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_IDR ? true : false;
checkf(bKeyFrame == true || Frame.InputFrame.bForceKeyFrame == false, TEXT("key frame requested by webrtc but not provided by Amf"));
Frame.EncodingFinishTs = FTimespan::FromSeconds(FPlatformTime::Seconds());
Packet.Timestamp = Frame.InputFrame.CaptureTs;
Packet.Duration = Frame.InputFrame.Duration;
Packet.Video.bKeyFrame = bKeyFrame;
Packet.Video.Width = Frame.InputFrame.Texture->GetSizeX();
Packet.Video.Height = Frame.InputFrame.Texture->GetSizeY();
Packet.Video.Framerate = Config.Framerate;
// #AVENCODER : How to get frame average QP from Amf ?
Packet.Video.FrameAvgQP = 20;
Packet.Data = TArray<uint8>(reinterpret_cast<const uint8*>(EncodedBufferPtr), EncodedBufferSize);
Packet.Timings.EncodeStartTs = Frame.EncodingStartTs;
Packet.Timings.EncodeFinishTs = Frame.EncodingFinishTs;
// Release AMF's buffer
Frame.OutputFrame.EncodedData = nullptr;
UE_LOG(LogAVEncoder, VeryVerbose, TEXT("encoded %s ts %lld, %d bytes")
, ToString((AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE_ENUM)PicType)
, Packet.Timestamp.GetTicks()
, (int)Packet.Data.Num());
{
SCOPE_CYCLE_COUNTER(STAT_Amf_OnEncodedVideoFrameCallback);
OnEncodedVideoFrame(Packet, MoveTemp(Frame.OutputFrame.Cookie));
}
Frame.State = EFrameState::Free;
}
void FAmfVideoEncoder::UpdateRes(FFrame& Frame, FIntPoint Resolution)
{
check(IsInRenderingThread());
FInputFrame& InputFrame = Frame.InputFrame;
// check if target resolution matches our currently allocated `InputFrame.Texture` resolution
if (InputFrame.Texture->GetSizeX() == Resolution.X && InputFrame.Texture->GetSizeY() == Resolution.Y)
{
return;
}
ResetFrameInputBuffer(Frame, Resolution);
}
void FAmfVideoEncoder::EncodeFrameInRenderingThread(FFrame& Frame, uint32 Bitrate)
{
check(IsInRenderingThread());
check(Frame.State==EFrameState::Captured);
UpdateEncoderConfig(Frame.InputFrame.Texture->GetSizeXY(), Bitrate);
//
// Process the new input
{
FRHICommandList& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
if (RHICmdList.Bypass())
{
FRHISubmitFrameToEncoder Command(this, &Frame);
Command.Execute(RHICmdList);
}
else
{
ALLOC_COMMAND_CL(RHICmdList, FRHISubmitFrameToEncoder)(this, &Frame);
}
}
}
bool FAmfVideoEncoder::SubmitFrameToEncoder(FFrame& Frame)
{
SCOPE_CYCLE_COUNTER(STAT_Amf_SubmitFrameToEncoder);
check(Frame.State==EFrameState::Captured);
Frame.EncodingStartTs = FTimespan::FromSeconds(FPlatformTime::Seconds());
amf::AMFSurfacePtr AmfSurfaceIn;
ID3D11Texture2D* ResolvedBackBufferDX11 = static_cast<ID3D11Texture2D*>(GetD3D11TextureFromRHITexture(Frame.InputFrame.Texture)->GetResource());
CHECK_AMF_RET(AmfContext->CreateSurfaceFromDX11Native(ResolvedBackBufferDX11, &AmfSurfaceIn, nullptr));
if (Frame.InputFrame.bForceKeyFrame)
{
CHECK_AMF_RET(AmfSurfaceIn->SetProperty(AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_IDR));
}
{
// if `-d3ddebug` is enabled `SubmitInput` crashes with DX11 error, see output window
// we believe it's an internal AMF shader problem so we disable those errors explicitly, otherwise
// DX Debug Layer can't be used at all
FScopeDisabledDxDebugErrors Errors({
D3D11_MESSAGE_ID_DEVICE_UNORDEREDACCESSVIEW_RETURN_TYPE_MISMATCH,
D3D11_MESSAGE_ID_DEVICE_CSSETUNORDEREDACCESSVIEWS_TOOMANYVIEWS
});
{
SCOPE_CYCLE_COUNTER(STAT_Amf_SubmitInput);
//CHECK_AMF_RET(AmfEncoder->SubmitInput(AmfSurfaceIn));
AMF_RESULT Res = AmfEncoder->SubmitInput(AmfSurfaceIn);
if (Res == AMF_OK)
{
Frame.State = EFrameState::Encoding;
}
else
{
UE_LOG(LogAVEncoder, Error, TEXT("AmfEncoder->SubmitInput failed failed with error code: %d"), Res);
Frame.State = EFrameState::EncoderFailed;
}
EncodingQueue.Enqueue(&Frame);
}
}
return true;
}
void FAmfVideoEncoder::UpdateEncoderConfig(FIntPoint Resolution, uint32 Bitrate)
{
check(IsInRenderingThread());
//
// If an explicit Bitrate was specified, use that one, if not, use the one
// from the Config struct
//
uint32 AmfBitrate = 0;
CHECK_AMF_NORET(AmfEncoder->GetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, &AmfBitrate));
if (Bitrate)
{
if (AmfBitrate != Bitrate)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's bitrate to %u"), Bitrate);
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, Bitrate));
Config.Bitrate = Bitrate;
}
}
else if (AmfBitrate != Config.Bitrate)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's bitrate to %u"), Config.Bitrate);
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, Config.Bitrate));
}
//
// Update QP if required
//
uint32 AmfQP = 0;
CHECK_AMF_NORET(AmfEncoder->GetProperty(AMF_VIDEO_ENCODER_MIN_QP, &AmfQP));
if (AmfQP != ConfigH264.QP)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's MIN_QP/QP_I/QP_P/QP_B to %u"), ConfigH264.QP);
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_MIN_QP, ConfigH264.QP));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_I , ConfigH264.QP));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_P , ConfigH264.QP));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_QP_B , ConfigH264.QP));
}
//
// Update resolution if required
//
if (Resolution.Size())
{
AMFSize AmfResolution;
CHECK_AMF_NORET(AmfEncoder->GetProperty(AMF_VIDEO_ENCODER_FRAMESIZE, &AmfResolution));
if (Resolution != FIntPoint(AmfResolution.width, AmfResolution.height))
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's Resolution to %dx%d"), Resolution.X, Resolution.Y);
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_FRAMESIZE, ::AMFConstructSize(Resolution.X, Resolution.Y)));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_ASPECT_RATIO, ::AMFConstructRatio(Resolution.X, Resolution.Y)));
}
}
//
// Update framerate if necessary
//
AMFRate AmfFramerate;
CHECK_AMF_NORET(AmfEncoder->GetProperty(AMF_VIDEO_ENCODER_FRAMERATE, &AmfFramerate));
if (AmfFramerate.num != Config.Framerate)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's framerate to %d"), Config.Framerate);
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_FRAMERATE, ::AMFConstructRate(Config.Framerate, 1)));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_IDR_PERIOD, Config.Framerate));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_HEADER_INSERTION_SPACING, Config.Framerate));
}
//
// Implement Rate Control Mode
//
// #AVENCODER : This is not working. GetProperty AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD fails with error 3 (AMF_ACCESS_DENIED)
#if 0
amf_int64 AmfRcMode = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR;
amf_int64 CfgRcMode = ToAmfRcMode(ConfigH264.RcMode);
CHECK_AMF_NORET(AmfEncoder->GetProperty(AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD, &AmfRcMode));
if (AmfRcMode != CfgRcMode)
{
UE_LOG(LogAVEncoder, Verbose, TEXT("Setting AMF's control rate method to %d"), int(CfgRcMode));
CHECK_AMF_NORET(AmfEncoder->SetProperty(AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD, CfgRcMode));
}
#endif
}
//////////////////////////////////////////////////////////////////////////
// FAmfVideoEncoderFactory
//////////////////////////////////////////////////////////////////////////
FAmfVideoEncoderFactory::FAmfVideoEncoderFactory()
{
}
FAmfVideoEncoderFactory::~FAmfVideoEncoderFactory()
{
}
const TCHAR* FAmfVideoEncoderFactory::GetName() const
{
return TEXT("amf");
}
TArray<FString> FAmfVideoEncoderFactory::GetSupportedCodecs() const
{
TArray<FString> Codecs;
if (!IsRHIDeviceAMD())
{
UE_LOG(LogAVEncoder, Log, TEXT("No AMF available because no AMD card found"));
return Codecs;
}
void* Handle = FPlatformProcess::GetDllHandle(AMF_DLL_NAME);
if (Handle == nullptr)
{
UE_LOG(LogAVEncoder, Error, TEXT("AMD card found, but no AMF DLL installed."));
return Codecs;
}
else
{
FPlatformProcess::FreeDllHandle(Handle);
}
Codecs.Add(TEXT("h264"));
return Codecs;
}
TUniquePtr<FVideoEncoder> FAmfVideoEncoderFactory::CreateEncoder(const FString& Codec)
{
if (Codec=="h264")
{
return TUniquePtr<FVideoEncoder>(new FAmfVideoEncoder());
}
else
{
UE_LOG(LogAVEncoder, Error, TEXT("FAmfVideoEncoderFactory doesn't support the %s codec"), *Codec);
return nullptr;
}
}
} // namespace AVEncoder
#endif //AVENCODER_SUPPORTED_MICROSOFT_PLATFORM