【音视频】获取桌面程序窗口列表以及桌面、窗口的缩略图(4-4)
采集桌面、窗口,必然需要獲取其列表以及縮略圖。獲取桌面顯示器列表在《【音視頻】獲取視頻設備-MMDeviceAPI&MONITORINFOEX(2-3)》已經講過。本篇主要記錄一下如何獲取窗口列表,以及顯示器、窗口的縮略圖。
1、獲取窗口列表以及縮略圖
使用EnumWindows枚舉所有窗口,需要實現enumWindowProc方法
int VideoDevice::getApplicationDevices(std::list<VIDEO_DEVICE>& devices) {int err = ERROR_CODE_OK;devices.clear();BOOL ret = EnumWindows(enumWindowProc, reinterpret_cast<LPARAM>(&devices));if (!ret || devices.empty()) {err = ERROR_CODE_DEVICE_GET_MONITOR_FAILED;}if (err != ERROR_CODE_OK) {LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] get video devices error: %s", __FUNCTION__,HCMDR_GET_ERROR_DESC(err));}return err; }其中enumWindowProc方法的實現如下:
BOOL CALLBACK VideoDevice::enumWindowProc(HWND hwnd, LPARAM dwData) {std::list<VIDEO_DEVICE>* devices = reinterpret_cast<std::list<VIDEO_DEVICE>*>(dwData);if (devices == nullptr) {return FALSE;}// 忽略看不見的窗口和子窗口if (!IsWindowVisible(hwnd) || GetParent(hwnd) != nullptr) {return TRUE;}VIDEO_DEVICE device = {};long handle = reinterpret_cast<long>(hwnd); #ifdef UNICODEdevice.id = HELPER::StringConverter::convertUnicodeToUtf8(std::to_wstring(handle)); #elsedevice.id = HELPER::StringConverter::convertAsciiToUtf8(std::to_string(handle)); #endif // UNICODETCHAR title[MAX_PATH] = { 0 };::GetWindowText(hwnd, title, MAX_PATH); #ifdef UNICODEdevice.name = HELPER::StringConverter::convertUnicodeToUtf8(title); #elsedevice.name = HELPER::StringConverter::convertAsciiToUtf8(title); #endifdevice.isDefault = devices->empty() ? 1 : 0;if (!device.id.empty() && !device.name.empty()) {int err = allocWindowThumbnail(hwnd, device.thumbnail);if (err != ERROR_CODE_OK) {LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] alloc window thumbnail error: %s", __FUNCTION__,HCMDR_GET_ERROR_DESC(err));}devices->push_back(device);}return TRUE; }其中VIDEO_DEVICE結構體如下:
typedef struct _VIDEO_DEVICE {std::string id; // device idstd::string name; // device nameuint8_t isDefault; // is default device or notVIDEO_THUMBNAIL thumbnail; // Desktop or application thumbnail } VIDEO_DEVICE;typedef struct _VIDEO_THUMBNAIL {std::string type = ""; // Image typeint width = 0; // Thumbnail widthint height = 0; // Thumbnail heightuint8_t* buffer = nullptr; // Thumbnail datauint32_t size = 0; // Thumbnail data size } VIDEO_THUMBNAIL;而獲取窗口縮略圖的方法實現如下。主要使用PrintWindow和Gdiplus獲取縮略圖。首先調用GetWindowPlacement判斷窗口是否最小化,如果是就將窗口顯示并移動到屏幕外,這里sleep了50毫秒是因為有些窗口顯示很慢,太快了無法截圖(我目前沒有更好的辦法,有解決辦法的大佬請指教一二,感謝),接著調用PrintWindow截圖,最后用Gdiplus拿到縮略圖的數據。另外需要注意還原窗口位置的情況,此時調用SetWindowPlacement將窗口還原為最小化狀態。
int VideoDevice::allocWindowThumbnail(HWND hwnd, VIDEO_THUMBNAIL& thumbnail) {int err = ERROR_CODE_OK;ULONG_PTR gdiplusToken;Gdiplus::GdiplusStartupInput gdiplusStartupInput;Gdiplus::GdiplusStartupOutput gdiplusStartupOutput;Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, &gdiplusStartupOutput);HDC srcDc = nullptr;HDC memDc = nullptr;HBITMAP oldBitmap = nullptr;HBITMAP bitmap = nullptr;WINDOWPLACEMENT wndpl;do {int width = 0;int height = 0;GetWindowPlacement(hwnd, &wndpl);if (wndpl.showCmd == SW_SHOWMINIMIZED) { // 將最小化窗口顯示出來width = wndpl.rcNormalPosition.right - wndpl.rcNormalPosition.left;height = wndpl.rcNormalPosition.bottom - wndpl.rcNormalPosition.top;::ShowWindow(hwnd, SW_RESTORE);::MoveWindow(hwnd, wndpl.ptMinPosition.x, wndpl.ptMinPosition.y, width, height, FALSE);av_usleep(50 * 1000);}else {RECT rect = { 0 };::GetWindowRect(hwnd, &rect);width = rect.right - rect.left;height = rect.bottom - rect.top;}srcDc = GetWindowDC(hwnd);if (srcDc == nullptr) {err = ERROR_CODE_DEVICE_GET_DC_FAILED;break;}memDc = CreateCompatibleDC(srcDc);if (memDc == nullptr) {err = ERROR_CODE_DEVICE_CREATE_DC_FAILED;break;}bitmap = CreateCompatibleBitmap(srcDc, width, height);if (bitmap == nullptr) {err = ERROR_CODE_DEVICE_CREATE_BITMAP_FAILED;break;}oldBitmap = (HBITMAP)SelectObject(memDc, bitmap);if (!PrintWindow(hwnd, memDc, PW_RENDERFULLCONTENT)) {err = ERROR_CODE_DEVICE_PRINT_WINDOW_ERROR;break;}thumbnail.type = "png";thumbnail.width = 320;thumbnail.height = 180;CLSID bmpClsid;getImageEncoderClsid(L"image/png", &bmpClsid);Gdiplus::Bitmap bmp(bitmap, nullptr);Gdiplus::Image* scaledImage = bmp.GetThumbnailImage(thumbnail.width, thumbnail.height, nullptr, nullptr);if (scaledImage == nullptr) {err = ERROR_CODE_GDIPLUS_GET_THUMBNAIL_FAILED;break;}IStream* stream = nullptr;HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, &stream);if (FAILED(hr) || stream == nullptr) {err = ERROR_CODE_DEVICE_CREATE_GLOBAL_STREAM_FAILED;break;}Gdiplus::Status status = scaledImage->Save(stream, &bmpClsid);if (status != Gdiplus::Ok) {err = ERROR_CODE_GDIPLUS_SAVE_IMAGE_ERROR;break;}ULARGE_INTEGER streamSize;LARGE_INTEGER streamOffset;streamOffset.QuadPart = 0;hr = stream->Seek(streamOffset, STREAM_SEEK_END, &streamSize);hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr);if (FAILED(hr)) {err = ERROR_CODE_GDIPLUS_SEEK_STREAM_ERROR;break;}thumbnail.buffer = new uint8_t[streamSize.QuadPart];thumbnail.size = static_cast<uint32_t>(streamSize.QuadPart);if (thumbnail.buffer == nullptr) {err = ERROR_CODE_ALLOC_FAILED;break;}unsigned long readSize = 0;hr = stream->Read(thumbnail.buffer, thumbnail.size, &readSize);if (FAILED(hr)) {err = ERROR_CODE_GDIPLUS_READ_STREAM_ERROR;break;}stream->Release(); #if 0std::string filename = std::to_string(reinterpret_cast<long>(hwnd)).append(".png");FILE* pFile = nullptr;fopen_s(&pFile, filename.c_str(), "wb");if (pFile != nullptr) {fwrite(thumbnail.buffer, 1, thumbnail.size, pFile);fclose(pFile);} #endif} while (0);if (wndpl.showCmd == SW_SHOWMINIMIZED) { // 恢復窗口最小化狀態::SetWindowPlacement(hwnd, &wndpl);}if (oldBitmap != nullptr) {::SelectObject(memDc, oldBitmap);oldBitmap = nullptr;}if (bitmap != nullptr) {::DeleteObject(bitmap);bitmap = nullptr;}if (memDc != nullptr) {::DeleteDC(memDc);memDc = nullptr;}if (srcDc != nullptr) {::ReleaseDC(hwnd, srcDc);srcDc = nullptr;}Gdiplus::GdiplusShutdown(gdiplusToken);if (err != ERROR_CODE_OK) {if (thumbnail.buffer != nullptr) {delete[] thumbnail.buffer;thumbnail.buffer = nullptr;}thumbnail.size = 0;thumbnail.width = thumbnail.height = 0;}return err; }再貼一下獲取圖片編碼的class id實現如下:
int VideoDevice::getImageEncoderClsid(const WCHAR* format, CLSID* pClsid) {int err = ERROR_CODE_OK;do {UINT num = 0; // number of image encodersUINT size = 0; // size of the image encoder array in bytesGdiplus::ImageCodecInfo* pImageCodecInfo = nullptr;Gdiplus::GetImageEncodersSize(&num, &size);if (size == 0) {err = ERROR_CODE_GDIPLUS_GET_IMAGE_ENCODER_SIZE_FAILED;break;}pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));if (pImageCodecInfo == nullptr) {err = ERROR_CODE_ALLOC_FAILED;break;}bool found = false;GetImageEncoders(num, size, pImageCodecInfo);for (UINT j = 0; j < num; ++j) {if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {*pClsid = pImageCodecInfo[j].Clsid;found = true;break;}}free(pImageCodecInfo);} while (0);return err; }2、獲取顯示器的縮略圖
修改EnumDisplayMonitors的enumMonitorProc實現,主要是添加allocMonitorThumbnail的調用
BOOL CALLBACK VideoDevice::enumMonitorProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {std::list<VIDEO_DEVICE>* devices = reinterpret_cast<std::list<VIDEO_DEVICE>*>(dwData);/* 省略 */MONITORINFOEX monitorInfo;monitorInfo.cbSize = sizeof(MONITORINFOEX);BOOL ret = GetMonitorInfo(hMonitor, &monitorInfo);if (ret) {VIDEO_DEVICE device;/* 省略 */int err = allocMonitorThumbnail(monitorInfo.szDevice, device.thumbnail);if (err != ERROR_CODE_OK) {LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] alloc thumbnail error: %s", __FUNCTION__,HCMDR_GET_ERROR_DESC(err));}devices->push_back(device);}return ret; }我們主要看allocMonitorThumbnail的實現。跟獲取窗口縮略圖挺相似的,但有些不同,顯示器縮略圖主要使用BitBlt和Gdiplus獲取的,而且不是根據HWND獲取的HDC,而是根據deviceName創建了HDC。另外需要注意的是要獲取顯示器物理像素而不是邏輯像素進行縮略圖截取,否則當屏幕縮放比不是100%的時候只能截取一部分桌面,使用EnumDisplaySettings可以解決此問題。
int VideoDevice::allocMonitorThumbnail(const std::wstring& deviceName, VIDEO_THUMBNAIL& thumbnail) {int err = ERROR_CODE_OK;ULONG_PTR gdiplusToken;Gdiplus::GdiplusStartupInput gdiplusStartupInput;Gdiplus::GdiplusStartupOutput gdiplusStartupOutput;Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, &gdiplusStartupOutput);HDC srcDc = nullptr;HDC memDc = nullptr;HBITMAP oldBitmap = nullptr;HBITMAP bitmap = nullptr;do {srcDc = CreateDCW(deviceName.c_str(), deviceName.c_str(), nullptr, nullptr);if (srcDc == nullptr) {err = ERROR_CODE_DEVICE_CREATE_DC_FAILED;break;}memDc = CreateCompatibleDC(srcDc);if (memDc == nullptr) {err = ERROR_CODE_DEVICE_CREATE_DC_FAILED;break;}DEVMODE devMode;if (!EnumDisplaySettings(deviceName.c_str(), ENUM_CURRENT_SETTINGS, &devMode)) {err = ERROR_CODE_DEVICE_ENUM_DISPLAY_SETTINGS_FAILED;break;}int width = devMode.dmPelsWidth;int height = devMode.dmPelsHeight;bitmap = CreateCompatibleBitmap(srcDc, width, height);if (bitmap == nullptr) {err = ERROR_CODE_DEVICE_CREATE_BITMAP_FAILED;break;}oldBitmap = (HBITMAP)SelectObject(memDc, bitmap);if (!BitBlt(memDc, 0, 0, width, height, srcDc, 0, 0, SRCCOPY)) {err = ERROR_CODE_DEVICE_PRINT_WINDOW_ERROR;break;}thumbnail.type = "png";thumbnail.width = 320;thumbnail.height = 180;CLSID bmpClsid;getImageEncoderClsid(L"image/png", &bmpClsid);Gdiplus::Bitmap bmp(bitmap, nullptr);Gdiplus::Image* scaledImage = bmp.GetThumbnailImage(thumbnail.width, thumbnail.height, nullptr, nullptr);if (scaledImage == nullptr) {err = ERROR_CODE_GDIPLUS_GET_THUMBNAIL_FAILED;break;}IStream* stream = nullptr;HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, &stream);if (FAILED(hr) || stream == nullptr) {err = ERROR_CODE_DEVICE_CREATE_GLOBAL_STREAM_FAILED;break;}Gdiplus::Status status = scaledImage->Save(stream, &bmpClsid);if (status != Gdiplus::Ok) {err = ERROR_CODE_GDIPLUS_SAVE_IMAGE_ERROR;break;}ULARGE_INTEGER streamSize;LARGE_INTEGER streamOffset;streamOffset.QuadPart = 0;hr = stream->Seek(streamOffset, STREAM_SEEK_END, &streamSize);hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr);if (FAILED(hr)) {err = ERROR_CODE_GDIPLUS_SEEK_STREAM_ERROR;break;}thumbnail.buffer = new uint8_t[streamSize.QuadPart];thumbnail.size = static_cast<uint32_t>(streamSize.QuadPart);if (thumbnail.buffer == nullptr) {err = ERROR_CODE_ALLOC_FAILED;break;}unsigned long readSize = 0;hr = stream->Read(thumbnail.buffer, thumbnail.size, &readSize);if (FAILED(hr)) {err = ERROR_CODE_GDIPLUS_READ_STREAM_ERROR;break;}stream->Release();} while (0);if (oldBitmap != nullptr) {::SelectObject(memDc, oldBitmap);oldBitmap = nullptr;}if (bitmap != nullptr) {::DeleteObject(bitmap);bitmap = nullptr;}if (memDc != nullptr) {::DeleteDC(memDc);memDc = nullptr;}Gdiplus::GdiplusShutdown(gdiplusToken);if (err != ERROR_CODE_OK) {if (thumbnail.buffer != nullptr) {delete[] thumbnail.buffer;thumbnail.buffer = nullptr;}thumbnail.size = 0;thumbnail.width = thumbnail.height = 0;}return err; }3、總結
總的來說,算是完成了標題的內容,但是獲取最小化窗口的縮略圖有些瑕疵,目前沒有更好的辦法,希望繼續研究可以解決此問題。
總結
以上是生活随笔為你收集整理的【音视频】获取桌面程序窗口列表以及桌面、窗口的缩略图(4-4)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 一文教你Kali信息收集
- 下一篇: Flutter弹起键盘页面布局超限问题以
