首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >OpenGL到FFMpeg编码

OpenGL到FFMpeg编码
EN

Stack Overflow用户
提问于 2018-04-16 16:58:48
回答 2查看 4.7K关注 0票数 11

我有一个opengl缓冲区,需要直接转发给ffmpeg来执行基于nvenc的h264编码。

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 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名称:

代码语言:javascript
复制
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上下文:

代码语言:javascript
复制
    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对象所需的只是在分配调用之后获得缓冲区:

代码语言:javascript
复制
// 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调用分配):

代码语言:javascript
复制
//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,这一切都成功了!

代码语言:javascript
复制
        ret = avcodec_send_frame(c, rgb_frame); 

注意:我忽略了大部分代码,因为它不是公开的。我可能有一些细节不正确,这就是我如何能够理解我收集的所有数据在过去的month...feel免费纠正任何不正确的东西。此外,有趣的是,在所有这些过程中,我的电脑崩溃了,我失去了所有的初步调查(所有我没有登录到源代码管理),其中包括我在互联网上找到的所有示例代码。所以如果你看到什么东西是你的,请大声说出来。这可以帮助别人得出我的结论。

喊出来

在BtbN # https://webchat.freenode.net/ #ffmpeg上大声呼喊,没有他们的帮助,我是不会得到任何帮助的。

票数 23
EN

Stack Overflow用户

发布于 2018-04-16 22:25:35

首先要检查的是,它可能是“坏的”,但它跑得够快吗?提高效率总是很好的,但如果有效,就不要破坏它。

如果真的有表演问题..。

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

2寻找一个NVIDIA (我假设这是GPU给您讨论的nvenc) GL扩展纹理格式和/或命令,将执行GPU H264编码直接到OpenGL缓冲区。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49862610

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档