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(&#34;QAI for ID3D11VideoDevice&#34;);
return false;
}
hr = m_d3d_context->QueryInterface(__uuidof(ID3D11VideoContext), (void**)&m_video_context);
if (FAILED(hr)){
Debug(&#34;QAI for ID3D11VideoContext&#34;);
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(&#34;QAI for ID3D11VideoDevice&#34;);
return false;
}
hr = m_d3d_context->QueryInterface(__uuidof(ID3D11VideoContext), (void**)&m_video_context);
if (FAILED(hr)){
Debug(&#34;QAI for ID3D11VideoContext&#34;);
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(&#34;CreateVideoProcessorEnumerator failed&#34;);
return false;
}
hr = m_video_device->CreateVideoProcessor(m_processor_enum, 0, &m_video_processor);;
if (FAILED(hr))
{
SAFE_RELEASE(m_video_processor);
Debug(&#34;CreateVideoProcessor failed&#34;);
return false;
}
}上面是videoporcessor的初始化。
下面是每一帧的转换代码。
ID3D11VideoProcessorInputView* videoProcInputView;
hr = m_video_device->CreateVideoProcessorInputView(m_nv12_texture, m_processor_enum, &m_input_desc, &videoProcInputView);
if (FAILED(hr)) {
Debug(&#34;CreateVideoProcessorInputView failed&#34;);
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(&#34;CreateVideoProcessorOutputView failed&#34;);
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(&#34;VideoProcessorBlt failed&#34;);
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硬解插件...... 请问有整个的参考代码么,想学习一下[思考] 工程比较大,而且暂时不太方便公开[笑哭],不过文章中的链接够学习了的
页:
[1]