OBS框架流程和源码分析七一视频流捕获机制 您所在的位置:网站首页 直播推流拉流技术原理 OBS框架流程和源码分析七一视频流捕获机制

OBS框架流程和源码分析七一视频流捕获机制

2024-07-17 18:01| 来源: 网络整理| 查看: 265

1. 视频流捕获机制 1.1. 视频流捕获基本原理

所谓视频流,实际上是由一张张图像组成,由于人体眼睛的捕获频率,以及视觉暂留机制,在图像连续播放时,会让大脑以为产生连贯性的动画效果。常见的电影帧率是24帧/秒(24fps),而游戏一般要到60fps(高清)才不会觉得明显卡顿(这个讨论见https://www.zhihu.com/question/21081976/answer/34748080)。也就是,对于电影或者一般游戏而言,按照某个帧率播放,只要按照这个帧率捕获数据,基本可以采集到每一帧完整的数据。 再来一个直观的感受:

帧率在24fps,播放一张图的时间是41.7ms,如果1s播放一张图,那么帧率在1fps。帧率在30fps,播放一张图的时间是33.3ms帧率在60fps,播放一张图的时间是16.7ms 1.2. 窗口图像帧捕获 1.2.1. 基本原理

为了取得窗口图像帧,首先得清楚窗口图像帧原理。

大概的原理: Windows为了提升显示性能,图形相关的操作,都在内核层(win32k.sys),在这里,设计上有两条线:

一条是管理线,在win32k.sys中有专门的窗口管理器,处理窗口的父子层级关系、foreground窗口、焦点信息、各窗口句柄,以及各个消息队列;一条是渲染线,这里win32k应该是提供了与显示设备驱动交互的功能,通过GDI接口对外暴露出来。应用程序通过调用GDI接口,对窗口绑定的显示缓存进行绘制。最终还是结合管理线中的窗口层级,来做显示的消隐处理。

应用层使用的基本方式,就是创建消息循环,创建窗口及绑定窗口过程函数,通过消息循环派发消息事件给过程函数,去执行不同动作。渲染线最后是通过WM_PAINT消息驱动,去刷新每个窗口自身的显示区,最终触发屏幕对应的FrontBuffer数据展示。这里的每个窗口自身的显示区,windows中用DC来关联,每一个窗口都有一个DC以及一段buffer,实际的操作,都是通过DC来处理这段buffer。我们可以通过DC来获取到窗口内的数据。

窗口管理器负责管理了各个窗口的显示资源buffer地址,应用程序的渲染过程负责对窗口显示buffer资源进行修改。那么获取图像帧,只需要拿到最终修改完成的buffer即可,可通过DC操作来拿。

1.2.2. 窗口捕获代码 void SaveBitmapToFile(HBITMAP hBitMap, LPCWSTR lpstrFileName) { BITMAP bitmap; GetObject(hBitMap, sizeof(BITMAP), &bitmap); BITMAPFILEHEADER bmfHdr; //位图文件头结构 BITMAPINFOHEADER bi; //位图信息头结构 LPBITMAPINFOHEADER lpbi; //指向位图信息头结构 bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bitmap.bmWidth; bi.biHeight = bitmap.bmHeight; bi.biPlanes = 1; HDC hDC = CreateDC(L"DISPLAY", NULL, NULL, NULL); int iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits SelectPalette(hDC, hOldPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); } HANDLE hFile = CreateFile(lpstrFileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); // 写入位图文件头 DWORD dwWritten = 0; WriteFile(hFile, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件其余内容 WriteFile(hFile, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); GlobalUnlock(hDib); GlobalFree(hDib); CloseHandle(hFile); } void Capture(HWND hWnd) { HDC hdc = GetDC(hWnd); RECT rcWnd = { 0 }; GetClientRect(hWnd, &rcWnd); int cx = rcWnd.right - rcWnd.left; int cy = rcWnd.bottom - rcWnd.top; HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP bitmap = CreateCompatibleBitmap(hdc, cx, cy); SelectObject(hdcMem, bitmap); BitBlt(hdcMem, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY); SaveBitmapToFile(bitmap, L"d:\\test.bmp"); ReleaseDC(hWnd, hdc); DeleteDC(hdcMem); DeleteObject(bitmap); }

核心是创建内存兼容DC,关联一个bitmap资源,然后BitBlt调用可以从显存dc拷贝到内存兼容dc,因为内存兼容dc与bitmap关联,数据就都到bitmap中去了,直接进行存储即可。

1.2.3. 窗口捕获测试 1.2.3.1. GDI窗口测试

被测试对象:

一个常规GDI绘制窗口窗口内部简单加载了一张bitmap

测试窗口代码如下:

void Load(HINSTANCE hInstance, HWND hWnd) { g_hBitMap = LoadBitmapW(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); InvalidateRect(hWnd, NULL, TRUE); } void OnPaint(HDC hdc) { HDC hdcMem = CreateCompatibleDC(NULL); SelectObject(hdcMem, g_hBitMap); BITMAP bm; GetObject(g_hBitMap, sizeof(bm), &bm); BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); DeleteDC(hdcMem); } 1.2.3.2. DX窗口应用测试

被测试对象:

一个DX9渲染的窗口窗口内部简单绘制了一个三角形

dx9

IDirect3D9Ex* g_pD3D9Ex = NULL; IDirect3DDevice9Ex* g_pD3D9DeviceEx = NULL; IDirect3DVertexBuffer9* g_pVB = NULL; struct CUSTOMVERTEX { D3DXVECTOR4 position; // The position D3DCOLOR color; // The color }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) void InitDevice(HWND hWnd) { HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &g_pD3D9Ex); if (FAILED(hr)) return; D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.hDeviceWindow = hWnd; if (FAILED(hr = g_pD3D9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, NULL, &g_pD3D9DeviceEx))) return; g_pD3D9DeviceEx->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); g_pD3D9DeviceEx->SetRenderState(D3DRS_LIGHTING, FALSE); g_pD3D9DeviceEx->SetRenderState(D3DRS_ZENABLE, FALSE); // 定义顶点 hr = g_pD3D9DeviceEx->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL); if (FAILED(hr)) return; CUSTOMVERTEX* pVertices; if (FAILED(g_pVB->Lock(0, 0, (void**)&pVertices, 0))) return; pVertices[0].position = D3DXVECTOR4(100.0f, 150.0f, 1.0f, 1.0f); pVertices[0].color = D3DCOLOR_XRGB(255, 0, 0); pVertices[1].position = D3DXVECTOR4(250.0f, 150.0f, 1.0f, 1.0f); pVertices[1].color = D3DCOLOR_XRGB(0, 255, 0); pVertices[2].position = D3DXVECTOR4(150.0f, 250.0f, 1.0f, 1.0f); pVertices[2].color = D3DCOLOR_XRGB(0, 0, 255); g_pVB->Unlock(); } void Render() { if (g_pD3D9DeviceEx && g_pVB) { g_pD3D9DeviceEx->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0); if (SUCCEEDED(g_pD3D9DeviceEx->BeginScene())) { g_pD3D9DeviceEx->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX)); g_pD3D9DeviceEx->SetFVF(D3DFVF_CUSTOMVERTEX); g_pD3D9DeviceEx->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); g_pD3D9DeviceEx->EndScene(); } g_pD3D9DeviceEx->PresentEx(0, 0, 0, 0, 0); } } void UnInitDevice() { if (g_pVB) g_pVB->Release(); if (g_pD3D9DeviceEx) g_pD3D9DeviceEx->Release(); if (g_pD3D9Ex) g_pD3D9Ex->Release(); }

在这里插入图片描述

1.2.4. 窗口捕获分析和总结 1.2.4.1. 窗口DC是什么

整个截图围绕着DC来操作,让我不禁思考,DC到底是什么?我们可以通过GetWindowDC以及GetDC获取窗口的两种不同设备上下文,但是当我对这个API重复调用时,发现返回的对象并不一样,如下调试结果: 在这里插入图片描述 为啥返回值不一样?难道DC不是窗口的一个固定的数据缓存么?

这里微软给出来解释:https://docs.microsoft.com/en-us/windows/win32/gdi/display-device-contexts 本质上,DC是一个系统内存中的结构体,这个结构体中,包含有:绘图所用的对象集合(例如:画笔pen、画刷brush、位图bitmap、调色板palette、字体font等)以及相关属性的集合,而显示位图bitmap信息,就是窗口的显示数据信息。

DC也分为好多类别,我们这里的DC指的是Display DC,而Display DC也分为好几类:

Common DC:大多数场景Private DC:特殊DC,窗口类stype存在标记位CS_OWNDC的场景Window DC:需要关注非客户区时使用Parent DC:Child Window使用

这其中,Private DC对窗口DC中绘图对象的修改,会直接修改到模板,下一次GetDC时,会拿修改后的模板值填充新DC,直到窗口销毁(即使被主动ReleaseDC,修改也不会消失);而其他的DC,在Get到之后,通过SelectObject修改DC中的绘图对象,当ReleaseDC时,所有的修改都会丢弃,下一次GetDC依旧使用默认值填充绘图对象。

简单测试一下,使用CS_OWNDC之后的效果如下: 在这里插入图片描述 这里会发现,Private DC只作用于客户区DC,对于非客户区DC,不起作用。

为啥连续调用返回值不一样呢?这里就解释了,因为每次都是新生成一个DC资源,然后从老资源中拷贝到新资源中来,只不过Private DC有点特殊。微软还解释了,DC资源是非常宝贵的资源,系统内是有限的,如果我们存在DC泄漏,很可能造成系统GDI绘图异常,也就是导致系统内其他绘图程序下一次调用GetDC会失败。那既然DC是存在于内存中的,为啥数量还要设限制呢?

另外,在分析DX时,发现DX的surface接口中有个GetDC接口,是一个有限使用的功能接口,为了可以供GDI函数相互作用。那么,是否可以理解为,DX底层和GDI底层,都关联DC,DC也都关联窗口整个显示区,所以无论是DX窗口还是GDI窗口,都能通过DC去操作。这里又有个问题,DC在内存中,到底是显存映射的内存,还是非显存映射的系统内存?既然系统要限制使用数量,而且DX的Surface接口还与之关联(测试所使用的surface对象在显存中),并且通过DC截图可以捕获到无论是GDI渲染还是DX渲染的数据内容,那么是否可以理解成DC本质就是存储在显存中,只不过被映射到了系统内存(如果不是,那么我们能通过窗口的DC捕获到,只能说明,DX绘制的时候,会在系统内存拷贝一份镜像,而我们每次通过窗口DC去获取数据,都是从这个镜像中获取的,但是很明显,DX没有这么做,因为我们在渲染三角形时,使用了D3DPOOL_DEFAULT,它的资源只会放置在显存中,不会在系统内存中镜像一份。)

由此分析,得出的结论是,我们在GetDC时,应该是在系统内存中,映射出窗口的显存buffer,当我们使用BitBlt,应该是从映射内存拷贝输出到系统内存。

1.2.4.2. GDI和DX的关系

从上述GDI窗口的捕获结果,以及DX应用窗口的捕获结果来看,关联窗口的DC是整个窗口渲染的最终过程,也就是说,无论使用DX引擎如何渲染,最终展示效果,都是可以通过DC来捕获,那么GDI和DX他们的关系,应该是如下: 在这里插入图片描述

1.2.4.3. 存储分析

显示器显示一张图,原始尺寸大小,可以这么计算:分辨率颜色位。颜色位现在一般是32位(再高就没有太大意义,人眼无法分辨),这样计算的话,如果分辨率在1366768,那么一张图的大小将是:136676832/8=4MB,也就是,一张全屏的图片,没有任何压缩,就需要4MB存储空间,甚至包括一些比屏幕更大的窗口,占用空间就更大了,比如高清屏33602100分辨率,全屏窗口将占用空间:33602100*32/8=26.9MB,这个数据量还是相当大的,如果同时监控2个这样的窗口,就到达50MB。

1.2.4.4. 性能分析

无论是DX渲染的窗口,还是GDI渲染的窗口,最终输出的显示缓存,都可以由DC来获取。而获取的方式,主要是调用BitBlt来将显示数据不断拷贝到创建的内存。根据内存占用分析,每次BitBlt要拷贝这么大数据量,考虑效率的话,如果窗口展示的动画,显示很快(60fps),以上述高清屏为例(26.9MB),那么相当于1s需要拷贝26.9*60=1614MB,已经1.5GB的数据量了。那么,再考虑BitBlt传输速率,我们就可以得到一个拷贝帧数极限数值。

遗留问题:BitBlt的性能测试

BitBlt本质是将显示数据,从显存拷贝到内存。这里占用的系统资源,除了前面分析的存储空间之外,还有显存到内存到传输带宽。硬件连接上,显存是作为外设,介入到CPU系统中,一般通过PCIe总线连接。CPU如果想让内存和显存进行通信,所采用到机制,是MMIO,即内存映射IO,也就是将显存映射为系统的一段内存,进而CPU就可以操作显存资源了。但本质上,硬件连接还是分离的,数据拷贝操作,还得通过类似DMA直接存储机制,这里拷贝数据,还占用了这个带宽。当拷贝占用带宽导致PCIe总线忙时,也会影响其他渲染操作。

此外,BitBlt拷贝数据,还需要考虑是否有锁的问题,因为它是针对目标窗口的图像帧拷贝数据,而针对DC的操作,是否带锁?如果不带锁,对DC的操作则必须是单线程,否则容易导致数据混乱,多进程操作时,本质就是多线程在操作DC资源;如果带锁,那么只要使用它,应该就会对捕获目标造成影响。猜测的话,因为这个东西最终是需要占用PCIe总线的,至少在总线这里是独占的,可以理解为有锁,GDI操作自身是否有锁,这个有待逆向分析。

遗留问题:BitBlt逆向分析是否有锁??

由于我们是从进程外去拷贝窗口图像帧数据,所以是无法区分dirty空间大小,也就无法区分具体每次拷贝的大小,而每次都拷贝完整的全屏数据,这里很明显存在可以优化的空间。如果利用窗口捕获方式,可以注入到目标进程,在渲染时,获取到脏数据的位置,来拿到增量渲染数据,这样可以在采集时,降低DMA带宽的使用量。

1.2.4.5. 采样频率

由于我们在进程外获取窗口数据帧,我们实际上是不知道渲染何时触发,例如静态的窗口,可能一只不渲染,但是我们是很难区分开的,所以为了采集窗口数据帧,我们需要做的,是定时去做窗口数据采样。这样导致的问题,就是可能采样到一堆无用数据,对于我们无论是存储,还是网络传输,都是不利的(当然,对于网络传输,我们也可以分析图像差异来计算是否重复,但是这样只会导致消耗更多计算性能)。

如果我们注入到系统进程内,通过Hook绘制操作,那么便可以尽可能减少这种无效数据。

1.3. 游戏捕获

对于直播而言,游戏是一个重要分支,对于游戏自身而言,都是为了尽可能争取到高性能的渲染,而基本上采用DirectX、OpenGL、VulKan等与显卡紧密结合的渲染SDK进行游戏画面的渲染,游戏的帧率,也普遍非常之高。高清高速高性能的追求,对于数据采集而言,本就是一个挑战,如果采样前述的窗口捕获的方式,对于DMA带宽占用,本身就会影响当下游戏性能,而且游戏帧率随着场景变化,对于我们设置的采样间隔,会造成极大的浪费。所以,游戏这种捕获,OBS采用了独立功能和产品模块来实现捕获,以获取最佳性能。

为了能掌控渲染帧数的变化,实时获取图像帧数据,基本方式是:注入 + 绘制函数Hook。

1.3.2. 进程内图像帧获取

首先,不考虑注入,直接在测试窗口进程中,模拟注入场景,测试DX是如何捕获图像帧。

1.3.2.1. DX8图像帧捕获

关键流程:

Step1:通过GetRenderTarget获取渲染目标,并通过渲染目标的GetDesc接口获取格式参数Step2:利用Step1获取到的渲染目标的格式参数,使用CreateImageSurface创建一个兼容的surfaceStep3:通过CopyRects,直接将渲染目标数据,拷贝到兼容surfaceStep4:LockRect读取兼容surface的像素数据,增加bmp头,生成bmp图片 void OnPrePresent() { static bool bFirst = true; if (bFirst) { bFirst = false; // Present之前,创建一个兼容的离屏表面 IDirect3DSurface8* pBackBuffer = NULL; g_d3d8Device->GetRenderTarget(&pBackBuffer); D3DSURFACE_DESC desc; pBackBuffer->GetDesc(&desc); IDirect3DSurface8* pCopyBuffer = NULL; g_d3d8Device->CreateImageSurface(desc.Width, desc.Height, desc.Format, &pCopyBuffer); g_d3d8Device->CopyRects(pBackBuffer, NULL, 0, pCopyBuffer, NULL); SaveSurfaceToFile(pCopyBuffer, L"d:\\d3d8.bmp"); pCopyBuffer->Release(); pBackBuffer->Release(); } } void SaveSurfaceToFile(IDirect3DSurface8* pCopyBuffer, LPCWSTR lpstrFileName) { BITMAPFILEHEADER bmfHdr; //位图文件头结构 BITMAPINFOHEADER bi; //位图信息头结构 LPBITMAPINFOHEADER lpbi; //指向位图信息头结构 bi.biSize = sizeof(BITMAPINFOHEADER); D3DSURFACE_DESC desc; pCopyBuffer->GetDesc(&desc); bi.biWidth = desc.Width; bi.biHeight = desc.Height; bi.biPlanes = 1; HDC hDC = CreateDC(L"DISPLAY", NULL, NULL, NULL); int iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits SelectPalette(hDC, hOldPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); } HANDLE hFile = CreateFile(lpstrFileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); // 写入位图文件头 DWORD dwWritten = 0; WriteFile(hFile, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件其余内容 void* pData = lpbi + dwPaletteSize + sizeof(BITMAPINFOHEADER); memcpy(pData, data.pBits, dwBmBitsSize); pCopyBuffer->UnlockRect(); WriteFile(hFile, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); GlobalUnlock(hDib); GlobalFree(hDib); CloseHandle(hFile); } 1.3.2.2. DX9图像帧捕获

关键流程:

Step1:通过GetRenderTarget获取渲染目标,并通过渲染目标的GetDesc接口获取格式参数Step2:利用Step1获取到的渲染目标的格式参数,使用CreateOffscreenPlainSurface创建一个兼容的surface。有两种方式,一种是创建内存资源D3DPOOL_SYSTEMMEM,一种是创建显存资源D3DPOOL_DEFAULTStep3:如果Step2创建的是内存资源,则使用GetRenderTargetData拷贝资源;如果是Step2创建的是显存资源,则使用StrechRect拷贝数据Step4:D3DXSaveSurfaceToFile将兼容surface生成bmp图片 void OnPrePresent() { static bool bFirst = true; if (bFirst) { bFirst = false; // Present之前,创建一个兼容的离屏表面 IDirect3DSurface9* pBackBuffer = NULL; g_pD3D9DeviceEx->GetRenderTarget(0, &pBackBuffer); D3DSURFACE_DESC desc; pBackBuffer->GetDesc(&desc); IDirect3DSurface9* pCopyBuffer = NULL; g_pD3D9DeviceEx->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format, D3DPOOL_SYSTEMMEM, &pCopyBuffer, NULL); pCopyBuffer->UnlockRect(); //g_pD3D9DeviceEx->StretchRect(pBackBuffer, NULL, pCopyBuffer, NULL, D3DTEXTUREFILTERTYPE::D3DTEXF_NONE); g_pD3D9DeviceEx->GetRenderTargetData(pBackBuffer, pCopyBuffer); D3DXSaveSurfaceToFileW(L"d:\\d3d9.bmp", D3DXIMAGE_FILEFORMAT::D3DXIFF_BMP, pCopyBuffer, NULL, NULL); pCopyBuffer->Release(); pBackBuffer->Release(); } } 1.3.2.3. DX10图像帧捕获

关键流程:

Step1:通过SwapChain获取0号索引的后备缓存(也即最顶上渲染目标),通过GetDesc获取该后备缓存的格式参数Step2:通过Step1获取到的格式参数,使用CreateTexture2D创建一个兼容的texture(注意,经测试,当samplecnt为1,无所谓创建显存还是内存资源,使用CopyResource均可复制;当samplecnt大于1,必须创建显存资源。具体原因,后续待分析)Step3:调用CopyResource,直接将后备缓存数据拷贝到兼容textureStep4:D3DX10SaveTextureToFile将texture存储为图片 vvoid OnPrePresent() { static bool bFirst = true; if (bFirst) { bFirst = false; // 获取后备缓存 ID3D10Resource* pBackBuffer = NULL; g_pSwapChain->GetBuffer(0, __uuidof(ID3D10Resource), (void**)&pBackBuffer); DXGI_SWAP_CHAIN_DESC swapdesc; g_pSwapChain->GetDesc(&swapdesc); // 创建一个CPU和GPU皆能访问的texture2D资源 D3D10_TEXTURE2D_DESC desc = {}; desc.Width = swapdesc.BufferDesc.Width; desc.Height = swapdesc.BufferDesc.Height; desc.Format = swapdesc.BufferDesc.Format; desc.MipLevels = 1; desc.ArraySize = 1; desc.SampleDesc.Count = 1; desc.Usage = D3D10_USAGE_DEFAULT; ID3D10Texture2D* tex = NULL; g_pd3dDevice->CreateTexture2D(&desc, nullptr, &tex); if (swapdesc.SampleDesc.Count > 1) g_pd3dDevice->ResolveSubresource(tex, 0, pBackBuffer, 0, swapdesc.BufferDesc.Format); else g_pd3dDevice->CopyResource(tex, pBackBuffer); D3DX10SaveTextureToFile(tex, D3DX10_IMAGE_FILE_FORMAT::D3DX10_IFF_BMP, L"D:\\d3d10.bmp"); if (pBackBuffer) pBackBuffer->Release(); if (tex) tex->Release(); } } 1.3.2.4. DX11图像帧捕获

关键流程,同DX10

void OnPrePresent() { static bool bFirst = true; if (bFirst) { bFirst = false; // 获取后备缓存 ID3D11Resource* pBackBuffer = NULL; g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Resource), (void**)&pBackBuffer); DXGI_SWAP_CHAIN_DESC swapdesc; g_pSwapChain->GetDesc(&swapdesc); // 创建一个CPU和GPU皆能访问的texture2D资源 D3D11_TEXTURE2D_DESC desc = {}; desc.Width = swapdesc.BufferDesc.Width; desc.Height = swapdesc.BufferDesc.Height; desc.Format = swapdesc.BufferDesc.Format; desc.MipLevels = 1; desc.ArraySize = 1; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; ID3D11Texture2D* tex = NULL; g_pd3dDevice->CreateTexture2D(&desc, nullptr, &tex); if (swapdesc.SampleDesc.Count > 1) g_pd3dImmediateContext->ResolveSubresource(tex, 0, pBackBuffer, 0, swapdesc.BufferDesc.Format); else g_pd3dImmediateContext->CopyResource(tex, pBackBuffer); D3DX11SaveTextureToFileW(g_pd3dImmediateContext, tex, D3DX11_IMAGE_FILE_FORMAT::D3DX11_IFF_BMP, L"D:\\d3d11.bmp"); if (pBackBuffer) pBackBuffer->Release(); if (tex) tex->Release(); } } 1.3.2.5. DX12图像帧捕获

DX12是DX11的进阶版,完全适应现代GPU架构。其关键流程,也是继承了DX11,捕获技术,基本是利用了11On12技术。

关键流程:

Step1:根据DX12设备接口,通过D3D11On12CreateDevice创建关联的DX11设备对象(注意,这个能力是在D3D11.dll中)Step2:同时,还得通过dx11对象,拿到dx11on12设备对象,许多参数,是通过这个外挂式接口来操作的。Step3: void OnPrePresent() { static bool bFirst = true; if (bFirst) { bFirst = false; DXGI_SWAP_CHAIN_DESC swapdesc; g_pd3dSwapChain->GetDesc(&swapdesc); ComPtr pd3d11Device; ComPtr pd3d11Context; // d3d12需要使用d3d11on12技术,通过d3d11接口获取数据 D3D11On12CreateDevice(g_pd3dDevice.Get(), 0, NULL, 0, NULL, 0, 0, &pd3d11Device, &pd3d11Context, NULL); ComPtr pd3d11On12Device; pd3d11Device->QueryInterface(__uuidof(ID3D11On12Device), &pd3d11On12Device); UINT cur_idx = g_pd3dSwapChain->GetCurrentBackBufferIndex(); ComPtr backbuffer; g_pd3dSwapChain->GetBuffer(cur_idx, __uuidof(ID3D12Resource), (void**)&backbuffer); //backbuffer->Release(); ComPtr backbuffer11; D3D11_RESOURCE_FLAGS rf11 = {}; pd3d11On12Device->CreateWrappedResource( backbuffer.Get(), &rf11, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_PRESENT, __uuidof(ID3D11Resource), (void**)&backbuffer11); pd3d11On12Device->ReleaseWrappedResources(backbuffer11.GetAddressOf(), 1); pd3d11On12Device->AcquireWrappedResources(backbuffer11.GetAddressOf(), 1); // 创建一个CPU和GPU皆能访问的texture2D资源 D3D11_TEXTURE2D_DESC desc = {}; desc.Width = swapdesc.BufferDesc.Width; desc.Height = swapdesc.BufferDesc.Height; desc.Format = swapdesc.BufferDesc.Format; desc.MipLevels = 1; desc.ArraySize = 1; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; ComPtr tex; pd3d11Device->CreateTexture2D(&desc, nullptr, &tex); pd3d11Context->CopyResource(tex.Get(), backbuffer11.Get()); pd3d11On12Device->ReleaseWrappedResources(backbuffer11.GetAddressOf(), 1); pd3d11Context->Flush(); ThrowIfFailed(D3DX11SaveTextureToFileW(pd3d11Context.Get(), tex.Get(), D3DX11_IMAGE_FILE_FORMAT::D3DX11_IFF_BMP, L"D:\\d3d12.bmp")); } }

参考文章: https://www.notion.so/willarun365/OBS-814f392623c0446fabacbe8deea02a61



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有