LiteralliJeff 发表于 2021-12-17 15:30

UE4 FFmpeg硬解插件

此插件主要目的是利用FFmpeg对mp4硬解解码,然后把解码出来的每一帧(GPU纹理)传递给UE4(Unity后续讲解),本文主要记录的是项目过程中伤脑筋的部分。主要参考:
,在此非常感谢天天不在的热心指导。
项目之初,已经参考官方文档实现了硬件解码,但是该文档使用av_hwframe_transfer_data 函数把硬解数据从GPU转成CPU(NV12),当mp4的分辨率是2048x2048时,耗时6ms,提高分辨率到4096x4096时,耗时增加到24ms。明显,该函数不适合大分辨率的转换,如果项目不要求播放分辨率超过2048x2048的视频,或者对性能要求不高,可以不用往下看了。不幸的是本项目不允许24ms的耗时,所以得想办法把GPU得数据直接传给UE4,那么问题来了,怎么获取GPU数据,UE4怎么去拿,UE4怎么显示,针对这3个问题,我们来一一解答。
ps:如果有童鞋在程序中报错: Static surface pool size exceeded,是因为在解码后没有掉用av_frame_free()。


<hr/>1、获取GPU数据

在window平台,使用dx11作为ffmpeg和UE4传递的桥梁,GPU数据保存为ID3D11Texture2D*格式,使用dx11新建纹理前必须得到ID3D11Device*,ID3D11DeviceContext*,当然我这里还需要得到ID3D11VideoDevice*和ID3D11VideoContext*,后面两个指针的主要作用时格式转换: ID3D11Texture2D* NV12->RGBA。格式转换主要参考:
如果还有其他转换方式可以私信我,尤其是使用计算管线(hlsl),我有尝试使用hlsl,但是时间紧张放弃了。
下面是设备的初始化代码,这里我遇到了最大的坑,我最开始使用的第二种方式,最后播放出现问题,播放到51帧就卡卡卡住了...,调试发现一直卡在avcodec_send_packet(),如果使用第一种方式就没问题,个人猜测是D3D11CreateDevice函数有些参数没有设置正确。(如果有知道原因可以私信告诉我,万分感谢!!!)
方式 1:
ID3D11Texture2D* hwTexture = reinterpret_cast<ID3D11Texture2D*>(dat->pAvFrame->data);
if (!m_d3d_device)
        hwTexture->GetDevice(&m_d3d_device);
if (!m_d3d_context)
        m_d3d_device->GetImmediateContext(&m_d3d_context);
HRESULT hr = m_d3d_device->QueryInterface(__uuidof(ID3D11VideoDevice), (void**)&m_video_device);
if (FAILED(hr)){
    Debug("QAI for ID3D11VideoDevice");
    return false;
}
hr = m_d3d_context->QueryInterface(__uuidof(ID3D11VideoContext), (void**)&m_video_context);
if (FAILED(hr)){
    Debug("QAI for ID3D11VideoContext");
    return false;
}

方式 2:
HRESULT hr = S_OK;
UINT uCreationFlags = D3D11_CREATE_DEVICE_SINGLETHREADED;// D3D11_CREATE_DEVICE_SINGLETHREADED;//D3D11_CREATE_DEVICE_BGRA_SUPPORT;

D3D_FEATURE_LEVEL flOut;
static const D3D_FEATURE_LEVEL flvl[] = {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
        };//D3D_FEATURE_LEVEL_10_1,
bool bHaveCompute = false;
hr = D3D11CreateDevice(
        nullptr,                   // Use default graphics card
        D3D_DRIVER_TYPE_HARDWARE,// Try to create a hardware accelerated                                                                   // device
        nullptr,         // Do not use external software rasterizer module
        uCreationFlags,// Device creation flags
        flvl, sizeof(flvl) / sizeof(D3D_FEATURE_LEVEL),
        D3D11_SDK_VERSION,// SDK version
        deviceDx11,         // Device out
        &flOut,             // Actual feature level created
        ctxDx11);         // Context out
