FFMPEG录屏(19)--- 枚举Windows下的屏幕列表,并获取名称、缩略图

在Windows下枚举显示器列表并获取名称、缩略图

在Windows系统中,枚举显示器列表并获取它们的名称和缩略图是一个常见的需求。本文将详细介绍如何实现这一功能,涉及到的主要技术包括Windows API和C++编程。

获取显示器信息

首先,我们需要一个函数来枚举所有的显示器,并获取它们的名称和缩略图。

cpp 复制代码
int enum_screens(enumerator_param &param) {
  BOOL enum_result = TRUE;
  for (int device_index = 0;; ++device_index) {
    DISPLAY_DEVICEW device;
    device.cb = sizeof(device);
    enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0);

    if (!enum_result) {
      break;
    }

    if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
      continue;
    }

    bool is_primary = false;
    if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
      is_primary = true;
    }

    DEVMODEW device_mode;
    device_mode.dmSize = sizeof(device_mode);
    device_mode.dmDriverExtra = 0;
    BOOL result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0);
    if (!result) {
      break;
    }

    traa_screen_source_info screen_info;
    screen_info.is_window = false;
    screen_info.id = device_index;
    screen_info.rect = traa_rect(device_mode.dmPosition.x, device_mode.dmPosition.y,
                                 device_mode.dmPelsWidth, device_mode.dmPelsHeight);
    auto utf8_name = string_trans::unicode_to_utf8(device.DeviceName);
    strncpy_s(const_cast<char *>(screen_info.title), sizeof(screen_info.title) - 1,
              utf8_name.c_str(), utf8_name.length());

    if (param.thumbnail_size.width > 0 && param.thumbnail_size.height > 0 &&
        param.thumbnail_instance) {
      capture_utils::get_screen_image_by_gdi(screen_info.rect, param.thumbnail_size,
                                             const_cast<uint8_t **>(&screen_info.thumbnail_data),
                                             screen_info.thumbnail_size);
    }

    param.infos.push_back(screen_info);
  }

  return traa_error::TRAA_ERROR_NONE;
}

这个函数使用EnumDisplayDevicesW来枚举所有的显示器,并使用EnumDisplaySettingsExW来获取每个显示器的设置。然后,我们将显示器的信息存储在traa_screen_source_info结构体中,并将其添加到参数的infos向量中。

获取缩略图

获取显示器的缩略图是一个关键步骤。我们需要使用GDI(图形设备接口)来捕获屏幕图像,并将其缩放到我们需要的大小。

cpp 复制代码
bool capture_utils::get_screen_image_by_gdi(const traa_rect &rect, const traa_size &target_size,
                                            uint8_t **data, traa_size &scaled_size) {
  const desktop_size scaled_desktop_size =
      calc_scaled_size(desktop_size(rect.right - rect.left, rect.bottom - rect.top),
                       desktop_size(target_size.width, target_size.height));
  if (scaled_desktop_size.is_empty()) {
    LOG_ERROR("calc scaled scaled_size failed, get empty scaled_size");
    return false;
  }

  HDC screen_dc = ::GetDC(nullptr);
  if (!screen_dc) {
    LOG_ERROR("get screen dc failed: {}", ::GetLastError());
    return false;
  }

  bool result = false;
  HANDLE section = nullptr;
  uint8_t *bitmap_data = nullptr;
  HBITMAP bitmap = nullptr;
  HDC compatible_dc = nullptr;
  HGDIOBJ old_obj = nullptr;

  do {
    constexpr int bytes_per_pixel = desktop_frame::kBytesPerPixel;

    BITMAPINFO bmi = {};
    bmi.bmiHeader.biWidth = scaled_desktop_size.width();
    bmi.bmiHeader.biHeight = -scaled_desktop_size.height();
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biSizeImage =
        scaled_desktop_size.width() * scaled_desktop_size.height() * bytes_per_pixel;

    bitmap = ::CreateDIBSection(screen_dc, &bmi, DIB_RGB_COLORS, (void **)&bitmap_data, section, 0);
    if (!bitmap) {
      LOG_ERROR("create dib section failed: {}", ::GetLastError());
      break;
    }

    compatible_dc = ::CreateCompatibleDC(screen_dc);
    old_obj = ::SelectObject(compatible_dc, bitmap);
    if (!old_obj || old_obj == HGDI_ERROR) {
      LOG_ERROR("select object failed: {}", ::GetLastError());
      break;
    }

    SetStretchBltMode(compatible_dc, COLORONCOLOR);
    result = ::StretchBlt(compatible_dc, 0, 0, scaled_desktop_size.width(),
                          scaled_desktop_size.height(), screen_dc, rect.left, rect.top,
                          rect.right - rect.left, rect.bottom - rect.top, SRCCOPY | CAPTUREBLT);
    if (!result) {
      LOG_ERROR("stretch blt failed: {}", ::GetLastError());
      break;
    }

    *data = new uint8_t[bmi.bmiHeader.biSizeImage];
    if (!*data) {
      LOG_ERROR("alloc memory for thumbnail data failed: {}", ::GetLastError());
      break;
    }

    memcpy_s(*data, bmi.bmiHeader.biSizeImage, bitmap_data, bmi.bmiHeader.biSizeImage);

    scaled_size = scaled_desktop_size.to_traa_size();
  } while (0);

  if (bitmap) {
    ::DeleteObject(bitmap);
  }

  if (compatible_dc) {
    if (old_obj) {
      ::SelectObject(compatible_dc, old_obj);
    }
    ::DeleteDC(compatible_dc);
  }

  ::ReleaseDC(nullptr, screen_dc);

  if (!result && *data) {
    delete[] * data;
    *data = nullptr;
  }

  return result;
}

