Update WgcScreenSource* to use device indices instead of HMONITORs.

To maintain interoperability between different capturer implementations
this change updates WgcScreenSourceEnumerator to return a list of
device indices instead of a list of HMONITORs, and WgcScreenSource to
accept a device index as the input SourceId. WGC still requires an
HMONITOR to create the capture item, so this change also adds a utility
function GetHmonitorFromDeviceIndex to convert them, as well as new
tests to cover these changes.

Bug: webrtc:12663
Change-Id: Ic29faa0f023ebc26b4276cf29ef3d15d976e8615
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214600
Commit-Queue: Austin Orion <auorion@microsoft.com>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#33673}
This commit is contained in:
Austin Orion
2021-04-09 14:45:15 -07:00
committed by Commit Bot
parent f2f9bb66ca
commit 061d89877a
6 changed files with 129 additions and 83 deletions

View File

@ -16,48 +16,13 @@
#include <vector>
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/win32.h"
namespace webrtc {
namespace {
BOOL CALLBACK GetMonitorListHandler(HMONITOR monitor,
HDC hdc,
LPRECT rect,
LPARAM data) {
auto monitor_list = reinterpret_cast<DesktopCapturer::SourceList*>(data);
// Get the name of the monitor.
MONITORINFOEXA monitor_info;
monitor_info.cbSize = sizeof(MONITORINFOEXA);
if (!GetMonitorInfoA(monitor, &monitor_info)) {
// Continue the enumeration, but don't add this monitor to |monitor_list|.
return TRUE;
}
DesktopCapturer::Source monitor_source;
monitor_source.id = reinterpret_cast<intptr_t>(monitor);
monitor_source.title = monitor_info.szDevice;
monitor_list->push_back(monitor_source);
return TRUE;
}
} // namespace
// |monitors| is populated with HMONITOR values for each display monitor found.
// This is in contrast to |GetScreenList| which returns the display indices.
bool GetMonitorList(DesktopCapturer::SourceList* monitors) {
RTC_DCHECK_EQ(monitors->size(), 0U);
// |EnumDisplayMonitors| accepts a display context and a rectangle, which
// allows us to specify a certain region and return only the monitors that
// intersect that region. We, however, want all the monitors, so we pass in
// NULL parameters.
return EnumDisplayMonitors(/*hdc=*/NULL, /*clip_rect=*/NULL,
GetMonitorListHandler,
reinterpret_cast<LPARAM>(monitors));
}
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names /* = nullptr */) {
@ -73,12 +38,14 @@ bool GetScreenList(DesktopCapturer::SourceList* screens,
enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0);
// |enum_result| is 0 if we have enumerated all devices.
if (!enum_result)
if (!enum_result) {
break;
}
// We only care about active displays.
if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
continue;
}
screens->push_back({device_index, std::string()});
if (device_names) {
@ -88,13 +55,52 @@ bool GetScreenList(DesktopCapturer::SourceList* screens,
return true;
}
bool IsMonitorValid(DesktopCapturer::SourceId monitor) {
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(MONITORINFO);
return GetMonitorInfoA(reinterpret_cast<HMONITOR>(monitor), &monitor_info);
bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index,
HMONITOR* hmonitor) {
// A device index of |kFullDesktopScreenId| or -1 represents all screens, an
// HMONITOR of 0 indicates the same.
if (device_index == kFullDesktopScreenId) {
*hmonitor = 0;
return true;
}
bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key) {
std::wstring device_key;
if (!IsScreenValid(device_index, &device_key)) {
return false;
}
DesktopRect screen_rect = GetScreenRect(device_index, device_key);
if (screen_rect.is_empty()) {
return false;
}
RECT rect = {screen_rect.left(), screen_rect.top(), screen_rect.right(),
screen_rect.bottom()};
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (monitor == NULL) {
RTC_LOG(LS_WARNING) << "No HMONITOR found for supplied device index.";
return false;
}
*hmonitor = monitor;
return true;
}
bool IsMonitorValid(const HMONITOR monitor) {
// An HMONITOR of 0 refers to a virtual monitor that spans all physical
// monitors.
if (monitor == 0) {
return true;
}
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(MONITORINFO);
return GetMonitorInfoA(monitor, &monitor_info);
}
bool IsScreenValid(const DesktopCapturer::SourceId screen,
std::wstring* device_key) {
if (screen == kFullDesktopScreenId) {
*device_key = L"";
return true;
@ -103,8 +109,9 @@ bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key) {
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0);
if (enum_result)
if (enum_result) {
*device_key = device.DeviceKey;
}
return !!enum_result;
}
@ -116,7 +123,7 @@ DesktopRect GetFullscreenRect() {
GetSystemMetrics(SM_CYVIRTUALSCREEN));
}
DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
const std::wstring& device_key) {
if (screen == kFullDesktopScreenId) {
return GetFullscreenRect();
@ -125,23 +132,26 @@ DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0);
if (!result)
if (!result) {
return DesktopRect();
}
// Verifies the device index still maps to the same display device, to make
// sure we are capturing the same device when devices are added or removed.
// DeviceKey is documented as reserved, but it actually contains the registry
// key for the device and is unique for each monitor, while DeviceID is not.
if (device_key != device.DeviceKey)
if (device_key != device.DeviceKey) {
return DesktopRect();
}
DEVMODEW device_mode;
device_mode.dmSize = sizeof(device_mode);
device_mode.dmDriverExtra = 0;
result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS,
&device_mode, 0);
if (!result)
if (!result) {
return DesktopRect();
}
return DesktopRect::MakeXYWH(
device_mode.dmPosition.x, device_mode.dmPosition.y,

View File

@ -19,10 +19,6 @@
namespace webrtc {
// Outputs the HMONITOR values of all display monitors into |monitors|. Returns
// true if succeeded, or false if it fails to enumerate the display monitors.
bool GetMonitorList(DesktopCapturer::SourceList* monitors);
// Output the list of active screens into |screens|. Returns true if succeeded,
// or false if it fails to enumerate the display devices. If the |device_names|
// is provided, it will be filled with the DISPLAY_DEVICE.DeviceName in UTF-8
@ -31,16 +27,22 @@ bool GetMonitorList(DesktopCapturer::SourceList* monitors);
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names = nullptr);
// Returns true if |monitor| is an HMONITOR that represents a valid display
// monitor. Consumers should check that the results of |GetMonitorList| are
// valid before use if a WM_DISPLAYCHANGE message has been received.
bool IsMonitorValid(DesktopCapturer::SourceId monitor);
// Converts a device index (which are returned by |GetScreenList|) into an
// HMONITOR.
bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index,
HMONITOR* hmonitor);
// Returns true if |monitor| represents a valid display
// monitor. Consumers should recheck the validity of HMONITORs before use if a
// WM_DISPLAYCHANGE message has been received.
bool IsMonitorValid(const HMONITOR monitor);
// Returns true if |screen| is a valid screen. The screen device key is
// returned through |device_key| if the screen is valid. The device key can be
// used in GetScreenRect to verify the screen matches the previously obtained
// id.
bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key);
bool IsScreenValid(const DesktopCapturer::SourceId screen,
std::wstring* device_key);
// Get the rect of the entire system in system coordinate system. I.e. the
// primary monitor always starts from (0, 0).
@ -49,7 +51,7 @@ DesktopRect GetFullscreenRect();
// Get the rect of the screen identified by |screen|, relative to the primary
// display's top-left. If the screen device key does not match |device_key|, or
// the screen does not exist, or any error happens, an empty rect is returned.
RTC_EXPORT DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
RTC_EXPORT DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
const std::wstring& device_key);
} // namespace webrtc

