Use kcp as QoS module

This commit is contained in:
dijunkun
2023-08-30 17:44:22 +08:00
parent a4cd77dcb0
commit 3c1f7973d0
79 changed files with 14442 additions and 3150 deletions

View File

@@ -0,0 +1,909 @@
/*
* Copyright 2017-2020 NVIDIA Corporation. All rights reserved.
*
* Please refer to the NVIDIA end user license agreement (EULA) associated
* with this source code for terms and conditions that govern your use of
* this software. Any use, reproduction, disclosure, or distribution of
* this software and related documentation outside the terms of the EULA
* is strictly prohibited.
*
*/
#include "NvEncoder.h"
#ifndef _WIN32
#include <cstring>
static inline bool operator==(const GUID &guid1, const GUID &guid2) {
return !memcmp(&guid1, &guid2, sizeof(GUID));
}
static inline bool operator!=(const GUID &guid1, const GUID &guid2) {
return !(guid1 == guid2);
}
#endif
NvEncoder::NvEncoder(NV_ENC_DEVICE_TYPE eDeviceType, void *pDevice,
uint32_t nWidth, uint32_t nHeight,
NV_ENC_BUFFER_FORMAT eBufferFormat,
uint32_t nExtraOutputDelay, bool bMotionEstimationOnly,
bool bOutputInVideoMemory)
: m_pDevice(pDevice),
m_eDeviceType(eDeviceType),
m_nWidth(nWidth),
m_nHeight(nHeight),
m_nMaxEncodeWidth(nWidth),
m_nMaxEncodeHeight(nHeight),
m_eBufferFormat(eBufferFormat),
m_bMotionEstimationOnly(bMotionEstimationOnly),
m_bOutputInVideoMemory(bOutputInVideoMemory),
m_nExtraOutputDelay(nExtraOutputDelay),
m_hEncoder(nullptr) {
LoadNvEncApi();
if (!m_nvenc.nvEncOpenEncodeSession) {
m_nEncoderBuffer = 0;
NVENC_THROW_ERROR("EncodeAPI not found", NV_ENC_ERR_NO_ENCODE_DEVICE);
}
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS encodeSessionExParams = {
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
encodeSessionExParams.device = m_pDevice;
encodeSessionExParams.deviceType = m_eDeviceType;
encodeSessionExParams.apiVersion = NVENCAPI_VERSION;
void *hEncoder = NULL;
NVENC_API_CALL(
m_nvenc.nvEncOpenEncodeSessionEx(&encodeSessionExParams, &hEncoder));
m_hEncoder = hEncoder;
}
void NvEncoder::LoadNvEncApi() {
uint32_t version = 0;
uint32_t currentVersion =
(NVENCAPI_MAJOR_VERSION << 4) | NVENCAPI_MINOR_VERSION;
NVENC_API_CALL(NvEncodeAPIGetMaxSupportedVersion(&version));
if (currentVersion > version) {
NVENC_THROW_ERROR(
"Current Driver Version does not support this NvEncodeAPI version, "
"please upgrade driver",
NV_ENC_ERR_INVALID_VERSION);
}
m_nvenc = {NV_ENCODE_API_FUNCTION_LIST_VER};
NVENC_API_CALL(NvEncodeAPICreateInstance(&m_nvenc));
}
NvEncoder::~NvEncoder() { DestroyHWEncoder(); }
void NvEncoder::CreateDefaultEncoderParams(
NV_ENC_INITIALIZE_PARAMS *pIntializeParams, GUID codecGuid, GUID presetGuid,
NV_ENC_TUNING_INFO tuningInfo) {
if (!m_hEncoder) {
NVENC_THROW_ERROR("Encoder Initialization failed",
NV_ENC_ERR_NO_ENCODE_DEVICE);
return;
}
if (pIntializeParams == nullptr ||
pIntializeParams->encodeConfig == nullptr) {
NVENC_THROW_ERROR(
"pInitializeParams and pInitializeParams->encodeConfig can't be NULL",
NV_ENC_ERR_INVALID_PTR);
}
memset(pIntializeParams->encodeConfig, 0, sizeof(NV_ENC_CONFIG));
auto pEncodeConfig = pIntializeParams->encodeConfig;
memset(pIntializeParams, 0, sizeof(NV_ENC_INITIALIZE_PARAMS));
pIntializeParams->encodeConfig = pEncodeConfig;
pIntializeParams->encodeConfig->version = NV_ENC_CONFIG_VER;
pIntializeParams->version = NV_ENC_INITIALIZE_PARAMS_VER;
pIntializeParams->encodeGUID = codecGuid;
pIntializeParams->presetGUID = presetGuid;
pIntializeParams->encodeWidth = m_nWidth;
pIntializeParams->encodeHeight = m_nHeight;
pIntializeParams->darWidth = m_nWidth;
pIntializeParams->darHeight = m_nHeight;
pIntializeParams->frameRateNum = 30;
pIntializeParams->frameRateDen = 1;
pIntializeParams->enablePTD = 1;
pIntializeParams->reportSliceOffsets = 0;
pIntializeParams->enableSubFrameWrite = 0;
pIntializeParams->maxEncodeWidth = m_nWidth;
pIntializeParams->maxEncodeHeight = m_nHeight;
pIntializeParams->enableMEOnlyMode = m_bMotionEstimationOnly;
pIntializeParams->enableOutputInVidmem = m_bOutputInVideoMemory;
#if defined(_WIN32)
if (!m_bOutputInVideoMemory) {
pIntializeParams->enableEncodeAsync =
GetCapabilityValue(codecGuid, NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT);
}
#endif
NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER,
{NV_ENC_CONFIG_VER}};
m_nvenc.nvEncGetEncodePresetConfig(m_hEncoder, codecGuid, presetGuid,
&presetConfig);
memcpy(pIntializeParams->encodeConfig, &presetConfig.presetCfg,
sizeof(NV_ENC_CONFIG));
pIntializeParams->encodeConfig->frameIntervalP = 1;
pIntializeParams->encodeConfig->gopLength = NVENC_INFINITE_GOPLENGTH;
pIntializeParams->encodeConfig->rcParams.rateControlMode =
NV_ENC_PARAMS_RC_CONSTQP;
if (!m_bMotionEstimationOnly) {
pIntializeParams->tuningInfo = tuningInfo;
NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER,
{NV_ENC_CONFIG_VER}};
m_nvenc.nvEncGetEncodePresetConfigEx(m_hEncoder, codecGuid, presetGuid,
tuningInfo, &presetConfig);
memcpy(pIntializeParams->encodeConfig, &presetConfig.presetCfg,
sizeof(NV_ENC_CONFIG));
} else {
m_encodeConfig.version = NV_ENC_CONFIG_VER;
m_encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP;
m_encodeConfig.rcParams.constQP = {28, 31, 25};
}
if (pIntializeParams->encodeGUID == NV_ENC_CODEC_H264_GUID) {
if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
pIntializeParams->encodeConfig->encodeCodecConfig.h264Config
.chromaFormatIDC = 3;
}
pIntializeParams->encodeConfig->encodeCodecConfig.h264Config.idrPeriod =
pIntializeParams->encodeConfig->gopLength;
} else if (pIntializeParams->encodeGUID == NV_ENC_CODEC_HEVC_GUID) {
pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig
.pixelBitDepthMinus8 =
(m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT)
? 2
: 0;
if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig
.chromaFormatIDC = 3;
}
pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig.idrPeriod =
pIntializeParams->encodeConfig->gopLength;
}
return;
}
void NvEncoder::CreateEncoder(const NV_ENC_INITIALIZE_PARAMS *pEncoderParams) {
if (!m_hEncoder) {
NVENC_THROW_ERROR("Encoder Initialization failed",
NV_ENC_ERR_NO_ENCODE_DEVICE);
}
if (!pEncoderParams) {
NVENC_THROW_ERROR("Invalid NV_ENC_INITIALIZE_PARAMS ptr",
NV_ENC_ERR_INVALID_PTR);
}
if (pEncoderParams->encodeWidth == 0 || pEncoderParams->encodeHeight == 0) {
NVENC_THROW_ERROR("Invalid encoder width and height",
NV_ENC_ERR_INVALID_PARAM);
}
if (pEncoderParams->encodeGUID != NV_ENC_CODEC_H264_GUID &&
pEncoderParams->encodeGUID != NV_ENC_CODEC_HEVC_GUID) {
NVENC_THROW_ERROR("Invalid codec guid", NV_ENC_ERR_INVALID_PARAM);
}
if (pEncoderParams->encodeGUID == NV_ENC_CODEC_H264_GUID) {
if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
NVENC_THROW_ERROR("10-bit format isn't supported by H264 encoder",
NV_ENC_ERR_INVALID_PARAM);
}
}
// set other necessary params if not set yet
if (pEncoderParams->encodeGUID == NV_ENC_CODEC_H264_GUID) {
if ((m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444) &&
(pEncoderParams->encodeConfig->encodeCodecConfig.h264Config
.chromaFormatIDC != 3)) {
NVENC_THROW_ERROR("Invalid ChromaFormatIDC", NV_ENC_ERR_INVALID_PARAM);
}
}
if (pEncoderParams->encodeGUID == NV_ENC_CODEC_HEVC_GUID) {
bool yuv10BitFormat =
(m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT)
? true
: false;
if (yuv10BitFormat && pEncoderParams->encodeConfig->encodeCodecConfig
.hevcConfig.pixelBitDepthMinus8 != 2) {
NVENC_THROW_ERROR("Invalid PixelBitdepth", NV_ENC_ERR_INVALID_PARAM);
}
if ((m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 ||
m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) &&
(pEncoderParams->encodeConfig->encodeCodecConfig.hevcConfig
.chromaFormatIDC != 3)) {
NVENC_THROW_ERROR("Invalid ChromaFormatIDC", NV_ENC_ERR_INVALID_PARAM);
}
}
memcpy(&m_initializeParams, pEncoderParams, sizeof(m_initializeParams));
m_initializeParams.version = NV_ENC_INITIALIZE_PARAMS_VER;
if (pEncoderParams->encodeConfig) {
memcpy(&m_encodeConfig, pEncoderParams->encodeConfig,
sizeof(m_encodeConfig));
m_encodeConfig.version = NV_ENC_CONFIG_VER;
} else {
NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER,
{NV_ENC_CONFIG_VER}};
if (!m_bMotionEstimationOnly) {
m_nvenc.nvEncGetEncodePresetConfigEx(
m_hEncoder, pEncoderParams->encodeGUID, pEncoderParams->presetGUID,
pEncoderParams->tuningInfo, &presetConfig);
memcpy(&m_encodeConfig, &presetConfig.presetCfg, sizeof(NV_ENC_CONFIG));
} else {
m_encodeConfig.version = NV_ENC_CONFIG_VER;
m_encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP;
m_encodeConfig.rcParams.constQP = {28, 31, 25};
}
}
m_initializeParams.encodeConfig = &m_encodeConfig;
NVENC_API_CALL(
m_nvenc.nvEncInitializeEncoder(m_hEncoder, &m_initializeParams));
m_bEncoderInitialized = true;
m_nWidth = m_initializeParams.encodeWidth;
m_nHeight = m_initializeParams.encodeHeight;
m_nMaxEncodeWidth = m_initializeParams.maxEncodeWidth;
m_nMaxEncodeHeight = m_initializeParams.maxEncodeHeight;
m_nEncoderBuffer = m_encodeConfig.frameIntervalP +
m_encodeConfig.rcParams.lookaheadDepth +
m_nExtraOutputDelay;
m_nOutputDelay = m_nEncoderBuffer - 1;
m_vMappedInputBuffers.resize(m_nEncoderBuffer, nullptr);
if (!m_bOutputInVideoMemory) {
m_vpCompletionEvent.resize(m_nEncoderBuffer, nullptr);
}
#if defined(_WIN32)
for (uint32_t i = 0; i < m_vpCompletionEvent.size(); i++) {
m_vpCompletionEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
NV_ENC_EVENT_PARAMS eventParams = {NV_ENC_EVENT_PARAMS_VER};
eventParams.completionEvent = m_vpCompletionEvent[i];
m_nvenc.nvEncRegisterAsyncEvent(m_hEncoder, &eventParams);
}
#endif
if (m_bMotionEstimationOnly) {
m_vMappedRefBuffers.resize(m_nEncoderBuffer, nullptr);
if (!m_bOutputInVideoMemory) {
InitializeMVOutputBuffer();
}
} else {
if (!m_bOutputInVideoMemory) {
m_vBitstreamOutputBuffer.resize(m_nEncoderBuffer, nullptr);
InitializeBitstreamBuffer();
}
}
AllocateInputBuffers(m_nEncoderBuffer);
}
void NvEncoder::DestroyEncoder() {
if (!m_hEncoder) {
return;
}
ReleaseInputBuffers();
DestroyHWEncoder();
}
void NvEncoder::DestroyHWEncoder() {
if (!m_hEncoder) {
return;
}
#if defined(_WIN32)
for (uint32_t i = 0; i < m_vpCompletionEvent.size(); i++) {
if (m_vpCompletionEvent[i]) {
NV_ENC_EVENT_PARAMS eventParams = {NV_ENC_EVENT_PARAMS_VER};
eventParams.completionEvent = m_vpCompletionEvent[i];
m_nvenc.nvEncUnregisterAsyncEvent(m_hEncoder, &eventParams);
CloseHandle(m_vpCompletionEvent[i]);
}
}
m_vpCompletionEvent.clear();
#endif
if (m_bMotionEstimationOnly) {
DestroyMVOutputBuffer();
} else {
DestroyBitstreamBuffer();
}
m_nvenc.nvEncDestroyEncoder(m_hEncoder);
m_hEncoder = nullptr;
m_bEncoderInitialized = false;
}
const NvEncInputFrame *NvEncoder::GetNextInputFrame() {
int i = m_iToSend % m_nEncoderBuffer;
return &m_vInputFrames[i];
}
const NvEncInputFrame *NvEncoder::GetNextReferenceFrame() {
int i = m_iToSend % m_nEncoderBuffer;
return &m_vReferenceFrames[i];
}
void NvEncoder::MapResources(uint32_t bfrIdx) {
NV_ENC_MAP_INPUT_RESOURCE mapInputResource = {NV_ENC_MAP_INPUT_RESOURCE_VER};
mapInputResource.registeredResource = m_vRegisteredResources[bfrIdx];
NVENC_API_CALL(m_nvenc.nvEncMapInputResource(m_hEncoder, &mapInputResource));
m_vMappedInputBuffers[bfrIdx] = mapInputResource.mappedResource;
if (m_bMotionEstimationOnly) {
mapInputResource.registeredResource =
m_vRegisteredResourcesForReference[bfrIdx];
NVENC_API_CALL(
m_nvenc.nvEncMapInputResource(m_hEncoder, &mapInputResource));
m_vMappedRefBuffers[bfrIdx] = mapInputResource.mappedResource;
}
}
void NvEncoder::EncodeFrame(std::vector<std::vector<uint8_t>> &vPacket,
NV_ENC_PIC_PARAMS *pPicParams) {
vPacket.clear();
if (!IsHWEncoderInitialized()) {
NVENC_THROW_ERROR("Encoder device not found", NV_ENC_ERR_NO_ENCODE_DEVICE);
}
int bfrIdx = m_iToSend % m_nEncoderBuffer;
MapResources(bfrIdx);
NVENCSTATUS nvStatus = DoEncode(m_vMappedInputBuffers[bfrIdx],
m_vBitstreamOutputBuffer[bfrIdx], pPicParams);
if (nvStatus == NV_ENC_SUCCESS || nvStatus == NV_ENC_ERR_NEED_MORE_INPUT) {
m_iToSend++;
GetEncodedPacket(m_vBitstreamOutputBuffer, vPacket, true);
} else {
NVENC_THROW_ERROR("nvEncEncodePicture API failed", nvStatus);
}
}
void NvEncoder::RunMotionEstimation(std::vector<uint8_t> &mvData) {
if (!m_hEncoder) {
NVENC_THROW_ERROR("Encoder Initialization failed",
NV_ENC_ERR_NO_ENCODE_DEVICE);
return;
}
const uint32_t bfrIdx = m_iToSend % m_nEncoderBuffer;
MapResources(bfrIdx);
NVENCSTATUS nvStatus = DoMotionEstimation(m_vMappedInputBuffers[bfrIdx],
m_vMappedRefBuffers[bfrIdx],
m_vMVDataOutputBuffer[bfrIdx]);
if (nvStatus == NV_ENC_SUCCESS) {
m_iToSend++;
std::vector<std::vector<uint8_t>> vPacket;
GetEncodedPacket(m_vMVDataOutputBuffer, vPacket, true);
if (vPacket.size() != 1) {
NVENC_THROW_ERROR(
"GetEncodedPacket() doesn't return one (and only one) MVData",
NV_ENC_ERR_GENERIC);
}
mvData = vPacket[0];
} else {
NVENC_THROW_ERROR("nvEncEncodePicture API failed", nvStatus);
}
}
void NvEncoder::GetSequenceParams(std::vector<uint8_t> &seqParams) {
uint8_t spsppsData[1024]; // Assume maximum spspps data is 1KB or less
memset(spsppsData, 0, sizeof(spsppsData));
NV_ENC_SEQUENCE_PARAM_PAYLOAD payload = {NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER};
uint32_t spsppsSize = 0;
payload.spsppsBuffer = spsppsData;
payload.inBufferSize = sizeof(spsppsData);
payload.outSPSPPSPayloadSize = &spsppsSize;
NVENC_API_CALL(m_nvenc.nvEncGetSequenceParams(m_hEncoder, &payload));
seqParams.clear();
seqParams.insert(seqParams.end(), &spsppsData[0], &spsppsData[spsppsSize]);
}
NVENCSTATUS NvEncoder::DoEncode(NV_ENC_INPUT_PTR inputBuffer,
NV_ENC_OUTPUT_PTR outputBuffer,
NV_ENC_PIC_PARAMS *pPicParams) {
NV_ENC_PIC_PARAMS picParams = {};
if (pPicParams) {
picParams = *pPicParams;
}
picParams.version = NV_ENC_PIC_PARAMS_VER;
picParams.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
picParams.inputBuffer = inputBuffer;
picParams.bufferFmt = GetPixelFormat();
picParams.inputWidth = GetEncodeWidth();
picParams.inputHeight = GetEncodeHeight();
picParams.outputBitstream = outputBuffer;
picParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer);
NVENCSTATUS nvStatus = m_nvenc.nvEncEncodePicture(m_hEncoder, &picParams);
return nvStatus;
}
void NvEncoder::SendEOS() {
NV_ENC_PIC_PARAMS picParams = {NV_ENC_PIC_PARAMS_VER};
picParams.encodePicFlags = NV_ENC_PIC_FLAG_EOS;
picParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer);
NVENC_API_CALL(m_nvenc.nvEncEncodePicture(m_hEncoder, &picParams));
}
void NvEncoder::EndEncode(std::vector<std::vector<uint8_t>> &vPacket) {
vPacket.clear();
if (!IsHWEncoderInitialized()) {
NVENC_THROW_ERROR("Encoder device not initialized",
NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
}
SendEOS();
GetEncodedPacket(m_vBitstreamOutputBuffer, vPacket, false);
}
void NvEncoder::GetEncodedPacket(std::vector<NV_ENC_OUTPUT_PTR> &vOutputBuffer,
std::vector<std::vector<uint8_t>> &vPacket,
bool bOutputDelay) {
unsigned i = 0;
int iEnd = bOutputDelay ? m_iToSend - m_nOutputDelay : m_iToSend;
for (; m_iGot < iEnd; m_iGot++) {
WaitForCompletionEvent(m_iGot % m_nEncoderBuffer);
NV_ENC_LOCK_BITSTREAM lockBitstreamData = {NV_ENC_LOCK_BITSTREAM_VER};
lockBitstreamData.outputBitstream =
vOutputBuffer[m_iGot % m_nEncoderBuffer];
lockBitstreamData.doNotWait = false;
NVENC_API_CALL(m_nvenc.nvEncLockBitstream(m_hEncoder, &lockBitstreamData));
uint8_t *pData = (uint8_t *)lockBitstreamData.bitstreamBufferPtr;
if (vPacket.size() < i + 1) {
vPacket.push_back(std::vector<uint8_t>());
}
vPacket[i].clear();
vPacket[i].insert(vPacket[i].end(), &pData[0],
&pData[lockBitstreamData.bitstreamSizeInBytes]);
i++;
NVENC_API_CALL(m_nvenc.nvEncUnlockBitstream(
m_hEncoder, lockBitstreamData.outputBitstream));
if (m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer]) {
NVENC_API_CALL(m_nvenc.nvEncUnmapInputResource(
m_hEncoder, m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer]));
m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer] = nullptr;
}
if (m_bMotionEstimationOnly &&
m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer]) {
NVENC_API_CALL(m_nvenc.nvEncUnmapInputResource(
m_hEncoder, m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer]));
m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer] = nullptr;
}
}
}
bool NvEncoder::Reconfigure(
const NV_ENC_RECONFIGURE_PARAMS *pReconfigureParams) {
NVENC_API_CALL(m_nvenc.nvEncReconfigureEncoder(
m_hEncoder, const_cast<NV_ENC_RECONFIGURE_PARAMS *>(pReconfigureParams)));
memcpy(&m_initializeParams, &(pReconfigureParams->reInitEncodeParams),
sizeof(m_initializeParams));
if (pReconfigureParams->reInitEncodeParams.encodeConfig) {
memcpy(&m_encodeConfig, pReconfigureParams->reInitEncodeParams.encodeConfig,
sizeof(m_encodeConfig));
}
m_nWidth = m_initializeParams.encodeWidth;
m_nHeight = m_initializeParams.encodeHeight;
m_nMaxEncodeWidth = m_initializeParams.maxEncodeWidth;
m_nMaxEncodeHeight = m_initializeParams.maxEncodeHeight;
return true;
}
NV_ENC_REGISTERED_PTR NvEncoder::RegisterResource(
void *pBuffer, NV_ENC_INPUT_RESOURCE_TYPE eResourceType, int width,
int height, int pitch, NV_ENC_BUFFER_FORMAT bufferFormat,
NV_ENC_BUFFER_USAGE bufferUsage) {
NV_ENC_REGISTER_RESOURCE registerResource = {NV_ENC_REGISTER_RESOURCE_VER};
registerResource.resourceType = eResourceType;
registerResource.resourceToRegister = pBuffer;
registerResource.width = width;
registerResource.height = height;
registerResource.pitch = pitch;
registerResource.bufferFormat = bufferFormat;
registerResource.bufferUsage = bufferUsage;
NVENC_API_CALL(m_nvenc.nvEncRegisterResource(m_hEncoder, &registerResource));
return registerResource.registeredResource;
}
void NvEncoder::RegisterInputResources(std::vector<void *> inputframes,
NV_ENC_INPUT_RESOURCE_TYPE eResourceType,
int width, int height, int pitch,
NV_ENC_BUFFER_FORMAT bufferFormat,
bool bReferenceFrame) {
for (uint32_t i = 0; i < inputframes.size(); ++i) {
NV_ENC_REGISTERED_PTR registeredPtr =
RegisterResource(inputframes[i], eResourceType, width, height, pitch,
bufferFormat, NV_ENC_INPUT_IMAGE);
std::vector<uint32_t> _chromaOffsets;
NvEncoder::GetChromaSubPlaneOffsets(bufferFormat, pitch, height,
_chromaOffsets);
NvEncInputFrame inputframe = {};
inputframe.inputPtr = (void *)inputframes[i];
inputframe.chromaOffsets[0] = 0;
inputframe.chromaOffsets[1] = 0;
for (uint32_t ch = 0; ch < _chromaOffsets.size(); ch++) {
inputframe.chromaOffsets[ch] = _chromaOffsets[ch];
}
inputframe.numChromaPlanes = NvEncoder::GetNumChromaPlanes(bufferFormat);
inputframe.pitch = pitch;
inputframe.chromaPitch = NvEncoder::GetChromaPitch(bufferFormat, pitch);
inputframe.bufferFormat = bufferFormat;
inputframe.resourceType = eResourceType;
if (bReferenceFrame) {
m_vRegisteredResourcesForReference.push_back(registeredPtr);
m_vReferenceFrames.push_back(inputframe);
} else {
m_vRegisteredResources.push_back(registeredPtr);
m_vInputFrames.push_back(inputframe);
}
}
}
void NvEncoder::FlushEncoder() {
if (!m_bMotionEstimationOnly && !m_bOutputInVideoMemory) {
// Incase of error it is possible for buffers still mapped to encoder.
// flush the encoder queue and then unmapped it if any surface is still
// mapped
try {
std::vector<std::vector<uint8_t>> vPacket;
EndEncode(vPacket);
} catch (...) {
}
}
}
void NvEncoder::UnregisterInputResources() {
FlushEncoder();
if (m_bMotionEstimationOnly) {
for (uint32_t i = 0; i < m_vMappedRefBuffers.size(); ++i) {
if (m_vMappedRefBuffers[i]) {
m_nvenc.nvEncUnmapInputResource(m_hEncoder, m_vMappedRefBuffers[i]);
}
}
}
m_vMappedRefBuffers.clear();
for (uint32_t i = 0; i < m_vMappedInputBuffers.size(); ++i) {
if (m_vMappedInputBuffers[i]) {
m_nvenc.nvEncUnmapInputResource(m_hEncoder, m_vMappedInputBuffers[i]);
}
}
m_vMappedInputBuffers.clear();
for (uint32_t i = 0; i < m_vRegisteredResources.size(); ++i) {
if (m_vRegisteredResources[i]) {
m_nvenc.nvEncUnregisterResource(m_hEncoder, m_vRegisteredResources[i]);
}
}
m_vRegisteredResources.clear();
for (uint32_t i = 0; i < m_vRegisteredResourcesForReference.size(); ++i) {
if (m_vRegisteredResourcesForReference[i]) {
m_nvenc.nvEncUnregisterResource(m_hEncoder,
m_vRegisteredResourcesForReference[i]);
}
}
m_vRegisteredResourcesForReference.clear();
}
void NvEncoder::WaitForCompletionEvent(int iEvent) {
#if defined(_WIN32)
// Check if we are in async mode. If not, don't wait for event;
NV_ENC_CONFIG sEncodeConfig = {0};
NV_ENC_INITIALIZE_PARAMS sInitializeParams = {0};
sInitializeParams.encodeConfig = &sEncodeConfig;
GetInitializeParams(&sInitializeParams);
if (0U == sInitializeParams.enableEncodeAsync) {
return;
}
#ifdef DEBUG
WaitForSingleObject(m_vpCompletionEvent[iEvent], INFINITE);
#else
// wait for 20s which is infinite on terms of gpu time
if (WaitForSingleObject(m_vpCompletionEvent[iEvent], 20000) == WAIT_FAILED) {
NVENC_THROW_ERROR("Failed to encode frame", NV_ENC_ERR_GENERIC);
}
#endif
#endif
}
uint32_t NvEncoder::GetWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t width) {
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_NV12:
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
case NV_ENC_BUFFER_FORMAT_YUV444:
return width;
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return width * 2;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return width * 4;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return 0;
}
}
uint32_t NvEncoder::GetNumChromaPlanes(
const NV_ENC_BUFFER_FORMAT bufferFormat) {
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_NV12:
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return 1;
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
case NV_ENC_BUFFER_FORMAT_YUV444:
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return 2;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return 0;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return -1;
}
}
uint32_t NvEncoder::GetChromaPitch(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t lumaPitch) {
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_NV12:
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
case NV_ENC_BUFFER_FORMAT_YUV444:
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return lumaPitch;
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
return (lumaPitch + 1) / 2;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return 0;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return -1;
}
}
void NvEncoder::GetChromaSubPlaneOffsets(
const NV_ENC_BUFFER_FORMAT bufferFormat, const uint32_t pitch,
const uint32_t height, std::vector<uint32_t> &chromaOffsets) {
chromaOffsets.clear();
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_NV12:
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
chromaOffsets.push_back(pitch * height);
return;
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
chromaOffsets.push_back(pitch * height);
chromaOffsets.push_back(chromaOffsets[0] +
(NvEncoder::GetChromaPitch(bufferFormat, pitch) *
GetChromaHeight(bufferFormat, height)));
return;
case NV_ENC_BUFFER_FORMAT_YUV444:
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
chromaOffsets.push_back(pitch * height);
chromaOffsets.push_back(chromaOffsets[0] + (pitch * height));
return;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return;
}
}
uint32_t NvEncoder::GetChromaHeight(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t lumaHeight) {
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
case NV_ENC_BUFFER_FORMAT_NV12:
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return (lumaHeight + 1) / 2;
case NV_ENC_BUFFER_FORMAT_YUV444:
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return lumaHeight;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return 0;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return 0;
}
}
uint32_t NvEncoder::GetChromaWidthInBytes(
const NV_ENC_BUFFER_FORMAT bufferFormat, const uint32_t lumaWidth) {
switch (bufferFormat) {
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
return (lumaWidth + 1) / 2;
case NV_ENC_BUFFER_FORMAT_NV12:
return lumaWidth;
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return 2 * lumaWidth;
case NV_ENC_BUFFER_FORMAT_YUV444:
return lumaWidth;
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return 2 * lumaWidth;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return 0;
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return 0;
}
}
int NvEncoder::GetCapabilityValue(GUID guidCodec, NV_ENC_CAPS capsToQuery) {
if (!m_hEncoder) {
return 0;
}
NV_ENC_CAPS_PARAM capsParam = {NV_ENC_CAPS_PARAM_VER};
capsParam.capsToQuery = capsToQuery;
int v;
m_nvenc.nvEncGetEncodeCaps(m_hEncoder, guidCodec, &capsParam, &v);
return v;
}
int NvEncoder::GetFrameSize() const {
switch (GetPixelFormat()) {
case NV_ENC_BUFFER_FORMAT_YV12:
case NV_ENC_BUFFER_FORMAT_IYUV:
case NV_ENC_BUFFER_FORMAT_NV12:
return GetEncodeWidth() *
(GetEncodeHeight() + (GetEncodeHeight() + 1) / 2);
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return 2 * GetEncodeWidth() *
(GetEncodeHeight() + (GetEncodeHeight() + 1) / 2);
case NV_ENC_BUFFER_FORMAT_YUV444:
return GetEncodeWidth() * GetEncodeHeight() * 3;
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return 2 * GetEncodeWidth() * GetEncodeHeight() * 3;
case NV_ENC_BUFFER_FORMAT_ARGB:
case NV_ENC_BUFFER_FORMAT_ARGB10:
case NV_ENC_BUFFER_FORMAT_AYUV:
case NV_ENC_BUFFER_FORMAT_ABGR:
case NV_ENC_BUFFER_FORMAT_ABGR10:
return 4 * GetEncodeWidth() * GetEncodeHeight();
default:
NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM);
return 0;
}
}
void NvEncoder::GetInitializeParams(
NV_ENC_INITIALIZE_PARAMS *pInitializeParams) {
if (!pInitializeParams || !pInitializeParams->encodeConfig) {
NVENC_THROW_ERROR(
"Both pInitializeParams and pInitializeParams->encodeConfig can't be "
"NULL",
NV_ENC_ERR_INVALID_PTR);
}
NV_ENC_CONFIG *pEncodeConfig = pInitializeParams->encodeConfig;
*pEncodeConfig = m_encodeConfig;
*pInitializeParams = m_initializeParams;
pInitializeParams->encodeConfig = pEncodeConfig;
}
void NvEncoder::InitializeBitstreamBuffer() {
for (int i = 0; i < m_nEncoderBuffer; i++) {
NV_ENC_CREATE_BITSTREAM_BUFFER createBitstreamBuffer = {
NV_ENC_CREATE_BITSTREAM_BUFFER_VER};
NVENC_API_CALL(
m_nvenc.nvEncCreateBitstreamBuffer(m_hEncoder, &createBitstreamBuffer));
m_vBitstreamOutputBuffer[i] = createBitstreamBuffer.bitstreamBuffer;
}
}
void NvEncoder::DestroyBitstreamBuffer() {
for (uint32_t i = 0; i < m_vBitstreamOutputBuffer.size(); i++) {
if (m_vBitstreamOutputBuffer[i]) {
m_nvenc.nvEncDestroyBitstreamBuffer(m_hEncoder,
m_vBitstreamOutputBuffer[i]);
}
}
m_vBitstreamOutputBuffer.clear();
}
void NvEncoder::InitializeMVOutputBuffer() {
for (int i = 0; i < m_nEncoderBuffer; i++) {
NV_ENC_CREATE_MV_BUFFER createMVBuffer = {NV_ENC_CREATE_MV_BUFFER_VER};
NVENC_API_CALL(m_nvenc.nvEncCreateMVBuffer(m_hEncoder, &createMVBuffer));
m_vMVDataOutputBuffer.push_back(createMVBuffer.mvBuffer);
}
}
void NvEncoder::DestroyMVOutputBuffer() {
for (uint32_t i = 0; i < m_vMVDataOutputBuffer.size(); i++) {
if (m_vMVDataOutputBuffer[i]) {
m_nvenc.nvEncDestroyMVBuffer(m_hEncoder, m_vMVDataOutputBuffer[i]);
}
}
m_vMVDataOutputBuffer.clear();
}
NVENCSTATUS NvEncoder::DoMotionEstimation(
NV_ENC_INPUT_PTR inputBuffer, NV_ENC_INPUT_PTR inputBufferForReference,
NV_ENC_OUTPUT_PTR outputBuffer) {
NV_ENC_MEONLY_PARAMS meParams = {NV_ENC_MEONLY_PARAMS_VER};
meParams.inputBuffer = inputBuffer;
meParams.referenceFrame = inputBufferForReference;
meParams.inputWidth = GetEncodeWidth();
meParams.inputHeight = GetEncodeHeight();
meParams.mvBuffer = outputBuffer;
meParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer);
NVENCSTATUS nvStatus =
m_nvenc.nvEncRunMotionEstimationOnly(m_hEncoder, &meParams);
return nvStatus;
}