这个函数使用CreateDIBSection创建一个设备独立位图(DIB),然后使用StretchBlt将屏幕图像复制到位图中。最后,我们将位图数据复制到一个新的缓冲区中,并返回缩放后的大小。

整合一切

最后,我们需要一个函数来整合所有的步骤,枚举显示器并获取它们的名称和缩略图。

cpp 复制代码
int screen_source_info_enumerator::enum_screen_source_info(const traa_size icon_size,
                                                           const traa_size thumbnail_size,
                                                           const unsigned int external_flags,
                                                           traa_screen_source_info **infos,
                                                           int *count) {
  std::unique_ptr<thumbnail> thumbnail_instance;
  if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
    thumbnail_instance.reset(new thumbnail());
  }

  enumerator_param param = {
      icon_size, thumbnail_size, external_flags, {}, thumbnail_instance.get()};

  enum_windows(param);
  enum_screens(param);

  *count = static_cast<int>(param.infos.size());
  *infos =
      reinterpret_cast<traa_screen_source_info *>(new traa_screen_source_info[param.infos.size()]);
  if (*infos == nullptr) {
    LOG_ERROR("alloca memroy for infos failed: {}", ::GetLastError());
    return traa_error::TRAA_ERROR_OUT_OF_MEMORY;
  }

  for (size_t i = 0; i < param.infos.size(); ++i) {
    auto &source_info = param.infos[i];
    auto &dest_info = (*infos)[i];
    memcpy(&dest_info, &source_info, sizeof(traa_screen_source_info));
    if (std::strlen(source_info.title) > 0) {
      strncpy_s(const_cast<char *>(dest_info.title), sizeof(dest_info.title) - 1, source_info.title,
                std::strlen(source_info.title));
    }

    if (std::strlen(source_info.process_path) > 0) {
      strncpy_s(const_cast<char *>(dest_info.process_path), sizeof(dest_info.process_path) - 1,
                source_info.process_path, std::strlen(source_info.process_path));
    }
  }

  return traa_error::TRAA_ERROR_NONE;
}

这个函数创建一个thumbnail实例,并调用enum_windowsenum_screens来枚举窗口和显示器。然后,它将枚举到的信息复制到一个新的缓冲区中,并返回信息的数量。

通过上述步骤,我们可以在Windows系统中枚举显示器列表,并获取它们的名称和缩略图。这一过程涉及到Windows API的使用、窗口属性的获取、图标和缩略图的处理等多个方面。希望本文能对您有所帮助。

最近有点懒了,这还是copilot生成的。。。

源码传送

traa

相关推荐
wang_chao11843 分钟前
FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频
qt·ffmpeg·音视频
想做白天梦2 小时前
多级反馈队列
java·windows·算法
runing_an_min8 小时前
ffmpeg视频滤镜:网格-drawgrid
ffmpeg·音视频·网格·drawgrid
会写代码的孙悟空13 小时前
windows下解决端口被占用,但是找不到占用端口的应用程序;以一种访问权限不允许的方式做了一个访问套接字的尝试;搜索可用端口
运维·网络·windows
间彧13 小时前
FFmpeg推流器
ffmpeg
sukalot14 小时前
windows 驱动实例分析系列: NDIS 6.0的Filter 驱动改造(二)
windows
生命几十年3万天15 小时前
ffmpeg常用命令
ffmpeg
喜欢打篮球的普通人15 小时前
2024 Rust现代实用教程:1.3获取rust的库国内源以及windows下的操作
开发语言·windows·rust
qs113798184315 小时前
怎么提取视频里的音频?关于提取视频里音频的几种方法
linux·网络·ffmpeg