我有一个opengl缓冲区,需要直接转发给ffmpeg来执行基于nvenc的h264编码。
我目前这样做的方法是glReadPixels将像素从帧缓冲区中取出,然后将该指针传递到ffmpeg中,这样它就可以将该帧编码为用于RTSP的H264数据包。但是,这是不好的,因为我不得不将字节从GPU内存复制到CPU内存中,只需将它们复制回GPU进行编码。
发布于 2018-05-14 22:40:21
如果你看看发帖的日期和这个答案的日期,你会注意到我花了很多时间在这上面。(这是我过去4周的全职工作)。
因为我很难让这件事起作用,所以我会写一篇简短的指南,希望能帮助那些发现这个问题的人。
提纲
我的基本流程是OGL帧缓冲对象颜色标记(纹理)→nvenc (nvidia编码器)
要注意的事情
有些事情要注意:
1) nvidia编码器可以接受YUV或RGB型图像。
2) FFMPEG 4.0及以下版本不能将RGB图像传递给nvenc。
3)根据我的问题,FFMPEG是接受RGB输入的已更新。
有一些不同的事情需要知道:
1) AVHWDeviceContext- -把这看作是ffmpegs设备抽象层。
2) AVHWFramesContext- -把这看作是ffmpegs硬件帧抽象层。
3) cuMemcpy2D- -将cuda映射的OGL纹理复制到ffmpeg创建的cuda缓冲区中所需的方法。
全面性
本指南是标准软件编码指南的补充。这是不完整的代码,应该只用于标准流。
代码细节
设置
您需要首先获得您的gpu名称,为此,我找到了一些代码(我不记得是从哪里得到的),这些代码发出了一些cuda调用,并得到了GPU名称:
int getDeviceName(std::string& gpuName)
{
//Setup the cuda context for hardware encoding with ffmpeg
NV_ENC_BUFFER_FORMAT eFormat = NV_ENC_BUFFER_FORMAT_IYUV;
int iGpu = 0;
CUresult res;
ck(cuInit(0));
int nGpu = 0;
ck(cuDeviceGetCount(&nGpu));
if (iGpu < 0 || iGpu >= nGpu)
{
std::cout << "GPU ordinal out of range. Should be within [" << 0 << ", "
<< nGpu - 1 << "]" << std::endl;
return 1;
}
CUdevice cuDevice = 0;
ck(cuDeviceGet(&cuDevice, iGpu));
char szDeviceName[80];
ck(cuDeviceGetName(szDeviceName, sizeof(szDeviceName), cuDevice));
gpuName = szDeviceName;
epLog::msg(epMSG_STATUS, "epVideoEncode:H264Encoder", "...using device \"%s\"", szDeviceName);
return 0;
}接下来,您需要设置hwdevice和hwframe上下文:
getDeviceName(gpuName);
ret = av_hwdevice_ctx_create(&m_avBufferRefDevice, AV_HWDEVICE_TYPE_CUDA, gpuName.c_str(), NULL, NULL);
if (ret < 0)
{
return -1;
}
//Example of casts needed to get down to the cuda context
AVHWDeviceContext* hwDevContext = (AVHWDeviceContext*)(m_avBufferRefDevice->data);
AVCUDADeviceContext* cudaDevCtx = (AVCUDADeviceContext*)(hwDevContext->hwctx);
m_cuContext = &(cudaDevCtx->cuda_ctx);
//Create the hwframe_context
// This is an abstraction of a cuda buffer for us. This enables us to, with one call, setup the cuda buffer and ready it for input
m_avBufferRefFrame = av_hwframe_ctx_alloc(m_avBufferRefDevice);
//Setup some values before initialization
AVHWFramesContext* frameCtxPtr = (AVHWFramesContext*)(m_avBufferRefFrame->data);
frameCtxPtr->width = width;
frameCtxPtr->height = height;
frameCtxPtr->sw_format = AV_PIX_FMT_0BGR32; // There are only certain supported types here, we need to conform to these types
frameCtxPtr->format = AV_PIX_FMT_CUDA;
frameCtxPtr->device_ref = m_avBufferRefDevice;
frameCtxPtr->device_ctx = (AVHWDeviceContext*)m_avBufferRefDevice->data;
//Initialization - This must be done to actually allocate the cuda buffer.
// NOTE: This call will only work for our input format if the FFMPEG library is >4.0 version..
ret = av_hwframe_ctx_init(m_avBufferRefFrame);
if (ret < 0) {
return -1;
}
//Cast the OGL texture/buffer to cuda ptr
CUresult res;
CUcontext oldCtx;
m_inputTexture = texture;
res = cuCtxPopCurrent(&oldCtx); // THIS IS ALLOWED TO FAIL
res = cuCtxPushCurrent(*m_cuContext);
res = cuGraphicsGLRegisterImage(&cuInpTexRes, m_inputTexture, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
res = cuCtxPopCurrent(&oldCtx); // THIS IS ALLOWED TO FAIL
//Assign some hardware accel specific data to AvCodecContext
c->hw_device_ctx = m_avBufferRefDevice;//This must be done BEFORE avcodec_open2()
c->pix_fmt = AV_PIX_FMT_CUDA; //Since this is a cuda buffer, although its really opengl with a cuda ptr
c->hw_frames_ctx = m_avBufferRefFrame;
c->codec_type = AVMEDIA_TYPE_VIDEO;
c->sw_pix_fmt = AV_PIX_FMT_0BGR32;
// Setup some cuda stuff for memcpy-ing later
m_memCpyStruct.srcXInBytes = 0;
m_memCpyStruct.srcY = 0;
m_memCpyStruct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY;
m_memCpyStruct.dstXInBytes = 0;
m_memCpyStruct.dstY = 0;
m_memCpyStruct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;请记住,尽管上面做了很多工作,但所显示的代码是标准软件编码代码之外的代码。确保还包括所有这些调用/对象初始化。
与软件版本不同的是,输入AVFrame对象所需的只是在分配调用之后获得缓冲区:
// allocate RGB video frame buffer
ret = av_hwframe_get_buffer(m_avBufferRefFrame, rgb_frame, 0); // 0 is for flags, not used at the moment注意,它以hwframe_context作为参数,它是如何知道在gpu上分配什么设备、大小、格式等的。
调用对每个帧进行编码
现在我们已经安装好了,准备好编码了。在每个编码之前,我们需要将框架从纹理复制到cuda缓冲区。为此,我们将库达数组映射到纹理,然后将该数组复制到cuDeviceptr (由上面的av_hwframe_get_buffer调用分配):
//Perform cuda mem copy for input buffer
CUresult cuRes;
CUarray mappedArray;
CUcontext oldCtx;
//Get context
cuRes = cuCtxPopCurrent(&oldCtx); // THIS IS ALLOWED TO FAIL
cuRes = cuCtxPushCurrent(*m_cuContext);
//Get Texture
cuRes = cuGraphicsResourceSetMapFlags(cuInpTexRes, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
cuRes = cuGraphicsMapResources(1, &cuInpTexRes, 0);
//Map texture to cuda array
cuRes = cuGraphicsSubResourceGetMappedArray(&mappedArray, cuInpTexRes, 0, 0); // Nvidia says its good practice to remap each iteration as OGL can move things around
//Release texture
cuRes = cuGraphicsUnmapResources(1, &cuInpTexRes, 0);
//Setup for memcopy
m_memCpyStruct.srcArray = mappedArray;
m_memCpyStruct.dstDevice = (CUdeviceptr)rgb_frame->data[0]; // Make sure to copy devptr as it could change, upon resize
m_memCpyStruct.dstPitch = rgb_frame->linesize[0]; // Linesize is generated by hwframe_context
m_memCpyStruct.WidthInBytes = rgb_frame->width * 4; //* 4 needed for each pixel
m_memCpyStruct.Height = rgb_frame->height; //Vanilla height for frame
//Do memcpy
cuRes = cuMemcpy2D(&m_memCpyStruct);
//release context
cuRes = cuCtxPopCurrent(&oldCtx); // THIS IS ALLOWED TO FAIL现在,我们可以简单地调用send_frame,这一切都成功了!
ret = avcodec_send_frame(c, rgb_frame); 注意:我忽略了大部分代码,因为它不是公开的。我可能有一些细节不正确,这就是我如何能够理解我收集的所有数据在过去的month...feel免费纠正任何不正确的东西。此外,有趣的是,在所有这些过程中,我的电脑崩溃了,我失去了所有的初步调查(所有我没有登录到源代码管理),其中包括我在互联网上找到的所有示例代码。所以如果你看到什么东西是你的,请大声说出来。这可以帮助别人得出我的结论。
喊出来
在BtbN # https://webchat.freenode.net/ #ffmpeg上大声呼喊,没有他们的帮助,我是不会得到任何帮助的。
发布于 2018-04-16 22:25:35
首先要检查的是,它可能是“坏的”,但它跑得够快吗?提高效率总是很好的,但如果有效,就不要破坏它。
如果真的有表演问题..。
1仅使用FFMPEG软件编码,无需硬件辅助。然后,您将只复制从GPU到CPU一次。(如果视频编码器在GPU上,并且您通过RTSP发送数据包,那么在编码之后还有第二个GPU到CPU。)
2寻找一个NVIDIA (我假设这是GPU给您讨论的nvenc) GL扩展纹理格式和/或命令,将执行GPU H264编码直接到OpenGL缓冲区。
https://stackoverflow.com/questions/49862610
复制相似问题