OpenGL到FFMpeg编码

OpenGL到FFMpeg编码,opengl,ffmpeg,encode,h.264,Opengl,Ffmpeg,Encode,H.264,我有一个opengl缓冲区,我需要将它直接转发到ffmpeg以执行基于nvenc的h264编码 我目前的做法是glReadPixels将像素从帧缓冲区中取出,然后将指针传递到ffmpeg中,这样它就可以将帧编码到H264数据包中,用于RTSP。但是,这是不好的,因为我必须将字节从GPU ram复制到CPU ram中,以便只将它们复制回GPU进行编码。首先要检查的是,它可能是“坏的”,但它是否运行得足够快?提高效率总是好的,但如果它有效,不要破坏它 如果确实存在性能问题 1仅使用FFMPEG软件编

我有一个
opengl
缓冲区,我需要将它直接转发到
ffmpeg
以执行基于nvenc的
h264
编码


我目前的做法是
glReadPixels
将像素从帧缓冲区中取出,然后将指针传递到
ffmpeg
中,这样它就可以将帧编码到
H264
数据包中,用于
RTSP
。但是,这是不好的,因为我必须将字节从GPU ram复制到CPU ram中,以便只将它们复制回GPU进行编码。

首先要检查的是,它可能是“坏的”,但它是否运行得足够快?提高效率总是好的,但如果它有效,不要破坏它

如果确实存在性能问题

1仅使用FFMPEG软件编码,无需硬件协助。然后,您将只从GPU复制到CPU一次。(如果视频编码器位于GPU上,并且您正在通过RTSP发送数据包,则编码后会有第二个GPU发送到CPU。)


2寻找一个NVIDIA(我假设这是你所说的nvenc的GPU)GL对纹理格式的扩展和/或将在GPU H264上直接编码到OpenGL缓冲区的命令。

如果你看看发布日期和这个答案的日期,你会注意到我在这方面花了很多时间。(这是我过去4周的全职工作)

因为我很难做到这一点,我将写一个简短的指南,希望能帮助任何人找到这个

轮廓 我的基本流程是OGL帧缓冲区对象颜色附加(纹理)→ nvenc(nvidia编码器)

注意事项 需要注意的事项:
1)英伟达编码器可以接受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;
}
注意,它在hwframe_上下文中作为参数,这是它如何知道在gpu上分配什么设备、大小、格式等的

调用对每个帧进行编码 现在我们已经设置好了,可以开始编码了。在每次编码之前,我们需要将帧从纹理复制到cuda缓冲区。我们通过将cuda数组映射到纹理,然后将该数组复制到cuDeviceptr(由上面的av_hwframe_get_buffer调用分配):

现在,我们可以简单地调用send_frame,一切正常

        ret = avcodec_send_frame(c, rgb_frame); 
注意:我遗漏了大部分代码,因为它不是面向公众的。我可能有一些细节不正确,这就是我如何能够理解我在过去一个月收集的所有数据…请随意更正任何不正确的内容。另外,有趣的是,在这一切发生的过程中,我的电脑死机了,我失去了所有的初步调查(所有我没有检查到的源代码控制),其中包括我在互联网上找到的各种示例代码。所以,如果你看到什么是你的,请大声说出来。这可以帮助其他人得出我得出的结论

叫喊
在#ffmpeg向BtbN大声呼喊,如果没有他们的帮助,我不会得到任何这些。

谢谢你花时间写下这个过程!您是否看到使用此方法时有任何显著的性能差异?是的。对于我们的使用,硬件方法大约快了10倍左右。
// allocate RGB video frame buffer
    ret = av_hwframe_get_buffer(m_avBufferRefFrame, rgb_frame, 0);  // 0 is for flags, not used at the moment
//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
        ret = avcodec_send_frame(c, rgb_frame);