View File

@@ -0,0 +1,482 @@
/*
* Copyright 2017-2020 NVIDIA Corporation. All rights reserved.
*
* Please refer to the NVIDIA end user license agreement (EULA) associated
* with this source code for terms and conditions that govern your use of
* this software. Any use, reproduction, disclosure, or distribution of
* this software and related documentation outside the terms of the EULA
* is strictly prohibited.
*
*/
#pragma once
#include <stdint.h>
#include <string.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#include "nvEncodeAPI.h"
/**
* @brief Exception class for error reporting from NvEncodeAPI calls.
*/
class NVENCException : public std::exception {
public:
NVENCException(const std::string& errorStr, const NVENCSTATUS errorCode)
: m_errorString(errorStr), m_errorCode(errorCode) {}
virtual ~NVENCException() throw() {}
virtual const char* what() const throw() { return m_errorString.c_str(); }
NVENCSTATUS getErrorCode() const { return m_errorCode; }
const std::string& getErrorString() const { return m_errorString; }
static NVENCException makeNVENCException(const std::string& errorStr,
const NVENCSTATUS errorCode,
const std::string& functionName,
const std::string& fileName,
int lineNo);
private:
std::string m_errorString;
NVENCSTATUS m_errorCode;
};
inline NVENCException NVENCException::makeNVENCException(
const std::string& errorStr, const NVENCSTATUS errorCode,
const std::string& functionName, const std::string& fileName, int lineNo) {
std::ostringstream errorLog;
errorLog << functionName << " : " << errorStr << " at " << fileName << ":"
<< lineNo << std::endl;
NVENCException exception(errorLog.str(), errorCode);
return exception;
}
#define NVENC_THROW_ERROR(errorStr, errorCode) \
do { \
throw NVENCException::makeNVENCException( \
errorStr, errorCode, __FUNCTION__, __FILE__, __LINE__); \
} while (0)
#define NVENC_API_CALL(nvencAPI) \
do { \
NVENCSTATUS errorCode = nvencAPI; \
if (errorCode != NV_ENC_SUCCESS) { \
std::ostringstream errorLog; \
errorLog << #nvencAPI << " returned error " << errorCode; \
throw NVENCException::makeNVENCException( \
errorLog.str(), errorCode, __FUNCTION__, __FILE__, __LINE__); \
} \
} while (0)
struct NvEncInputFrame {
void* inputPtr = nullptr;
uint32_t chromaOffsets[2];
uint32_t numChromaPlanes;
uint32_t pitch;
uint32_t chromaPitch;
NV_ENC_BUFFER_FORMAT bufferFormat;
NV_ENC_INPUT_RESOURCE_TYPE resourceType;
};
/**
* @brief Shared base class for different encoder interfaces.
*/
class NvEncoder {
public:
/**
* @brief This function is used to initialize the encoder session.
* Application must call this function to initialize the encoder, before
* starting to encode any frames.
*/
void CreateEncoder(const NV_ENC_INITIALIZE_PARAMS* pEncodeParams);
/**
* @brief This function is used to destroy the encoder session.
* Application must call this function to destroy the encoder session and
* clean up any allocated resources. The application must call EndEncode()
* function to get any queued encoded frames before calling DestroyEncoder().
*/
void DestroyEncoder();
/**
* @brief This function is used to reconfigure an existing encoder session.
* Application can use this function to dynamically change the bitrate,
* resolution and other QOS parameters. If the application changes the
* resolution, it must set NV_ENC_RECONFIGURE_PARAMS::forceIDR.
*/
bool Reconfigure(const NV_ENC_RECONFIGURE_PARAMS* pReconfigureParams);
/**
* @brief This function is used to get the next available input buffer.
* Applications must call this function to obtain a pointer to the next
* input buffer. The application must copy the uncompressed data to the
* input buffer and then call EncodeFrame() function to encode it.
*/
const NvEncInputFrame* GetNextInputFrame();
/**
* @brief This function is used to encode a frame.
* Applications must call EncodeFrame() function to encode the uncompressed
* data, which has been copied to an input buffer obtained from the
* GetNextInputFrame() function.
*/
void EncodeFrame(std::vector<std::vector<uint8_t>>& vPacket,
NV_ENC_PIC_PARAMS* pPicParams = nullptr);
/**
* @brief This function to flush the encoder queue.
* The encoder might be queuing frames for B picture encoding or lookahead;
* the application must call EndEncode() to get all the queued encoded frames
* from the encoder. The application must call this function before
* destroying an encoder session.
*/
void EndEncode(std::vector<std::vector<uint8_t>>& vPacket);
/**
* @brief This function is used to query hardware encoder capabilities.
* Applications can call this function to query capabilities like maximum
* encode dimensions, support for lookahead or the ME-only mode etc.
*/
int GetCapabilityValue(GUID guidCodec, NV_ENC_CAPS capsToQuery);
/**
* @brief This function is used to get the current device on which encoder
* is running.
*/
void* GetDevice() const { return m_pDevice; }
/**
* @brief This function is used to get the current device type which encoder
* is running.
*/
NV_ENC_DEVICE_TYPE GetDeviceType() const { return m_eDeviceType; }
/**
* @brief This function is used to get the current encode width.
* The encode width can be modified by Reconfigure() function.
*/
int GetEncodeWidth() const { return m_nWidth; }
/**
* @brief This function is used to get the current encode height.
* The encode height can be modified by Reconfigure() function.
*/
int GetEncodeHeight() const { return m_nHeight; }
/**
* @brief This function is used to get the current frame size based on
* pixel format.
*/
int GetFrameSize() const;
/**
* @brief This function is used to initialize config parameters based on
* given codec and preset guids.
* The application can call this function to get the default configuration
* for a certain preset. The application can either use these parameters
* directly or override them with application-specific settings before
* using them in CreateEncoder() function.
*/
void CreateDefaultEncoderParams(
NV_ENC_INITIALIZE_PARAMS* pIntializeParams, GUID codecGuid,
GUID presetGuid,
NV_ENC_TUNING_INFO tuningInfo = NV_ENC_TUNING_INFO_UNDEFINED);
/**
* @brief This function is used to get the current initialization
* parameters, which had been used to configure the encoder session. The
* initialization parameters are modified if the application calls
* Reconfigure() function.
*/
void GetInitializeParams(NV_ENC_INITIALIZE_PARAMS* pInitializeParams);
/**
* @brief This function is used to run motion estimation
* This is used to run motion estimation on a a pair of frames. The
* application must copy the reference frame data to the buffer obtained
* by calling GetNextReferenceFrame(), and copy the input frame data to
* the buffer obtained by calling GetNextInputFrame() before calling the
* RunMotionEstimation() function.
*/
void RunMotionEstimation(std::vector<uint8_t>& mvData);
/**
* @brief This function is used to get an available reference frame.
* Application must call this function to get a pointer to reference buffer,
* to be used in the subsequent RunMotionEstimation() function.
*/
const NvEncInputFrame* GetNextReferenceFrame();
/**
* @brief This function is used to get sequence and picture parameter
* headers. Application can call this function after encoder is initialized to
* get SPS and PPS nalus for the current encoder instance. The sequence header
* data might change when application calls Reconfigure() function.
*/
void GetSequenceParams(std::vector<uint8_t>& seqParams);
/**
* @brief NvEncoder class virtual destructor.
*/
virtual ~NvEncoder();
public:
/**
* @brief This a static function to get chroma offsets for YUV planar
* formats.
*/
static void GetChromaSubPlaneOffsets(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t pitch,
const uint32_t height,
std::vector<uint32_t>& chromaOffsets);
/**
* @brief This a static function to get the chroma plane pitch for YUV planar
* formats.
*/
static uint32_t GetChromaPitch(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t lumaPitch);
/**
* @brief This a static function to get the number of chroma planes for YUV
* planar formats.
*/
static uint32_t GetNumChromaPlanes(const NV_ENC_BUFFER_FORMAT bufferFormat);
/**
* @brief This a static function to get the chroma plane width in bytes for
* YUV planar formats.
*/
static uint32_t GetChromaWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t lumaWidth);
/**
* @brief This a static function to get the chroma planes height in bytes for
* YUV planar formats.
*/
static uint32_t GetChromaHeight(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t lumaHeight);
/**
* @brief This a static function to get the width in bytes for the frame.
* For YUV planar format this is the width in bytes of the luma plane.
*/
static uint32_t GetWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat,
const uint32_t width);
/**
* @brief This function returns the number of allocated buffers.
*/
uint32_t GetEncoderBufferCount() const { return m_nEncoderBuffer; }
protected:
/**
* @brief NvEncoder class constructor.
* NvEncoder class constructor cannot be called directly by the application.
*/
NvEncoder(NV_ENC_DEVICE_TYPE eDeviceType, void* pDevice, uint32_t nWidth,
uint32_t nHeight, NV_ENC_BUFFER_FORMAT eBufferFormat,
uint32_t nOutputDelay, bool bMotionEstimationOnly,
bool bOutputInVideoMemory = false);
/**
* @brief This function is used to check if hardware encoder is properly
* initialized.
*/
bool IsHWEncoderInitialized() const {
return m_hEncoder != NULL && m_bEncoderInitialized;
}
/**
* @brief This function is used to register CUDA, D3D or OpenGL input buffers
* with NvEncodeAPI. This is non public function and is called by derived
* class for allocating and registering input buffers.
*/
void RegisterInputResources(std::vector<void*> inputframes,
NV_ENC_INPUT_RESOURCE_TYPE eResourceType,
int width, int height, int pitch,
NV_ENC_BUFFER_FORMAT bufferFormat,
bool bReferenceFrame = false);
/**
* @brief This function is used to unregister resources which had been
* previously registered for encoding using RegisterInputResources() function.
*/
void UnregisterInputResources();
/**
* @brief This function is used to register CUDA, D3D or OpenGL input or
* output buffers with NvEncodeAPI.
*/
NV_ENC_REGISTERED_PTR RegisterResource(
void* pBuffer, NV_ENC_INPUT_RESOURCE_TYPE eResourceType, int width,
int height, int pitch, NV_ENC_BUFFER_FORMAT bufferFormat,
NV_ENC_BUFFER_USAGE bufferUsage = NV_ENC_INPUT_IMAGE);
/**
* @brief This function returns maximum width used to open the encoder
* session. All encode input buffers are allocated using maximum dimensions.
*/
uint32_t GetMaxEncodeWidth() const { return m_nMaxEncodeWidth; }
/**
* @brief This function returns maximum height used to open the encoder
* session. All encode input buffers are allocated using maximum dimensions.
*/
uint32_t GetMaxEncodeHeight() const { return m_nMaxEncodeHeight; }
/**
* @brief This function returns the completion event.
*/
void* GetCompletionEvent(uint32_t eventIdx) {
return (m_vpCompletionEvent.size() == m_nEncoderBuffer)
? m_vpCompletionEvent[eventIdx]
: nullptr;
}
/**
* @brief This function returns the current pixel format.
*/
NV_ENC_BUFFER_FORMAT GetPixelFormat() const { return m_eBufferFormat; }
/**
* @brief This function is used to submit the encode commands to the
* NVENC hardware.
*/
NVENCSTATUS DoEncode(NV_ENC_INPUT_PTR inputBuffer,
NV_ENC_OUTPUT_PTR outputBuffer,
NV_ENC_PIC_PARAMS* pPicParams);
/**
* @brief This function is used to submit the encode commands to the
* NVENC hardware for ME only mode.
*/
NVENCSTATUS DoMotionEstimation(NV_ENC_INPUT_PTR inputBuffer,
NV_ENC_INPUT_PTR inputBufferForReference,
NV_ENC_OUTPUT_PTR outputBuffer);
/**
* @brief This function is used to map the input buffers to NvEncodeAPI.
*/
void MapResources(uint32_t bfrIdx);
/**
* @brief This function is used to wait for completion of encode command.
*/
void WaitForCompletionEvent(int iEvent);
/**
* @brief This function is used to send EOS to HW encoder.
*/
void SendEOS();
private:
/**
* @brief This is a private function which is used to check if there is any
buffering done by encoder.
* The encoder generally buffers data to encode B frames or for lookahead
* or pipelining.
*/
bool IsZeroDelay() { return m_nOutputDelay == 0; }
/**
* @brief This is a private function which is used to load the encode api
* shared library.
*/
void LoadNvEncApi();
/**
* @brief This is a private function which is used to get the output packets
* from the encoder HW.
* This is called by DoEncode() function. If there is buffering enabled,
* this may return without any output data.
*/
void GetEncodedPacket(std::vector<NV_ENC_OUTPUT_PTR>& vOutputBuffer,
std::vector<std::vector<uint8_t>>& vPacket,
bool bOutputDelay);
/**
* @brief This is a private function which is used to initialize the
* bitstream buffers. This is only used in the encoding mode.
*/
void InitializeBitstreamBuffer();
/**
* @brief This is a private function which is used to destroy the bitstream
* buffers. This is only used in the encoding mode.
*/
void DestroyBitstreamBuffer();
/**
* @brief This is a private function which is used to initialize MV output
* buffers. This is only used in ME-only Mode.
*/
void InitializeMVOutputBuffer();
/**
* @brief This is a private function which is used to destroy MV output
* buffers. This is only used in ME-only Mode.
*/
void DestroyMVOutputBuffer();
/**
* @brief This is a private function which is used to destroy HW encoder.
*/
void DestroyHWEncoder();
/**
* @brief This function is used to flush the encoder queue.
*/
void FlushEncoder();
private:
/**
* @brief This is a pure virtual function which is used to allocate input
* buffers. The derived classes must implement this function.
*/
virtual void AllocateInputBuffers(int32_t numInputBuffers) = 0;
/**
* @brief This is a pure virtual function which is used to destroy input
* buffers. The derived classes must implement this function.
*/
virtual void ReleaseInputBuffers() = 0;
protected:
bool m_bMotionEstimationOnly = false;
bool m_bOutputInVideoMemory = false;
void* m_hEncoder = nullptr;
NV_ENCODE_API_FUNCTION_LIST m_nvenc;
std::vector<NvEncInputFrame> m_vInputFrames;
std::vector<NV_ENC_REGISTERED_PTR> m_vRegisteredResources;
std::vector<NvEncInputFrame> m_vReferenceFrames;
std::vector<NV_ENC_REGISTERED_PTR> m_vRegisteredResourcesForReference;
std::vector<NV_ENC_INPUT_PTR> m_vMappedInputBuffers;
std::vector<NV_ENC_INPUT_PTR> m_vMappedRefBuffers;
std::vector<void*> m_vpCompletionEvent;
int32_t m_iToSend = 0;
int32_t m_iGot = 0;
int32_t m_nEncoderBuffer = 0;
int32_t m_nOutputDelay = 0;
private:
uint32_t m_nWidth;
uint32_t m_nHeight;
NV_ENC_BUFFER_FORMAT m_eBufferFormat;
void* m_pDevice;
NV_ENC_DEVICE_TYPE m_eDeviceType;
NV_ENC_INITIALIZE_PARAMS m_initializeParams = {};
NV_ENC_CONFIG m_encodeConfig = {};
bool m_bEncoderInitialized = false;
uint32_t m_nExtraOutputDelay =
3; // To ensure encode and graphics can work in parallel,
// m_nExtraOutputDelay should be set to at least 1
std::vector<NV_ENC_OUTPUT_PTR> m_vBitstreamOutputBuffer;
std::vector<NV_ENC_OUTPUT_PTR> m_vMVDataOutputBuffer;
uint32_t m_nMaxEncodeWidth = 0;
uint32_t m_nMaxEncodeHeight = 0;
};

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2017-2020 NVIDIA Corporation. All rights reserved.
*
* Please refer to the NVIDIA end user license agreement (EULA) associated
* with this source code for terms and conditions that govern your use of
* this software. Any use, reproduction, disclosure, or distribution of
* this software and related documentation outside the terms of the EULA
* is strictly prohibited.
*
*/
#include "NvEncoderCuda.h"
NvEncoderCuda::NvEncoderCuda(CUcontext cuContext, uint32_t nWidth,
uint32_t nHeight,
NV_ENC_BUFFER_FORMAT eBufferFormat,
uint32_t nExtraOutputDelay,
bool bMotionEstimationOnly,
bool bOutputInVideoMemory)
: NvEncoder(NV_ENC_DEVICE_TYPE_CUDA, cuContext, nWidth, nHeight,
eBufferFormat, nExtraOutputDelay, bMotionEstimationOnly,
bOutputInVideoMemory),
m_cuContext(cuContext) {
if (!m_hEncoder) {
NVENC_THROW_ERROR("Encoder Initialization failed",
NV_ENC_ERR_INVALID_DEVICE);
}
if (!m_cuContext) {
NVENC_THROW_ERROR("Invalid Cuda Context", NV_ENC_ERR_INVALID_DEVICE);
}
}
NvEncoderCuda::~NvEncoderCuda() { ReleaseCudaResources(); }
void NvEncoderCuda::AllocateInputBuffers(int32_t numInputBuffers) {
if (!IsHWEncoderInitialized()) {
NVENC_THROW_ERROR("Encoder intialization failed",
NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
}
// for MEOnly mode we need to allocate seperate set of buffers for reference
// frame
int numCount = m_bMotionEstimationOnly ? 2 : 1;
for (int count = 0; count < numCount; count++) {
CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext));
std::vector<void *> inputFrames;
for (int i = 0; i < numInputBuffers; i++) {
CUdeviceptr pDeviceFrame;
uint32_t chromaHeight =
GetNumChromaPlanes(GetPixelFormat()) *
GetChromaHeight(GetPixelFormat(), GetMaxEncodeHeight());
if (GetPixelFormat() == NV_ENC_BUFFER_FORMAT_YV12 ||
GetPixelFormat() == NV_ENC_BUFFER_FORMAT_IYUV)
chromaHeight = GetChromaHeight(GetPixelFormat(), GetMaxEncodeHeight());
CUDA_DRVAPI_CALL(cuMemAllocPitch(
(CUdeviceptr *)&pDeviceFrame, &m_cudaPitch,
GetWidthInBytes(GetPixelFormat(), GetMaxEncodeWidth()),
GetMaxEncodeHeight() + chromaHeight, 16));
inputFrames.push_back((void *)pDeviceFrame);
}
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
RegisterInputResources(
inputFrames, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
GetMaxEncodeWidth(), GetMaxEncodeHeight(), (int)m_cudaPitch,
GetPixelFormat(), (count == 1) ? true : false);
}
}
void NvEncoderCuda::SetIOCudaStreams(NV_ENC_CUSTREAM_PTR inputStream,
NV_ENC_CUSTREAM_PTR outputStream) {
NVENC_API_CALL(
m_nvenc.nvEncSetIOCudaStreams(m_hEncoder, inputStream, outputStream));
}
void NvEncoderCuda::ReleaseInputBuffers() { ReleaseCudaResources(); }
void NvEncoderCuda::ReleaseCudaResources() {
if (!m_hEncoder) {
return;
}
if (!m_cuContext) {
return;
}
UnregisterInputResources();
cuCtxPushCurrent(m_cuContext);
for (uint32_t i = 0; i < m_vInputFrames.size(); ++i) {
if (m_vInputFrames[i].inputPtr) {
cuMemFree(reinterpret_cast<CUdeviceptr>(m_vInputFrames[i].inputPtr));
}
}
m_vInputFrames.clear();
for (uint32_t i = 0; i < m_vReferenceFrames.size(); ++i) {
if (m_vReferenceFrames[i].inputPtr) {
cuMemFree(reinterpret_cast<CUdeviceptr>(m_vReferenceFrames[i].inputPtr));
}
}
m_vReferenceFrames.clear();
cuCtxPopCurrent(NULL);
m_cuContext = nullptr;
}
void NvEncoderCuda::CopyToDeviceFrame(
CUcontext device, void *pSrcFrame, uint32_t nSrcPitch,
CUdeviceptr pDstFrame, uint32_t dstPitch, int width, int height,
CUmemorytype srcMemoryType, NV_ENC_BUFFER_FORMAT pixelFormat,
const uint32_t dstChromaOffsets[], uint32_t numChromaPlanes,
bool bUnAlignedDeviceCopy, CUstream stream) {
if (srcMemoryType != CU_MEMORYTYPE_HOST &&
srcMemoryType != CU_MEMORYTYPE_DEVICE) {
NVENC_THROW_ERROR("Invalid source memory type for copy",
NV_ENC_ERR_INVALID_PARAM);
}
CUDA_DRVAPI_CALL(cuCtxPushCurrent(device));
uint32_t srcPitch =
nSrcPitch ? nSrcPitch : NvEncoder::GetWidthInBytes(pixelFormat, width);
CUDA_MEMCPY2D m = {0};
m.srcMemoryType = srcMemoryType;
if (srcMemoryType == CU_MEMORYTYPE_HOST) {
m.srcHost = pSrcFrame;
} else {
m.srcDevice = (CUdeviceptr)pSrcFrame;
}
m.srcPitch = srcPitch;
m.dstMemoryType = CU_MEMORYTYPE_DEVICE;
m.dstDevice = pDstFrame;
m.dstPitch = dstPitch;
m.WidthInBytes = NvEncoder::GetWidthInBytes(pixelFormat, width);
m.Height = height;
if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) {
CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m));
} else {
CUDA_DRVAPI_CALL(stream == NULL ? cuMemcpy2D(&m)
: cuMemcpy2DAsync(&m, stream));
}
std::vector<uint32_t> srcChromaOffsets;
NvEncoder::GetChromaSubPlaneOffsets(pixelFormat, srcPitch, height,
srcChromaOffsets);
uint32_t chromaHeight = NvEncoder::GetChromaHeight(pixelFormat, height);
uint32_t destChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, dstPitch);
uint32_t srcChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, srcPitch);
uint32_t chromaWidthInBytes =
NvEncoder::GetChromaWidthInBytes(pixelFormat, width);
for (uint32_t i = 0; i < numChromaPlanes; ++i) {
if (chromaHeight) {
if (srcMemoryType == CU_MEMORYTYPE_HOST) {
m.srcHost = ((uint8_t *)pSrcFrame + srcChromaOffsets[i]);
} else {
m.srcDevice = (CUdeviceptr)((uint8_t *)pSrcFrame + srcChromaOffsets[i]);
}
m.srcPitch = srcChromaPitch;
m.dstDevice = (CUdeviceptr)((uint8_t *)pDstFrame + dstChromaOffsets[i]);
m.dstPitch = destChromaPitch;
m.WidthInBytes = chromaWidthInBytes;
m.Height = chromaHeight;
if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) {
CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m));
} else {
CUDA_DRVAPI_CALL(stream == NULL ? cuMemcpy2D(&m)
: cuMemcpy2DAsync(&m, stream));
}
}
}
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
}
void NvEncoderCuda::CopyToDeviceFrame(
CUcontext device, void *pSrcFrame, uint32_t nSrcPitch,
CUdeviceptr pDstFrame, uint32_t dstPitch, int width, int height,
CUmemorytype srcMemoryType, NV_ENC_BUFFER_FORMAT pixelFormat,
CUdeviceptr dstChromaDevicePtrs[], uint32_t dstChromaPitch,
uint32_t numChromaPlanes, bool bUnAlignedDeviceCopy) {
if (srcMemoryType != CU_MEMORYTYPE_HOST &&
srcMemoryType != CU_MEMORYTYPE_DEVICE) {
NVENC_THROW_ERROR("Invalid source memory type for copy",
NV_ENC_ERR_INVALID_PARAM);
}
CUDA_DRVAPI_CALL(cuCtxPushCurrent(device));
uint32_t srcPitch =
nSrcPitch ? nSrcPitch : NvEncoder::GetWidthInBytes(pixelFormat, width);
CUDA_MEMCPY2D m = {0};
m.srcMemoryType = srcMemoryType;
if (srcMemoryType == CU_MEMORYTYPE_HOST) {
m.srcHost = pSrcFrame;
} else {
m.srcDevice = (CUdeviceptr)pSrcFrame;
}
m.srcPitch = srcPitch;
m.dstMemoryType = CU_MEMORYTYPE_DEVICE;
m.dstDevice = pDstFrame;
m.dstPitch = dstPitch;
m.WidthInBytes = NvEncoder::GetWidthInBytes(pixelFormat, width);
m.Height = height;
if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) {
CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m));
} else {
CUDA_DRVAPI_CALL(cuMemcpy2D(&m));
}
std::vector<uint32_t> srcChromaOffsets;
NvEncoder::GetChromaSubPlaneOffsets(pixelFormat, srcPitch, height,
srcChromaOffsets);
uint32_t chromaHeight = NvEncoder::GetChromaHeight(pixelFormat, height);
uint32_t srcChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, srcPitch);
uint32_t chromaWidthInBytes =
NvEncoder::GetChromaWidthInBytes(pixelFormat, width);
for (uint32_t i = 0; i < numChromaPlanes; ++i) {
if (chromaHeight) {
if (srcMemoryType == CU_MEMORYTYPE_HOST) {
m.srcHost = ((uint8_t *)pSrcFrame + srcChromaOffsets[i]);
} else {
m.srcDevice = (CUdeviceptr)((uint8_t *)pSrcFrame + srcChromaOffsets[i]);
}
m.srcPitch = srcChromaPitch;
m.dstDevice = dstChromaDevicePtrs[i];
m.dstPitch = dstChromaPitch;
m.WidthInBytes = chromaWidthInBytes;
m.Height = chromaHeight;
if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) {
CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m));
} else {
CUDA_DRVAPI_CALL(cuMemcpy2D(&m));
}
}
}
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2017-2020 NVIDIA Corporation. All rights reserved.
*
* Please refer to the NVIDIA end user license agreement (EULA) associated
* with this source code for terms and conditions that govern your use of
* this software. Any use, reproduction, disclosure, or distribution of
* this software and related documentation outside the terms of the EULA
* is strictly prohibited.
*
*/
#pragma once
#include <cuda.h>
#include <stdint.h>
#include <mutex>
#include <vector>
#include "NvEncoder.h"
#define CUDA_DRVAPI_CALL(call) \
do { \
CUresult err__ = call; \
if (err__ != CUDA_SUCCESS) { \
const char* szErrName = NULL; \
cuGetErrorName(err__, &szErrName); \
std::ostringstream errorLog; \
errorLog << "CUDA driver API error " << szErrName; \
throw NVENCException::makeNVENCException( \
errorLog.str(), NV_ENC_ERR_GENERIC, __FUNCTION__, __FILE__, \
__LINE__); \
} \
} while (0)
/**
* @brief Encoder for CUDA device memory.
*/
class NvEncoderCuda : public NvEncoder {
public:
NvEncoderCuda(CUcontext cuContext, uint32_t nWidth, uint32_t nHeight,
NV_ENC_BUFFER_FORMAT eBufferFormat,
uint32_t nExtraOutputDelay = 3,
bool bMotionEstimationOnly = false,
bool bOPInVideoMemory = false);
virtual ~NvEncoderCuda();
/**
* @brief This is a static function to copy input data from host memory to
* device memory. This function assumes YUV plane is a single contiguous
* memory segment.
*/
static void CopyToDeviceFrame(
CUcontext device, void* pSrcFrame, uint32_t nSrcPitch,
CUdeviceptr pDstFrame, uint32_t dstPitch, int width, int height,
CUmemorytype srcMemoryType, NV_ENC_BUFFER_FORMAT pixelFormat,
const uint32_t dstChromaOffsets[], uint32_t numChromaPlanes,
bool bUnAlignedDeviceCopy = false, CUstream stream = NULL);
/**
* @brief This is a static function to copy input data from host memory to
* device memory. Application must pass a seperate device pointer for each YUV
* plane.
*/
static void CopyToDeviceFrame(
CUcontext device, void* pSrcFrame, uint32_t nSrcPitch,
CUdeviceptr pDstFrame, uint32_t dstPitch, int width, int height,
CUmemorytype srcMemoryType, NV_ENC_BUFFER_FORMAT pixelFormat,
CUdeviceptr dstChromaPtr[], uint32_t dstChromaPitch,
uint32_t numChromaPlanes, bool bUnAlignedDeviceCopy = false);
/**
* @brief This function sets input and output CUDA streams
*/
void SetIOCudaStreams(NV_ENC_CUSTREAM_PTR inputStream,
NV_ENC_CUSTREAM_PTR outputStream);
protected:
/**
* @brief This function is used to release the input buffers allocated for
* encoding. This function is an override of virtual function
* NvEncoder::ReleaseInputBuffers().
*/
virtual void ReleaseInputBuffers() override;
private:
/**
* @brief This function is used to allocate input buffers for encoding.
* This function is an override of virtual function
* NvEncoder::AllocateInputBuffers().
*/
virtual void AllocateInputBuffers(int32_t numInputBuffers) override;
private:
/**
* @brief This is a private function to release CUDA device memory used for
* encoding.
*/
void ReleaseCudaResources();
protected:
CUcontext m_cuContext;
private:
size_t m_cudaPitch = 0;
};