hr = m_d3d_device->QueryInterface(__uuidof(ID3D11VideoDevice), (void**)&m_video_device);
if (FAILED(hr)){
    Debug("QAI for ID3D11VideoDevice");
    return false;
}
hr = m_d3d_context->QueryInterface(__uuidof(ID3D11VideoContext), (void**)&m_video_context);
if (FAILED(hr)){
    Debug("QAI for ID3D11VideoContext");
    return false;
}
获取GPU(NV12):
m_d3d_context->CopySubresourceRegion(m_nv12_texture, 0, 0, 0, 0, hwTexture, (UINT)(unsigned long long)dat->pAvFrame->data, nullptr);GPU(NV12->RGBA):
bool FFmpegReader::initVideoProcessor()
{
        m_input_desc = { 0, D3D11_VPIV_DIMENSION_TEXTURE2D,{ 0,0 } };
        m_output_desc = { D3D11_VPOV_DIMENSION_TEXTURE2D };
        m_output_desc.Texture2D.MipSlice = 0;
        D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc =
        {
                D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE,
                { 1, 1 }, m_dx11_format.width, m_dx11_format.height,
                { 1, 1 }, m_dx11_format.width, m_dx11_format.height,
                D3D11_VIDEO_USAGE_PLAYBACK_NORMAL
        };
        HRESULT hr = m_video_device->CreateVideoProcessorEnumerator(&contentDesc, &m_processor_enum);
        if (FAILED(hr)) {
                SAFE_RELEASE(m_processor_enum);
                Debug("CreateVideoProcessorEnumerator failed");
                return false;
        }
        hr = m_video_device->CreateVideoProcessor(m_processor_enum, 0, &m_video_processor);;
        if (FAILED(hr))
        {
                SAFE_RELEASE(m_video_processor);
                Debug("CreateVideoProcessor failed");
                return false;
        }
}上面是videoporcessor的初始化。
下面是每一帧的转换代码。
ID3D11VideoProcessorInputView* videoProcInputView;
hr = m_video_device->CreateVideoProcessorInputView(m_nv12_texture, m_processor_enum, &m_input_desc, &videoProcInputView);
if (FAILED(hr)) {
    Debug("CreateVideoProcessorInputView failed");
    return false;
}

ID3D11VideoProcessorOutputView* videoProcOuputView;
hr = m_video_device->CreateVideoProcessorOutputView(m_rgba_texture, m_processor_enum, &m_output_desc, &videoProcOuputView);
if (FAILED(hr))
{
    SAFE_RELEASE(videoProcInputView);
    Debug("CreateVideoProcessorOutputView failed");
    return false;
}
// Setup Streams
D3D11_VIDEO_PROCESSOR_STREAM streams = { TRUE, 0, 0, 0, 0, nullptr, videoProcInputView, nullptr };
streams.Enable = TRUE;
streams.pInputSurface = videoProcInputView;
hr = m_video_context->VideoProcessorBlt(m_video_processor, videoProcOuputView, 0, 1, &streams);
if (FAILED(hr)) {
    Debug("VideoProcessorBlt failed");
    return false;
}Ps: RGBA的纹理,BindFlags = D3D11_BIND_RENDER_TARGET。
这里又遇到另外一个坑,怎么验证每一步的GPU纹理是对的,尤其是RGBA的ID3D11Texture2D纹理,参考代码:
里面有函数:getTextureData() ,具体可以搜ID3D11DeviceContext::Map函数,当我验证m_nv12_texture,Map之后数据都为0,我是这么解决的:
AVFrame* sw_frame = av_frame_alloc();
av_hwframe_transfer_data(sw_frame, dat->pAvFrame, 0);在获取GPU(NV12)之前加上这两句话,具体原因我也不大明白,个人猜想是av_hwframe_transfer_data 初始化了xxx。(如果知道原因可以私信告诉我,万分感谢!!!)
2、UE4怎么去拿

这部分问题倒没什么坑,主要要理解共享纹理,共享纹理是指创建ID3D11Texture2D* 时MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,如果对于D3D11_TEXTURE2D_DESC的各个参数不大明白,可参考:
对于如何ID3D11Texture2D*传递给UE4, 可参考:
里面有函数copySharedToTexture(), 这里有个坑,这里有个函数IDXGIKeyedMutex::AcquireSync() ,下面是注意事项,设备初次访问共享纹理,AcquireSync(0, 0)...ReleaseSync(1), 使用1作为锁 ,再次访问,AcquireSync(1,0)...ReleaseSync(0)。所以copySharedToTexture不能直接copy,至少在我项目里是pDX11Mutex->AcquireSync(1, 0),然后pDX11Mutex->ReleaseSync(0)。


3、UE4怎么显示

参考代码:
这里也有一个坑,对于UTexture2D::CreateTransient(width, height, format)函数,EPixelFormat是支持PF_NV12格式的,但是当我填进去时,程序crash。不得不说UE4的文档就是shit,这也是我在底层把ID3D11Texture2D* NV12 -> ID3D11Texture* RGBA 的原因,如果能直接传NV12可以私信我,感谢感谢。
下一期会讲解 Unity FFmpeg硬解插件......

unityloverz 发表于 2021-12-17 15:35

请问有整个的参考代码么,想学习一下[思考]

七彩极 发表于 2021-12-17 15:37

工程比较大,而且暂时不太方便公开[笑哭],不过文章中的链接够学习了的
页: [1]
查看完整版本: UE4 FFmpeg硬解插件