View File

@ -13,6 +13,7 @@
#include <string>
#include <vector>
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "rtc_base/logging.h"
#include "test/gtest.h"
@ -30,26 +31,29 @@ TEST(ScreenCaptureUtilsTest, GetScreenList) {
ASSERT_EQ(screens.size(), device_names.size());
}
TEST(ScreenCaptureUtilsTest, GetMonitorList) {
DesktopCapturer::SourceList monitors;
ASSERT_TRUE(GetMonitorList(&monitors));
}
TEST(ScreenCaptureUtilsTest, IsMonitorValid) {
DesktopCapturer::SourceList monitors;
ASSERT_TRUE(GetMonitorList(&monitors));
if (monitors.size() == 0) {
TEST(ScreenCaptureUtilsTest, DeviceIndexToHmonitor) {
DesktopCapturer::SourceList screens;
ASSERT_TRUE(GetScreenList(&screens));
if (screens.size() == 0) {
RTC_LOG(LS_INFO) << "Skip screen capture test on systems with no monitors.";
GTEST_SKIP();
}
ASSERT_TRUE(IsMonitorValid(monitors[0].id));
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(screens[0].id, &hmonitor));
ASSERT_TRUE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, InvalidMonitor) {
ASSERT_FALSE(IsMonitorValid(NULL));
TEST(ScreenCaptureUtilsTest, FullScreenDeviceIndexToHmonitor) {
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
ASSERT_TRUE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, InvalidDeviceIndexToHmonitor) {
HMONITOR hmonitor;
ASSERT_FALSE(GetHmonitorFromDeviceIndex(kInvalidScreenId, &hmonitor));
}
} // namespace webrtc

View File

@ -36,6 +36,14 @@ HRESULT WgcCaptureSource::GetCaptureItem(
return hr;
}
bool WgcCaptureSource::IsCapturable() {
// If we can create a capture item, then we can capture it. Unfortunately,
// we can't cache this item because it may be created in a different COM
// apartment than where capture will eventually start from.
ComPtr<WGC::IGraphicsCaptureItem> item;
return SUCCEEDED(CreateCaptureItem(&item));
}
WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default;
WgcWindowSourceFactory::WgcWindowSourceFactory() = default;
@ -59,7 +67,10 @@ WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id)
WgcWindowSource::~WgcWindowSource() = default;
bool WgcWindowSource::IsCapturable() {
return IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()));
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
return false;
return WgcCaptureSource::IsCapturable();
}
HRESULT WgcWindowSource::CreateCaptureItem(
@ -92,15 +103,25 @@ WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id)
WgcScreenSource::~WgcScreenSource() = default;
bool WgcScreenSource::IsCapturable() {
// 0 is the id used to capture all display monitors, so it is valid.
if (GetSourceId() == 0)
return true;
if (!hmonitor_) {
HMONITOR hmon;
if (!GetHmonitorFromDeviceIndex(GetSourceId(), &hmon))
return false;
return IsMonitorValid(GetSourceId());
hmonitor_ = hmon;
}
if (!IsMonitorValid(*hmonitor_))
return false;
return WgcCaptureSource::IsCapturable();
}
HRESULT WgcScreenSource::CreateCaptureItem(
ComPtr<WGC::IGraphicsCaptureItem>* result) {
if (!hmonitor_)
return E_ABORT;
if (!ResolveCoreWinRTDelayload())
return E_FAIL;
@ -112,8 +133,7 @@ HRESULT WgcScreenSource::CreateCaptureItem(
return hr;
ComPtr<WGC::IGraphicsCaptureItem> item;
hr = interop->CreateForMonitor(reinterpret_cast<HMONITOR>(GetSourceId()),
IID_PPV_ARGS(&item));
hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
if (FAILED(hr))
return hr;

View File

@ -13,9 +13,12 @@
#include <windows.graphics.capture.h>
#include <wrl/client.h>
#include <memory>
#include "absl/types/optional.h"
#include "modules/desktop_capture/desktop_capturer.h"
namespace webrtc {
// Abstract class to represent the source that WGC-based capturers capture
@ -27,7 +30,7 @@ class WgcCaptureSource {
explicit WgcCaptureSource(DesktopCapturer::SourceId source_id);
virtual ~WgcCaptureSource();
virtual bool IsCapturable() = 0;
virtual bool IsCapturable();
HRESULT GetCaptureItem(
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result);
@ -41,7 +44,7 @@ class WgcCaptureSource {
private:
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item_;
DesktopCapturer::SourceId source_id_;
const DesktopCapturer::SourceId source_id_;
};
class WgcCaptureSourceFactory {
@ -115,6 +118,12 @@ class WgcScreenSource final : public WgcCaptureSource {
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
override;
// To maintain compatibility with other capturers, this class accepts a
// device index as it's SourceId. However, WGC requires we use an HMONITOR to
// describe which screen to capture. So, we internally convert the supplied
// device index into an HMONITOR when |IsCapturable()| is called.
absl::optional<HMONITOR> hmonitor_;
};
} // namespace webrtc

View File

@ -13,6 +13,7 @@
#include <d3d11.h>
#include <wrl/client.h>
#include <map>
#include <memory>
@ -62,7 +63,7 @@ class ScreenEnumerator final : public SourceEnumerator {
~ScreenEnumerator() override = default;
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
return webrtc::GetMonitorList(sources);
return webrtc::GetScreenList(sources);
}
};