Good day to all!
I’m having some problems getting the nVenc encoder to work. I’m mostly using the C++ classes provided on the SDK (NvEncoder and NvEncoderCuda), with just some methods I added to NvEncoderCuda so I could make most of the validations suggested on the programming guide.
I simplified the application, so I could just test the encode part. I’m rendering a simple OpenGL scene, and sending the frames to the encoder through a registered pixelbuffer as a cuda resource. Then saving the output on a file, the same way is made on the examples.
I wonder if it is just some dumb mistake I made, or if the lack of correct rate control configuration (I was trying to get a reasonable output before fixing it)
If someone is willing to help, the important parts of the code are below. Thanks in advance!
This is what I’m rendering (screen captured) :
And this is the NVENC output:
The OpenGL code:
// (...)<setting other OpenGL things for the render> //
unsigned int frame_buffer;
glGenFramebuffers(1, &frame_buffer);
glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
unsigned int texture_color_buffer;
glGenTextures(1, &texture_color_buffer);
glBindTexture(GL_TEXTURE_2D, texture_color_buffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_color_buffer, 0);
unsigned int render_buffer_depth;
glGenRenderbuffers(1, &render_buffer_depth);
glBindRenderbuffer(GL_RENDERBUFFER, render_buffer_depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, render_buffer_depth);
unsigned int pixel_buffer;
glGenBuffers(1, &pixel_buffer);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pixel_buffer);
glBufferData(GL_PIXEL_PACK_BUFFER, SCR_WIDTH * SCR_HEIGHT * 4, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
// the call to set and create de encoder
EncodeNVENC encoder(0, pixel_buffer, SCR_WIDTH, SCR_HEIGHT, "encoded.h264");
while (!glfwWindowShouldClose(window))
{
glEnable(GL_DEPTH_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processInput(window);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// (...)<setting some uniforms, camera movement and rendering the model> //
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_buffer);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pixel_buffer);
glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
encoder.Encode();
// (...)<renders the output to a quad on the screen> //
}
encoder.CleanupEncoder();
the class I made to encode:
// .h part
class EncodeNVENC
{
public:
EncodeNVENC(unsigned int cuda_device_id, unsigned int gl_pixel_buffer, unsigned int width, unsigned int height, const char* output_file_path);
void Encode();
void CleanupEncoder();
private:
unsigned int cuda_device_id;
CUcontext cuda_context;
CUdevice cuda_device;
NvEncoderCuda* cuda_encoder;
struct cudaGraphicsResource* cuda_pixel_buffer;
unsigned int gl_pixel_buffer;
std::ofstream output_file;
unsigned int width, height;
};
// .cpp part
EncodeNVENC::EncodeNVENC(unsigned int cuda_device_id, unsigned int gl_pixel_buffer, unsigned int width, unsigned int height, const char* output_file_path) :
cuda_device_id(cuda_device_id), gl_pixel_buffer(gl_pixel_buffer), width(width), height(height)
{
// For now, hardcoded GUIDS
GUID encoder_GUID = NV_ENC_CODEC_H264_GUID;
GUID preset_GUID = NV_ENC_PRESET_P7_GUID;
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_ABGR;
NV_ENC_TUNING_INFO tuning_info = NV_ENC_TUNING_INFO_HIGH_QUALITY;
// --------------------------------------------- CUDA AND GUIDs VALIDATION START --------------------------------------------- //
if (cuInit(0) < 0)
{
std::cout << "CUDA driver initializing error!" << std::endl;
return;
}
int n_devices = 0;
if (cuDeviceGetCount(&n_devices) < 0)
{
std::cout << "Could not assert the number of CUDA devices!" << std::endl;
return;
}
if (cuda_device_id < 0 || cuda_device_id >= n_devices)
{
std::cout << "Requested device ID not found. The device ID must be between 0 and " << n_devices - 1 << std::endl;
return;
}
if (cuDeviceGet(&cuda_device, cuda_device_id) < 0)
{
std::cout << "Error getting the device with following ID: " << cuda_device_id << std::endl;
return;
}
if (cuCtxCreate(&cuda_context, 0, cuda_device) < 0)
{
std::cout << "Error on CUDA context creation!" << std::endl;
return;
}
cuda_encoder = new NvEncoderCuda(cuda_context, width, height, buffer_format);
unsigned int encode_GUID_count = cuda_encoder->GetEncodeGUIDCount();
if (encode_GUID_count < 1)
{
std::cout << "No compatible encode GUIDs with this device" << std::endl;
return;
}
std::vector<GUID> encode_GUID_list;
encode_GUID_list.resize(encode_GUID_count);
cuda_encoder->GetEncodeList(encode_GUID_list.data(), encode_GUID_count, &encode_GUID_count);
if (std::find(encode_GUID_list.begin(), encode_GUID_list.end(), encoder_GUID) == encode_GUID_list.end())
{
std::cout << "Selected encoder GUID not compatible with this device" << std::endl;
return;
}
unsigned int encode_preset_GUID_count = cuda_encoder->GetEncodePresetCount(encoder_GUID);
if (encode_preset_GUID_count < 1)
{
std::cout << "No presets were found for this encode GUID!" << std::endl;
return;
}
std::vector<GUID> encode_preset_GUID_list;
encode_preset_GUID_list.resize(encode_preset_GUID_count);
cuda_encoder->GetEncodePresetList(encoder_GUID, encode_preset_GUID_list.data(), encode_preset_GUID_count, &encode_preset_GUID_count);
if (std::find(encode_preset_GUID_list.begin(), encode_preset_GUID_list.end(), preset_GUID) == encode_preset_GUID_list.end())
{
std::cout << "Selected preset not found!" << std::endl;
return;
}
unsigned int input_format_count = cuda_encoder->GetInputFormatCount(encoder_GUID);
if (input_format_count < 1)
{
std::cout << "Error getting compatible input formats!" << std::endl;
return;
}
std::vector<NV_ENC_BUFFER_FORMAT> input_format_list;
input_format_list.resize(encode_preset_GUID_count);
cuda_encoder->GetInputFormats(encoder_GUID, input_format_list.data(), input_format_count, &input_format_count);
if (std::find(input_format_list.begin(), input_format_list.end(), buffer_format) == input_format_list.end())
{
std::cout << "Selected input format not found!" << std::endl;
return;
}
// --------------------------------------------- CUDA AND GUIDs VALIDATION END --------------------------------------------- //
// Encoder configuration
NV_ENC_INITIALIZE_PARAMS initialize_params = { NV_ENC_INITIALIZE_PARAMS_VER };
NV_ENC_CONFIG encode_config = { NV_ENC_CONFIG_VER };
NvEncoderInitParam encode_options;
initialize_params.encodeConfig = &encode_config;
encode_options.SetInitParams(&initialize_params, buffer_format);
cuda_encoder->CreateDefaultEncoderParams(&initialize_params, encoder_GUID, preset_GUID, tuning_info);
cuda_encoder->CreateEncoder(&initialize_params);
// OpenGL pixel buffer registration as cuda resource
cudaError_t result = cudaGraphicsGLRegisterBuffer(&cuda_pixel_buffer, gl_pixel_buffer, cudaGraphicsRegisterFlagsReadOnly);
if (result != cudaError::cudaSuccess)
{
std::cout << "Failed to register OpenGL pixel buffer as a cuda resource!" << std::endl;
}
// File oppening
output_file.open(output_file_path, std::ios::out, std::ios::binary);
}
void EncodeNVENC::Encode()
{
char* mapped_cuda_pixel_buffer = nullptr;
size_t num_bytes;
//Mapping the resource
cudaError_t result = cudaGraphicsMapResources(1, &cuda_pixel_buffer);
if (result != cudaError::cudaSuccess)
{
std::cout << "Failed to map the cuda resource" << std::endl;
return;
}
result = cudaGraphicsResourceGetMappedPointer((void**)&mapped_cuda_pixel_buffer, &num_bytes, cuda_pixel_buffer);
if (result != cudaError::cudaSuccess)
{
std::cout << "Failed to get mapped cuda resource pointer" << std::endl;
return;
}
// Transfer data to the input frame
const NvEncInputFrame* input_frame = cuda_encoder->GetNextInputFrame();
NvEncoderCuda::CopyToDeviceFrame(cuda_context, mapped_cuda_pixel_buffer, 0, (CUdeviceptr)input_frame->inputPtr,
(int)input_frame->pitch,
width,
height,
CU_MEMORYTYPE_DEVICE,
NV_ENC_BUFFER_FORMAT_ABGR,
input_frame->chromaOffsets,
input_frame->numChromaPlanes);
cudaGraphicsUnmapResources(1, &cuda_pixel_buffer);
std::vector<std::vector<uint8_t>> output_buffer{};
cuda_encoder->EncodeFrame(output_buffer);
for (std::vector<uint8_t>& packet : output_buffer)
{
output_file.write(reinterpret_cast<char*>(packet.data()), packet.size());
}
}
void EncodeNVENC::CleanupEncoder()
{
std::vector<std::vector<uint8_t>> drawing_buffer{};
cuda_encoder->EndEncode(drawing_buffer);
for (std::vector<uint8_t>& packet : drawing_buffer)
{
output_file.write(reinterpret_cast<char*>(packet.data()), packet.size());
}
output_file.close();
cuda_encoder->DestroyEncoder();
delete cuda_encoder;
}
I have also tried a infinitude of custom initialize parameters, presets, encode configs and encode codec configurations, but aways ended with similar results.