View File

@@ -0,0 +1,145 @@
#include "nv_encoder.h"
#include <chrono>
#include "log.h"
#define SAVE_ENCODER_STREAM 0
VideoEncoder::VideoEncoder() {
if (SAVE_ENCODER_STREAM) {
file_ = fopen("saved/stream.h264", "w+b");
if (!file_) {
LOG_WARN("Fail to open saved/stream.h264");
}
}
}
VideoEncoder::~VideoEncoder() {
if (SAVE_ENCODER_STREAM && file_) {
fflush(file_);
fclose(file_);
file_ = nullptr;
}
if (nv12_data_) {
free(nv12_data_);
nv12_data_ = nullptr;
}
}
int VideoEncoder::Init() {
// Init cuda context
int num_of_GPUs = 0;
CUdevice cuda_device;
bool cuda_ctx_succeed =
(index_of_GPU >= 0 && cuInit(0) == CUresult::CUDA_SUCCESS &&
cuDeviceGetCount(&num_of_GPUs) == CUresult::CUDA_SUCCESS &&
(num_of_GPUs > 0 && index_of_GPU < num_of_GPUs) &&
cuDeviceGet(&cuda_device, index_of_GPU) == CUresult::CUDA_SUCCESS &&
cuCtxCreate(&cuda_context_, 0, cuda_device) == CUresult::CUDA_SUCCESS);
if (!cuda_ctx_succeed) {
}
encoder_ = new NvEncoderCuda(cuda_context_, frame_width, frame_height,
NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_NV12);
// Init encoder_ session
NV_ENC_INITIALIZE_PARAMS init_params;
init_params.version = NV_ENC_INITIALIZE_PARAMS_VER;
NV_ENC_CONFIG encode_config = {NV_ENC_CONFIG_VER};
init_params.encodeConfig = &encode_config;
encoder_->CreateDefaultEncoderParams(&init_params, codec_guid, preset_guid,
tuning_info);
init_params.encodeWidth = frame_width;
init_params.encodeHeight = frame_height;
init_params.encodeConfig->profileGUID = NV_ENC_H264_PROFILE_BASELINE_GUID;
init_params.encodeConfig->encodeCodecConfig.h264Config.level =
NV_ENC_LEVEL::NV_ENC_LEVEL_H264_31;
// TO TEST: not tested yet
// init_params.encodeConfig->gopLength = NVENC_INFINITE_GOPLENGTH;
init_params.encodeConfig->gopLength = keyFrameInterval_;
// Donot use B-frame for realtime application
init_params.encodeConfig->frameIntervalP = 1;
init_params.encodeConfig->rcParams.rateControlMode =
NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR;
init_params.encodeConfig->rcParams.maxBitRate = maxBitrate_ * 1000;
init_params.encodeConfig->encodeCodecConfig.h264Config.sliceMode = 1;
init_params.encodeConfig->encodeCodecConfig.h264Config.sliceModeData =
max_payload_size_;
encoder_->CreateEncoder(&init_params);
return 0;
}
int VideoEncoder::Encode(const uint8_t *pData, int nSize) {
if (!encoder_) {
LOG_ERROR("Invalid encoder");
return -1;
}
if (0 == seq_++ % (30 * 5)) {
ForceIdr();
}
#ifdef SHOW_SUBMODULE_TIME_COST
auto start = std::chrono::steady_clock::now();
#endif
const NvEncInputFrame *encoder_inputframe = encoder_->GetNextInputFrame();
NvEncoderCuda::CopyToDeviceFrame(
cuda_context_,
(void *)pData, // NOLINT
0, (CUdeviceptr)encoder_inputframe->inputPtr, encoder_inputframe->pitch,
encoder_->GetEncodeWidth(), encoder_->GetEncodeHeight(),
CU_MEMORYTYPE_HOST, encoder_inputframe->bufferFormat,
encoder_inputframe->chromaOffsets, encoder_inputframe->numChromaPlanes);
encoder_->EncodeFrame(encoded_packets_);
if (encoded_packets_.size() < 1) {
LOG_WARN("empty encoded_packets_");
return -1;
}
for (const auto &packet : encoded_packets_) {
OnEncodedImage((char *)packet.data(), packet.size());
if (SAVE_ENCODER_STREAM) {
fwrite((unsigned char *)packet.data(), 1, packet.size(), file_);
}
}
#ifdef SHOW_SUBMODULE_TIME_COST
auto encode_time_cost = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
LOG_INFO("Encode time cost {}ms", encode_time_cost);
#endif
return 0;
}
int VideoEncoder::OnEncodedImage(char *encoded_packets, size_t size) {
LOG_INFO("output encoded image");
fwrite(encoded_packets, 1, size, file_);
return 0;
}
void VideoEncoder::ForceIdr() {
NV_ENC_RECONFIGURE_PARAMS reconfig_params;
reconfig_params.version = NV_ENC_RECONFIGURE_PARAMS_VER;
NV_ENC_INITIALIZE_PARAMS init_params;
NV_ENC_CONFIG encode_config = {NV_ENC_CONFIG_VER};
init_params.encodeConfig = &encode_config;
encoder_->GetInitializeParams(&init_params);
reconfig_params.reInitEncodeParams = init_params;
reconfig_params.forceIDR = 1;
reconfig_params.resetEncoder = 1;
encoder_->Reconfigure(&reconfig_params);
}

View File

@@ -0,0 +1,36 @@
#ifndef _NV_ENCODER_H_
#define _NV_ENCODER_H_
#include "NvEncoderCuda.h"
class VideoEncoder {
public:
VideoEncoder();
~VideoEncoder();
int Init();
int Encode(const uint8_t* pData, int nSize);
virtual int OnEncodedImage(char* encoded_packets, size_t size);
void ForceIdr();
private:
int index_of_GPU = 0;
GUID codec_guid = NV_ENC_CODEC_H264_GUID;
GUID preset_guid = NV_ENC_PRESET_P2_GUID;
NV_ENC_TUNING_INFO tuning_info =
NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
int frame_width = 1280;
int frame_height = 720;
int keyFrameInterval_ = 3000;
int maxBitrate_ = 2000;
int max_payload_size_ = 3000;
NvEncoder* encoder_ = nullptr;
CUcontext cuda_context_ = nullptr;
std::vector<std::vector<uint8_t>> encoded_packets_;
unsigned char* encoded_image_ = nullptr;
FILE* file_ = nullptr;
unsigned char* nv12_data_ = nullptr;
unsigned int seq_ = 0;
};
#endif