基于Media Foundation与Direct3D9的C++录屏程序改造需求:实现特定应用(Microsoft Word)的实时视频帧捕获
实现特定窗口的实时视频帧捕获(基于Media Foundation与Direct3D9)
当然可以实现仅捕获特定应用窗口的实时视频帧!你的现有代码已经基于Media Foundation和Direct3D9搭建了完整的桌面捕获框架,只需要针对窗口捕获做几个关键修改即可。下面我会提供完整的修改后代码,并拆解核心改动点。
完整修改后代码
// Required Components #pragma comment(lib, "mfreadwrite") #pragma comment(lib, "mfplat") #pragma comment(lib, "mfuuid") #pragma comment(lib, "d3d9.lib") #include <iostream> #include <Windows.h> #include <mfapi.h> #include <mfidl.h> #include <Mfreadwrite.h> #include <mferror.h> #include <d3d9.h> #include <string> #include <algorithm> template<class T> void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } #define REVERSE_IMAGE // Format constants const UINT32 VIDEO_FPS = 30; const UINT64 VIDEO_FRAME_DURATION = 10 * 1000 * 1000 / VIDEO_FPS; const UINT32 VIDEO_BIT_RATE = 800000000; const GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_H264; const GUID VIDEO_INPUT_FORMAT = MFVideoFormat_RGB32; const UINT32 VIDEO_FRAME_COUNT = 10 * VIDEO_FPS; // 获取目标窗口句柄(可根据窗口标题、进程名等调整) HWND GetTargetWindowHandle(const std::wstring& windowTitlePart) { return FindWindowW(NULL, windowTitlePart.c_str()); // 如果需要模糊匹配窗口标题,可使用EnumWindows遍历所有窗口进行匹配 // 示例:匹配包含"Word"的窗口 /* HWND targetHwnd = NULL; EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL { wchar_t title[256]; GetWindowTextW(hwnd, title, sizeof(title)/sizeof(wchar_t)); std::wstring windowTitle(title); std::wstring* targetPart = reinterpret_cast<std::wstring*>(lParam); if (windowTitle.find(*targetPart) != std::wstring::npos && IsWindowVisible(hwnd)) { *reinterpret_cast<HWND*>(lParam) = hwnd; return FALSE; } return TRUE; }, reinterpret_cast<LPARAM>(&targetHwnd)); return targetHwnd; */ } HRESULT InitializeDirect3D9(IDirect3DDevice9 **ppDevice, IDirect3DSurface9 **ppSurface, UINT32 &uiWidth, UINT32 &uiHeight, HWND targetHwnd) { if (!targetHwnd) return E_INVALIDARG; // 获取目标窗口的尺寸(包含边框) RECT windowRect; GetWindowRect(targetHwnd, &windowRect); uiWidth = windowRect.right - windowRect.left; uiHeight = windowRect.bottom - windowRect.top; IDirect3D9 *d3d = NULL; d3d = Direct3DCreate9(D3D_SDK_VERSION); if (d3d == NULL) return E_POINTER; D3DPRESENT_PARAMETERS parameters = {0}; parameters.Windowed = TRUE; parameters.BackBufferCount = 1; parameters.BackBufferHeight = uiHeight; parameters.BackBufferWidth = uiWidth; parameters.SwapEffect = D3DSWAPEFFECT_DISCARD; parameters.hDeviceWindow = NULL; HRESULT hr = d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL, D3DCREATE_SOFTWARE_VERTEXPROCESSING, ¶meters, ppDevice); if (FAILED(hr)) { SafeRelease(&d3d); return hr; } // 创建与窗口尺寸匹配的Offscreen Surface hr = (*ppDevice)->CreateOffscreenPlainSurface(uiWidth, uiHeight, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, ppSurface, nullptr); SafeRelease(&d3d); return hr; } HRESULT InitializeSinkWriter(IMFSinkWriter **ppWriter, DWORD *pStreamIndex, const UINT32 uiWidth, const UINT32 uiHeight) { *ppWriter = NULL; *pStreamIndex = NULL; IMFSinkWriter *pSinkWriter = NULL; IMFMediaType *pMediaTypeOut = NULL; IMFMediaType *pMediaTypeIn = NULL; DWORD streamIndex; HRESULT hr = MFCreateSinkWriterFromURL(L"output.mp4", NULL, NULL, &pSinkWriter); // Set the output media type. if (SUCCEEDED(hr)) { hr = MFCreateMediaType(&pMediaTypeOut); } if (SUCCEEDED(hr)) { hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); } if (SUCCEEDED(hr)) { hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, VIDEO_ENCODING_FORMAT); } if (SUCCEEDED(hr)) { hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE); } if (SUCCEEDED(hr)) { hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); } if (SUCCEEDED(hr)) { hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, uiWidth, uiHeight); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } if (SUCCEEDED(hr)) { hr = pSinkWriter->AddStream(pMediaTypeOut, &streamIndex); } // Set the input media type. if (SUCCEEDED(hr)) { hr = MFCreateMediaType(&pMediaTypeIn); } if (SUCCEEDED(hr)) { hr = pMediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); } if (SUCCEEDED(hr)) { hr = pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, VIDEO_INPUT_FORMAT); } if (SUCCEEDED(hr)) { hr = pMediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); } if (SUCCEEDED(hr)) { hr = MFSetAttributeSize(pMediaTypeIn, MF_MT_FRAME_SIZE, uiWidth, uiHeight); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_FRAME_RATE, VIDEO_FPS, 1); } if (SUCCEEDED(hr)) { hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } if (SUCCEEDED(hr)) { hr = pSinkWriter->SetInputMediaType(streamIndex, pMediaTypeIn, NULL); } // Tell the sink writer to start accepting data. if (SUCCEEDED(hr)) { hr = pSinkWriter->BeginWriting(); } // Return the pointer to the caller. if (SUCCEEDED(hr)) { *ppWriter = pSinkWriter; (*ppWriter)->AddRef(); *pStreamIndex = streamIndex; } SafeRelease(&pSinkWriter); SafeRelease(&pMediaTypeOut); SafeRelease(&pMediaTypeIn); return hr; } HRESULT WriteFrame(IDirect3DDevice9 *pDevice, IDirect3DSurface9 *pSurface, IMFSinkWriter *pWriter, DWORD streamIndex, const LONGLONG &rtStart, const UINT32 uiWidth, const UINT32 uiHeight, HWND targetHwnd) { if (!targetHwnd) return E_INVALIDARG; // 获取目标窗口的DC HDC hWndDC = GetDC(targetHwnd); if (!hWndDC) return E_HANDLE; // 创建兼容DC HDC hMemDC = CreateCompatibleDC(hWndDC); if (!hMemDC) { ReleaseDC(targetHwnd, hWndDC); return E_HANDLE; } // 创建兼容位图 HBITMAP hBitmap = CreateCompatibleBitmap(hWndDC, uiWidth, uiHeight); if (!hBitmap) { DeleteDC(hMemDC); ReleaseDC(targetHwnd, hWndDC); return E_HANDLE; } // 选入位图到兼容DC HGDIOBJ oldBitmap = SelectObject(hMemDC, hBitmap); // 捕获窗口图像到兼容DC BitBlt(hMemDC, 0, 0, uiWidth, uiHeight, hWndDC, 0, 0, SRCCOPY); // 将位图数据转换为D3D Surface D3DLOCKED_RECT lockedRect; HRESULT hr = pSurface->LockRect(&lockedRect, NULL, 0); if (SUCCEEDED(hr)) { BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = uiWidth; bmi.bmiHeader.biHeight = -uiHeight; // 负高度表示从上到下的像素顺序 bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; GetDIBits(hMemDC, hBitmap, 0, uiHeight, lockedRect.pBits, &bmi, DIB_RGB_COLORS); pSurface->UnlockRect(); } // 清理GDI资源 SelectObject(hMemDC, oldBitmap); DeleteObject(hBitmap); DeleteDC(hMemDC); ReleaseDC(targetHwnd, hWndDC); if (FAILED(hr)) { return hr; } D3DLOCKED_RECT rc; hr = pSurface->LockRect(&rc, NULL, 0); if (FAILED(hr)) { return hr; } IMFSample *pSample = NULL; IMFMediaBuffer *pBuffer = NULL; const LONG cbWidth = 4 * uiWidth; const DWORD cbBuffer = cbWidth * uiHeight; BYTE *pData = NULL; // Create a new memory buffer. hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer); // Lock the buffer and copy the video frame to the buffer. if (SUCCEEDED(hr)) { hr = pBuffer->Lock(&pData, NULL, NULL); } if (SUCCEEDED(hr)) { #ifdef REVERSE_IMAGE for (int i = 0, j = uiHeight - 1; i < uiHeight; i++, j--) for (int k = 0; k < cbWidth; k++) pData[(i * cbWidth) + k] = ((BYTE *) rc.pBits)[(j * cbWidth) + k]; #else hr = MFCopyImage(pData, cbWidth, (BYTE*)rc.pBits, rc.Pitch, cbWidth, uiHeight); #endif } if (pBuffer) { pBuffer->Unlock(); } // Set the data length of the buffer. if (SUCCEEDED(hr)) { hr = pBuffer->SetCurrentLength(cbBuffer); } // Create a media sample and add the buffer to the sample. if (SUCCEEDED(hr)) { hr = MFCreateSample(&pSample); } if (SUCCEEDED(hr)) { hr = pSample->AddBuffer(pBuffer); } // Set the time stamp and the duration. if (SUCCEEDED(hr)) { hr = pSample->SetSampleTime(rtStart); } if (SUCCEEDED(hr)) { hr = pSample->SetSampleDuration(VIDEO_FRAME_DURATION); } // Send the sample to the Sink Writer. if (SUCCEEDED(hr)) { hr = pWriter->WriteSample(streamIndex, pSample); } hr = pSurface->UnlockRect(); SafeRelease(&pSample); SafeRelease(&pBuffer); return hr; } void start_engine(){ HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { hr = MFStartup(MF_VERSION); if (SUCCEEDED(hr)) { // 获取目标窗口句柄,这里替换为你的Word窗口标题 HWND targetHwnd = GetTargetWindowHandle(L"Document1 - Microsoft Word"); if (!targetHwnd) { std::cout << "Failed to find target window!" << std::endl; MFShutdown(); CoUninitialize(); return; } UINT32 uiWidth = 0; UINT32 uiHeight = 0; IDirect3DDevice9 *pDevice = NULL; IDirect3DSurface9 *pSurface = NULL; hr = InitializeDirect3D9(&pDevice, &pSurface, uiWidth, uiHeight, targetHwnd); if (SUCCEEDED(hr)) { IMFSinkWriter *pSinkWriter = NULL; DWORD stream; hr = InitializeSinkWriter(&pSinkWriter, &stream, uiWidth, uiHeight); if (SUCCEEDED(hr)) { LONGLONG rtStart = 0; for (DWORD i = 0; i < VIDEO_FRAME_COUNT; ++i) { hr = WriteFrame(pDevice, pSurface, pSinkWriter, stream, rtStart, uiWidth, uiHeight, targetHwnd); if (FAILED(hr)) { break; } rtStart += VIDEO_FRAME_DURATION; // 添加小延迟,避免CPU占用过高 Sleep(1000/VIDEO_FPS - 1); } } if (SUCCEEDED(hr)) { hr = pSinkWriter->Finalize(); } SafeRelease(&pSinkWriter); } SafeRelease(&pDevice); SafeRelease(&pSurface); MFShutdown(); } CoUninitialize(); } } int main() { std::cout << "Window Capturing has started..." << std::endl; start_engine(); std::cout << "Check: 'output.mp4' in the same directory." << std::endl; return 0; }
关键改动说明
1. 新增窗口定位逻辑
- 添加了
GetTargetWindowHandle函数,支持通过精确窗口标题定位目标窗口 - 注释中提供了模糊匹配的方案(比如匹配包含"Word"的窗口),适合窗口标题动态变化的场景(比如Word会显示当前文档名)
2. 修改Direct3D初始化流程
- 不再依赖显示器分辨率,而是通过
GetWindowRect获取目标窗口的实际尺寸 - 创建与窗口尺寸完全匹配的Offscreen Surface,确保捕获的画面没有黑边或拉伸
3. 替换帧捕获实现
- 使用GDI的
BitBlt函数直接捕获目标窗口的图像数据,替代原有的全屏GetFrontBufferData - 将GDI位图转换为Direct3D Surface,确保后续编码流程兼容原有逻辑
4. 优化捕获性能
- 在帧循环中添加了
Sleep延迟,避免CPU占用过高,同时保证帧率稳定
使用注意事项
- 确保目标窗口处于可见状态(最小化窗口无法捕获)
- 如果需要捕获窗口的客户区(不含边框和标题栏),可以将
GetWindowRect替换为GetClientRect,并配合ClientToScreen转换坐标 - 若遇到窗口权限问题(比如UAC提升的程序),需要将你的捕获程序也以管理员权限运行
内容的提问来源于stack exchange,提问作者Muh
相关产品推荐
相关产品推荐

