Files
UnrealEngineUWP/Engine/Source/Runtime/GameplayMediaEncoder/Private/Microsoft/Windows/NvVideoEncoder.cpp
Ryan Vance 35eb0041ab Merging //UE4/Dev-Main to Dev-VR (//UE4/Dev-VR)
#rb integration

[CL 5387703 by Ryan Vance in Dev-VR branch]
2019-03-13 15:19:08 -04:00

742 lines
26 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "NvVideoEncoder.h"
#if PLATFORM_WINDOWS
#include "CoreMinimal.h"
#include "Misc/CoreDelegates.h"
#include "ScreenRendering.h"
#include "RendererInterface.h"
#include "PipelineStateCache.h"
#include "CommonRenderResources.h"
#include "Modules/ModuleManager.h"
#include "SceneUtils.h"
#include "EncoderDevice.h"
#include "VideoRecordingSystem.h"
DEFINE_LOG_CATEGORY(NvVideoEncoder);
const uint32 BitstreamSize = 1024 * 1024 * 2;
#define CHECK_NV_RES(NvCall)\
{\
NVENCSTATUS Res = NvCall;\
if (Res != NV_ENC_SUCCESS)\
{\
UE_LOG(NvVideoEncoder, Error, TEXT("`" #NvCall "` failed with error code: %d"), Res);\
/*check(false);*/\
return false;\
}\
}
CSV_DECLARE_CATEGORY_EXTERN(GameplayMediaEncoder);
DECLARE_CYCLE_STAT(TEXT("NvEnc_WaitForEncodeEvent"), STAT_NvEnc_WaitForEncodeEvent, STATGROUP_VideoRecordingSystem);
DECLARE_FLOAT_COUNTER_STAT(TEXT("NvEnc_CaptureToEncodeStart"), STAT_NvEnc_CaptureToEncodeStart, STATGROUP_VideoRecordingSystem);
DECLARE_FLOAT_COUNTER_STAT(TEXT("NvEnc_EncodeTime"), STAT_NvEnc_EncodeTime, STATGROUP_VideoRecordingSystem);
DECLARE_FLOAT_COUNTER_STAT(TEXT("NvEnc_EncodeToWriterTime"), STAT_NvEnc_EncodeToWriterTime, STATGROUP_VideoRecordingSystem);
#define CLOSE_EVENT_HANDLE(EventHandle) CloseHandle(EventHandle);
GAMEPLAYMEDIAENCODER_START
FNvVideoEncoder::FNvVideoEncoder(const FOutputSampleCallback& OutputCallback, TSharedPtr<FEncoderDevice> InEncoderDevice)
: FBaseVideoEncoder(OutputCallback)
, EncoderDevice(InEncoderDevice)
{
}
FNvVideoEncoder::~FNvVideoEncoder()
{
bExitEncoderThread = true;
if (EncoderThread.IsValid())
{
// Trigger an event to ensure we can get out of the encoder thread.
SetEvent(EncodeQueue.EncodeEvent);
// Exit encoder runnable thread before shutting down NvEnc interface
EncoderThread->Join();
}
ReleaseResources();
if (EncoderInterface)
{
NVENCSTATUS Result = NvEncodeAPI->nvEncDestroyEncoder(EncoderInterface);
if (Result != NV_ENC_SUCCESS)
{
UE_LOG(NvVideoEncoder, Error, TEXT("Failed to destroy NvEnc interface"));
}
EncoderInterface = nullptr;
}
if (DllHandle)
{
FPlatformProcess::FreeDllHandle(DllHandle);
}
}
bool FNvVideoEncoder::Initialize(const FVideoEncoderConfig& InConfig)
{
if (bInitialized)
{
UE_LOG(NvVideoEncoder, Error, TEXT("Encoder already initialized. Re-initialization is not implemented."));
return false;
}
// Fails to register a DX11 resource in nvEncRegisterResource, need to use CUDA on Win7
// Error: `NvEncodeAPI->nvEncRegisterResource(EncoderInterface, &RegisterResource)` failed with error code: 22
if (!FWindowsPlatformMisc::VerifyWindowsVersion(6, 2) /*Is Win8 or higher?*/)
{
UE_LOG(NvVideoEncoder, Error, TEXT("NvEncoder for Windows 7 is not implemented"));
return false;
}
if (!FBaseVideoEncoder::Initialize(InConfig))
{
return false;
}
DllHandle = FPlatformProcess::GetDllHandle(TEXT("nvEncodeAPI64.dll"));
if (DllHandle == nullptr)
{
UE_LOG(NvVideoEncoder, Error, TEXT("Failed to load NvEncode dll"));
return false;
}
// create the encoder instance
{
// define a function pointer for creating an instance of nvEncodeAPI
typedef NVENCSTATUS(NVENCAPI *NVENCAPIPROC)(NV_ENCODE_API_FUNCTION_LIST*);
NVENCAPIPROC NvEncodeAPICreateInstanceFunc;
# pragma warning(push)
# pragma warning(disable: 4191) // https://stackoverflow.com/a/4215425/453271
NvEncodeAPICreateInstanceFunc = (NVENCAPIPROC)FPlatformProcess::GetDllExport((HMODULE)DllHandle, TEXT("NvEncodeAPICreateInstance"));
# pragma warning(pop)
if (NvEncodeAPICreateInstanceFunc == nullptr)
{
UE_LOG(NvVideoEncoder, Error, TEXT("NvEncodeAPICreateInstance failed"));
return false;
}
NvEncodeAPI.Reset(new NV_ENCODE_API_FUNCTION_LIST);
FMemory::Memzero(NvEncodeAPI.Get(), sizeof(NV_ENCODE_API_FUNCTION_LIST));
NvEncodeAPI->version = NV_ENCODE_API_FUNCTION_LIST_VER;
CHECK_NV_RES(NvEncodeAPICreateInstanceFunc(NvEncodeAPI.Get()));
}
// Open an encoding session
{
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS OpenEncodeSessionExParams;
FMemory::Memzero(OpenEncodeSessionExParams);
OpenEncodeSessionExParams.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
OpenEncodeSessionExParams.device = EncoderDevice->Device;
OpenEncodeSessionExParams.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX; // Currently only DX11 is supported
OpenEncodeSessionExParams.apiVersion = NVENCAPI_VERSION;
CHECK_NV_RES(NvEncodeAPI->nvEncOpenEncodeSessionEx(&OpenEncodeSessionExParams, &EncoderInterface));
}
FMemory::Memzero(NvEncInitializeParams);
// Set initialization parameters
{
NvEncInitializeParams.version = NV_ENC_INITIALIZE_PARAMS_VER;
NvEncInitializeParams.encodeWidth = Config.Width;
NvEncInitializeParams.encodeHeight = Config.Height;
NvEncInitializeParams.darWidth = Config.Width;
NvEncInitializeParams.darHeight = Config.Height;
NvEncInitializeParams.encodeGUID = NV_ENC_CODEC_H264_GUID;
NvEncInitializeParams.presetGUID = NV_ENC_PRESET_HQ_GUID;
NvEncInitializeParams.frameRateNum = Config.Framerate;
NvEncInitializeParams.frameRateDen = 1;
NvEncInitializeParams.enablePTD = 1;
NvEncInitializeParams.reportSliceOffsets = 0;
NvEncInitializeParams.enableSubFrameWrite = 0;
NvEncInitializeParams.encodeConfig = &NvEncConfig;
NvEncInitializeParams.maxEncodeWidth = Config.Width;
NvEncInitializeParams.maxEncodeHeight = Config.Height;
}
// Get preset config and tweak it accordingly
{
NV_ENC_PRESET_CONFIG PresetConfig = {};
PresetConfig.version = NV_ENC_PRESET_CONFIG_VER;
PresetConfig.presetCfg.version = NV_ENC_CONFIG_VER;
CHECK_NV_RES(NvEncodeAPI->nvEncGetEncodePresetConfig(EncoderInterface, NvEncInitializeParams.encodeGUID, NvEncInitializeParams.presetGUID, &PresetConfig));
FMemory::Memcpy(&NvEncConfig, &PresetConfig.presetCfg, sizeof(NV_ENC_CONFIG));
//NvEncConfig.profileGUID = NV_ENC_H264_PROFILE_BASELINE_GUID;
NvEncConfig.profileGUID = NV_ENC_H264_PROFILE_MAIN_GUID;
NvEncConfig.gopLength = Config.Framerate; // once a sec
NvEncConfig.encodeCodecConfig.h264Config.idrPeriod = Config.Framerate;
//NvEncConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
//NvEncConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_2_PASS_VBR;
NvEncConfig.rcParams.averageBitRate = Config.Bitrate;
// configure "entire frame as a single slice"
NvEncConfig.encodeCodecConfig.h264Config.sliceMode = 3;
NvEncConfig.encodeCodecConfig.h264Config.sliceModeData = 1;
// repeat SPS/PPS with each key-frame for simplicity of saving recording ring-buffer to .mp4
// (video stream in .mp4 must start with SPS/PPS)
NvEncConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;
// high level is chosen because we aim at high bitrate
NvEncConfig.encodeCodecConfig.h264Config.level = NV_ENC_LEVEL_H264_51;
}
// Get encoder capability
{
NV_ENC_CAPS_PARAM CapsParam;
FMemory::Memzero(CapsParam);
CapsParam.version = NV_ENC_CAPS_PARAM_VER;
CapsParam.capsToQuery = NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT;
int32 AsyncMode = 0;
CHECK_NV_RES(NvEncodeAPI->nvEncGetEncodeCaps(EncoderInterface, NvEncInitializeParams.encodeGUID, &CapsParam, &AsyncMode));
if (AsyncMode == 0)
{
UE_LOG(NvVideoEncoder, Error, TEXT("NvEnc doesn't support async mode"));
return false;
}
NvEncInitializeParams.enableEncodeAsync = true;
}
CHECK_NV_RES(NvEncodeAPI->nvEncInitializeEncoder(EncoderInterface, &NvEncInitializeParams));
if (!InitializeResources())
{
return false;
}
EncoderThread.Reset(new FThread(TEXT("NvVideoEncoder"), [this]() { EncoderCheckLoop(); }));
bInitialized = true;
return true;
}
bool FNvVideoEncoder::Start()
{
return true;
}
void FNvVideoEncoder::Stop()
{
}
bool FNvVideoEncoder::SetBitrate(uint32 Bitrate)
{
// update config and `OutputType`
if (!FBaseVideoEncoder::SetBitrate(Bitrate))
{
return false;
}
NvEncInitializeParams.encodeConfig->rcParams.averageBitRate = Bitrate;
return Reconfigure();
}
bool FNvVideoEncoder::SetFramerate(uint32 Framerate)
{
// update config and `OutputType`
if (!FBaseVideoEncoder::SetFramerate(Framerate))
{
return false;
}
NvEncInitializeParams.frameRateNum = Framerate;
return Reconfigure();
}
bool FNvVideoEncoder::Reconfigure()
{
// reconfigure NvEnc
NV_ENC_RECONFIGURE_PARAMS NvEncReconfigureParams = {};
NvEncReconfigureParams.version = NV_ENC_RECONFIGURE_PARAMS_VER;
FMemory::Memcpy(&NvEncReconfigureParams.reInitEncodeParams, &NvEncInitializeParams, sizeof(NvEncInitializeParams));
CHECK_NV_RES(NvEncodeAPI->nvEncReconfigureEncoder(EncoderInterface, &NvEncReconfigureParams));
return true;
}
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_Process"), STAT_FNvVideoEncoder_Process, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_Process_CheckEncoded"), STAT_FNvVideoEncoder_Process_CheckEncoded, STATGROUP_VideoRecordingSystem);
bool FNvVideoEncoder::Process(const FTexture2DRHIRef& Texture, FTimespan Timestamp, FTimespan Duration)
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_Process);
check(IsInRenderingThread());
return ProcessInput(Texture, Timestamp, Duration);
}
bool FNvVideoEncoder::ProcessInput(const FTexture2DRHIRef& Texture, FTimespan Timestamp, FTimespan Duration)
{
UE_LOG(NvVideoEncoder, Verbose, TEXT("Video input #%u: time %.3f, duration %.3f"), (uint32)InputCount, Timestamp.GetTotalSeconds(), Duration.GetTotalSeconds());
uint32 BufferIndexToWrite = InputCount % NumBufferedFrames;
FFrame& Frame = BufferedFrames[BufferIndexToWrite];
// If we don't have any free buffers, then we skip this rendered frame
if (Frame.bEncoding)
{
return false;
}
Frame.bEncoding = true;
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, CopyBackBuffer);
CopyBackBuffer(Texture, Frame);
Frame.FrameIdx = InputCount;
Frame.TimeStamp = Timestamp;
Frame.Duration = Duration;
Frame.CaptureTimeStamp = FTimespan::FromSeconds(FPlatformTime::Seconds());
FFrame* FramePtr = &Frame;
ExecuteRHICommand([this, FramePtr]() {
EncodeQueue.Push(FramePtr);
});
InputCount++;
return true;
}
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_CopyBackBuffer"), STAT_FNvVideoEncoder_CopyBackBuffer, STATGROUP_VideoRecordingSystem);
void FNvVideoEncoder::CopyBackBuffer(const FTexture2DRHIRef& SrcBackBuffer, const FFrame& DstFrame)
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_CopyBackBuffer);
IRendererModule* RendererModule = &FModuleManager::GetModuleChecked<IRendererModule>("Renderer");
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
SCOPED_DRAW_EVENTF(RHICmdList, NvVideoEncoder_CopyBackBuffer, TEXT("NvVideoEncoder_CopyBackBuffer %u"), static_cast<uint32>(DstFrame.FrameIdx));
if (SrcBackBuffer->GetFormat() == DstFrame.ResolvedBackBuffer->GetFormat() &&
SrcBackBuffer->GetSizeXY() == DstFrame.ResolvedBackBuffer->GetSizeXY())
{
RHICmdList.CopyToResolveTarget(SrcBackBuffer, DstFrame.ResolvedBackBuffer, FResolveParams());
}
else // Texture format mismatch, use a shader to do the copy.
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SetRenderTarget(RHICmdList, DstFrame.ResolvedBackBuffer, FTextureRHIRef());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
RHICmdList.SetViewport(0, 0, 0.0f, DstFrame.ResolvedBackBuffer->GetSizeX(), DstFrame.ResolvedBackBuffer->GetSizeY(), 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
TShaderMap<FGlobalShaderType>* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
TShaderMapRef<FScreenPS> PixelShader(ShaderMap);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
if (DstFrame.ResolvedBackBuffer->GetSizeX() != SrcBackBuffer->GetSizeX() || DstFrame.ResolvedBackBuffer->GetSizeY() != SrcBackBuffer->GetSizeY())
PixelShader->SetParameters(RHICmdList, TStaticSamplerState<SF_Bilinear>::GetRHI(), SrcBackBuffer);
else
PixelShader->SetParameters(RHICmdList, TStaticSamplerState<SF_Point>::GetRHI(), SrcBackBuffer);
RendererModule->DrawRectangle(
RHICmdList,
0, 0, // Dest X, Y
DstFrame.ResolvedBackBuffer->GetSizeX(), // Dest Width
DstFrame.ResolvedBackBuffer->GetSizeY(), // Dest Height
0, 0, // Source U, V
1, 1, // Source USize, VSize
DstFrame.ResolvedBackBuffer->GetSizeXY(), // Target buffer size
FIntPoint(1, 1), // Source texture size
*VertexShader,
EDRF_Default);
}
}
bool FNvVideoEncoder::InitFrameInputBuffer(FFrame& Frame)
{
// Create resolved back buffer texture
{
// Make sure format used here is compatible with NV_ENC_BUFFER_FORMAT specified later in NV_ENC_REGISTER_RESOURCE bufferFormat
FRHIResourceCreateInfo CreateInfo;
// TexCreate_Shared textures are forced to be EPixelFormat::PF_B8G8R8A8.
Frame.ResolvedBackBuffer = RHICreateTexture2D(Config.Width, Config.Height, EPixelFormat::PF_B8G8R8A8, 1, 1, TexCreate_RenderTargetable | TexCreate_Shared, CreateInfo);
}
// Share this texture with the encoder device.
{
ID3D11Texture2D* ResolvedBackBuffer = (ID3D11Texture2D*)Frame.ResolvedBackBuffer->GetTexture2D()->GetNativeResource();
IDXGIResource* DXGIResource = NULL;
CHECK_HR(ResolvedBackBuffer->QueryInterface(__uuidof(IDXGIResource), (LPVOID*)&DXGIResource));
HANDLE SharedHandle;
CHECK_HR(DXGIResource->GetSharedHandle(&SharedHandle));
CHECK_HR(DXGIResource->Release());
CHECK_HR(EncoderDevice->Device->OpenSharedResource(SharedHandle, __uuidof(ID3D11Texture2D), (LPVOID*)&Frame.SharedBackBuffer));
}
FMemory::Memzero(Frame.InputFrame);
// Register input back buffer
{
NV_ENC_REGISTER_RESOURCE RegisterResource;
FMemory::Memzero(RegisterResource);
RegisterResource.version = NV_ENC_REGISTER_RESOURCE_VER;
RegisterResource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
RegisterResource.resourceToRegister = (void*)Frame.SharedBackBuffer;
RegisterResource.width = Config.Width;
RegisterResource.height = Config.Height;
// TexCreate_Shared textures are forced to be EPixelFormat::PF_B8G8R8A8.
RegisterResource.bufferFormat = NV_ENC_BUFFER_FORMAT_ABGR; // Make sure ResolvedBackBuffer is created with a compatible format
CHECK_NV_RES(NvEncodeAPI->nvEncRegisterResource(EncoderInterface, &RegisterResource));
Frame.InputFrame.RegisteredResource = RegisterResource.registeredResource;
Frame.InputFrame.BufferFormat = RegisterResource.bufferFormat;
}
// Map input buffer resource
{
NV_ENC_MAP_INPUT_RESOURCE MapInputResource;
FMemory::Memzero(MapInputResource);
MapInputResource.version = NV_ENC_MAP_INPUT_RESOURCE_VER;
MapInputResource.registeredResource = Frame.InputFrame.RegisteredResource;
CHECK_NV_RES(NvEncodeAPI->nvEncMapInputResource(EncoderInterface, &MapInputResource));
Frame.InputFrame.MappedResource = MapInputResource.mappedResource;
}
return true;
}
bool FNvVideoEncoder::InitializeResources()
{
for (uint32 i = 0; i < NumBufferedFrames; ++i)
{
FFrame& Frame = BufferedFrames[i];
if (!InitFrameInputBuffer(Frame))
{
return false;
}
FMemory::Memzero(Frame.OutputFrame);
// Create output bitstream buffer
{
NV_ENC_CREATE_BITSTREAM_BUFFER CreateBitstreamBuffer;
FMemory::Memzero(CreateBitstreamBuffer);
CreateBitstreamBuffer.version = NV_ENC_CREATE_BITSTREAM_BUFFER_VER;
CreateBitstreamBuffer.size = BitstreamSize;
CreateBitstreamBuffer.memoryHeap = NV_ENC_MEMORY_HEAP_SYSMEM_CACHED;
CHECK_NV_RES(NvEncodeAPI->nvEncCreateBitstreamBuffer(EncoderInterface, &CreateBitstreamBuffer));
Frame.OutputFrame.BitstreamBuffer = CreateBitstreamBuffer.bitstreamBuffer;
}
RegisterAsyncEvent(&Frame.OutputFrame.EventHandle);
}
return true;
}
bool FNvVideoEncoder::ReleaseFrameInputBuffer(FNvVideoEncoder::FFrame& Frame)
{
if (Frame.InputFrame.MappedResource)
{
CHECK_NV_RES(NvEncodeAPI->nvEncUnmapInputResource(EncoderInterface, Frame.InputFrame.MappedResource));
Frame.InputFrame.MappedResource = nullptr;
}
if (Frame.InputFrame.RegisteredResource)
{
CHECK_NV_RES(NvEncodeAPI->nvEncUnregisterResource(EncoderInterface, Frame.InputFrame.RegisteredResource));
Frame.InputFrame.RegisteredResource = nullptr;
}
Frame.ResolvedBackBuffer.SafeRelease();
if (Frame.SharedBackBuffer)
{
Frame.SharedBackBuffer->Release();
}
return true;
}
bool FNvVideoEncoder::RegisterAsyncEvent(void** OutEvent)
{
NV_ENC_EVENT_PARAMS EventParams;
FMemory::Memzero(EventParams);
EventParams.version = NV_ENC_EVENT_PARAMS_VER;
#if defined PLATFORM_WINDOWS
EventParams.completionEvent = CreateEvent(nullptr, true, false, nullptr);
#endif
CHECK_NV_RES(NvEncodeAPI->nvEncRegisterAsyncEvent(EncoderInterface, &EventParams));
*OutEvent = EventParams.completionEvent;
return true;
}
bool FNvVideoEncoder::UnregisterAsyncEvent(void* Event)
{
if (Event)
{
NV_ENC_EVENT_PARAMS EventParams;
FMemory::Memzero(EventParams);
EventParams.version = NV_ENC_EVENT_PARAMS_VER;
EventParams.completionEvent = Event;
CHECK_NV_RES(NvEncodeAPI->nvEncUnregisterAsyncEvent(EncoderInterface, &EventParams));
}
return true;
}
bool FNvVideoEncoder::ReleaseResources()
{
for (uint32 i = 0; i < NumBufferedFrames; ++i)
{
FFrame& Frame = BufferedFrames[i];
if (!ReleaseFrameInputBuffer(Frame))
{
return false;
}
if (Frame.OutputFrame.BitstreamBuffer)
{
CHECK_NV_RES(NvEncodeAPI->nvEncDestroyBitstreamBuffer(EncoderInterface, Frame.OutputFrame.BitstreamBuffer));
Frame.OutputFrame.BitstreamBuffer = nullptr;
}
if (Frame.OutputFrame.EventHandle)
{
UnregisterAsyncEvent(Frame.OutputFrame.EventHandle);
CLOSE_EVENT_HANDLE(Frame.OutputFrame.EventHandle);
Frame.OutputFrame.EventHandle = nullptr;
}
}
return true;
}
void FNvVideoEncoder::EncoderCheckLoop()
{
// This thread will both encode frames and will also wait for the next frame
// to finish encoding.
while (true)
{
// Wait for either the command to encode frames or the information
// that the next frame has finished encoding.
// The signalling events are a pair of handles for windows events so we
// can wait for one or the other.
enum EncodeEvents
{
START_ENCODING_EVENT = 0,
FINISHED_ENCODING_EVENT = 1,
NUM_ENCODING_EVENTS = 2
};
HANDLE Handles[NUM_ENCODING_EVENTS];
Handles[START_ENCODING_EVENT] = EncodeQueue.EncodeEvent;
FFrame& Frame = BufferedFrames[OutputCount % NumBufferedFrames];
Handles[FINISHED_ENCODING_EVENT] = Frame.OutputFrame.EventHandle;
DWORD Result = WaitForMultipleObjects(NUM_ENCODING_EVENTS, Handles, false, INFINITE);
if (!bExitEncoderThread)
{
if (Result == WAIT_OBJECT_0 + START_ENCODING_EVENT)
{
// Get the list of all frames we want to encode.
FFrame* Frames[NumBufferedFrames];
int NumFrames;
EncodeQueue.PopAll(Frames, NumFrames);
for (int Idx = 0; Idx < NumFrames; Idx++)
{
SubmitFrameToEncoder(*Frames[Idx]);
}
}
else if (Result == WAIT_OBJECT_0 + FINISHED_ENCODING_EVENT)
{
// A frame has finished encoding so we can now handle the
// encoded data.
Frame.EncodeEndTimeStamp = FTimespan::FromSeconds(FPlatformTime::Seconds());
ResetEvent(Frame.OutputFrame.EventHandle);
HandleEncodedFrame(Frame);
++OutputCount;
}
}
else
{
break;
}
}
}
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_TransferRenderTargetToHWEncoder"), STAT_FNvVideoEncoder_TransferRenderTargetToHWEncoder, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_TransferRenderTargetToHWEncoder_nvEncEncodePicture"), STAT_FNvVideoEncoder_TransferRenderTargetToHWEncoder_nvEncEncodePicture, STATGROUP_VideoRecordingSystem);
bool FNvVideoEncoder::SubmitFrameToEncoder(FFrame& Frame)
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_TransferRenderTargetToHWEncoder);
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, TransferRenderTargetToHwEncoder);
NV_ENC_PIC_PARAMS PicParams;
FMemory::Memzero(PicParams);
PicParams.version = NV_ENC_PIC_PARAMS_VER;
PicParams.inputBuffer = Frame.InputFrame.MappedResource;
PicParams.bufferFmt = Frame.InputFrame.BufferFormat;
PicParams.inputWidth = Config.Width;
PicParams.inputHeight = Config.Height;
PicParams.outputBitstream = Frame.OutputFrame.BitstreamBuffer;
PicParams.completionEvent = Frame.OutputFrame.EventHandle;
PicParams.inputTimeStamp = Frame.FrameIdx;
PicParams.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
Frame.EncodeStartTimeStamp = FTimespan::FromSeconds(FPlatformTime::Seconds());
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_TransferRenderTargetToHWEncoder_nvEncEncodePicture);
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, TransferRenderTargetToHWEncoder_nvEncEncodePicture);
FRHICommandList& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
SCOPED_DRAW_EVENTF(RHICmdList, NvVideoEncoder_TransferRenderTargetToHWEncoder, TEXT("NvVideoEncoder_TransferRenderTargetToHWEncoder %u"), static_cast<uint32>(Frame.FrameIdx));
CHECK_NV_RES(NvEncodeAPI->nvEncEncodePicture(EncoderInterface, &PicParams));
}
return true;
}
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_ProcessEncodedFrame"), STAT_FNvVideoEncoder_ProcessEncodedFrame, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_ProcessEncodedFrame_Lock"), STAT_FNvVideoEncoder_ProcessEncodedFrame_Lock, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_ProcessEncodedFrame_Copy"), STAT_FNvVideoEncoder_ProcessEncodedFrame_Copy, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_ProcessEncodedFrame_Unlock"), STAT_FNvVideoEncoder_ProcessEncodedFrame_Unlock, STATGROUP_VideoRecordingSystem);
DECLARE_CYCLE_STAT(TEXT("FNvVideoEncoder_ProcessEncodedFrame_Callback"), STAT_FNvVideoEncoder_ProcessEncodedFrame_Callback, STATGROUP_VideoRecordingSystem);
bool FNvVideoEncoder::HandleEncodedFrame(FNvVideoEncoder::FFrame& Frame)
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_ProcessEncodedFrame);
// If the expected frame hasn't been doing encoding, then nothing to do
checkf(Frame.bEncoding, TEXT("This should not happen"));
if (!Frame.bEncoding)
{
return false;
}
FTimespan Now = FTimespan::FromSeconds(FPlatformTime::Seconds());
double CaptureToEncodeStartTime = (Frame.EncodeStartTimeStamp - Frame.CaptureTimeStamp).GetTotalMilliseconds();
double EncodeTime = (Frame.EncodeEndTimeStamp - Frame.EncodeStartTimeStamp).GetTotalMilliseconds();
double EncodeToWriterTime = (Now - Frame.EncodeEndTimeStamp).GetTotalMilliseconds();
SET_FLOAT_STAT(STAT_NvEnc_CaptureToEncodeStart, CaptureToEncodeStartTime);
SET_FLOAT_STAT(STAT_NvEnc_EncodeTime, EncodeTime);
SET_FLOAT_STAT(STAT_NvEnc_EncodeToWriterTime, EncodeToWriterTime);
// log encoding latency for every 1000th frame
if (Frame.FrameIdx % 1000 == 0)
{
UE_LOG(NvVideoEncoder, Verbose, TEXT("#%d %.2f %.2f %.2f"),
Frame.FrameIdx,
CaptureToEncodeStartTime,
EncodeTime,
EncodeToWriterTime);
}
bool bIdrFrame = false;
// Retrieve encoded frame from output buffer
{
NV_ENC_LOCK_BITSTREAM LockBitstream = {};
LockBitstream.version = NV_ENC_LOCK_BITSTREAM_VER;
LockBitstream.outputBitstream = Frame.OutputFrame.BitstreamBuffer;
LockBitstream.doNotWait = true;
FRHICommandList& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_ProcessEncodedFrame_Lock);
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, ProcessEncodedFrame_Lock);
SCOPED_DRAW_EVENTF(RHICmdList, NvVideoEncoder_ProcessEncodedFrame_Lock, TEXT("NvVideoEncoder_ProcessEncodedFrame_Lock %u"), static_cast<uint32>(Frame.FrameIdx));
CHECK_NV_RES(NvEncodeAPI->nvEncLockBitstream(EncoderInterface, &LockBitstream));
}
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_ProcessEncodedFrame_Copy);
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, ProcessEncodedFrame_Copy);
Frame.EncodedFrame.SetNum(LockBitstream.bitstreamSizeInBytes);
FMemory::Memcpy(Frame.EncodedFrame.GetData(), LockBitstream.bitstreamBufferPtr, LockBitstream.bitstreamSizeInBytes);
}
bIdrFrame = LockBitstream.pictureType == NV_ENC_PIC_TYPE_IDR;
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_ProcessEncodedFrame_Unlock);
CSV_SCOPED_TIMING_STAT(GameplayMediaEncoder, ProcessEncodedFrame_Unlock);
SCOPED_DRAW_EVENTF(RHICmdList, NvVideoEncoder_ProcessEncodedFrame_Unlock, TEXT("NvVideoEncoder_ProcessEncodedFrame_Unlock %u"), static_cast<uint32>(Frame.FrameIdx));
CHECK_NV_RES(NvEncodeAPI->nvEncUnlockBitstream(EncoderInterface, Frame.OutputFrame.BitstreamBuffer));
}
}
{
SCOPE_CYCLE_COUNTER(STAT_FNvVideoEncoder_ProcessEncodedFrame_Callback);
FGameplayMediaEncoderSample OutputSample{ EMediaType::Video };
if (!OutputSample.CreateSample())
{
return false;
}
int32 BufferSize = Frame.EncodedFrame.Num();
uint32 Alignment = 0;
TRefCountPtr<IMFMediaBuffer> WmfBuffer;
CHECK_HR(MFCreateAlignedMemoryBuffer(BufferSize, Alignment, WmfBuffer.GetInitReference()));
CHECK_HR(OutputSample.GetSample()->SetUINT32(MFSampleExtension_CleanPoint, bIdrFrame ? 1 : 0));
// Copy data to the WmfBuffer
uint8* Dst = nullptr;
CHECK_HR(WmfBuffer->Lock(&Dst, nullptr, nullptr));
FMemory::Memcpy(Dst, Frame.EncodedFrame.GetData(), Frame.EncodedFrame.Num());
CHECK_HR(WmfBuffer->Unlock());
CHECK_HR(WmfBuffer->SetCurrentLength(Frame.EncodedFrame.Num()));
CHECK_HR(OutputSample.GetSample()->AddBuffer(WmfBuffer));
OutputSample.SetTime(Frame.TimeStamp);
OutputSample.SetDuration(Frame.Duration);
UE_LOG(NvVideoEncoder, Verbose, TEXT("encoded frame #%d: time %.3f, duration %.3f, size %d%s"), Frame.FrameIdx, OutputSample.GetTime().GetTotalSeconds(), OutputSample.GetDuration().GetTotalSeconds(), BufferSize, OutputSample.IsVideoKeyFrame() ? TEXT(", key frame") : TEXT(""));
if (!OutputCallback(OutputSample))
{
return false;
}
}
Frame.bEncoding = false;
return true;
}
#undef CHECK_NV_RES
GAMEPLAYMEDIAENCODER_END
#endif // PLATFORM_WINDOWS