概述
写在之前
耗时2个月,写完公司的音视频处理系统。对于整个音视频处理有了基本的了解。个人感觉最坑的地方有三:
- 编解码
- 音视频录制的同步
- 视频预览渲染(视频帧的渲染)
由于在以后要支持同时多路1080P录制及预览,所以对于性能的要求也是非常高的。虽然目前实现是分两步走,先录制再处理,但如果能做到一步到位就非常好了。有空再去优化整个项目。
渲染
选择API
对于视频的渲染来说,已经去世的雷博给了一个DEMO。其中包含了OPENGL/DIRECT3D9/GDI等不同的实现方式,也给了我很大的帮助。
必须要说的是,我个人喜欢用新不用旧。故在第一版的demo实现之后,便想摒弃D3D9的实现方式了。毕竟他是一个十几年前的API了,且WIN10系统已经不包含d3dx9这个组件了。在选择实现的方式上,通过各方资料来看。有这么一些选择:
- D3D11 —D3D11貌似是现有很多商业视频播放器必须包含的组件了。但D3D11在WIN10系统上的编译还是需要从原来的SDK中拷贝过来的头文件和库,不爽!= =
- D3D12 —D3D12是微软最新的库,而且是Win10 only,十分符合我的价值观嘻嘻嘻。但由于多线程及性能上的考虑,整个API设计的非常底层。原有的很多便利的接口已经没有了,原来很多显卡驱动做的事情也必须交由程序员自己去控制。学习成本我感觉是很高, 对于没有学习过图形API的人来说。然而我还是最终选择了他去实现。
- D2D —D2D是微软在最近几个Direct版本中,独立出来用于2D开发的一套API(貌似用于替换原有的DirectShow)。其底层还是使用D3D进行硬件加速。而且API风格与D3D很像,并可以和D3D、MF等组件方便的沟通。第二版程序便是用此实现。相对于D3D12来说,的确在绘制视频帧上有很大的方便。
- MMF —Microsoft Media Foundation是微软最新的音视频处理体系,在知乎上看轮子哥说非常强大,看官方的Demo的确非常好用,但是由于学习成本和我的职业规划路线,并没有选择这个API。(其实应该选用此的。。。)
所以最终,我们选择了D3D12来实现,本文会将第二版D2D实现一起给出。
D3D12 RGBA渲染
我们本文关注的重点为如何将获取到的RGBA数据或YUV数据以较低的开销渲染到屏幕上。
整个程序的UI是用QT5.8来画的,所以对于创建窗口来说也是非常简单。直接一个QWidget就解决问题了。直接利用Qt中的时间来驱动整个流程。
D3D12的初始化是比较麻烦的,借鉴了微软的官方例程D3D12HelloTexture(Git代码下载),其中已经利用循环生成了一个黑白块相间的纹理,并将其放在一个三角形上显示在屏幕上。
那么我们需要做的就是:
- 显示为全屏的矩形
- 在视频帧回调过来时,将纹理更新
所以我们改变灵活顶点的格式:
Vertex RectVertices[] =
{
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f } },
{ { 1.0f, 1.0f, 0.0f },{ 1.0f, 0.0f } },
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 1.0f } },
{ { 1.0f, 1.0f, 0.0f },{ 1.0f, 0.0f } },
{ { 1.0f, -1.0f, 0.0f },{ 1.0f, 1.0f } },
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 1.0f } },
};
这样就是将屏幕划分为上下两个三角形,并设置了每个顶点的纹理坐标:
这里只要注意一下平面的正反就行了,根据左手坐标系。
在生成纹理的时候,我们直接读取文件中的RGBA数据作为测试,纹理的像素格式设置为DXGI_FORMAT_R8G8B8A8_UNORM
。
std::vector<UINT8> GenerateTextureData()
{
const UINT rowPitch = 1920 * 4;
const UINT cellPitch = rowPitch >> 3; // The width of a cell in the checkboard texture.
const UINT cellHeight = 1080 >> 3; // The height of a cell in the checkerboard texture.
const UINT textureSize = rowPitch * 1080;
static FILE *fp = fopen("d:/rgb.rgb", "rb+");
static BYTE* buffer = (BYTE*)malloc(1920 * 1080 * 4);
if (fread(buffer, 1, 1920 * 1080 * 4, fp) != 1920 * 1080 * 4)
{
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, 1920 * 1080 * 4, fp);
}
std::vector<UINT8> data(textureSize);
UINT8* pData = &data[0];
memcpy(pData, buffer, textureSize);
return data;
}
//OnRender
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST));
UpdateSubresources(m_commandList.Get(), m_texture.Get(), textureUploadHeap.Get(), 0, 0, 1, &textureData);
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
这样就完成了在D3D12中渲染RGBA数据的操作。
D3D12 YUV420P渲染
在D3D12中,纹理直接支持了NV12等比较经常使用的YUV颜色格式,但是在使用上可能因为我的用法有些许问题,并不能直接以一张NV12或者YUV2的纹理来直接显示YUV格式(PS:我使用NV12渲染时,按照MSDN的说法,创建了两个子纹理,分变为R8->Y和R8G8->UV格式。但显示出来为红色,调试shader发现UV分量的子纹理数据为空,求高人解答)。
直接失败以后,我采用了比较直观的方法。
- 创建2个或3个纹理,这里我为了方便直接创建了3个纹理(Y,U,V),格式均为A8。当源数据为YUV420P时,Y纹理分辨率与原分辨率相同,UV纹理宽高均为原分辨率的一半。
- 在Pixshader中,从三个纹理中分别取出YUV分量,然后按照YUV->RGB的算法直接转换成RGB格式输出。
算法:
c++
在DX12中,取纹理刚好是用采样器加上纹理坐标的形式。对于三张纹理,一个点的采样刚好均为纹理坐标。所以也不用去特殊的内存操作,直接上传纹理采样计算。
其余的内容与D3D12渲染RGB数据一样不再赘述。
D2D RGB/YUV渲染
在D2D中,渲染的流程基本与D3D是一样的。但需要注意的几点是:
- D2D原生不支持很多普通的颜色格式。所以我们需要通过WIC去转换。
- 注意渲染的位置及图片大小。
- 特别的,对于YUV相关格式来说,我们需要创建两个WIC的位图来分别装载Y分量和UV分量。再将两个WIC位图转化为D2D位图。最后将两个D2D位图添加到
CLSID_D2D1YCbCr
格式的d2dEffect中。
具体的细节就不赘述了,最重要的就是设备的创建和位图的转化。这里需要花费一点功夫去理解代码。
最后
代码:Git
最后
以上就是发嗲砖头为你收集整理的D2D D3D12 渲染视频帧思路及实现的全部内容,希望文章能够帮你解决D2D D3D12 渲染视频帧思路及实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复