Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/cursor.h"
#include <algorithm>
#include <memory>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/win/scoped_gdi_object.h"
#include "rtc_base/logging.h"
#include "rtc_base/system/arch.h"
namespace webrtc {
namespace {
#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
#define RGBA(r, g, b, a) \
((((a) << 24) & 0xff000000) | (((b) << 16) & 0xff0000) | \
(((g) << 8) & 0xff00) | ((r)&0xff))
#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
#define RGBA(r, g, b, a) \
((((r) << 24) & 0xff000000) | (((g) << 16) & 0xff0000) | \
(((b) << 8) & 0xff00) | ((a)&0xff))
#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
// Pixel colors used when generating cursor outlines.
const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
// Expands the cursor shape to add a white outline for visibility against
// dark backgrounds.
void AddCursorOutline(int width, int height, uint32_t* data) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// If this is a transparent pixel (bgr == 0 and alpha = 0), check the
// neighbor pixels to see if this should be changed to an outline pixel.
if (*data == kPixelRgbaTransparent) {
// Change to white pixel if any neighbors (top, bottom, left, right)
// are black.
if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
(y < height - 1 && data[width] == kPixelRgbaBlack) ||
(x > 0 && data[-1] == kPixelRgbaBlack) ||
(x < width - 1 && data[1] == kPixelRgbaBlack)) {
*data = kPixelRgbaWhite;
}
}
data++;
}
}
}
// Premultiplies RGB components of the pixel data in the given image by
// the corresponding alpha components.
void AlphaMul(uint32_t* data, int width, int height) {
static_assert(sizeof(uint32_t) == kBytesPerPixel,
"size of uint32 should be the number of bytes per pixel");
for (uint32_t* data_end = data + width * height; data != data_end; ++data) {
RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data);
RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data);
to->rgbBlue =
(static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff;
to->rgbGreen =
(static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff;
to->rgbRed =
(static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff;
}
}
// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
// Returns true if non-zero alpha is found. `stride` is expressed in pixels.
bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
if (plane->rgbReserved != 0)
return true;
plane += 1;
}
plane += stride - width;
}
return false;
}
} // namespace
MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) {
ICONINFO iinfo;
if (!GetIconInfo(cursor, &iinfo)) {
RTC_LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
<< GetLastError();
return NULL;
}
int hotspot_x = iinfo.xHotspot;
int hotspot_y = iinfo.yHotspot;
// Make sure the bitmaps will be freed.
win::ScopedBitmap scoped_mask(iinfo.hbmMask);
win::ScopedBitmap scoped_color(iinfo.hbmColor);
bool is_color = iinfo.hbmColor != NULL;
// Get `scoped_mask` dimensions.
BITMAP bitmap_info;
if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
RTC_LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
<< GetLastError();
return NULL;
}
int width = bitmap_info.bmWidth;
int height = bitmap_info.bmHeight;
std::unique_ptr<uint32_t[]> mask_data(new uint32_t[width * height]);
// Get pixel data from `scoped_mask` converting it to 32bpp along the way.
// GetDIBits() sets the alpha component of every pixel to 0.
BITMAPV5HEADER bmi = {0};
bmi.bV5Size = sizeof(bmi);
bmi.bV5Width = width;
bmi.bV5Height = -height; // request a top-down bitmap.
bmi.bV5Planes = 1;
bmi.bV5BitCount = kBytesPerPixel * 8;
bmi.bV5Compression = BI_RGB;
bmi.bV5AlphaMask = 0xff000000;
bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmi.bV5Intent = LCS_GM_BUSINESS;
if (!GetDIBits(dc, scoped_mask, 0, height, mask_data.get(),
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) {
RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
<< GetLastError();
return NULL;
}
uint32_t* mask_plane = mask_data.get();
std::unique_ptr<DesktopFrame> image(
new BasicDesktopFrame(DesktopSize(width, height)));
bool has_alpha = false;
if (is_color) {
image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
// Get the pixels from the color bitmap.
if (!GetDIBits(dc, scoped_color, 0, height, image->data(),
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) {
RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
<< GetLastError();
return NULL;
}
// GetDIBits() does not provide any indication whether the bitmap has alpha
// channel, so we use HasAlphaChannel() below to find it out.
has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()),
width, width, height);
} else {
// For non-color cursors, the mask contains both an AND and an XOR mask and
// the height includes both. Thus, the width is correct, but we need to
// divide by 2 to get the correct mask height.
height /= 2;
image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
// The XOR mask becomes the color bitmap.
memcpy(image->data(), mask_plane + (width * height),
image->stride() * height);
}
// Reconstruct transparency from the mask if the color image does not has
// alpha channel.
if (!has_alpha) {
bool add_outline = false;
uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
uint32_t* mask = mask_plane;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// The two bitmaps combine as follows:
// mask color Windows Result Our result RGB Alpha
// 0 00 Black Black 00 ff
// 0 ff White White ff ff
// 1 00 Screen Transparent 00 00
// 1 ff Reverse-screen Black 00 ff
//
// Since we don't support XOR cursors, we replace the "Reverse Screen"
// with black. In this case, we also add an outline around the cursor
// so that it is visible against a dark background.
if (*mask == kPixelRgbWhite) {
if (*dst != 0) {
add_outline = true;
*dst = kPixelRgbaBlack;
} else {
*dst = kPixelRgbaTransparent;
}
} else {
*dst = kPixelRgbaBlack ^ *dst;
}
++dst;
++mask;
}
}
if (add_outline) {
AddCursorOutline(width, height,
reinterpret_cast<uint32_t*>(image->data()));
}
}
// Pre-multiply the resulting pixels since MouseCursor uses premultiplied
// images.
AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height);
return new MouseCursor(image.release(), DesktopVector(hotspot_x, hotspot_y));
}
} // namespace webrtc

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
#define MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
#include <windows.h>
namespace webrtc {
class MouseCursor;
// Converts an HCURSOR into a `MouseCursor` instance.
MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor);
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/cursor.h"
#include <memory>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/win/cursor_unittest_resources.h"
#include "modules/desktop_capture/win/scoped_gdi_object.h"
#include "test/gmock.h"
namespace webrtc {
namespace {
// Loads `left` from resources, converts it to a `MouseCursor` instance and
// compares pixels with `right`. Returns true of MouseCursor bits match `right`.
// `right` must be a 32bpp cursor with alpha channel.
bool ConvertToMouseShapeAndCompare(unsigned left, unsigned right) {
HMODULE instance = GetModuleHandle(NULL);
// Load `left` from the EXE module's resources.
win::ScopedCursor cursor(reinterpret_cast<HCURSOR>(
LoadImage(instance, MAKEINTRESOURCE(left), IMAGE_CURSOR, 0, 0, 0)));
EXPECT_TRUE(cursor != NULL);
// Convert `cursor` to `mouse_shape`.
HDC dc = GetDC(NULL);
std::unique_ptr<MouseCursor> mouse_shape(
CreateMouseCursorFromHCursor(dc, cursor));
ReleaseDC(NULL, dc);
EXPECT_TRUE(mouse_shape.get());
// Load `right`.
cursor.Set(reinterpret_cast<HCURSOR>(
LoadImage(instance, MAKEINTRESOURCE(right), IMAGE_CURSOR, 0, 0, 0)));
ICONINFO iinfo;
EXPECT_TRUE(GetIconInfo(cursor, &iinfo));
EXPECT_TRUE(iinfo.hbmColor);
// Make sure the bitmaps will be freed.
win::ScopedBitmap scoped_mask(iinfo.hbmMask);
win::ScopedBitmap scoped_color(iinfo.hbmColor);
// Get `scoped_color` dimensions.
BITMAP bitmap_info;
EXPECT_TRUE(GetObject(scoped_color, sizeof(bitmap_info), &bitmap_info));
int width = bitmap_info.bmWidth;
int height = bitmap_info.bmHeight;
EXPECT_TRUE(DesktopSize(width, height).equals(mouse_shape->image()->size()));
// Get the pixels from `scoped_color`.
int size = width * height;
std::unique_ptr<uint32_t[]> data(new uint32_t[size]);
EXPECT_TRUE(GetBitmapBits(scoped_color, size * sizeof(uint32_t), data.get()));
// Compare the 32bpp image in `mouse_shape` with the one loaded from `right`.
return memcmp(data.get(), mouse_shape->image()->data(),
size * sizeof(uint32_t)) == 0;
}
} // namespace
TEST(MouseCursorTest, MatchCursors) {
EXPECT_TRUE(
ConvertToMouseShapeAndCompare(IDD_CURSOR1_24BPP, IDD_CURSOR1_32BPP));
EXPECT_TRUE(
ConvertToMouseShapeAndCompare(IDD_CURSOR1_8BPP, IDD_CURSOR1_32BPP));
EXPECT_TRUE(
ConvertToMouseShapeAndCompare(IDD_CURSOR2_1BPP, IDD_CURSOR2_32BPP));
EXPECT_TRUE(
ConvertToMouseShapeAndCompare(IDD_CURSOR3_4BPP, IDD_CURSOR3_32BPP));
}
} // namespace webrtc

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
#define MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
#define IDD_CURSOR1_24BPP 101
#define IDD_CURSOR1_32BPP 102
#define IDD_CURSOR1_8BPP 103
#define IDD_CURSOR2_1BPP 104
#define IDD_CURSOR2_32BPP 105
#define IDD_CURSOR3_4BPP 106
#define IDD_CURSOR3_32BPP 107
#endif // MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/cursor_unittest_resources.h"
// These cursors are matched with their less than 32bpp counterparts below.
IDD_CURSOR1_32BPP CURSOR "cursor_test_data/1_32bpp.cur"
IDD_CURSOR2_32BPP CURSOR "cursor_test_data/2_32bpp.cur"
IDD_CURSOR3_32BPP CURSOR "cursor_test_data/3_32bpp.cur"
// Matches IDD_CURSOR1_32BPP.
IDD_CURSOR1_24BPP CURSOR "cursor_test_data/1_24bpp.cur"
// Matches IDD_CURSOR1_32BPP.
IDD_CURSOR1_8BPP CURSOR "cursor_test_data/1_8bpp.cur"
// Matches IDD_CURSOR2_32BPP.
IDD_CURSOR2_1BPP CURSOR "cursor_test_data/2_1bpp.cur"
// Matches IDD_CURSOR3_32BPP.
IDD_CURSOR3_4BPP CURSOR "cursor_test_data/3_4bpp.cur"

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/d3d_device.h"
#include <utility>
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/logging.h"
namespace webrtc {
using Microsoft::WRL::ComPtr;
D3dDevice::D3dDevice() = default;
D3dDevice::D3dDevice(const D3dDevice& other) = default;
D3dDevice::D3dDevice(D3dDevice&& other) = default;
D3dDevice::~D3dDevice() = default;
bool D3dDevice::Initialize(const ComPtr<IDXGIAdapter>& adapter) {
dxgi_adapter_ = adapter;
if (!dxgi_adapter_) {
RTC_LOG(LS_WARNING) << "An empty IDXGIAdapter instance has been received.";
return false;
}
D3D_FEATURE_LEVEL feature_level;
// Default feature levels contain D3D 9.1 through D3D 11.0.
_com_error error = D3D11CreateDevice(
adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED,
nullptr, 0, D3D11_SDK_VERSION, d3d_device_.GetAddressOf(), &feature_level,
context_.GetAddressOf());
if (error.Error() != S_OK || !d3d_device_ || !context_) {
RTC_LOG(LS_WARNING) << "D3D11CreateDevice returned: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
if (feature_level < D3D_FEATURE_LEVEL_11_0) {
RTC_LOG(LS_WARNING)
<< "D3D11CreateDevice returned an instance without DirectX 11 support, "
<< "level " << feature_level << ". Following initialization may fail.";
// D3D_FEATURE_LEVEL_11_0 is not officially documented on MSDN to be a
// requirement of Dxgi duplicator APIs.
}
error = d3d_device_.As(&dxgi_device_);
if (error.Error() != S_OK || !dxgi_device_) {
RTC_LOG(LS_WARNING)
<< "ID3D11Device is not an implementation of IDXGIDevice, "
<< "this usually means the system does not support DirectX "
<< "11. Error received: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
// static
std::vector<D3dDevice> D3dDevice::EnumDevices() {
ComPtr<IDXGIFactory1> factory;
_com_error error =
CreateDXGIFactory1(__uuidof(IDXGIFactory1),
reinterpret_cast<void**>(factory.GetAddressOf()));
if (error.Error() != S_OK || !factory) {
RTC_LOG(LS_WARNING) << "Cannot create IDXGIFactory1: "
<< desktop_capture::utils::ComErrorToString(error);
return std::vector<D3dDevice>();
}
std::vector<D3dDevice> result;
for (int i = 0;; i++) {
ComPtr<IDXGIAdapter> adapter;
error = factory->EnumAdapters(i, adapter.GetAddressOf());
if (error.Error() == S_OK) {
D3dDevice device;
if (device.Initialize(adapter)) {
result.push_back(std::move(device));
}
} else if (error.Error() == DXGI_ERROR_NOT_FOUND) {
break;
} else {
RTC_LOG(LS_WARNING)
<< "IDXGIFactory1::EnumAdapters returned an unexpected error: "
<< desktop_capture::utils::ComErrorToString(error);
}
}
return result;
}
} // namespace webrtc

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_
#define MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_
#include <comdef.h>
#include <d3d11.h>
#include <dxgi.h>
#include <wrl/client.h>
#include <vector>
namespace webrtc {
// A wrapper of ID3D11Device and its corresponding context and IDXGIAdapter.
// This class represents one video card in the system.
class D3dDevice {
public:
D3dDevice(const D3dDevice& other);
D3dDevice(D3dDevice&& other);
~D3dDevice();
ID3D11Device* d3d_device() const { return d3d_device_.Get(); }
ID3D11DeviceContext* context() const { return context_.Get(); }
IDXGIDevice* dxgi_device() const { return dxgi_device_.Get(); }
IDXGIAdapter* dxgi_adapter() const { return dxgi_adapter_.Get(); }
// Returns all D3dDevice instances on the system. Returns an empty vector if
// anything wrong.
static std::vector<D3dDevice> EnumDevices();
private:
// Instances of D3dDevice should only be created by EnumDevices() static
// function.
D3dDevice();
// Initializes the D3dDevice from an IDXGIAdapter.
bool Initialize(const Microsoft::WRL::ComPtr<IDXGIAdapter>& adapter);
Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context_;
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device_;
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/desktop.h"
#include <vector>
#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
namespace webrtc {
Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) {}
Desktop::~Desktop() {
if (own_ && desktop_ != NULL) {
if (!::CloseDesktop(desktop_)) {
RTC_LOG(LS_ERROR) << "Failed to close the owned desktop handle: "
<< GetLastError();
}
}
}
bool Desktop::GetName(std::wstring* desktop_name_out) const {
if (desktop_ == NULL)
return false;
DWORD length = 0;
int rv = GetUserObjectInformationW(desktop_, UOI_NAME, NULL, 0, &length);
if (rv || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
abort();
length /= sizeof(WCHAR);
std::vector<WCHAR> buffer(length);
if (!GetUserObjectInformationW(desktop_, UOI_NAME, &buffer[0],
length * sizeof(WCHAR), &length)) {
RTC_LOG(LS_ERROR) << "Failed to query the desktop name: " << GetLastError();
return false;
}
desktop_name_out->assign(&buffer[0], length / sizeof(WCHAR));
return true;
}
bool Desktop::IsSame(const Desktop& other) const {
std::wstring name;
if (!GetName(&name))
return false;
std::wstring other_name;
if (!other.GetName(&other_name))
return false;
return name == other_name;
}
bool Desktop::SetThreadDesktop() const {
if (!::SetThreadDesktop(desktop_)) {
RTC_LOG(LS_ERROR) << "Failed to assign the desktop to the current thread: "
<< GetLastError();
return false;
}
return true;
}
Desktop* Desktop::GetDesktop(const WCHAR* desktop_name) {
ACCESS_MASK desired_access = DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP | GENERIC_WRITE;
HDESK desktop = OpenDesktopW(desktop_name, 0, FALSE, desired_access);
if (desktop == NULL) {
RTC_LOG(LS_ERROR) << "Failed to open the desktop '"
<< rtc::ToUtf8(desktop_name) << "': " << GetLastError();
return NULL;
}
return new Desktop(desktop, true);
}
Desktop* Desktop::GetInputDesktop() {
HDESK desktop = OpenInputDesktop(
0, FALSE, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE);
if (desktop == NULL)
return NULL;
return new Desktop(desktop, true);
}
Desktop* Desktop::GetThreadDesktop() {
HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId());
if (desktop == NULL) {
RTC_LOG(LS_ERROR)
<< "Failed to retrieve the handle of the desktop assigned to "
"the current thread: "
<< GetLastError();
return NULL;
}
return new Desktop(desktop, false);
}
} // namespace webrtc

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_
#include <windows.h>
#include <string>
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
class RTC_EXPORT Desktop {
public:
~Desktop();
Desktop(const Desktop&) = delete;
Desktop& operator=(const Desktop&) = delete;
// Returns the name of the desktop represented by the object. Return false if
// quering the name failed for any reason.
bool GetName(std::wstring* desktop_name_out) const;
// Returns true if `other` has the same name as this desktop. Returns false
// in any other case including failing Win32 APIs and uninitialized desktop
// handles.
bool IsSame(const Desktop& other) const;
// Assigns the desktop to the current thread. Returns false is the operation
// failed for any reason.
bool SetThreadDesktop() const;
// Returns the desktop by its name or NULL if an error occurs.
static Desktop* GetDesktop(const wchar_t* desktop_name);
// Returns the desktop currently receiving user input or NULL if an error
// occurs.
static Desktop* GetInputDesktop();
// Returns the desktop currently assigned to the calling thread or NULL if
// an error occurs.
static Desktop* GetThreadDesktop();
private:
Desktop(HDESK desktop, bool own);
// The desktop handle.
HDESK desktop_;
// True if `desktop_` must be closed on teardown.
bool own_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
namespace desktop_capture {
namespace utils {
// Generates a human-readable string from a COM error.
std::string ComErrorToString(const _com_error& error) {
char buffer[1024];
rtc::SimpleStringBuilder string_builder(buffer);
// Use _bstr_t to simplify the wchar to char conversion for ErrorMessage().
_bstr_t error_message(error.ErrorMessage());
string_builder.AppendFormat("HRESULT: 0x%08X, Message: %s", error.Error(),
static_cast<const char*>(error_message));
return string_builder.str();
}
} // namespace utils
} // namespace desktop_capture
} // namespace webrtc

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_
#include <comdef.h>
#include <string>
namespace webrtc {
namespace desktop_capture {
namespace utils {
// Generates a human-readable string from a COM error.
std::string ComErrorToString(const _com_error& error);
} // namespace utils
} // namespace desktop_capture
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/display_configuration_monitor.h"
#include <windows.h>
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "rtc_base/logging.h"
namespace webrtc {
bool DisplayConfigurationMonitor::IsChanged(
DesktopCapturer::SourceId source_id) {
DesktopRect rect = GetFullscreenRect();
DesktopVector dpi = GetDpiForSourceId(source_id);
if (!initialized_) {
initialized_ = true;
rect_ = rect;
source_dpis_.emplace(source_id, std::move(dpi));
return false;
}
if (!source_dpis_.contains(source_id)) {
// If this is the first time we've seen this source_id, use the current DPI
// so the monitor does not indicate a change and possibly get reset.
source_dpis_.emplace(source_id, dpi);
}
bool has_changed = false;
if (!rect.equals(rect_) || !source_dpis_.at(source_id).equals(dpi)) {
has_changed = true;
rect_ = rect;
source_dpis_.emplace(source_id, std::move(dpi));
}
return has_changed;
}
void DisplayConfigurationMonitor::Reset() {
initialized_ = false;
source_dpis_.clear();
rect_ = {};
}
DesktopVector DisplayConfigurationMonitor::GetDpiForSourceId(
DesktopCapturer::SourceId source_id) {
HMONITOR monitor = 0;
if (source_id == kFullDesktopScreenId) {
// Get a handle to the primary monitor when capturing the full desktop.
monitor = MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY);
} else if (!GetHmonitorFromDeviceIndex(source_id, &monitor)) {
RTC_LOG(LS_WARNING) << "GetHmonitorFromDeviceIndex failed.";
}
return GetDpiForMonitor(monitor);
}
} // namespace webrtc

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "rtc_base/containers/flat_map.h"
namespace webrtc {
// A passive monitor to detect the change of display configuration on a Windows
// system.
// TODO(zijiehe): Also check for pixel format changes.
class DisplayConfigurationMonitor {
public:
// Checks whether the display configuration has changed since the last time
// IsChanged() was called. |source_id| is used to observe changes for a
// specific display or all displays if kFullDesktopScreenId is passed in.
// Returns false if object was Reset() or if IsChanged() has not been called.
bool IsChanged(DesktopCapturer::SourceId source_id);
// Resets to the initial state.
void Reset();
private:
DesktopVector GetDpiForSourceId(DesktopCapturer::SourceId source_id);
// Represents the size of the desktop which includes all displays.
DesktopRect rect_;
// Tracks the DPI for each display being captured. We need to track for each
// display as each one can be configured to use a different DPI which will not
// be reflected in calls to get the system DPI.
flat_map<DesktopCapturer::SourceId, DesktopVector> source_dpis_;
// Indicates whether |rect_| and |source_dpis_| have been initialized. This is
// used to prevent the monitor instance from signaling 'IsChanged()' before
// the initial values have been set.
bool initialized_ = false;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_

View file

@ -0,0 +1,185 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_adapter_duplicator.h"
#include <comdef.h>
#include <dxgi.h>
#include <algorithm>
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
using Microsoft::WRL::ComPtr;
namespace {
bool IsValidRect(const RECT& rect) {
return rect.right > rect.left && rect.bottom > rect.top;
}
} // namespace
DxgiAdapterDuplicator::DxgiAdapterDuplicator(const D3dDevice& device)
: device_(device) {}
DxgiAdapterDuplicator::DxgiAdapterDuplicator(DxgiAdapterDuplicator&&) = default;
DxgiAdapterDuplicator::~DxgiAdapterDuplicator() = default;
bool DxgiAdapterDuplicator::Initialize() {
if (DoInitialize()) {
return true;
}
duplicators_.clear();
return false;
}
bool DxgiAdapterDuplicator::DoInitialize() {
for (int i = 0;; i++) {
ComPtr<IDXGIOutput> output;
_com_error error =
device_.dxgi_adapter()->EnumOutputs(i, output.GetAddressOf());
if (error.Error() == DXGI_ERROR_NOT_FOUND) {
break;
}
if (error.Error() == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
RTC_LOG(LS_WARNING) << "IDXGIAdapter::EnumOutputs returned "
<< "NOT_CURRENTLY_AVAILABLE. This may happen when "
<< "running in session 0.";
break;
}
if (error.Error() != S_OK || !output) {
RTC_LOG(LS_WARNING) << "IDXGIAdapter::EnumOutputs returned an unexpected "
<< "result: "
<< desktop_capture::utils::ComErrorToString(error);
continue;
}
DXGI_OUTPUT_DESC desc;
error = output->GetDesc(&desc);
if (error.Error() == S_OK) {
if (desc.AttachedToDesktop && IsValidRect(desc.DesktopCoordinates)) {
ComPtr<IDXGIOutput1> output1;
error = output.As(&output1);
if (error.Error() != S_OK || !output1) {
RTC_LOG(LS_WARNING)
<< "Failed to convert IDXGIOutput to IDXGIOutput1, this usually "
<< "means the system does not support DirectX 11";
continue;
}
DxgiOutputDuplicator duplicator(device_, output1, desc);
if (!duplicator.Initialize()) {
RTC_LOG(LS_WARNING) << "Failed to initialize DxgiOutputDuplicator on "
<< "output " << i;
continue;
}
duplicators_.push_back(std::move(duplicator));
desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
} else {
RTC_LOG(LS_ERROR) << (desc.AttachedToDesktop ? "Attached" : "Detached")
<< " output " << i << " ("
<< desc.DesktopCoordinates.top << ", "
<< desc.DesktopCoordinates.left << ") - ("
<< desc.DesktopCoordinates.bottom << ", "
<< desc.DesktopCoordinates.right << ") is ignored.";
}
} else {
RTC_LOG(LS_WARNING) << "Failed to get output description of device " << i
<< ", ignore.";
}
}
if (duplicators_.empty()) {
RTC_LOG(LS_WARNING)
<< "Cannot initialize any DxgiOutputDuplicator instance.";
}
return !duplicators_.empty();
}
void DxgiAdapterDuplicator::Setup(Context* context) {
RTC_DCHECK(context->contexts.empty());
context->contexts.resize(duplicators_.size());
for (size_t i = 0; i < duplicators_.size(); i++) {
duplicators_[i].Setup(&context->contexts[i]);
}
}
void DxgiAdapterDuplicator::Unregister(const Context* const context) {
RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size());
for (size_t i = 0; i < duplicators_.size(); i++) {
duplicators_[i].Unregister(&context->contexts[i]);
}
}
bool DxgiAdapterDuplicator::Duplicate(Context* context,
SharedDesktopFrame* target) {
RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size());
for (size_t i = 0; i < duplicators_.size(); i++) {
if (!duplicators_[i].Duplicate(&context->contexts[i],
duplicators_[i].desktop_rect().top_left(),
target)) {
return false;
}
}
return true;
}
bool DxgiAdapterDuplicator::DuplicateMonitor(Context* context,
int monitor_id,
SharedDesktopFrame* target) {
RTC_DCHECK_GE(monitor_id, 0);
RTC_DCHECK_LT(monitor_id, duplicators_.size());
RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size());
return duplicators_[monitor_id].Duplicate(&context->contexts[monitor_id],
DesktopVector(), target);
}
DesktopRect DxgiAdapterDuplicator::ScreenRect(int id) const {
RTC_DCHECK_GE(id, 0);
RTC_DCHECK_LT(id, duplicators_.size());
return duplicators_[id].desktop_rect();
}
const std::string& DxgiAdapterDuplicator::GetDeviceName(int id) const {
RTC_DCHECK_GE(id, 0);
RTC_DCHECK_LT(id, duplicators_.size());
return duplicators_[id].device_name();
}
int DxgiAdapterDuplicator::screen_count() const {
return static_cast<int>(duplicators_.size());
}
int64_t DxgiAdapterDuplicator::GetNumFramesCaptured() const {
int64_t min = INT64_MAX;
for (const auto& duplicator : duplicators_) {
min = std::min(min, duplicator.num_frames_captured());
}
return min;
}
void DxgiAdapterDuplicator::TranslateRect(const DesktopVector& position) {
desktop_rect_.Translate(position);
RTC_DCHECK_GE(desktop_rect_.left(), 0);
RTC_DCHECK_GE(desktop_rect_.top(), 0);
for (auto& duplicator : duplicators_) {
duplicator.TranslateRect(position);
}
}
} // namespace webrtc

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_
#include <wrl/client.h>
#include <vector>
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/d3d_device.h"
#include "modules/desktop_capture/win/dxgi_context.h"
#include "modules/desktop_capture/win/dxgi_output_duplicator.h"
namespace webrtc {
// A container of DxgiOutputDuplicators to duplicate monitors attached to a
// single video card.
class DxgiAdapterDuplicator {
public:
using Context = DxgiAdapterContext;
// Creates an instance of DxgiAdapterDuplicator from a D3dDevice. Only
// DxgiDuplicatorController can create an instance.
explicit DxgiAdapterDuplicator(const D3dDevice& device);
// Move constructor, to make it possible to store instances of
// DxgiAdapterDuplicator in std::vector<>.
DxgiAdapterDuplicator(DxgiAdapterDuplicator&& other);
~DxgiAdapterDuplicator();
// Initializes the DxgiAdapterDuplicator from a D3dDevice.
bool Initialize();
// Sequentially calls Duplicate function of all the DxgiOutputDuplicator
// instances owned by this instance, and writes into `target`.
bool Duplicate(Context* context, SharedDesktopFrame* target);
// Captures one monitor and writes into `target`. `monitor_id` should be
// between [0, screen_count()).
bool DuplicateMonitor(Context* context,
int monitor_id,
SharedDesktopFrame* target);
// Returns desktop rect covered by this DxgiAdapterDuplicator.
DesktopRect desktop_rect() const { return desktop_rect_; }
// Returns the size of one screen owned by this DxgiAdapterDuplicator. `id`
// should be between [0, screen_count()).
DesktopRect ScreenRect(int id) const;
// Returns the device name of one screen owned by this DxgiAdapterDuplicator
// in utf8 encoding. `id` should be between [0, screen_count()).
const std::string& GetDeviceName(int id) const;
// Returns the count of screens owned by this DxgiAdapterDuplicator. These
// screens can be retrieved by an interger in the range of
// [0, screen_count()).
int screen_count() const;
void Setup(Context* context);
void Unregister(const Context* const context);
// The minimum num_frames_captured() returned by `duplicators_`.
int64_t GetNumFramesCaptured() const;
// Moves `desktop_rect_` and all underlying `duplicators_`. See
// DxgiDuplicatorController::TranslateRect().
void TranslateRect(const DesktopVector& position);
private:
bool DoInitialize();
const D3dDevice device_;
std::vector<DxgiOutputDuplicator> duplicators_;
DesktopRect desktop_rect_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_context.h"
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
namespace webrtc {
DxgiAdapterContext::DxgiAdapterContext() = default;
DxgiAdapterContext::DxgiAdapterContext(const DxgiAdapterContext& context) =
default;
DxgiAdapterContext::~DxgiAdapterContext() = default;
DxgiFrameContext::DxgiFrameContext() = default;
DxgiFrameContext::~DxgiFrameContext() {
Reset();
}
void DxgiFrameContext::Reset() {
DxgiDuplicatorController::Instance()->Unregister(this);
controller_id = 0;
}
} // namespace webrtc

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_
#include <vector>
#include "modules/desktop_capture/desktop_region.h"
namespace webrtc {
// A DxgiOutputContext stores the status of a single DxgiFrame of
// DxgiOutputDuplicator.
struct DxgiOutputContext final {
// The updated region DxgiOutputDuplicator::DetectUpdatedRegion() output
// during last Duplicate() function call. It's always relative to the (0, 0).
DesktopRegion updated_region;
};
// A DxgiAdapterContext stores the status of a single DxgiFrame of
// DxgiAdapterDuplicator.
struct DxgiAdapterContext final {
DxgiAdapterContext();
DxgiAdapterContext(const DxgiAdapterContext& other);
~DxgiAdapterContext();
// Child DxgiOutputContext belongs to this AdapterContext.
std::vector<DxgiOutputContext> contexts;
};
// A DxgiFrameContext stores the status of a single DxgiFrame of
// DxgiDuplicatorController.
struct DxgiFrameContext final {
public:
DxgiFrameContext();
// Unregister this Context instance from DxgiDuplicatorController during
// destructing.
~DxgiFrameContext();
// Reset current Context, so it will be reinitialized next time.
void Reset();
// A Context will have an exactly same `controller_id` as
// DxgiDuplicatorController, to ensure it has been correctly setted up after
// each DxgiDuplicatorController::Initialize().
int controller_id = 0;
// Child DxgiAdapterContext belongs to this DxgiFrameContext.
std::vector<DxgiAdapterContext> contexts;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_

View file

@ -0,0 +1,515 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
#include <windows.h>
#include <algorithm>
#include <string>
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/win/dxgi_frame.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/sleep.h"
namespace webrtc {
namespace {
constexpr DWORD kInvalidSessionId = 0xFFFFFFFF;
DWORD GetCurrentSessionId() {
DWORD session_id = kInvalidSessionId;
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
RTC_LOG(LS_WARNING)
<< "Failed to retrieve current session Id, current binary "
"may not have required priviledge.";
}
return session_id;
}
bool IsConsoleSession() {
return WTSGetActiveConsoleSessionId() == GetCurrentSessionId();
}
} // namespace
// static
std::string DxgiDuplicatorController::ResultName(
DxgiDuplicatorController::Result result) {
switch (result) {
case Result::SUCCEEDED:
return "Succeeded";
case Result::UNSUPPORTED_SESSION:
return "Unsupported session";
case Result::FRAME_PREPARE_FAILED:
return "Frame preparation failed";
case Result::INITIALIZATION_FAILED:
return "Initialization failed";
case Result::DUPLICATION_FAILED:
return "Duplication failed";
case Result::INVALID_MONITOR_ID:
return "Invalid monitor id";
default:
return "Unknown error";
}
}
// static
rtc::scoped_refptr<DxgiDuplicatorController>
DxgiDuplicatorController::Instance() {
// The static instance won't be deleted to ensure it can be used by other
// threads even during program exiting.
static DxgiDuplicatorController* instance = new DxgiDuplicatorController();
return rtc::scoped_refptr<DxgiDuplicatorController>(instance);
}
// static
bool DxgiDuplicatorController::IsCurrentSessionSupported() {
DWORD current_session_id = GetCurrentSessionId();
return current_session_id != kInvalidSessionId && current_session_id != 0;
}
DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {}
void DxgiDuplicatorController::AddRef() {
int refcount = (++refcount_);
RTC_DCHECK(refcount > 0);
}
void DxgiDuplicatorController::Release() {
int refcount = (--refcount_);
RTC_DCHECK(refcount >= 0);
if (refcount == 0) {
RTC_LOG(LS_WARNING) << "Count of references reaches zero, "
"DxgiDuplicatorController will be unloaded.";
Unload();
}
}
bool DxgiDuplicatorController::IsSupported() {
MutexLock lock(&mutex_);
return Initialize();
}
bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
bool result = false;
{
MutexLock lock(&mutex_);
result = Initialize();
*info = d3d_info_;
}
if (!result) {
RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo "
"retrieved may not accurate or out of date.";
}
return result;
}
DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(
DxgiFrame* frame) {
return DoDuplicate(frame, -1);
}
DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(
DxgiFrame* frame,
int monitor_id) {
RTC_DCHECK_GE(monitor_id, 0);
return DoDuplicate(frame, monitor_id);
}
DesktopVector DxgiDuplicatorController::system_dpi() {
MutexLock lock(&mutex_);
if (Initialize()) {
return system_dpi_;
}
return DesktopVector();
}
int DxgiDuplicatorController::ScreenCount() {
MutexLock lock(&mutex_);
if (Initialize()) {
return ScreenCountUnlocked();
}
return 0;
}
bool DxgiDuplicatorController::GetDeviceNames(
std::vector<std::string>* output) {
MutexLock lock(&mutex_);
if (Initialize()) {
GetDeviceNamesUnlocked(output);
return true;
}
return false;
}
DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(
DxgiFrame* frame,
int monitor_id) {
RTC_DCHECK(frame);
MutexLock lock(&mutex_);
// The dxgi components and APIs do not update the screen resolution without
// a reinitialization. So we use the GetDC() function to retrieve the screen
// resolution to decide whether dxgi components need to be reinitialized.
// If the screen resolution changed, it's very likely the next Duplicate()
// function call will fail because of a missing monitor or the frame size is
// not enough to store the output. So we reinitialize dxgi components in-place
// to avoid a capture failure.
// But there is no guarantee GetDC() function returns the same resolution as
// dxgi APIs, we still rely on dxgi components to return the output frame
// size.
// TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
// IDXGIOutputDuplication::GetDesc() can detect the resolution change without
// reinitialization.
if (display_configuration_monitor_.IsChanged(frame->source_id_)) {
Deinitialize();
}
if (!Initialize()) {
if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) {
RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI "
"components cannot be initialized.";
return Result::UNSUPPORTED_SESSION;
}
// Cannot initialize COM components now, display mode may be changing.
return Result::INITIALIZATION_FAILED;
}
if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) {
return Result::FRAME_PREPARE_FAILED;
}
frame->frame()->mutable_updated_region()->Clear();
if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) {
succeeded_duplications_++;
return Result::SUCCEEDED;
}
if (monitor_id >= ScreenCountUnlocked()) {
// It's a user error to provide a `monitor_id` larger than screen count. We
// do not need to deinitialize.
return Result::INVALID_MONITOR_ID;
}
// If the `monitor_id` is valid, but DoDuplicateUnlocked() failed, something
// must be wrong from capturer APIs. We should Deinitialize().
Deinitialize();
return Result::DUPLICATION_FAILED;
}
void DxgiDuplicatorController::Unload() {
MutexLock lock(&mutex_);
Deinitialize();
}
void DxgiDuplicatorController::Unregister(const Context* const context) {
MutexLock lock(&mutex_);
if (ContextExpired(context)) {
// The Context has not been setup after a recent initialization, so it
// should not been registered in duplicators.
return;
}
for (size_t i = 0; i < duplicators_.size(); i++) {
duplicators_[i].Unregister(&context->contexts[i]);
}
}
bool DxgiDuplicatorController::Initialize() {
if (!duplicators_.empty()) {
return true;
}
if (DoInitialize()) {
return true;
}
Deinitialize();
return false;
}
bool DxgiDuplicatorController::DoInitialize() {
RTC_DCHECK(desktop_rect_.is_empty());
RTC_DCHECK(duplicators_.empty());
d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
std::vector<D3dDevice> devices = D3dDevice::EnumDevices();
if (devices.empty()) {
RTC_LOG(LS_WARNING) << "No D3dDevice found.";
return false;
}
for (size_t i = 0; i < devices.size(); i++) {
D3D_FEATURE_LEVEL feature_level =
devices[i].d3d_device()->GetFeatureLevel();
if (d3d_info_.max_feature_level == 0 ||
feature_level > d3d_info_.max_feature_level) {
d3d_info_.max_feature_level = feature_level;
}
if (d3d_info_.min_feature_level == 0 ||
feature_level < d3d_info_.min_feature_level) {
d3d_info_.min_feature_level = feature_level;
}
DxgiAdapterDuplicator duplicator(devices[i]);
// There may be several video cards on the system, some of them may not
// support IDXGOutputDuplication. But they should not impact others from
// taking effect, so we should continually try other adapters. This usually
// happens when a non-official virtual adapter is installed on the system.
if (!duplicator.Initialize()) {
RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on "
"adapter "
<< i;
continue;
}
RTC_DCHECK(!duplicator.desktop_rect().is_empty());
duplicators_.push_back(std::move(duplicator));
desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
}
TranslateRect();
HDC hdc = GetDC(nullptr);
// Use old DPI value if failed.
if (hdc) {
system_dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX),
GetDeviceCaps(hdc, LOGPIXELSY));
ReleaseDC(nullptr, hdc);
}
identity_++;
if (duplicators_.empty()) {
RTC_LOG(LS_WARNING)
<< "Cannot initialize any DxgiAdapterDuplicator instance.";
}
return !duplicators_.empty();
}
void DxgiDuplicatorController::Deinitialize() {
desktop_rect_ = DesktopRect();
duplicators_.clear();
display_configuration_monitor_.Reset();
}
bool DxgiDuplicatorController::ContextExpired(
const Context* const context) const {
RTC_DCHECK(context);
return context->controller_id != identity_ ||
context->contexts.size() != duplicators_.size();
}
void DxgiDuplicatorController::Setup(Context* context) {
if (ContextExpired(context)) {
RTC_DCHECK(context);
context->contexts.clear();
context->contexts.resize(duplicators_.size());
for (size_t i = 0; i < duplicators_.size(); i++) {
duplicators_[i].Setup(&context->contexts[i]);
}
context->controller_id = identity_;
}
}
bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
int monitor_id,
SharedDesktopFrame* target) {
Setup(context);
if (!EnsureFrameCaptured(context, target)) {
return false;
}
bool result = false;
if (monitor_id < 0) {
// Capture entire screen.
result = DoDuplicateAll(context, target);
} else {
result = DoDuplicateOne(context, monitor_id, target);
}
if (result) {
target->set_dpi(system_dpi_);
return true;
}
return false;
}
bool DxgiDuplicatorController::DoDuplicateAll(Context* context,
SharedDesktopFrame* target) {
for (size_t i = 0; i < duplicators_.size(); i++) {
if (!duplicators_[i].Duplicate(&context->contexts[i], target)) {
return false;
}
}
return true;
}
bool DxgiDuplicatorController::DoDuplicateOne(Context* context,
int monitor_id,
SharedDesktopFrame* target) {
RTC_DCHECK(monitor_id >= 0);
for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size();
i++) {
if (monitor_id >= duplicators_[i].screen_count()) {
monitor_id -= duplicators_[i].screen_count();
} else {
if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id,
target)) {
target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left());
return true;
}
return false;
}
}
return false;
}
int64_t DxgiDuplicatorController::GetNumFramesCaptured() const {
int64_t min = INT64_MAX;
for (const auto& duplicator : duplicators_) {
min = std::min(min, duplicator.GetNumFramesCaptured());
}
return min;
}
DesktopSize DxgiDuplicatorController::desktop_size() const {
return desktop_rect_.size();
}
DesktopRect DxgiDuplicatorController::ScreenRect(int id) const {
RTC_DCHECK(id >= 0);
for (size_t i = 0; i < duplicators_.size(); i++) {
if (id >= duplicators_[i].screen_count()) {
id -= duplicators_[i].screen_count();
} else {
return duplicators_[i].ScreenRect(id);
}
}
return DesktopRect();
}
int DxgiDuplicatorController::ScreenCountUnlocked() const {
int result = 0;
for (auto& duplicator : duplicators_) {
result += duplicator.screen_count();
}
return result;
}
void DxgiDuplicatorController::GetDeviceNamesUnlocked(
std::vector<std::string>* output) const {
RTC_DCHECK(output);
for (auto& duplicator : duplicators_) {
for (int i = 0; i < duplicator.screen_count(); i++) {
output->push_back(duplicator.GetDeviceName(i));
}
}
}
DesktopSize DxgiDuplicatorController::SelectedDesktopSize(
int monitor_id) const {
if (monitor_id < 0) {
return desktop_size();
}
return ScreenRect(monitor_id).size();
}
bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
SharedDesktopFrame* target) {
// On a modern system, the FPS / monitor refresh rate is usually larger than
// or equal to 60. So 17 milliseconds is enough to capture at least one frame.
const int64_t ms_per_frame = 17;
// Skip frames to ensure a full frame refresh has occurred and the DXGI
// machinery is producing frames before this function returns.
int64_t frames_to_skip = 1;
// The total time out milliseconds for this function. If we cannot get enough
// frames during this time interval, this function returns false, and cause
// the DXGI components to be reinitialized. This usually should not happen
// unless the system is switching display mode when this function is being
// called. 500 milliseconds should be enough for ~30 frames.
const int64_t timeout_ms = 500;
if (GetNumFramesCaptured() == 0 && !IsConsoleSession()) {
// When capturing a console session, waiting for a single frame is
// sufficient to ensure that DXGI output duplication is working. When the
// session is not attached to the console, it has been observed that DXGI
// may produce up to 4 frames (typically 1-2 though) before stopping. When
// this condition occurs, no errors are returned from the output duplication
// API, it simply appears that nothing is changing on the screen. Thus for
// detached sessions, we need to capture a few extra frames before we can be
// confident that output duplication was initialized properly.
frames_to_skip = 5;
}
if (GetNumFramesCaptured() >= frames_to_skip) {
return true;
}
std::unique_ptr<SharedDesktopFrame> fallback_frame;
SharedDesktopFrame* shared_frame = nullptr;
if (target->size().width() >= desktop_size().width() &&
target->size().height() >= desktop_size().height()) {
// `target` is large enough to cover entire screen, we do not need to use
// `fallback_frame`.
shared_frame = target;
} else {
fallback_frame = SharedDesktopFrame::Wrap(
std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(desktop_size())));
shared_frame = fallback_frame.get();
}
const int64_t start_ms = rtc::TimeMillis();
while (GetNumFramesCaptured() < frames_to_skip) {
if (!DoDuplicateAll(context, shared_frame)) {
return false;
}
// Calling DoDuplicateAll() may change the number of frames captured.
if (GetNumFramesCaptured() >= frames_to_skip) {
break;
}
if (rtc::TimeMillis() - start_ms > timeout_ms) {
RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip
<< " frames "
"within "
<< timeout_ms << " milliseconds.";
return false;
}
// Sleep `ms_per_frame` before attempting to capture the next frame to
// ensure the video adapter has time to update the screen.
webrtc::SleepMs(ms_per_frame);
}
return true;
}
void DxgiDuplicatorController::TranslateRect() {
const DesktopVector position =
DesktopVector().subtract(desktop_rect_.top_left());
desktop_rect_.Translate(position);
for (auto& duplicator : duplicators_) {
duplicator.TranslateRect(position);
}
}
} // namespace webrtc

View file

@ -0,0 +1,257 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
#include <d3dcommon.h>
#include <atomic>
#include <string>
#include <vector>
#include "api/scoped_refptr.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/d3d_device.h"
#include "modules/desktop_capture/win/display_configuration_monitor.h"
#include "modules/desktop_capture/win/dxgi_adapter_duplicator.h"
#include "modules/desktop_capture/win/dxgi_context.h"
#include "modules/desktop_capture/win/dxgi_frame.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
// A controller for all the objects we need to call Windows DirectX capture APIs
// It's a singleton because only one IDXGIOutputDuplication instance per monitor
// is allowed per application.
//
// Consumers should create a DxgiDuplicatorController::Context and keep it
// throughout their lifetime, and pass it when calling Duplicate(). Consumers
// can also call IsSupported() to determine whether the system supports DXGI
// duplicator or not. If a previous IsSupported() function call returns true,
// but a later Duplicate() returns false, this usually means the display mode is
// changing. Consumers should retry after a while. (Typically 50 milliseconds,
// but according to hardware performance, this time may vary.)
// The underlying DxgiOutputDuplicators may take an additional reference on the
// frame passed in to the Duplicate methods so that they can guarantee delivery
// of new frames when requested; since if there have been no updates to the
// surface, they may be unable to capture a frame.
class RTC_EXPORT DxgiDuplicatorController {
public:
using Context = DxgiFrameContext;
// A collection of D3d information we are interested in, which may impact
// capturer performance or reliability.
struct D3dInfo {
// Each video adapter has its own D3D_FEATURE_LEVEL, so this structure
// contains the minimum and maximium D3D_FEATURE_LEVELs current system
// supports.
// Both fields can be 0, which is the default value to indicate no valid
// D3D_FEATURE_LEVEL has been retrieved from underlying OS APIs.
D3D_FEATURE_LEVEL min_feature_level;
D3D_FEATURE_LEVEL max_feature_level;
// TODO(zijiehe): Add more fields, such as manufacturer name, mode, driver
// version.
};
// These values are persisted to logs. Entries should not be renumbered or
// reordered and numeric values should never be reused. This enum corresponds
// to WebRtcDirectXCapturerResult in tools/metrics/histograms/enums.xml.
enum class Result {
SUCCEEDED = 0,
UNSUPPORTED_SESSION = 1,
FRAME_PREPARE_FAILED = 2,
INITIALIZATION_FAILED = 3,
DUPLICATION_FAILED = 4,
INVALID_MONITOR_ID = 5,
MAX_VALUE = INVALID_MONITOR_ID
};
// Converts `result` into user-friendly string representation. The return
// value should not be used to identify error types.
static std::string ResultName(Result result);
// Returns the singleton instance of DxgiDuplicatorController.
static rtc::scoped_refptr<DxgiDuplicatorController> Instance();
// See ScreenCapturerWinDirectx::IsCurrentSessionSupported().
static bool IsCurrentSessionSupported();
// All the following public functions implicitly call Initialize() function.
// Detects whether the system supports DXGI based capturer.
bool IsSupported();
// Returns a copy of D3dInfo composed by last Initialize() function call. This
// function always copies the latest information into `info`. But once the
// function returns false, the information in `info` may not accurate.
bool RetrieveD3dInfo(D3dInfo* info);
// Captures current screen and writes into `frame`. May retain a reference to
// `frame`'s underlying |SharedDesktopFrame|.
// TODO(zijiehe): Windows cannot guarantee the frames returned by each
// IDXGIOutputDuplication are synchronized. But we are using a totally
// different threading model than the way Windows suggested, it's hard to
// synchronize them manually. We should find a way to do it.
Result Duplicate(DxgiFrame* frame);
// Captures one monitor and writes into target. `monitor_id` must be >= 0. If
// `monitor_id` is greater than the total screen count of all the Duplicators,
// this function returns false. May retain a reference to `frame`'s underlying
// |SharedDesktopFrame|.
Result DuplicateMonitor(DxgiFrame* frame, int monitor_id);
// Returns dpi of current system. Returns an empty DesktopVector if system
// does not support DXGI based capturer.
DesktopVector system_dpi();
// Returns the count of screens on the system. These screens can be retrieved
// by an integer in the range of [0, ScreenCount()). If system does not
// support DXGI based capturer, this function returns 0.
int ScreenCount();
// Returns the device names of all screens on the system in utf8 encoding.
// These screens can be retrieved by an integer in the range of
// [0, output->size()). If system does not support DXGI based capturer, this
// function returns false.
bool GetDeviceNames(std::vector<std::string>* output);
private:
// DxgiFrameContext calls private Unregister(Context*) function in Reset().
friend void DxgiFrameContext::Reset();
// scoped_refptr<DxgiDuplicatorController> accesses private AddRef() and
// Release() functions.
friend class webrtc::scoped_refptr<DxgiDuplicatorController>;
// A private constructor to ensure consumers to use
// DxgiDuplicatorController::Instance().
DxgiDuplicatorController();
// Not implemented: The singleton DxgiDuplicatorController instance should not
// be deleted.
~DxgiDuplicatorController();
// RefCountedInterface implementations.
void AddRef();
void Release();
// Does the real duplication work. Setting `monitor_id` < 0 to capture entire
// screen. This function calls Initialize(). And if the duplication failed,
// this function calls Deinitialize() to ensure the Dxgi components can be
// reinitialized next time.
Result DoDuplicate(DxgiFrame* frame, int monitor_id);
// Unload all the DXGI components and releases the resources. This function
// wraps Deinitialize() with `mutex_`.
void Unload();
// Unregisters Context from this instance and all DxgiAdapterDuplicator(s)
// it owns.
void Unregister(const Context* const context);
// All functions below should be called in `mutex_` locked scope and should be
// after a successful Initialize().
// If current instance has not been initialized, executes DoInitialize()
// function, and returns initialize result. Otherwise directly returns true.
// This function may calls Deinitialize() if initialization failed.
bool Initialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Does the real initialization work, this function should only be called in
// Initialize().
bool DoInitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Clears all COM components referred to by this instance. So next Duplicate()
// call will eventually initialize this instance again.
void Deinitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// A helper function to check whether a Context has been expired.
bool ContextExpired(const Context* const context) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Updates Context if needed.
void Setup(Context* context) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
bool DoDuplicateUnlocked(Context* context,
int monitor_id,
SharedDesktopFrame* target)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Captures all monitors.
bool DoDuplicateAll(Context* context, SharedDesktopFrame* target)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Captures one monitor.
bool DoDuplicateOne(Context* context,
int monitor_id,
SharedDesktopFrame* target)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// The minimum GetNumFramesCaptured() returned by `duplicators_`.
int64_t GetNumFramesCaptured() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns a DesktopSize to cover entire `desktop_rect_`.
DesktopSize desktop_size() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns the size of one screen. `id` should be >= 0. If system does not
// support DXGI based capturer, or `id` is greater than the total screen count
// of all the Duplicators, this function returns an empty DesktopRect.
DesktopRect ScreenRect(int id) const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
int ScreenCountUnlocked() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void GetDeviceNamesUnlocked(std::vector<std::string>* output) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns the desktop size of the selected screen `monitor_id`. Setting
// `monitor_id` < 0 to return the entire screen size.
DesktopSize SelectedDesktopSize(int monitor_id) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Retries DoDuplicateAll() for several times until GetNumFramesCaptured() is
// large enough. Returns false if DoDuplicateAll() returns false, or
// GetNumFramesCaptured() has never reached the requirement.
// According to http://crbug.com/682112, dxgi capturer returns a black frame
// during first several capture attempts.
bool EnsureFrameCaptured(Context* context, SharedDesktopFrame* target)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Moves `desktop_rect_` and all underlying `duplicators_`, putting top left
// corner of the desktop at (0, 0). This is necessary because DXGI_OUTPUT_DESC
// may return negative coordinates. Called from DoInitialize() after all
// DxgiAdapterDuplicator and DxgiOutputDuplicator instances are initialized.
void TranslateRect() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// The count of references which are now "living".
std::atomic_int refcount_;
// This lock must be locked whenever accessing any of the following objects.
Mutex mutex_;
// A self-incremented integer to compare with the one in Context. It ensures
// a Context instance is always initialized after DxgiDuplicatorController.
int identity_ RTC_GUARDED_BY(mutex_) = 0;
DesktopRect desktop_rect_ RTC_GUARDED_BY(mutex_);
DesktopVector system_dpi_ RTC_GUARDED_BY(mutex_);
std::vector<DxgiAdapterDuplicator> duplicators_ RTC_GUARDED_BY(mutex_);
D3dInfo d3d_info_ RTC_GUARDED_BY(mutex_);
DisplayConfigurationMonitor display_configuration_monitor_
RTC_GUARDED_BY(mutex_);
// A number to indicate how many successful duplications have been performed.
uint32_t succeeded_duplications_ RTC_GUARDED_BY(mutex_) = 0;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_frame.h"
#include <string.h>
#include <utility>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
DxgiFrame::DxgiFrame(SharedMemoryFactory* factory) : factory_(factory) {}
DxgiFrame::~DxgiFrame() = default;
bool DxgiFrame::Prepare(DesktopSize size, DesktopCapturer::SourceId source_id) {
if (source_id != source_id_) {
// Once the source has been changed, the entire source should be copied.
source_id_ = source_id;
context_.Reset();
}
if (resolution_tracker_.SetResolution(size)) {
// Once the output size changed, recreate the SharedDesktopFrame.
frame_.reset();
}
if (!frame_) {
std::unique_ptr<DesktopFrame> frame;
if (factory_) {
frame = SharedMemoryDesktopFrame::Create(size, factory_);
if (!frame) {
RTC_LOG(LS_WARNING) << "DxgiFrame cannot create a new DesktopFrame.";
return false;
}
// DirectX capturer won't paint each pixel in the frame due to its one
// capturer per monitor design. So once the new frame is created, we
// should clear it to avoid the legacy image to be remained on it. See
// http://crbug.com/708766.
RTC_DCHECK_EQ(frame->stride(),
frame->size().width() * DesktopFrame::kBytesPerPixel);
memset(frame->data(), 0, frame->stride() * frame->size().height());
} else {
frame.reset(new BasicDesktopFrame(size));
}
frame_ = SharedDesktopFrame::Wrap(std::move(frame));
}
return !!frame_;
}
SharedDesktopFrame* DxgiFrame::frame() const {
RTC_DCHECK(frame_);
return frame_.get();
}
DxgiFrame::Context* DxgiFrame::context() {
RTC_DCHECK(frame_);
return &context_;
}
} // namespace webrtc

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_
#include <memory>
#include <vector>
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/resolution_tracker.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/shared_memory.h"
#include "modules/desktop_capture/win/dxgi_context.h"
namespace webrtc {
class DxgiDuplicatorController;
// A pair of a SharedDesktopFrame and a DxgiDuplicatorController::Context for
// the client of DxgiDuplicatorController.
class DxgiFrame final {
public:
using Context = DxgiFrameContext;
// DxgiFrame does not take ownership of `factory`, consumers should ensure it
// outlives this instance. nullptr is acceptable.
explicit DxgiFrame(SharedMemoryFactory* factory);
~DxgiFrame();
// Should not be called if Prepare() is not executed or returns false.
SharedDesktopFrame* frame() const;
private:
// Allows DxgiDuplicatorController to access Prepare() and context() function
// as well as Context class.
friend class DxgiDuplicatorController;
// Prepares current instance with desktop size and source id.
bool Prepare(DesktopSize size, DesktopCapturer::SourceId source_id);
// Should not be called if Prepare() is not executed or returns false.
Context* context();
SharedMemoryFactory* const factory_;
ResolutionTracker resolution_tracker_;
DesktopCapturer::SourceId source_id_ = kFullDesktopScreenId;
std::unique_ptr<SharedDesktopFrame> frame_;
Context context_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_

View file

@ -0,0 +1,427 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_output_duplicator.h"
#include <dxgi.h>
#include <dxgiformat.h>
#include <string.h>
#include <unknwn.h>
#include <windows.h>
#include <algorithm>
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "modules/desktop_capture/win/dxgi_texture_mapping.h"
#include "modules/desktop_capture/win/dxgi_texture_staging.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/win32.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
using Microsoft::WRL::ComPtr;
namespace {
// Timeout for AcquireNextFrame() call.
// DxgiDuplicatorController leverages external components to do the capture
// scheduling. So here DxgiOutputDuplicator does not need to actively wait for a
// new frame.
const int kAcquireTimeoutMs = 0;
DesktopRect RECTToDesktopRect(const RECT& rect) {
return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}
Rotation DxgiRotationToRotation(DXGI_MODE_ROTATION rotation) {
switch (rotation) {
case DXGI_MODE_ROTATION_IDENTITY:
case DXGI_MODE_ROTATION_UNSPECIFIED:
return Rotation::CLOCK_WISE_0;
case DXGI_MODE_ROTATION_ROTATE90:
return Rotation::CLOCK_WISE_90;
case DXGI_MODE_ROTATION_ROTATE180:
return Rotation::CLOCK_WISE_180;
case DXGI_MODE_ROTATION_ROTATE270:
return Rotation::CLOCK_WISE_270;
}
RTC_DCHECK_NOTREACHED();
return Rotation::CLOCK_WISE_0;
}
} // namespace
DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device,
const ComPtr<IDXGIOutput1>& output,
const DXGI_OUTPUT_DESC& desc)
: device_(device),
output_(output),
device_name_(rtc::ToUtf8(desc.DeviceName)),
desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) {
RTC_DCHECK(output_);
RTC_DCHECK(!desktop_rect_.is_empty());
RTC_DCHECK_GT(desktop_rect_.width(), 0);
RTC_DCHECK_GT(desktop_rect_.height(), 0);
}
DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) =
default;
DxgiOutputDuplicator::~DxgiOutputDuplicator() {
if (duplication_) {
duplication_->ReleaseFrame();
}
texture_.reset();
}
bool DxgiOutputDuplicator::Initialize() {
if (DuplicateOutput()) {
if (desc_.DesktopImageInSystemMemory) {
texture_.reset(new DxgiTextureMapping(duplication_.Get()));
} else {
texture_.reset(new DxgiTextureStaging(device_));
}
return true;
} else {
duplication_.Reset();
return false;
}
}
bool DxgiOutputDuplicator::DuplicateOutput() {
RTC_DCHECK(!duplication_);
_com_error error =
output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()),
duplication_.GetAddressOf());
if (error.Error() != S_OK || !duplication_) {
RTC_LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
memset(&desc_, 0, sizeof(desc_));
duplication_->GetDesc(&desc_);
// DXGI_FORMAT_R16G16B16A16_FLOAT is returned for HDR monitor,
// DXGI_FORMAT_B8G8R8A8_UNORM for others.
if ((desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) &&
(desc_.ModeDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT)) {
RTC_LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8, 16 bit)"
<< "which is required by downstream components"
<< "format is " << desc_.ModeDesc.Format;
return false;
}
if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() ||
static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) {
RTC_LOG(LS_ERROR)
<< "IDXGIDuplicateOutput does not return a same size as its "
<< "IDXGIOutput1, size returned by IDXGIDuplicateOutput is "
<< desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height
<< ", size returned by IDXGIOutput1 is " << desktop_rect_.width()
<< " x " << desktop_rect_.height();
return false;
}
rotation_ = DxgiRotationToRotation(desc_.Rotation);
unrotated_size_ = RotateSize(desktop_size(), ReverseRotation(rotation_));
return true;
}
bool DxgiOutputDuplicator::ReleaseFrame() {
RTC_DCHECK(duplication_);
_com_error error = duplication_->ReleaseFrame();
if (error.Error() != S_OK) {
RTC_LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
bool DxgiOutputDuplicator::ContainsMouseCursor(
const DXGI_OUTDUPL_FRAME_INFO& frame_info) {
// The DXGI_OUTDUPL_POINTER_POSITION structure that describes the most recent
// mouse position is only valid if the LastMouseUpdateTime member is a non-
// zero value.
if (frame_info.LastMouseUpdateTime.QuadPart == 0)
return false;
// Ignore cases when the mouse shape has changed and not the position.
const bool new_pointer_shape = (frame_info.PointerShapeBufferSize != 0);
if (new_pointer_shape)
return false;
// The mouse cursor has moved and we can now query if the mouse pointer is
// drawn onto the desktop image or not to decide if we must draw the mouse
// pointer shape onto the desktop image (always done by default currently).
// Either the mouse pointer is already drawn onto the desktop image that
// IDXGIOutputDuplication::AcquireNextFrame provides or the mouse pointer is
// separate from the desktop image. If the mouse pointer is drawn onto the
// desktop image, the pointer position data that is reported by
// AcquireNextFrame indicates that a separate pointer isnt visible, hence
// `frame_info.PointerPosition.Visible` is false.
const bool cursor_embedded_in_frame = !frame_info.PointerPosition.Visible;
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.Win.DirectXCursorEmbedded",
cursor_embedded_in_frame);
return cursor_embedded_in_frame;
}
bool DxgiOutputDuplicator::Duplicate(Context* context,
DesktopVector offset,
SharedDesktopFrame* target) {
RTC_DCHECK(duplication_);
RTC_DCHECK(texture_);
RTC_DCHECK(target);
if (!DesktopRect::MakeSize(target->size())
.ContainsRect(GetTranslatedDesktopRect(offset))) {
// target size is not large enough to cover current output region.
return false;
}
DXGI_OUTDUPL_FRAME_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
ComPtr<IDXGIResource> resource;
_com_error error = duplication_->AcquireNextFrame(
kAcquireTimeoutMs, &frame_info, resource.GetAddressOf());
if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) {
RTC_LOG(LS_ERROR) << "Failed to capture frame: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
const bool cursor_embedded_in_frame = ContainsMouseCursor(frame_info);
// We need to merge updated region with the one from context, but only spread
// updated region from current frame. So keeps a copy of updated region from
// context here. The `updated_region` always starts from (0, 0).
DesktopRegion updated_region;
updated_region.Swap(&context->updated_region);
if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0 && resource) {
DetectUpdatedRegion(frame_info, &context->updated_region);
SpreadContextChange(context);
if (!texture_->CopyFrom(frame_info, resource.Get())) {
return false;
}
updated_region.AddRegion(context->updated_region);
// TODO(zijiehe): Figure out why clearing context->updated_region() here
// triggers screen flickering?
const DesktopFrame& source = texture_->AsDesktopFrame();
if (rotation_ != Rotation::CLOCK_WISE_0) {
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
it.Advance()) {
// The `updated_region` returned by Windows is rotated, but the `source`
// frame is not. So we need to rotate it reversely.
const DesktopRect source_rect =
RotateRect(it.rect(), desktop_size(), ReverseRotation(rotation_));
RotateDesktopFrame(source, source_rect, rotation_, offset, target);
}
} else {
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
it.Advance()) {
// The DesktopRect in `target`, starts from offset.
DesktopRect dest_rect = it.rect();
dest_rect.Translate(offset);
target->CopyPixelsFrom(source, it.rect().top_left(), dest_rect);
}
}
last_frame_ = target->Share();
last_frame_offset_ = offset;
updated_region.Translate(offset.x(), offset.y());
target->mutable_updated_region()->AddRegion(updated_region);
target->set_may_contain_cursor(cursor_embedded_in_frame);
num_frames_captured_++;
return texture_->Release() && ReleaseFrame();
}
if (last_frame_) {
// No change since last frame or AcquireNextFrame() timed out, we will
// export last frame to the target.
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
it.Advance()) {
// The DesktopRect in `source`, starts from last_frame_offset_.
DesktopRect source_rect = it.rect();
// The DesktopRect in `target`, starts from offset.
DesktopRect target_rect = source_rect;
source_rect.Translate(last_frame_offset_);
target_rect.Translate(offset);
target->CopyPixelsFrom(*last_frame_, source_rect.top_left(), target_rect);
}
updated_region.Translate(offset.x(), offset.y());
target->mutable_updated_region()->AddRegion(updated_region);
target->set_may_contain_cursor(cursor_embedded_in_frame);
} else {
// If we were at the very first frame, and capturing failed, the
// context->updated_region should be kept unchanged for next attempt.
context->updated_region.Swap(&updated_region);
}
// If AcquireNextFrame() failed with timeout error, we do not need to release
// the frame.
return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame();
}
DesktopRect DxgiOutputDuplicator::GetTranslatedDesktopRect(
DesktopVector offset) const {
DesktopRect result(DesktopRect::MakeSize(desktop_size()));
result.Translate(offset);
return result;
}
DesktopRect DxgiOutputDuplicator::GetUntranslatedDesktopRect() const {
return DesktopRect::MakeSize(desktop_size());
}
void DxgiOutputDuplicator::DetectUpdatedRegion(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
DesktopRegion* updated_region) {
if (DoDetectUpdatedRegion(frame_info, updated_region)) {
// Make sure even a region returned by Windows API is out of the scope of
// desktop_rect_, we still won't export it to the target DesktopFrame.
updated_region->IntersectWith(GetUntranslatedDesktopRect());
} else {
updated_region->SetRect(GetUntranslatedDesktopRect());
}
}
bool DxgiOutputDuplicator::DoDetectUpdatedRegion(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
DesktopRegion* updated_region) {
RTC_DCHECK(updated_region);
updated_region->Clear();
if (frame_info.TotalMetadataBufferSize == 0) {
// This should not happen, since frame_info.AccumulatedFrames > 0.
RTC_LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, "
<< "but TotalMetadataBufferSize == 0";
return false;
}
if (metadata_.size() < frame_info.TotalMetadataBufferSize) {
metadata_.clear(); // Avoid data copy
metadata_.resize(frame_info.TotalMetadataBufferSize);
}
UINT buff_size = 0;
DXGI_OUTDUPL_MOVE_RECT* move_rects =
reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata_.data());
size_t move_rects_count = 0;
_com_error error = duplication_->GetFrameMoveRects(
static_cast<UINT>(metadata_.size()), move_rects, &buff_size);
if (error.Error() != S_OK) {
RTC_LOG(LS_ERROR) << "Failed to get move rectangles: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
RECT* dirty_rects = reinterpret_cast<RECT*>(metadata_.data() + buff_size);
size_t dirty_rects_count = 0;
error = duplication_->GetFrameDirtyRects(
static_cast<UINT>(metadata_.size()) - buff_size, dirty_rects, &buff_size);
if (error.Error() != S_OK) {
RTC_LOG(LS_ERROR) << "Failed to get dirty rectangles: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
dirty_rects_count = buff_size / sizeof(RECT);
while (move_rects_count > 0) {
// DirectX capturer API may randomly return unmoved move_rects, which should
// be skipped to avoid unnecessary wasting of differing and encoding
// resources.
// By using testing application it2me_standalone_host_main, this check
// reduces average capture time by 0.375% (4.07 -> 4.055), and average
// encode time by 0.313% (8.042 -> 8.016) without other impacts.
if (move_rects->SourcePoint.x != move_rects->DestinationRect.left ||
move_rects->SourcePoint.y != move_rects->DestinationRect.top) {
updated_region->AddRect(
RotateRect(DesktopRect::MakeXYWH(move_rects->SourcePoint.x,
move_rects->SourcePoint.y,
move_rects->DestinationRect.right -
move_rects->DestinationRect.left,
move_rects->DestinationRect.bottom -
move_rects->DestinationRect.top),
unrotated_size_, rotation_));
updated_region->AddRect(
RotateRect(DesktopRect::MakeLTRB(move_rects->DestinationRect.left,
move_rects->DestinationRect.top,
move_rects->DestinationRect.right,
move_rects->DestinationRect.bottom),
unrotated_size_, rotation_));
} else {
RTC_LOG(LS_INFO) << "Unmoved move_rect detected, ["
<< move_rects->DestinationRect.left << ", "
<< move_rects->DestinationRect.top << "] - ["
<< move_rects->DestinationRect.right << ", "
<< move_rects->DestinationRect.bottom << "].";
}
move_rects++;
move_rects_count--;
}
while (dirty_rects_count > 0) {
updated_region->AddRect(RotateRect(
DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top,
dirty_rects->right, dirty_rects->bottom),
unrotated_size_, rotation_));
dirty_rects++;
dirty_rects_count--;
}
return true;
}
void DxgiOutputDuplicator::Setup(Context* context) {
RTC_DCHECK(context->updated_region.is_empty());
// Always copy entire monitor during the first Duplicate() function call.
context->updated_region.AddRect(GetUntranslatedDesktopRect());
RTC_DCHECK(std::find(contexts_.begin(), contexts_.end(), context) ==
contexts_.end());
contexts_.push_back(context);
}
void DxgiOutputDuplicator::Unregister(const Context* const context) {
auto it = std::find(contexts_.begin(), contexts_.end(), context);
RTC_DCHECK(it != contexts_.end());
contexts_.erase(it);
}
void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) {
for (Context* dest : contexts_) {
RTC_DCHECK(dest);
if (dest != source) {
dest->updated_region.AddRegion(source->updated_region);
}
}
}
DesktopSize DxgiOutputDuplicator::desktop_size() const {
return desktop_rect_.size();
}
int64_t DxgiOutputDuplicator::num_frames_captured() const {
#if !defined(NDEBUG)
RTC_DCHECK_EQ(!!last_frame_, num_frames_captured_ > 0);
#endif
return num_frames_captured_;
}
void DxgiOutputDuplicator::TranslateRect(const DesktopVector& position) {
desktop_rect_.Translate(position);
RTC_DCHECK_GE(desktop_rect_.left(), 0);
RTC_DCHECK_GE(desktop_rect_.top(), 0);
}
} // namespace webrtc

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
#include <comdef.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <wrl/client.h>
#include <memory>
#include <string>
#include <vector>
#include "modules/desktop_capture/desktop_frame_rotation.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/d3d_device.h"
#include "modules/desktop_capture/win/dxgi_context.h"
#include "modules/desktop_capture/win/dxgi_texture.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
// Duplicates the content on one IDXGIOutput, i.e. one monitor attached to one
// video card. None of functions in this class is thread-safe.
class DxgiOutputDuplicator {
public:
using Context = DxgiOutputContext;
// Creates an instance of DxgiOutputDuplicator from a D3dDevice and one of its
// IDXGIOutput1. Caller must maintain the lifetime of device, to make sure it
// outlives this instance. Only DxgiAdapterDuplicator can create an instance.
DxgiOutputDuplicator(const D3dDevice& device,
const Microsoft::WRL::ComPtr<IDXGIOutput1>& output,
const DXGI_OUTPUT_DESC& desc);
// To allow this class to work with vector.
DxgiOutputDuplicator(DxgiOutputDuplicator&& other);
// Destructs this instance. We need to make sure texture_ has been released
// before duplication_.
~DxgiOutputDuplicator();
// Initializes duplication_ object.
bool Initialize();
// Copies the content of current IDXGIOutput to the `target`. To improve the
// performance, this function copies only regions merged from
// `context`->updated_region and DetectUpdatedRegion(). The `offset` decides
// the offset in the `target` where the content should be copied to. i.e. this
// function copies the content to the rectangle of (offset.x(), offset.y()) to
// (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()).
// Returns false in case of a failure.
// May retain a reference to `target` so that a "captured" frame can be
// returned in the event that a new frame is not ready to be captured yet.
// (Or in other words, if the call to IDXGIOutputDuplication::AcquireNextFrame
// indicates that there is not yet a new frame, this is usually because no
// updates have occurred to the frame).
bool Duplicate(Context* context,
DesktopVector offset,
SharedDesktopFrame* target);
// Returns the desktop rect covered by this DxgiOutputDuplicator.
DesktopRect desktop_rect() const { return desktop_rect_; }
// Returns the device name from DXGI_OUTPUT_DESC in utf8 encoding.
const std::string& device_name() const { return device_name_; }
void Setup(Context* context);
void Unregister(const Context* const context);
// How many frames have been captured by this DxigOutputDuplicator.
int64_t num_frames_captured() const;
// Moves `desktop_rect_`. See DxgiDuplicatorController::TranslateRect().
void TranslateRect(const DesktopVector& position);
private:
// Calls DoDetectUpdatedRegion(). If it fails, this function sets the
// `updated_region` as entire UntranslatedDesktopRect().
void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
DesktopRegion* updated_region);
// Returns untranslated updated region, which are directly returned by Windows
// APIs. Returns false in case of a failure.
bool DoDetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
DesktopRegion* updated_region);
// Returns true if the mouse cursor is embedded in the captured frame and
// false if not. Also logs the same boolean as
// WebRTC.DesktopCapture.Win.DirectXCursorEmbedded UMA.
bool ContainsMouseCursor(const DXGI_OUTDUPL_FRAME_INFO& frame_info);
bool ReleaseFrame();
// Initializes duplication_ instance. Expects duplication_ is in empty status.
// Returns false if system does not support IDXGIOutputDuplication.
bool DuplicateOutput();
// Returns a DesktopRect with the same size of desktop_size(), but translated
// by offset.
DesktopRect GetTranslatedDesktopRect(DesktopVector offset) const;
// Returns a DesktopRect with the same size of desktop_size(), but starts from
// (0, 0).
DesktopRect GetUntranslatedDesktopRect() const;
// Spreads changes from `context` to other registered Context(s) in
// contexts_.
void SpreadContextChange(const Context* const context);
// Returns the size of desktop rectangle current instance representing.
DesktopSize desktop_size() const;
const D3dDevice device_;
const Microsoft::WRL::ComPtr<IDXGIOutput1> output_;
const std::string device_name_;
DesktopRect desktop_rect_;
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
DXGI_OUTDUPL_DESC desc_;
std::vector<uint8_t> metadata_;
std::unique_ptr<DxgiTexture> texture_;
Rotation rotation_;
DesktopSize unrotated_size_;
// After each AcquireNextFrame() function call, updated_region_(s) of all
// active Context(s) need to be updated. Since they have missed the
// change this time. And during next Duplicate() function call, their
// updated_region_ will be merged and copied.
std::vector<Context*> contexts_;
// The last full frame of this output and its offset. If on AcquireNextFrame()
// failed because of timeout, i.e. no update, we can copy content from
// `last_frame_`.
std::unique_ptr<SharedDesktopFrame> last_frame_;
DesktopVector last_frame_offset_;
int64_t num_frames_captured_ = 0;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_texture.h"
#include <comdef.h>
#include <d3d11.h>
#include <wrl/client.h>
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
using Microsoft::WRL::ComPtr;
namespace webrtc {
namespace {
class DxgiDesktopFrame : public DesktopFrame {
public:
explicit DxgiDesktopFrame(const DxgiTexture& texture)
: DesktopFrame(texture.desktop_size(),
texture.pitch(),
texture.bits(),
nullptr) {}
~DxgiDesktopFrame() override = default;
};
} // namespace
DxgiTexture::DxgiTexture() = default;
DxgiTexture::~DxgiTexture() = default;
bool DxgiTexture::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) {
RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0);
RTC_DCHECK(resource);
ComPtr<ID3D11Texture2D> texture;
_com_error error = resource->QueryInterface(
__uuidof(ID3D11Texture2D),
reinterpret_cast<void**>(texture.GetAddressOf()));
if (error.Error() != S_OK || !texture) {
RTC_LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
D3D11_TEXTURE2D_DESC desc = {0};
texture->GetDesc(&desc);
desktop_size_.set(desc.Width, desc.Height);
return CopyFromTexture(frame_info, texture.Get());
}
const DesktopFrame& DxgiTexture::AsDesktopFrame() {
if (!frame_) {
frame_.reset(new DxgiDesktopFrame(*this));
}
return *frame_;
}
bool DxgiTexture::Release() {
frame_.reset();
return DoRelease();
}
DXGI_MAPPED_RECT* DxgiTexture::rect() {
return &rect_;
}
} // namespace webrtc

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_
#include <d3d11.h>
#include <dxgi1_2.h>
#include <memory>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
class DesktopRegion;
// A texture copied or mapped from a DXGI_OUTDUPL_FRAME_INFO and IDXGIResource.
class DxgiTexture {
public:
// Creates a DxgiTexture instance, which represents the `desktop_size` area of
// entire screen -- usually a monitor on the system.
DxgiTexture();
virtual ~DxgiTexture();
// Copies selected regions of a frame represented by frame_info and resource.
// Returns false if anything wrong.
bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource);
const DesktopSize& desktop_size() const { return desktop_size_; }
uint8_t* bits() const { return static_cast<uint8_t*>(rect_.pBits); }
int pitch() const { return static_cast<int>(rect_.Pitch); }
// Releases the resource currently holds by this instance. Returns false if
// anything wrong, and this instance should be deprecated in this state. bits,
// pitch and AsDesktopFrame are only valid after a success CopyFrom() call,
// but before Release() call.
bool Release();
// Returns a DesktopFrame snapshot of a DxgiTexture instance. This
// DesktopFrame is used to copy a DxgiTexture content to another DesktopFrame
// only. And it should not outlive its DxgiTexture instance.
const DesktopFrame& AsDesktopFrame();
protected:
DXGI_MAPPED_RECT* rect();
virtual bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) = 0;
virtual bool DoRelease() = 0;
private:
DXGI_MAPPED_RECT rect_ = {0};
DesktopSize desktop_size_;
std::unique_ptr<DesktopFrame> frame_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_texture_mapping.h"
#include <comdef.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
DxgiTextureMapping::DxgiTextureMapping(IDXGIOutputDuplication* duplication)
: duplication_(duplication) {
RTC_DCHECK(duplication_);
}
DxgiTextureMapping::~DxgiTextureMapping() = default;
bool DxgiTextureMapping::CopyFromTexture(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) {
RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0);
RTC_DCHECK(texture);
*rect() = {0};
_com_error error = duplication_->MapDesktopSurface(rect());
if (error.Error() != S_OK) {
*rect() = {0};
RTC_LOG(LS_ERROR)
<< "Failed to map the IDXGIOutputDuplication to a bitmap: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
bool DxgiTextureMapping::DoRelease() {
_com_error error = duplication_->UnMapDesktopSurface();
if (error.Error() != S_OK) {
RTC_LOG(LS_ERROR) << "Failed to unmap the IDXGIOutputDuplication: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_
#include <d3d11.h>
#include <dxgi1_2.h>
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/win/dxgi_texture.h"
namespace webrtc {
// A DxgiTexture which directly maps bitmap from IDXGIResource. This class is
// used when DXGI_OUTDUPL_DESC.DesktopImageInSystemMemory is true. (This usually
// means the video card shares main memory with CPU, instead of having its own
// individual memory.)
class DxgiTextureMapping : public DxgiTexture {
public:
// Creates a DxgiTextureMapping instance. Caller must maintain the lifetime
// of input `duplication` to make sure it outlives this instance.
explicit DxgiTextureMapping(IDXGIOutputDuplication* duplication);
~DxgiTextureMapping() override;
protected:
bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) override;
bool DoRelease() override;
private:
IDXGIOutputDuplication* const duplication_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/dxgi_texture_staging.h"
#include <comdef.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <unknwn.h>
#include "modules/desktop_capture/win/desktop_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "system_wrappers/include/metrics.h"
using Microsoft::WRL::ComPtr;
namespace webrtc {
DxgiTextureStaging::DxgiTextureStaging(const D3dDevice& device)
: device_(device) {}
DxgiTextureStaging::~DxgiTextureStaging() = default;
bool DxgiTextureStaging::InitializeStage(ID3D11Texture2D* texture) {
RTC_DCHECK(texture);
D3D11_TEXTURE2D_DESC desc = {0};
texture->GetDesc(&desc);
desc.ArraySize = 1;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MipLevels = 1;
desc.MiscFlags = 0;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_STAGING;
if (stage_) {
AssertStageAndSurfaceAreSameObject();
D3D11_TEXTURE2D_DESC current_desc;
stage_->GetDesc(&current_desc);
const bool recreate_needed =
(memcmp(&desc, &current_desc, sizeof(D3D11_TEXTURE2D_DESC)) != 0);
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.StagingTextureRecreate",
recreate_needed);
if (!recreate_needed) {
return true;
}
// The descriptions are not consistent, we need to create a new
// ID3D11Texture2D instance.
stage_.Reset();
surface_.Reset();
} else {
RTC_DCHECK(!surface_);
}
_com_error error = device_.d3d_device()->CreateTexture2D(
&desc, nullptr, stage_.GetAddressOf());
if (error.Error() != S_OK || !stage_) {
RTC_LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
error = stage_.As(&surface_);
if (error.Error() != S_OK || !surface_) {
RTC_LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
void DxgiTextureStaging::AssertStageAndSurfaceAreSameObject() {
ComPtr<IUnknown> left;
ComPtr<IUnknown> right;
bool left_result = SUCCEEDED(stage_.As(&left));
bool right_result = SUCCEEDED(surface_.As(&right));
RTC_DCHECK(left_result);
RTC_DCHECK(right_result);
RTC_DCHECK(left.Get() == right.Get());
}
bool DxgiTextureStaging::CopyFromTexture(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) {
RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0);
RTC_DCHECK(texture);
// AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to
// copy it to a CPU accessible staging ID3D11Texture2D.
if (!InitializeStage(texture)) {
return false;
}
device_.context()->CopyResource(static_cast<ID3D11Resource*>(stage_.Get()),
static_cast<ID3D11Resource*>(texture));
*rect() = {0};
_com_error error = surface_->Map(rect(), DXGI_MAP_READ);
if (error.Error() != S_OK) {
*rect() = {0};
RTC_LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap: "
<< desktop_capture::utils::ComErrorToString(error);
return false;
}
return true;
}
bool DxgiTextureStaging::DoRelease() {
_com_error error = surface_->Unmap();
if (error.Error() != S_OK) {
stage_.Reset();
surface_.Reset();
}
// If using staging mode, we only need to recreate ID3D11Texture2D instance.
// This will happen during next CopyFrom call. So this function always returns
// true.
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_
#include <d3d11.h>
#include <dxgi1_2.h>
#include <wrl/client.h>
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/win/d3d_device.h"
#include "modules/desktop_capture/win/dxgi_texture.h"
namespace webrtc {
// A pair of an ID3D11Texture2D and an IDXGISurface. We need an ID3D11Texture2D
// instance to copy GPU texture to RAM, but an IDXGISurface instance to map the
// texture into a bitmap buffer. These two instances are pointing to a same
// object.
//
// An ID3D11Texture2D is created by an ID3D11Device, so a DxgiTexture cannot be
// shared between two DxgiAdapterDuplicators.
class DxgiTextureStaging : public DxgiTexture {
public:
// Creates a DxgiTextureStaging instance. Caller must maintain the lifetime
// of input device to make sure it outlives this instance.
explicit DxgiTextureStaging(const D3dDevice& device);
~DxgiTextureStaging() override;
protected:
// Copies selected regions of a frame represented by frame_info and texture.
// Returns false if anything wrong.
bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) override;
bool DoRelease() override;
private:
// Initializes stage_ from a CPU inaccessible IDXGIResource. Returns false if
// it failed to execute Windows APIs, or the size of the texture is not
// consistent with desktop_rect.
bool InitializeStage(ID3D11Texture2D* texture);
// Makes sure stage_ and surface_ are always pointing to a same object.
// We need an ID3D11Texture2D instance for
// ID3D11DeviceContext::CopySubresourceRegion, but an IDXGISurface for
// IDXGISurface::Map.
void AssertStageAndSurfaceAreSameObject();
const DesktopRect desktop_rect_;
const D3dDevice device_;
Microsoft::WRL::ComPtr<ID3D11Texture2D> stage_;
Microsoft::WRL::ComPtr<IDXGISurface> surface_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_

View file

@ -0,0 +1,294 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/full_screen_win_application_handler.h"
#include <algorithm>
#include <cwctype>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/logging.h" // For RTC_LOG_GLE
#include "rtc_base/string_utils.h"
namespace webrtc {
namespace {
// Utility function to verify that `window` has class name equal to `class_name`
bool CheckWindowClassName(HWND window, const wchar_t* class_name) {
const size_t classNameLength = wcslen(class_name);
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
// says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't
// need to have a buffer bigger than that.
constexpr size_t kMaxClassNameLength = 256;
WCHAR buffer[kMaxClassNameLength];
const int length = ::GetClassNameW(window, buffer, kMaxClassNameLength);
if (length <= 0)
return false;
if (static_cast<size_t>(length) != classNameLength)
return false;
return wcsncmp(buffer, class_name, classNameLength) == 0;
}
std::string WindowText(HWND window) {
size_t len = ::GetWindowTextLength(window);
if (len == 0)
return std::string();
std::vector<wchar_t> buffer(len + 1, 0);
size_t copied = ::GetWindowTextW(window, buffer.data(), buffer.size());
if (copied == 0)
return std::string();
return rtc::ToUtf8(buffer.data(), copied);
}
DWORD WindowProcessId(HWND window) {
DWORD dwProcessId = 0;
::GetWindowThreadProcessId(window, &dwProcessId);
return dwProcessId;
}
std::wstring FileNameFromPath(const std::wstring& path) {
auto found = path.rfind(L"\\");
if (found == std::string::npos)
return path;
return path.substr(found + 1);
}
// Returns windows which belong to given process id
// `sources` is a full list of available windows
// `processId` is a process identifier (window owner)
// `window_to_exclude` is a window to be exluded from result
DesktopCapturer::SourceList GetProcessWindows(
const DesktopCapturer::SourceList& sources,
DWORD processId,
HWND window_to_exclude) {
DesktopCapturer::SourceList result;
std::copy_if(sources.begin(), sources.end(), std::back_inserter(result),
[&](DesktopCapturer::Source source) {
const HWND source_hwnd = reinterpret_cast<HWND>(source.id);
return window_to_exclude != source_hwnd &&
WindowProcessId(source_hwnd) == processId;
});
return result;
}
class FullScreenPowerPointHandler : public FullScreenApplicationHandler {
public:
explicit FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId)
: FullScreenApplicationHandler(sourceId) {}
~FullScreenPowerPointHandler() override {}
DesktopCapturer::SourceId FindFullScreenWindow(
const DesktopCapturer::SourceList& window_list,
int64_t timestamp) const override {
if (window_list.empty())
return 0;
HWND original_window = reinterpret_cast<HWND>(GetSourceId());
DWORD process_id = WindowProcessId(original_window);
DesktopCapturer::SourceList powerpoint_windows =
GetProcessWindows(window_list, process_id, original_window);
if (powerpoint_windows.empty())
return 0;
if (GetWindowType(original_window) != WindowType::kEditor)
return 0;
const auto original_document = GetDocumentFromEditorTitle(original_window);
for (const auto& source : powerpoint_windows) {
HWND window = reinterpret_cast<HWND>(source.id);
// Looking for slide show window for the same document
if (GetWindowType(window) != WindowType::kSlideShow ||
GetDocumentFromSlideShowTitle(window) != original_document) {
continue;
}
return source.id;
}
return 0;
}
private:
enum class WindowType { kEditor, kSlideShow, kOther };
WindowType GetWindowType(HWND window) const {
if (IsEditorWindow(window))
return WindowType::kEditor;
else if (IsSlideShowWindow(window))
return WindowType::kSlideShow;
else
return WindowType::kOther;
}
constexpr static char kDocumentTitleSeparator[] = " - ";
std::string GetDocumentFromEditorTitle(HWND window) const {
std::string title = WindowText(window);
auto position = title.find(kDocumentTitleSeparator);
return std::string(absl::StripAsciiWhitespace(
absl::string_view(title).substr(0, position)));
}
std::string GetDocumentFromSlideShowTitle(HWND window) const {
std::string title = WindowText(window);
auto left_pos = title.find(kDocumentTitleSeparator);
auto right_pos = title.rfind(kDocumentTitleSeparator);
constexpr size_t kSeparatorLength = arraysize(kDocumentTitleSeparator) - 1;
if (left_pos == std::string::npos || right_pos == std::string::npos)
return title;
if (right_pos > left_pos + kSeparatorLength) {
auto result_len = right_pos - left_pos - kSeparatorLength;
auto document = absl::string_view(title).substr(
left_pos + kSeparatorLength, result_len);
return std::string(absl::StripAsciiWhitespace(document));
} else {
auto document = absl::string_view(title).substr(
left_pos + kSeparatorLength, std::wstring::npos);
return std::string(absl::StripAsciiWhitespace(document));
}
}
bool IsEditorWindow(HWND window) const {
return CheckWindowClassName(window, L"PPTFrameClass");
}
bool IsSlideShowWindow(HWND window) const {
const LONG style = ::GetWindowLong(window, GWL_STYLE);
const bool min_box = WS_MINIMIZEBOX & style;
const bool max_box = WS_MAXIMIZEBOX & style;
return !min_box && !max_box;
}
};
class OpenOfficeApplicationHandler : public FullScreenApplicationHandler {
public:
explicit OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId)
: FullScreenApplicationHandler(sourceId) {}
DesktopCapturer::SourceId FindFullScreenWindow(
const DesktopCapturer::SourceList& window_list,
int64_t timestamp) const override {
if (window_list.empty())
return 0;
DWORD process_id = WindowProcessId(reinterpret_cast<HWND>(GetSourceId()));
DesktopCapturer::SourceList app_windows =
GetProcessWindows(window_list, process_id, nullptr);
DesktopCapturer::SourceList document_windows;
std::copy_if(
app_windows.begin(), app_windows.end(),
std::back_inserter(document_windows),
[this](const DesktopCapturer::Source& x) { return IsEditorWindow(x); });
// Check if we have only one document window, otherwise it's not possible
// to securely match a document window and a slide show window which has
// empty title.
if (document_windows.size() != 1) {
return 0;
}
// Check if document window has been selected as a source
if (document_windows.front().id != GetSourceId()) {
return 0;
}
// Check if we have a slide show window.
auto slide_show_window =
std::find_if(app_windows.begin(), app_windows.end(),
[this](const DesktopCapturer::Source& x) {
return IsSlideShowWindow(x);
});
if (slide_show_window == app_windows.end())
return 0;
return slide_show_window->id;
}
private:
bool IsEditorWindow(const DesktopCapturer::Source& source) const {
if (source.title.empty()) {
return false;
}
return CheckWindowClassName(reinterpret_cast<HWND>(source.id), L"SALFRAME");
}
bool IsSlideShowWindow(const DesktopCapturer::Source& source) const {
// Check title size to filter out a Presenter Control window which shares
// window class with Slide Show window but has non empty title.
if (!source.title.empty()) {
return false;
}
return CheckWindowClassName(reinterpret_cast<HWND>(source.id),
L"SALTMPSUBFRAME");
}
};
std::wstring GetPathByWindowId(HWND window_id) {
DWORD process_id = WindowProcessId(window_id);
HANDLE process =
::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
if (process == NULL)
return L"";
DWORD path_len = MAX_PATH;
WCHAR path[MAX_PATH];
std::wstring result;
if (::QueryFullProcessImageNameW(process, 0, path, &path_len))
result = std::wstring(path, path_len);
else
RTC_LOG_GLE(LS_ERROR) << "QueryFullProcessImageName failed.";
::CloseHandle(process);
return result;
}
} // namespace
std::unique_ptr<FullScreenApplicationHandler>
CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id) {
std::unique_ptr<FullScreenApplicationHandler> result;
HWND hwnd = reinterpret_cast<HWND>(source_id);
std::wstring exe_path = GetPathByWindowId(hwnd);
std::wstring file_name = FileNameFromPath(exe_path);
std::transform(file_name.begin(), file_name.end(), file_name.begin(),
std::towupper);
if (file_name == L"POWERPNT.EXE") {
result = std::make_unique<FullScreenPowerPointHandler>(source_id);
} else if (file_name == L"SOFFICE.BIN" &&
absl::EndsWith(WindowText(hwnd), "OpenOffice Impress")) {
result = std::make_unique<OpenOfficeApplicationHandler>(source_id);
}
return result;
}
} // namespace webrtc

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_
#define MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_
#include <memory>
#include "modules/desktop_capture/full_screen_application_handler.h"
namespace webrtc {
std::unique_ptr<FullScreenApplicationHandler>
CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId sourceId);
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
#include <windows.h>
namespace webrtc {
namespace win {
// Scoper for GDI objects.
template <class T, class Traits>
class ScopedGDIObject {
public:
ScopedGDIObject() : handle_(NULL) {}
explicit ScopedGDIObject(T object) : handle_(object) {}
~ScopedGDIObject() { Traits::Close(handle_); }
ScopedGDIObject(const ScopedGDIObject&) = delete;
ScopedGDIObject& operator=(const ScopedGDIObject&) = delete;
T Get() { return handle_; }
void Set(T object) {
if (handle_ && object != handle_)
Traits::Close(handle_);
handle_ = object;
}
ScopedGDIObject& operator=(T object) {
Set(object);
return *this;
}
T release() {
T object = handle_;
handle_ = NULL;
return object;
}
operator T() { return handle_; }
private:
T handle_;
};
// The traits class that uses DeleteObject() to close a handle.
template <typename T>
class DeleteObjectTraits {
public:
DeleteObjectTraits() = delete;
DeleteObjectTraits(const DeleteObjectTraits&) = delete;
DeleteObjectTraits& operator=(const DeleteObjectTraits&) = delete;
// Closes the handle.
static void Close(T handle) {
if (handle)
DeleteObject(handle);
}
};
// The traits class that uses DestroyCursor() to close a handle.
class DestroyCursorTraits {
public:
DestroyCursorTraits() = delete;
DestroyCursorTraits(const DestroyCursorTraits&) = delete;
DestroyCursorTraits& operator=(const DestroyCursorTraits&) = delete;
// Closes the handle.
static void Close(HCURSOR handle) {
if (handle)
DestroyCursor(handle);
}
};
typedef ScopedGDIObject<HBITMAP, DeleteObjectTraits<HBITMAP> > ScopedBitmap;
typedef ScopedGDIObject<HCURSOR, DestroyCursorTraits> ScopedCursor;
} // namespace win
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/scoped_thread_desktop.h"
#include "modules/desktop_capture/win/desktop.h"
namespace webrtc {
ScopedThreadDesktop::ScopedThreadDesktop()
: initial_(Desktop::GetThreadDesktop()) {}
ScopedThreadDesktop::~ScopedThreadDesktop() {
Revert();
}
bool ScopedThreadDesktop::IsSame(const Desktop& desktop) {
if (assigned_.get() != NULL) {
return assigned_->IsSame(desktop);
} else {
return initial_->IsSame(desktop);
}
}
void ScopedThreadDesktop::Revert() {
if (assigned_.get() != NULL) {
initial_->SetThreadDesktop();
assigned_.reset();
}
}
bool ScopedThreadDesktop::SetThreadDesktop(Desktop* desktop) {
Revert();
std::unique_ptr<Desktop> scoped_desktop(desktop);
if (initial_->IsSame(*desktop))
return true;
if (!desktop->SetThreadDesktop())
return false;
assigned_.reset(scoped_desktop.release());
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_
#include <windows.h>
#include <memory>
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
class Desktop;
class RTC_EXPORT ScopedThreadDesktop {
public:
ScopedThreadDesktop();
~ScopedThreadDesktop();
ScopedThreadDesktop(const ScopedThreadDesktop&) = delete;
ScopedThreadDesktop& operator=(const ScopedThreadDesktop&) = delete;
// Returns true if `desktop` has the same desktop name as the currently
// assigned desktop (if assigned) or as the initial desktop (if not assigned).
// Returns false in any other case including failing Win32 APIs and
// uninitialized desktop handles.
bool IsSame(const Desktop& desktop);
// Reverts the calling thread to use the initial desktop.
void Revert();
// Assigns `desktop` to be the calling thread. Returns true if the thread has
// been switched to `desktop` successfully. Takes ownership of `desktop`.
bool SetThreadDesktop(Desktop* desktop);
private:
// The desktop handle assigned to the calling thread by Set
std::unique_ptr<Desktop> assigned_;
// The desktop handle assigned to the calling thread at creation.
std::unique_ptr<Desktop> initial_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_

View file

@ -0,0 +1,206 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include <shellscalingapi.h>
#include <windows.h>
#include <string>
#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 {
bool HasActiveDisplay() {
DesktopCapturer::SourceList screens;
return GetScreenList(&screens) && !screens.empty();
}
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names /* = nullptr */) {
RTC_DCHECK(screens->empty());
RTC_DCHECK(!device_names || device_names->empty());
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);
// `enum_result` is 0 if we have enumerated all devices.
if (!enum_result) {
break;
}
// We only care about active displays.
if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
continue;
}
screens->push_back({device_index, std::string()});
if (device_names) {
device_names->push_back(rtc::ToUtf8(device.DeviceName));
}
}
return true;
}
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;
}
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) {
// There is a bug in a Windows OS API that causes a crash when capturing if
// there are no active displays. We must ensure there is an active display
// before returning true.
if (!HasActiveDisplay())
return false;
return true;
}
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(MONITORINFO);
return GetMonitorInfoA(monitor, &monitor_info);
}
DesktopRect GetMonitorRect(const HMONITOR monitor) {
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(MONITORINFO);
if (!GetMonitorInfoA(monitor, &monitor_info)) {
return DesktopRect();
}
return DesktopRect::MakeLTRB(
monitor_info.rcMonitor.left, monitor_info.rcMonitor.top,
monitor_info.rcMonitor.right, monitor_info.rcMonitor.bottom);
}
bool IsScreenValid(const DesktopCapturer::SourceId screen,
std::wstring* device_key) {
if (screen == kFullDesktopScreenId) {
*device_key = L"";
return true;
}
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0);
if (enum_result) {
*device_key = device.DeviceKey;
}
return !!enum_result;
}
DesktopRect GetFullscreenRect() {
return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN),
GetSystemMetrics(SM_YVIRTUALSCREEN),
GetSystemMetrics(SM_CXVIRTUALSCREEN),
GetSystemMetrics(SM_CYVIRTUALSCREEN));
}
DesktopVector GetDpiForMonitor(HMONITOR monitor) {
UINT dpi_x, dpi_y;
// MDT_EFFECTIVE_DPI includes the scale factor as well as the system DPI.
HRESULT hr = ::GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y);
if (SUCCEEDED(hr)) {
return {static_cast<INT>(dpi_x), static_cast<INT>(dpi_y)};
}
RTC_LOG_GLE_EX(LS_WARNING, hr) << "GetDpiForMonitor() failed";
// If we can't get the per-monitor DPI, then return the system DPI.
HDC hdc = GetDC(nullptr);
if (hdc) {
DesktopVector dpi{GetDeviceCaps(hdc, LOGPIXELSX),
GetDeviceCaps(hdc, LOGPIXELSY)};
ReleaseDC(nullptr, hdc);
return dpi;
}
// If everything fails, then return the default DPI for Windows.
return {96, 96};
}
DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
const std::wstring& device_key) {
if (screen == kFullDesktopScreenId) {
return GetFullscreenRect();
}
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0);
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) {
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) {
return DesktopRect();
}
return DesktopRect::MakeXYWH(
device_mode.dmPosition.x, device_mode.dmPosition.y,
device_mode.dmPelsWidth, device_mode.dmPelsHeight);
}
} // namespace webrtc

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
#if defined(WEBRTC_WIN)
// Forward declare HMONITOR in a windows.h compatible way so that we can avoid
// including windows.h.
#define WEBRTC_DECLARE_HANDLE(name) \
struct name##__; \
typedef struct name##__* name
WEBRTC_DECLARE_HANDLE(HMONITOR);
#undef WEBRTC_DECLARE_HANDLE
#endif
#include <string>
#include <vector>
#include "modules/desktop_capture/desktop_capturer.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
// Returns true if the system has at least one active display.
bool HasActiveDisplay();
// 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
// encoding. If this function returns true, consumers can always assume that
// `screens`[i] and `device_names`[i] indicate the same monitor on the system.
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names = nullptr);
// Converts a device index (which are returned by `GetScreenList`) into an
// HMONITOR.
bool GetHmonitorFromDeviceIndex(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(HMONITOR monitor);
// Returns the rect of the monitor identified by `monitor`, relative to the
// primary display's top-left. On failure, returns an empty rect.
DesktopRect GetMonitorRect(HMONITOR monitor);
// Returns the DPI for the specified monitor. On failure, returns the system DPI
// or the Windows default DPI (96x96) if the system DPI can't be retrieved.
DesktopVector GetDpiForMonitor(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);
// Get the rect of the entire system in system coordinate system. I.e. the
// primary monitor always starts from (0, 0).
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,
const std::wstring& device_key);
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capture_utils.h"
#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"
namespace webrtc {
TEST(ScreenCaptureUtilsTest, GetScreenList) {
DesktopCapturer::SourceList screens;
std::vector<std::string> device_names;
ASSERT_TRUE(GetScreenList(&screens));
screens.clear();
ASSERT_TRUE(GetScreenList(&screens, &device_names));
ASSERT_EQ(screens.size(), device_names.size());
}
TEST(ScreenCaptureUtilsTest, DeviceIndexToHmonitor) {
DesktopCapturer::SourceList screens;
ASSERT_TRUE(GetScreenList(&screens));
if (screens.empty()) {
RTC_LOG(LS_INFO)
<< "Skip ScreenCaptureUtilsTest on systems with no monitors.";
GTEST_SKIP();
}
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(screens[0].id, &hmonitor));
ASSERT_TRUE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, FullScreenDeviceIndexToHmonitor) {
if (!HasActiveDisplay()) {
RTC_LOG(LS_INFO)
<< "Skip ScreenCaptureUtilsTest on systems with no monitors.";
GTEST_SKIP();
}
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
ASSERT_TRUE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, NoMonitors) {
if (HasActiveDisplay()) {
RTC_LOG(LS_INFO) << "Skip ScreenCaptureUtilsTest designed specifically for "
"systems with no monitors";
GTEST_SKIP();
}
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
// The monitor should be invalid since the system has no attached displays.
ASSERT_FALSE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, InvalidDeviceIndexToHmonitor) {
HMONITOR hmonitor;
ASSERT_FALSE(GetHmonitorFromDeviceIndex(kInvalidScreenId, &hmonitor));
}
} // namespace webrtc

View file

@ -0,0 +1,246 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capturer_win_directx.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/trace_event.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
using Microsoft::WRL::ComPtr;
// static
bool ScreenCapturerWinDirectx::IsSupported() {
// Forwards IsSupported() function call to DxgiDuplicatorController.
return DxgiDuplicatorController::Instance()->IsSupported();
}
// static
bool ScreenCapturerWinDirectx::RetrieveD3dInfo(D3dInfo* info) {
// Forwards SupportedFeatureLevels() function call to
// DxgiDuplicatorController.
return DxgiDuplicatorController::Instance()->RetrieveD3dInfo(info);
}
// static
bool ScreenCapturerWinDirectx::IsCurrentSessionSupported() {
return DxgiDuplicatorController::IsCurrentSessionSupported();
}
// static
bool ScreenCapturerWinDirectx::GetScreenListFromDeviceNames(
const std::vector<std::string>& device_names,
DesktopCapturer::SourceList* screens) {
RTC_DCHECK(screens->empty());
DesktopCapturer::SourceList gdi_screens;
std::vector<std::string> gdi_names;
if (!GetScreenList(&gdi_screens, &gdi_names)) {
return false;
}
RTC_DCHECK_EQ(gdi_screens.size(), gdi_names.size());
ScreenId max_screen_id = -1;
for (const DesktopCapturer::Source& screen : gdi_screens) {
max_screen_id = std::max(max_screen_id, screen.id);
}
for (const auto& device_name : device_names) {
const auto it = std::find(gdi_names.begin(), gdi_names.end(), device_name);
if (it == gdi_names.end()) {
// devices_names[i] has not been found in gdi_names, so use max_screen_id.
max_screen_id++;
screens->push_back({max_screen_id});
} else {
screens->push_back({gdi_screens[it - gdi_names.begin()]});
}
}
return true;
}
// static
int ScreenCapturerWinDirectx::GetIndexFromScreenId(
ScreenId id,
const std::vector<std::string>& device_names) {
DesktopCapturer::SourceList screens;
if (!GetScreenListFromDeviceNames(device_names, &screens)) {
return -1;
}
RTC_DCHECK_EQ(device_names.size(), screens.size());
for (size_t i = 0; i < screens.size(); i++) {
if (screens[i].id == id) {
return static_cast<int>(i);
}
}
return -1;
}
ScreenCapturerWinDirectx::ScreenCapturerWinDirectx()
: controller_(DxgiDuplicatorController::Instance()) {}
ScreenCapturerWinDirectx::ScreenCapturerWinDirectx(
const DesktopCaptureOptions& options)
: ScreenCapturerWinDirectx() {
options_ = options;
}
ScreenCapturerWinDirectx::~ScreenCapturerWinDirectx() = default;
void ScreenCapturerWinDirectx::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinDirectx);
callback_ = callback;
}
void ScreenCapturerWinDirectx::SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
shared_memory_factory_ = std::move(shared_memory_factory);
}
void ScreenCapturerWinDirectx::CaptureFrame() {
RTC_DCHECK(callback_);
TRACE_EVENT0("webrtc", "ScreenCapturerWinDirectx::CaptureFrame");
int64_t capture_start_time_nanos = rtc::TimeNanos();
// Note that the [] operator will create the ScreenCaptureFrameQueue if it
// doesn't exist, so this is safe.
ScreenCaptureFrameQueue<DxgiFrame>& frames =
frame_queue_map_[current_screen_id_];
frames.MoveToNextFrame();
if (!frames.current_frame()) {
frames.ReplaceCurrentFrame(
std::make_unique<DxgiFrame>(shared_memory_factory_.get()));
}
DxgiDuplicatorController::Result result;
if (current_screen_id_ == kFullDesktopScreenId) {
result = controller_->Duplicate(frames.current_frame());
} else {
result = controller_->DuplicateMonitor(frames.current_frame(),
current_screen_id_);
}
using DuplicateResult = DxgiDuplicatorController::Result;
if (result != DuplicateResult::SUCCEEDED) {
RTC_LOG(LS_ERROR) << "DxgiDuplicatorController failed to capture desktop, "
"error code "
<< DxgiDuplicatorController::ResultName(result);
}
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.DesktopCapture.Win.DirectXCapturerResult",
static_cast<int>(result),
static_cast<int>(DxgiDuplicatorController::Result::MAX_VALUE));
switch (result) {
case DuplicateResult::UNSUPPORTED_SESSION: {
RTC_LOG(LS_ERROR)
<< "Current binary is running on a session not supported "
"by DirectX screen capturer.";
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
break;
}
case DuplicateResult::FRAME_PREPARE_FAILED: {
RTC_LOG(LS_ERROR) << "Failed to allocate a new DesktopFrame.";
// This usually means we do not have enough memory or SharedMemoryFactory
// cannot work correctly.
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
break;
}
case DuplicateResult::INVALID_MONITOR_ID: {
RTC_LOG(LS_ERROR) << "Invalid monitor id " << current_screen_id_;
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
break;
}
case DuplicateResult::INITIALIZATION_FAILED:
case DuplicateResult::DUPLICATION_FAILED: {
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
break;
}
case DuplicateResult::SUCCEEDED: {
std::unique_ptr<DesktopFrame> frame =
frames.current_frame()->frame()->Share();
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec;
RTC_HISTOGRAM_COUNTS_1000(
"WebRTC.DesktopCapture.Win.DirectXCapturerFrameTime",
capture_time_ms);
frame->set_capture_time_ms(capture_time_ms);
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinDirectx);
// The DXGI Output Duplicator supports embedding the cursor but it is
// only supported on very few display adapters. This switch allows us
// to exclude an integrated cursor for all captured frames.
if (!options_.prefer_cursor_embedded()) {
frame->set_may_contain_cursor(false);
}
// TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
// the frame, see WindowCapturerMac::CaptureFrame.
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
break;
}
}
}
bool ScreenCapturerWinDirectx::GetSourceList(SourceList* sources) {
std::vector<std::string> device_names;
if (!controller_->GetDeviceNames(&device_names)) {
return false;
}
return GetScreenListFromDeviceNames(device_names, sources);
}
bool ScreenCapturerWinDirectx::SelectSource(SourceId id) {
if (id == kFullDesktopScreenId) {
current_screen_id_ = id;
return true;
}
std::vector<std::string> device_names;
if (!controller_->GetDeviceNames(&device_names)) {
return false;
}
int index;
index = GetIndexFromScreenId(id, device_names);
if (index == -1) {
return false;
}
current_screen_id_ = index;
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
#include <d3dcommon.h>
#include <memory>
#include <unordered_map>
#include <vector>
#include "api/scoped_refptr.h"
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
#include "modules/desktop_capture/win/dxgi_frame.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
// ScreenCapturerWinDirectx captures 32bit RGBA using DirectX.
class RTC_EXPORT ScreenCapturerWinDirectx : public DesktopCapturer {
public:
using D3dInfo = DxgiDuplicatorController::D3dInfo;
// Whether the system supports DirectX based capturing.
static bool IsSupported();
// Returns a most recent D3dInfo composed by
// DxgiDuplicatorController::Initialize() function. This function implicitly
// calls DxgiDuplicatorController::Initialize() if it has not been
// initialized. This function returns false and output parameter is kept
// unchanged if DxgiDuplicatorController::Initialize() failed.
// The D3dInfo may change based on hardware configuration even without
// restarting the hardware and software. Refer to https://goo.gl/OOCppq. So
// consumers should not cache the result returned by this function.
static bool RetrieveD3dInfo(D3dInfo* info);
// Whether current process is running in a Windows session which is supported
// by ScreenCapturerWinDirectx.
// Usually using ScreenCapturerWinDirectx in unsupported sessions will fail.
// But this behavior may vary on different Windows version. So consumers can
// always try IsSupported() function.
static bool IsCurrentSessionSupported();
// Maps `device_names` with the result from GetScreenList() and creates a new
// SourceList to include only the ones in `device_names`. If this function
// returns true, consumers can always assume `device_names`.size() equals to
// `screens`->size(), meanwhile `device_names`[i] and `screens`[i] indicate
// the same monitor on the system.
// Public for test only.
static bool GetScreenListFromDeviceNames(
const std::vector<std::string>& device_names,
DesktopCapturer::SourceList* screens);
// Maps `id` with the result from GetScreenListFromDeviceNames() and returns
// the index of the entity in `device_names`. This function returns -1 if `id`
// cannot be found.
// Public for test only.
static int GetIndexFromScreenId(ScreenId id,
const std::vector<std::string>& device_names);
// This constructor is deprecated. Please don't use it in new implementations.
ScreenCapturerWinDirectx();
explicit ScreenCapturerWinDirectx(const DesktopCaptureOptions& options);
~ScreenCapturerWinDirectx() override;
ScreenCapturerWinDirectx(const ScreenCapturerWinDirectx&) = delete;
ScreenCapturerWinDirectx& operator=(const ScreenCapturerWinDirectx&) = delete;
// DesktopCapturer implementation.
void Start(Callback* callback) override;
void SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
void CaptureFrame() override;
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
private:
const rtc::scoped_refptr<DxgiDuplicatorController> controller_;
DesktopCaptureOptions options_;
// The underlying DxgiDuplicators may retain a reference to the frames that
// we ask them to duplicate so that they can continue returning valid frames
// in the event that the target has not been updated. Thus, we need to ensure
// that we have a separate frame queue for each source id, so that these held
// frames don't get overwritten with the data from another Duplicator/monitor.
std::unordered_map<SourceId, ScreenCaptureFrameQueue<DxgiFrame>>
frame_queue_map_;
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
Callback* callback_ = nullptr;
SourceId current_screen_id_ = kFullDesktopScreenId;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capturer_win_directx.h"
#include <string>
#include <vector>
#include "modules/desktop_capture/desktop_capturer.h"
#include "test/gtest.h"
namespace webrtc {
// This test cannot ensure GetScreenListFromDeviceNames() won't reorder the
// devices in its output, since the device name is missing.
TEST(ScreenCaptureUtilsTest, GetScreenListFromDeviceNamesAndGetIndex) {
const std::vector<std::string> device_names = {
"\\\\.\\DISPLAY0",
"\\\\.\\DISPLAY1",
"\\\\.\\DISPLAY2",
};
DesktopCapturer::SourceList screens;
ASSERT_TRUE(ScreenCapturerWinDirectx::GetScreenListFromDeviceNames(
device_names, &screens));
ASSERT_EQ(device_names.size(), screens.size());
for (size_t i = 0; i < screens.size(); i++) {
ASSERT_EQ(ScreenCapturerWinDirectx::GetIndexFromScreenId(screens[i].id,
device_names),
static_cast<int>(i));
}
}
} // namespace webrtc

View file

@ -0,0 +1,240 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capturer_win_gdi.h"
#include <utility>
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_frame_win.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/win/cursor.h"
#include "modules/desktop_capture/win/desktop.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/trace_event.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
// Constants from dwmapi.h.
const UINT DWM_EC_DISABLECOMPOSITION = 0;
const UINT DWM_EC_ENABLECOMPOSITION = 1;
const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
} // namespace
ScreenCapturerWinGdi::ScreenCapturerWinGdi(
const DesktopCaptureOptions& options) {
if (options.disable_effects()) {
// Load dwmapi.dll dynamically since it is not available on XP.
if (!dwmapi_library_)
dwmapi_library_ = LoadLibraryW(kDwmapiLibraryName);
if (dwmapi_library_) {
composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
}
}
}
ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
if (desktop_dc_)
ReleaseDC(NULL, desktop_dc_);
if (memory_dc_)
DeleteDC(memory_dc_);
// Restore Aero.
if (composition_func_)
(*composition_func_)(DWM_EC_ENABLECOMPOSITION);
if (dwmapi_library_)
FreeLibrary(dwmapi_library_);
}
void ScreenCapturerWinGdi::SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
shared_memory_factory_ = std::move(shared_memory_factory);
}
void ScreenCapturerWinGdi::CaptureFrame() {
TRACE_EVENT0("webrtc", "ScreenCapturerWinGdi::CaptureFrame");
int64_t capture_start_time_nanos = rtc::TimeNanos();
queue_.MoveToNextFrame();
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
}
// Make sure the GDI capture resources are up-to-date.
PrepareCaptureResources();
if (!CaptureImage()) {
RTC_LOG(LS_WARNING) << "Failed to capture screen by GDI.";
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
return;
}
// Emit the current frame.
std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
frame->mutable_updated_region()->SetRect(
DesktopRect::MakeSize(frame->size()));
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec;
RTC_HISTOGRAM_COUNTS_1000(
"WebRTC.DesktopCapture.Win.ScreenGdiCapturerFrameTime", capture_time_ms);
frame->set_capture_time_ms(capture_time_ms);
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinGdi);
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
}
bool ScreenCapturerWinGdi::GetSourceList(SourceList* sources) {
return webrtc::GetScreenList(sources);
}
bool ScreenCapturerWinGdi::SelectSource(SourceId id) {
bool valid = IsScreenValid(id, &current_device_key_);
if (valid)
current_screen_id_ = id;
return valid;
}
void ScreenCapturerWinGdi::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinGdi);
callback_ = callback;
// Vote to disable Aero composited desktop effects while capturing. Windows
// will restore Aero automatically if the process exits. This has no effect
// under Windows 8 or higher. See crbug.com/124018.
if (composition_func_)
(*composition_func_)(DWM_EC_DISABLECOMPOSITION);
}
void ScreenCapturerWinGdi::PrepareCaptureResources() {
// Switch to the desktop receiving user input if different from the current
// one.
std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
if (input_desktop && !desktop_.IsSame(*input_desktop)) {
// Release GDI resources otherwise SetThreadDesktop will fail.
if (desktop_dc_) {
ReleaseDC(NULL, desktop_dc_);
desktop_dc_ = nullptr;
}
if (memory_dc_) {
DeleteDC(memory_dc_);
memory_dc_ = nullptr;
}
// If SetThreadDesktop() fails, the thread is still assigned a desktop.
// So we can continue capture screen bits, just from the wrong desktop.
desktop_.SetThreadDesktop(input_desktop.release());
// Re-assert our vote to disable Aero.
// See crbug.com/124018 and crbug.com/129906.
if (composition_func_) {
(*composition_func_)(DWM_EC_DISABLECOMPOSITION);
}
}
// If the display configurations have changed then recreate GDI resources.
if (display_configuration_monitor_.IsChanged(kFullDesktopScreenId)) {
if (desktop_dc_) {
ReleaseDC(NULL, desktop_dc_);
desktop_dc_ = nullptr;
}
if (memory_dc_) {
DeleteDC(memory_dc_);
memory_dc_ = nullptr;
}
}
if (!desktop_dc_) {
RTC_DCHECK(!memory_dc_);
// Create GDI device contexts to capture from the desktop into memory.
desktop_dc_ = GetDC(nullptr);
RTC_CHECK(desktop_dc_);
memory_dc_ = CreateCompatibleDC(desktop_dc_);
RTC_CHECK(memory_dc_);
// Make sure the frame buffers will be reallocated.
queue_.Reset();
}
}
bool ScreenCapturerWinGdi::CaptureImage() {
DesktopRect screen_rect =
GetScreenRect(current_screen_id_, current_device_key_);
if (screen_rect.is_empty()) {
RTC_LOG(LS_WARNING) << "Failed to get screen rect.";
return false;
}
DesktopSize size = screen_rect.size();
// If the current buffer is from an older generation then allocate a new one.
// Note that we can't reallocate other buffers at this point, since the caller
// may still be reading from them.
if (!queue_.current_frame() ||
!queue_.current_frame()->size().equals(screen_rect.size())) {
RTC_DCHECK(desktop_dc_);
RTC_DCHECK(memory_dc_);
std::unique_ptr<DesktopFrame> buffer = DesktopFrameWin::Create(
size, shared_memory_factory_.get(), desktop_dc_);
if (!buffer) {
RTC_LOG(LS_WARNING) << "Failed to create frame buffer.";
return false;
}
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
}
queue_.current_frame()->set_top_left(
screen_rect.top_left().subtract(GetFullscreenRect().top_left()));
// Select the target bitmap into the memory dc and copy the rect from desktop
// to memory.
DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
queue_.current_frame()->GetUnderlyingFrame());
HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
if (!previous_object || previous_object == HGDI_ERROR) {
RTC_LOG(LS_WARNING) << "Failed to select current bitmap into memery dc.";
return false;
}
bool result = (BitBlt(memory_dc_, 0, 0, screen_rect.width(),
screen_rect.height(), desktop_dc_, screen_rect.left(),
screen_rect.top(), SRCCOPY | CAPTUREBLT) != FALSE);
if (!result) {
RTC_LOG_GLE(LS_WARNING) << "BitBlt failed";
}
// Select back the previously selected object to that the device contect
// could be destroyed independently of the bitmap if needed.
SelectObject(memory_dc_, previous_object);
return result;
}
} // namespace webrtc

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_
#include <windows.h>
#include <memory>
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/display_configuration_monitor.h"
#include "modules/desktop_capture/win/scoped_thread_desktop.h"
namespace webrtc {
// ScreenCapturerWinGdi captures 32bit RGB using GDI.
//
// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer.
// This class does not detect DesktopFrame::updated_region(), the field is
// always set to the entire frame rectangle. ScreenCapturerDifferWrapper should
// be used if that functionality is necessary.
class ScreenCapturerWinGdi : public DesktopCapturer {
public:
explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options);
~ScreenCapturerWinGdi() override;
ScreenCapturerWinGdi(const ScreenCapturerWinGdi&) = delete;
ScreenCapturerWinGdi& operator=(const ScreenCapturerWinGdi&) = delete;
// Overridden from ScreenCapturer:
void Start(Callback* callback) override;
void SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
void CaptureFrame() override;
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
private:
typedef HRESULT(WINAPI* DwmEnableCompositionFunc)(UINT);
// Make sure that the device contexts match the screen configuration.
void PrepareCaptureResources();
// Captures the current screen contents into the current buffer. Returns true
// if succeeded.
bool CaptureImage();
// Capture the current cursor shape.
void CaptureCursor();
Callback* callback_ = nullptr;
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
SourceId current_screen_id_ = kFullDesktopScreenId;
std::wstring current_device_key_;
ScopedThreadDesktop desktop_;
// GDI resources used for screen capture.
HDC desktop_dc_ = NULL;
HDC memory_dc_ = NULL;
// Queue of the frames buffers.
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
DisplayConfigurationMonitor display_configuration_monitor_;
HMODULE dwmapi_library_ = NULL;
DwmEnableCompositionFunc composition_func_ = nullptr;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_

View file

@ -0,0 +1,390 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/screen_capturer_win_magnifier.h"
#include <utility>
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_frame_win.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/win/cursor.h"
#include "modules/desktop_capture/win/desktop.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
DWORD GetTlsIndex() {
static const DWORD tls_index = TlsAlloc();
RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES);
return tls_index;
}
} // namespace
// kMagnifierWindowClass has to be "Magnifier" according to the Magnification
// API. The other strings can be anything.
static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost";
static wchar_t kHostWindowName[] = L"MagnifierHost";
static wchar_t kMagnifierWindowClass[] = L"Magnifier";
static wchar_t kMagnifierWindowName[] = L"MagnifierWindow";
ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default;
ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
// DestroyWindow must be called before MagUninitialize. magnifier_window_ is
// destroyed automatically when host_window_ is destroyed.
if (host_window_)
DestroyWindow(host_window_);
if (magnifier_initialized_)
mag_uninitialize_func_();
if (mag_lib_handle_)
FreeLibrary(mag_lib_handle_);
if (desktop_dc_)
ReleaseDC(NULL, desktop_dc_);
}
void ScreenCapturerWinMagnifier::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinMagnifier);
callback_ = callback;
if (!InitializeMagnifier()) {
RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
}
}
void ScreenCapturerWinMagnifier::SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
shared_memory_factory_ = std::move(shared_memory_factory);
}
void ScreenCapturerWinMagnifier::CaptureFrame() {
RTC_DCHECK(callback_);
if (!magnifier_initialized_) {
RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
return;
}
int64_t capture_start_time_nanos = rtc::TimeNanos();
// Switch to the desktop receiving user input if different from the current
// one.
std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
// Release GDI resources otherwise SetThreadDesktop will fail.
if (desktop_dc_) {
ReleaseDC(NULL, desktop_dc_);
desktop_dc_ = NULL;
}
// If SetThreadDesktop() fails, the thread is still assigned a desktop.
// So we can continue capture screen bits, just from the wrong desktop.
desktop_.SetThreadDesktop(input_desktop.release());
}
DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
queue_.MoveToNextFrame();
CreateCurrentFrameIfNecessary(rect.size());
// CaptureImage may fail in some situations, e.g. windows8 metro mode. So
// defer to the fallback capturer if magnifier capturer did not work.
if (!CaptureImage(rect)) {
RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame.";
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
return;
}
// Emit the current frame.
std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
frame->mutable_updated_region()->SetRect(
DesktopRect::MakeSize(frame->size()));
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec;
RTC_HISTOGRAM_COUNTS_1000(
"WebRTC.DesktopCapture.Win.MagnifierCapturerFrameTime", capture_time_ms);
frame->set_capture_time_ms(capture_time_ms);
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinMagnifier);
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
}
bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) {
return webrtc::GetScreenList(sources);
}
bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) {
if (IsScreenValid(id, &current_device_key_)) {
current_screen_id_ = id;
return true;
}
return false;
}
void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) {
excluded_window_ = (HWND)excluded_window;
if (excluded_window_ && magnifier_initialized_) {
set_window_filter_list_func_(magnifier_window_, MW_FILTERMODE_EXCLUDE, 1,
&excluded_window_);
}
}
bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) {
RTC_DCHECK(magnifier_initialized_);
// Set the magnifier control to cover the captured rect. The content of the
// magnifier control will be the captured image.
BOOL result = SetWindowPos(magnifier_window_, NULL, rect.left(), rect.top(),
rect.width(), rect.height(), 0);
if (!result) {
RTC_LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError()
<< ". Rect = {" << rect.left() << ", " << rect.top()
<< ", " << rect.right() << ", " << rect.bottom()
<< "}";
return false;
}
magnifier_capture_succeeded_ = false;
RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()};
TlsSetValue(GetTlsIndex(), this);
// OnCaptured will be called via OnMagImageScalingCallback and fill in the
// frame before set_window_source_func_ returns.
result = set_window_source_func_(magnifier_window_, native_rect);
if (!result) {
RTC_LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: "
<< GetLastError() << ". Rect = {" << rect.left()
<< ", " << rect.top() << ", " << rect.right() << ", "
<< rect.bottom() << "}";
return false;
}
return magnifier_capture_succeeded_;
}
BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback(
HWND hwnd,
void* srcdata,
MAGIMAGEHEADER srcheader,
void* destdata,
MAGIMAGEHEADER destheader,
RECT unclipped,
RECT clipped,
HRGN dirty) {
ScreenCapturerWinMagnifier* owner =
reinterpret_cast<ScreenCapturerWinMagnifier*>(TlsGetValue(GetTlsIndex()));
TlsSetValue(GetTlsIndex(), nullptr);
owner->OnCaptured(srcdata, srcheader);
return TRUE;
}
// TODO(zijiehe): These functions are available on Windows Vista or upper, so we
// do not need to use LoadLibrary and GetProcAddress anymore. Use regular
// include and function calls instead of a dynamical loaded library.
bool ScreenCapturerWinMagnifier::InitializeMagnifier() {
RTC_DCHECK(!magnifier_initialized_);
if (GetSystemMetrics(SM_CMONITORS) != 1) {
// Do not try to use the magnifier in multi-screen setup (where the API
// crashes sometimes).
RTC_LOG_F(LS_WARNING) << "Magnifier capturer cannot work on multi-screen "
"system.";
return false;
}
desktop_dc_ = GetDC(nullptr);
mag_lib_handle_ = LoadLibraryW(L"Magnification.dll");
if (!mag_lib_handle_)
return false;
// Initialize Magnification API function pointers.
mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>(
GetProcAddress(mag_lib_handle_, "MagInitialize"));
mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>(
GetProcAddress(mag_lib_handle_, "MagUninitialize"));
set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>(
GetProcAddress(mag_lib_handle_, "MagSetWindowSource"));
set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>(
GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList"));
set_image_scaling_callback_func_ =
reinterpret_cast<MagSetImageScalingCallbackFunc>(
GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback"));
if (!mag_initialize_func_ || !mag_uninitialize_func_ ||
!set_window_source_func_ || !set_window_filter_list_func_ ||
!set_image_scaling_callback_func_) {
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"library functions missing.";
return false;
}
BOOL result = mag_initialize_func_();
if (!result) {
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"error from MagInitialize "
<< GetLastError();
return false;
}
HMODULE hInstance = nullptr;
result =
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<char*>(&DefWindowProc), &hInstance);
if (!result) {
mag_uninitialize_func_();
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"error from GetModulehandleExA "
<< GetLastError();
return false;
}
// Register the host window class. See the MSDN documentation of the
// Magnification API for more infomation.
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = &DefWindowProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.lpszClassName = kMagnifierHostClass;
// Ignore the error which may happen when the class is already registered.
RegisterClassExW(&wcex);
// Create the host window.
host_window_ =
CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0,
0, 0, 0, nullptr, nullptr, hInstance, nullptr);
if (!host_window_) {
mag_uninitialize_func_();
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"error from creating host window "
<< GetLastError();
return false;
}
// Create the magnifier control.
magnifier_window_ = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName,
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
host_window_, nullptr, hInstance, nullptr);
if (!magnifier_window_) {
mag_uninitialize_func_();
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"error from creating magnifier window "
<< GetLastError();
return false;
}
// Hide the host window.
ShowWindow(host_window_, SW_HIDE);
// Set the scaling callback to receive captured image.
result = set_image_scaling_callback_func_(
magnifier_window_,
&ScreenCapturerWinMagnifier::OnMagImageScalingCallback);
if (!result) {
mag_uninitialize_func_();
RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
"error from MagSetImageScalingCallback "
<< GetLastError();
return false;
}
if (excluded_window_) {
result = set_window_filter_list_func_(
magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
if (!result) {
mag_uninitialize_func_();
RTC_LOG_F(LS_WARNING)
<< "Failed to initialize ScreenCapturerWinMagnifier: "
"error from MagSetWindowFilterList "
<< GetLastError();
return false;
}
}
magnifier_initialized_ = true;
return true;
}
void ScreenCapturerWinMagnifier::OnCaptured(void* data,
const MAGIMAGEHEADER& header) {
DesktopFrame* current_frame = queue_.current_frame();
// Verify the format.
// TODO(jiayl): support capturing sources with pixel formats other than RGBA.
int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
if (header.format != GUID_WICPixelFormat32bppRGBA ||
header.width != static_cast<UINT>(current_frame->size().width()) ||
header.height != static_cast<UINT>(current_frame->size().height()) ||
header.stride != static_cast<UINT>(current_frame->stride()) ||
captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
RTC_LOG_F(LS_WARNING)
<< "Output format does not match the captured format: "
"width = "
<< header.width
<< ", "
"height = "
<< header.height
<< ", "
"stride = "
<< header.stride
<< ", "
"bpp = "
<< captured_bytes_per_pixel
<< ", "
"pixel format RGBA ? "
<< (header.format == GUID_WICPixelFormat32bppRGBA) << ".";
return;
}
// Copy the data into the frame.
current_frame->CopyPixelsFrom(
reinterpret_cast<uint8_t*>(data), header.stride,
DesktopRect::MakeXYWH(0, 0, header.width, header.height));
magnifier_capture_succeeded_ = true;
}
void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary(
const DesktopSize& size) {
// If the current buffer is from an older generation then allocate a new one.
// Note that we can't reallocate other buffers at this point, since the caller
// may still be reading from them.
if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) {
std::unique_ptr<DesktopFrame> frame =
shared_memory_factory_
? SharedMemoryDesktopFrame::Create(size,
shared_memory_factory_.get())
: std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(size));
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
}
}
} // namespace webrtc

View file

@ -0,0 +1,140 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_
#include <magnification.h>
#include <wincodec.h>
#include <windows.h>
#include <memory>
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/screen_capturer_helper.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/scoped_thread_desktop.h"
namespace webrtc {
class DesktopFrame;
class DesktopRect;
// Captures the screen using the Magnification API to support window exclusion.
// Each capturer must run on a dedicated thread because it uses thread local
// storage for redirecting the library callback. Also the thread must have a UI
// message loop to handle the window messages for the magnifier window.
//
// This class does not detect DesktopFrame::updated_region(), the field is
// always set to the entire frame rectangle. ScreenCapturerDifferWrapper should
// be used if that functionality is necessary.
class ScreenCapturerWinMagnifier : public DesktopCapturer {
public:
ScreenCapturerWinMagnifier();
~ScreenCapturerWinMagnifier() override;
ScreenCapturerWinMagnifier(const ScreenCapturerWinMagnifier&) = delete;
ScreenCapturerWinMagnifier& operator=(const ScreenCapturerWinMagnifier&) =
delete;
// Overridden from ScreenCapturer:
void Start(Callback* callback) override;
void SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
void CaptureFrame() override;
bool GetSourceList(SourceList* screens) override;
bool SelectSource(SourceId id) override;
void SetExcludedWindow(WindowId window) override;
private:
typedef BOOL(WINAPI* MagImageScalingCallback)(HWND hwnd,
void* srcdata,
MAGIMAGEHEADER srcheader,
void* destdata,
MAGIMAGEHEADER destheader,
RECT unclipped,
RECT clipped,
HRGN dirty);
typedef BOOL(WINAPI* MagInitializeFunc)(void);
typedef BOOL(WINAPI* MagUninitializeFunc)(void);
typedef BOOL(WINAPI* MagSetWindowSourceFunc)(HWND hwnd, RECT rect);
typedef BOOL(WINAPI* MagSetWindowFilterListFunc)(HWND hwnd,
DWORD dwFilterMode,
int count,
HWND* pHWND);
typedef BOOL(WINAPI* MagSetImageScalingCallbackFunc)(
HWND hwnd,
MagImageScalingCallback callback);
static BOOL WINAPI OnMagImageScalingCallback(HWND hwnd,
void* srcdata,
MAGIMAGEHEADER srcheader,
void* destdata,
MAGIMAGEHEADER destheader,
RECT unclipped,
RECT clipped,
HRGN dirty);
// Captures the screen within `rect` in the desktop coordinates. Returns true
// if succeeded.
// It can only capture the primary screen for now. The magnification library
// crashes under some screen configurations (e.g. secondary screen on top of
// primary screen) if it tries to capture a non-primary screen. The caller
// must make sure not calling it on non-primary screens.
bool CaptureImage(const DesktopRect& rect);
// Helper method for setting up the magnifier control. Returns true if
// succeeded.
bool InitializeMagnifier();
// Called by OnMagImageScalingCallback to output captured data.
void OnCaptured(void* data, const MAGIMAGEHEADER& header);
// Makes sure the current frame exists and matches `size`.
void CreateCurrentFrameIfNecessary(const DesktopSize& size);
Callback* callback_ = nullptr;
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
ScreenId current_screen_id_ = kFullDesktopScreenId;
std::wstring current_device_key_;
HWND excluded_window_ = NULL;
// Queue of the frames buffers.
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
ScopedThreadDesktop desktop_;
// Used for getting the screen dpi.
HDC desktop_dc_ = NULL;
HMODULE mag_lib_handle_ = NULL;
MagInitializeFunc mag_initialize_func_ = nullptr;
MagUninitializeFunc mag_uninitialize_func_ = nullptr;
MagSetWindowSourceFunc set_window_source_func_ = nullptr;
MagSetWindowFilterListFunc set_window_filter_list_func_ = nullptr;
MagSetImageScalingCallbackFunc set_image_scaling_callback_func_ = nullptr;
// The hidden window hosting the magnifier control.
HWND host_window_ = NULL;
// The magnifier control that captures the screen.
HWND magnifier_window_ = NULL;
// True if the magnifier control has been successfully initialized.
bool magnifier_initialized_ = false;
// True if the last OnMagImageScalingCallback was called and handled
// successfully. Reset at the beginning of each CaptureImage call.
bool magnifier_capture_succeeded_ = true;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/selected_window_context.h"
namespace webrtc {
SelectedWindowContext::SelectedWindowContext(
HWND selected_window,
DesktopRect selected_window_rect,
WindowCaptureHelperWin* window_capture_helper)
: selected_window_(selected_window),
selected_window_rect_(selected_window_rect),
window_capture_helper_(window_capture_helper) {
selected_window_thread_id_ =
GetWindowThreadProcessId(selected_window, &selected_window_process_id_);
}
bool SelectedWindowContext::IsSelectedWindowValid() const {
return selected_window_thread_id_ != 0;
}
bool SelectedWindowContext::IsWindowOwnedBySelectedWindow(HWND hwnd) const {
// This check works for drop-down menus & dialog pop-up windows.
if (GetAncestor(hwnd, GA_ROOTOWNER) == selected_window_) {
return true;
}
// Assume that all other windows are unrelated to the selected window.
// This will cause some windows that are actually related to be missed,
// e.g. context menus and tool-tips, but avoids the risk of capturing
// unrelated windows. Using heuristics such as matching the thread and
// process Ids suffers from false-positives, e.g. in multi-document
// applications.
return false;
}
bool SelectedWindowContext::IsWindowOverlappingSelectedWindow(HWND hwnd) const {
return window_capture_helper_->AreWindowsOverlapping(hwnd, selected_window_,
selected_window_rect_);
}
HWND SelectedWindowContext::selected_window() const {
return selected_window_;
}
WindowCaptureHelperWin* SelectedWindowContext::window_capture_helper() const {
return window_capture_helper_;
}
} // namespace webrtc

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_
#define MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_
#include <windows.h>
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
namespace webrtc {
class SelectedWindowContext {
public:
SelectedWindowContext(HWND selected_window,
DesktopRect selected_window_rect,
WindowCaptureHelperWin* window_capture_helper);
bool IsSelectedWindowValid() const;
bool IsWindowOwnedBySelectedWindow(HWND hwnd) const;
bool IsWindowOverlappingSelectedWindow(HWND hwnd) const;
HWND selected_window() const;
WindowCaptureHelperWin* window_capture_helper() const;
private:
const HWND selected_window_;
const DesktopRect selected_window_rect_;
WindowCaptureHelperWin* const window_capture_helper_;
DWORD selected_window_thread_id_;
DWORD selected_window_process_id_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/test_support/test_window.h"
namespace webrtc {
namespace {
const WCHAR kWindowClass[] = L"DesktopCaptureTestWindowClass";
const int kWindowHeight = 200;
const int kWindowWidth = 300;
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM w_param,
LPARAM l_param) {
switch (msg) {
case WM_PAINT:
PAINTSTRUCT paint_struct;
HDC hdc = BeginPaint(hwnd, &paint_struct);
// Paint the window so the color is consistent and we can inspect the
// pixels in tests and know what to expect.
FillRect(hdc, &paint_struct.rcPaint,
CreateSolidBrush(RGB(kTestWindowRValue, kTestWindowGValue,
kTestWindowBValue)));
EndPaint(hwnd, &paint_struct);
}
return DefWindowProc(hwnd, msg, w_param, l_param);
}
} // namespace
WindowInfo CreateTestWindow(const WCHAR* window_title,
const int height,
const int width,
const LONG extended_styles) {
WindowInfo info;
::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&WindowProc),
&info.window_instance);
WNDCLASSEXW wcex;
memset(&wcex, 0, sizeof(wcex));
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.hInstance = info.window_instance;
wcex.lpfnWndProc = &WindowProc;
wcex.lpszClassName = kWindowClass;
info.window_class = ::RegisterClassExW(&wcex);
// Use the default height and width if the caller did not supply the optional
// height and width parameters, or if they supplied invalid values.
int window_height = height <= 0 ? kWindowHeight : height;
int window_width = width <= 0 ? kWindowWidth : width;
info.hwnd =
::CreateWindowExW(extended_styles, kWindowClass, window_title,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
window_width, window_height, /*parent_window=*/nullptr,
/*menu_bar=*/nullptr, info.window_instance,
/*additional_params=*/nullptr);
::ShowWindow(info.hwnd, SW_SHOWNORMAL);
::UpdateWindow(info.hwnd);
return info;
}
void ResizeTestWindow(const HWND hwnd, const int width, const int height) {
// SWP_NOMOVE results in the x and y params being ignored.
::SetWindowPos(hwnd, HWND_TOP, /*x-coord=*/0, /*y-coord=*/0, width, height,
SWP_SHOWWINDOW | SWP_NOMOVE);
::UpdateWindow(hwnd);
}
void MoveTestWindow(const HWND hwnd, const int x, const int y) {
// SWP_NOSIZE results in the width and height params being ignored.
::SetWindowPos(hwnd, HWND_TOP, x, y, /*width=*/0, /*height=*/0,
SWP_SHOWWINDOW | SWP_NOSIZE);
::UpdateWindow(hwnd);
}
void MinimizeTestWindow(const HWND hwnd) {
::ShowWindow(hwnd, SW_MINIMIZE);
}
void UnminimizeTestWindow(const HWND hwnd) {
::OpenIcon(hwnd);
}
void DestroyTestWindow(WindowInfo info) {
::DestroyWindow(info.hwnd);
::UnregisterClass(MAKEINTATOM(info.window_class), info.window_instance);
}
} // namespace webrtc

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_
#define MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
namespace webrtc {
typedef unsigned char uint8_t;
// Define an arbitrary color for the test window with unique R, G, and B values
// so consumers can verify captured content in tests.
const uint8_t kTestWindowRValue = 191;
const uint8_t kTestWindowGValue = 99;
const uint8_t kTestWindowBValue = 12;
struct WindowInfo {
HWND hwnd;
HINSTANCE window_instance;
ATOM window_class;
};
WindowInfo CreateTestWindow(const WCHAR* window_title,
int height = 0,
int width = 0,
LONG extended_styles = 0);
void ResizeTestWindow(HWND hwnd, int width, int height);
void MoveTestWindow(HWND hwnd, int x, int y);
void MinimizeTestWindow(HWND hwnd);
void UnminimizeTestWindow(HWND hwnd);
void DestroyTestWindow(WindowInfo info);
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_

View file

@ -0,0 +1,585 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_capture_session.h"
#include <DispatcherQueue.h>
#include <windows.graphics.capture.interop.h>
#include <windows.graphics.directX.direct3d11.interop.h>
#include <windows.graphics.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/win/create_direct3d_device.h"
#include "rtc_base/win/get_activation_factory.h"
#include "system_wrappers/include/metrics.h"
#include "system_wrappers/include/sleep.h"
using Microsoft::WRL::ComPtr;
namespace WGC = ABI::Windows::Graphics::Capture;
namespace webrtc {
namespace {
// We must use a BGRA pixel format that has 4 bytes per pixel, as required by
// the DesktopFrame interface.
constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX::
DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class StartCaptureResult {
kSuccess = 0,
kSourceClosed = 1,
kAddClosedFailed = 2,
kDxgiDeviceCastFailed = 3,
kD3dDelayLoadFailed = 4,
kD3dDeviceCreationFailed = 5,
kFramePoolActivationFailed = 6,
// kFramePoolCastFailed = 7, (deprecated)
// kGetItemSizeFailed = 8, (deprecated)
kCreateFramePoolFailed = 9,
kCreateCaptureSessionFailed = 10,
kStartCaptureFailed = 11,
kMaxValue = kStartCaptureFailed
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GetFrameResult {
kSuccess = 0,
kItemClosed = 1,
kTryGetNextFrameFailed = 2,
kFrameDropped = 3,
kGetSurfaceFailed = 4,
kDxgiInterfaceAccessFailed = 5,
kTexture2dCastFailed = 6,
kCreateMappedTextureFailed = 7,
kMapFrameFailed = 8,
kGetContentSizeFailed = 9,
kResizeMappedTextureFailed = 10,
kRecreateFramePoolFailed = 11,
kFramePoolEmpty = 12,
kMaxValue = kFramePoolEmpty
};
void RecordStartCaptureResult(StartCaptureResult error) {
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult",
static_cast<int>(error), static_cast<int>(StartCaptureResult::kMaxValue));
}
void RecordGetFrameResult(GetFrameResult error) {
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.DesktopCapture.Win.WgcCaptureSessionGetFrameResult",
static_cast<int>(error), static_cast<int>(GetFrameResult::kMaxValue));
}
bool SizeHasChanged(ABI::Windows::Graphics::SizeInt32 size_new,
ABI::Windows::Graphics::SizeInt32 size_old) {
return (size_new.Height != size_old.Height ||
size_new.Width != size_old.Width);
}
} // namespace
WgcCaptureSession::WgcCaptureSession(ComPtr<ID3D11Device> d3d11_device,
ComPtr<WGC::IGraphicsCaptureItem> item,
ABI::Windows::Graphics::SizeInt32 size)
: d3d11_device_(std::move(d3d11_device)),
item_(std::move(item)),
size_(size) {}
WgcCaptureSession::~WgcCaptureSession() {
RemoveEventHandler();
}
HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(!is_capture_started_);
if (item_closed_) {
RTC_LOG(LS_ERROR) << "The target source has been closed.";
RecordStartCaptureResult(StartCaptureResult::kSourceClosed);
return E_ABORT;
}
RTC_DCHECK(d3d11_device_);
RTC_DCHECK(item_);
// Listen for the Closed event, to detect if the source we are capturing is
// closed (e.g. application window is closed or monitor is disconnected). If
// it is, we should abort the capture.
item_closed_token_ = std::make_unique<EventRegistrationToken>();
auto closed_handler =
Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
WGC::GraphicsCaptureItem*, IInspectable*>>(
this, &WgcCaptureSession::OnItemClosed);
HRESULT hr =
item_->add_Closed(closed_handler.Get(), item_closed_token_.get());
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kAddClosedFailed);
return hr;
}
ComPtr<IDXGIDevice> dxgi_device;
hr = d3d11_device_->QueryInterface(IID_PPV_ARGS(&dxgi_device));
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kDxgiDeviceCastFailed);
return hr;
}
if (!ResolveCoreWinRTDirect3DDelayload()) {
RecordStartCaptureResult(StartCaptureResult::kD3dDelayLoadFailed);
return E_FAIL;
}
hr = CreateDirect3DDeviceFromDXGIDevice(dxgi_device.Get(), &direct3d_device_);
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kD3dDeviceCreationFailed);
return hr;
}
ComPtr<WGC::IDirect3D11CaptureFramePoolStatics> frame_pool_statics;
hr = GetActivationFactory<
WGC::IDirect3D11CaptureFramePoolStatics,
RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool>(
&frame_pool_statics);
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kFramePoolActivationFailed);
return hr;
}
hr = frame_pool_statics->Create(direct3d_device_.Get(), kPixelFormat,
kNumBuffers, size_, &frame_pool_);
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kCreateFramePoolFailed);
return hr;
}
hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_);
if (FAILED(hr)) {
RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed);
return hr;
}
if (!options.prefer_cursor_embedded()) {
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession2> session2;
if (SUCCEEDED(session_->QueryInterface(
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession2,
&session2))) {
session2->put_IsCursorCaptureEnabled(false);
}
}
// By default, the WGC capture API adds a yellow border around the captured
// window or display to indicate that a capture is in progress. The section
// below is an attempt to remove this yellow border to make the capture
// experience more inline with the DXGI capture path.
// This requires 10.0.20348.0 or later, which practically means Windows 11.
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession3> session3;
if (SUCCEEDED(session_->QueryInterface(
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession3,
&session3))) {
session3->put_IsBorderRequired(false);
}
allow_zero_hertz_ = options.allow_wgc_zero_hertz();
hr = session_->StartCapture();
if (FAILED(hr)) {
RTC_LOG(LS_ERROR) << "Failed to start CaptureSession: " << hr;
RecordStartCaptureResult(StartCaptureResult::kStartCaptureFailed);
return hr;
}
RecordStartCaptureResult(StartCaptureResult::kSuccess);
is_capture_started_ = true;
return hr;
}
void WgcCaptureSession::EnsureFrame() {
// Try to process the captured frame and copy it to the `queue_`.
HRESULT hr = ProcessFrame();
if (SUCCEEDED(hr)) {
RTC_CHECK(queue_.current_frame());
return;
}
// We failed to process the frame, but we do have a frame so just return that.
if (queue_.current_frame()) {
RTC_LOG(LS_ERROR) << "ProcessFrame failed, using existing frame: " << hr;
return;
}
// ProcessFrame failed and we don't have a current frame. This could indicate
// a startup path where we may need to try/wait a few times to ensure that we
// have a frame. We try to get a new frame from the frame pool for a maximum
// of 10 times after sleeping for 20ms. We choose 20ms as it's just a bit
// longer than 17ms (for 60fps*) and hopefully avoids unlucky timing causing
// us to wait two frames when we mostly seem to only need to wait for one.
// This approach should ensure that GetFrame() always delivers a valid frame
// with a max latency of 200ms and often after sleeping only once.
// The scheme is heuristic and based on manual testing.
// (*) On a modern system, the FPS / monitor refresh rate is usually larger
// than or equal to 60.
const int max_sleep_count = 10;
const int sleep_time_ms = 20;
int sleep_count = 0;
while (!queue_.current_frame() && sleep_count < max_sleep_count) {
sleep_count++;
webrtc::SleepMs(sleep_time_ms);
hr = ProcessFrame();
if (FAILED(hr)) {
RTC_DLOG(LS_WARNING) << "ProcessFrame failed during startup: " << hr;
}
}
RTC_LOG_IF(LS_ERROR, !queue_.current_frame())
<< "Unable to process a valid frame even after trying 10 times.";
}
bool WgcCaptureSession::GetFrame(std::unique_ptr<DesktopFrame>* output_frame,
bool source_should_be_capturable) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Try to process the captured frame and wait some if needed. Avoid trying
// if we know that the source will not be capturable. This can happen e.g.
// when captured window is minimized and if EnsureFrame() was called in this
// state a large amount of kFrameDropped errors would be logged.
if (source_should_be_capturable)
EnsureFrame();
// Return a NULL frame and false as `result` if we still don't have a valid
// frame. This will lead to a DesktopCapturer::Result::ERROR_PERMANENT being
// posted by the WGC capturer.
DesktopFrame* current_frame = queue_.current_frame();
if (!current_frame) {
RTC_LOG(LS_ERROR) << "GetFrame failed.";
return false;
}
// Swap in the DesktopRegion in `damage_region_` which is updated in
// ProcessFrame(). The updated region is either empty or the full rect being
// captured where an empty damage region corresponds to "no change in content
// since last frame".
current_frame->mutable_updated_region()->Swap(&damage_region_);
damage_region_.Clear();
// Emit the current frame.
std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share();
*output_frame = std::move(new_frame);
return true;
}
HRESULT WgcCaptureSession::CreateMappedTexture(
ComPtr<ID3D11Texture2D> src_texture,
UINT width,
UINT height) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
D3D11_TEXTURE2D_DESC src_desc;
src_texture->GetDesc(&src_desc);
D3D11_TEXTURE2D_DESC map_desc;
map_desc.Width = width == 0 ? src_desc.Width : width;
map_desc.Height = height == 0 ? src_desc.Height : height;
map_desc.MipLevels = src_desc.MipLevels;
map_desc.ArraySize = src_desc.ArraySize;
map_desc.Format = src_desc.Format;
map_desc.SampleDesc = src_desc.SampleDesc;
map_desc.Usage = D3D11_USAGE_STAGING;
map_desc.BindFlags = 0;
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
map_desc.MiscFlags = 0;
return d3d11_device_->CreateTexture2D(&map_desc, nullptr, &mapped_texture_);
}
HRESULT WgcCaptureSession::ProcessFrame() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (item_closed_) {
RTC_LOG(LS_ERROR) << "The target source has been closed.";
RecordGetFrameResult(GetFrameResult::kItemClosed);
return E_ABORT;
}
RTC_DCHECK(is_capture_started_);
ComPtr<WGC::IDirect3D11CaptureFrame> capture_frame;
HRESULT hr = frame_pool_->TryGetNextFrame(&capture_frame);
if (FAILED(hr)) {
RTC_LOG(LS_ERROR) << "TryGetNextFrame failed: " << hr;
RecordGetFrameResult(GetFrameResult::kTryGetNextFrameFailed);
return hr;
}
if (!capture_frame) {
if (!queue_.current_frame()) {
// The frame pool was empty and so is the external queue.
RTC_DLOG(LS_ERROR) << "Frame pool was empty => kFrameDropped.";
RecordGetFrameResult(GetFrameResult::kFrameDropped);
} else {
// The frame pool was empty but there is still one old frame available in
// external the queue.
RTC_DLOG(LS_WARNING) << "Frame pool was empty => kFramePoolEmpty.";
RecordGetFrameResult(GetFrameResult::kFramePoolEmpty);
}
return E_FAIL;
}
queue_.MoveToNextFrame();
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared.";
}
// We need to get `capture_frame` as an `ID3D11Texture2D` so that we can get
// the raw image data in the format required by the `DesktopFrame` interface.
ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>
d3d_surface;
hr = capture_frame->get_Surface(&d3d_surface);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kGetSurfaceFailed);
return hr;
}
ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
direct3DDxgiInterfaceAccess;
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kDxgiInterfaceAccessFailed);
return hr;
}
ComPtr<ID3D11Texture2D> texture_2D;
hr = direct3DDxgiInterfaceAccess->GetInterface(IID_PPV_ARGS(&texture_2D));
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kTexture2dCastFailed);
return hr;
}
if (!mapped_texture_) {
hr = CreateMappedTexture(texture_2D);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed);
return hr;
}
}
// We need to copy `texture_2D` into `mapped_texture_` as the latter has the
// D3D11_CPU_ACCESS_READ flag set, which lets us access the image data.
// Otherwise it would only be readable by the GPU.
ComPtr<ID3D11DeviceContext> d3d_context;
d3d11_device_->GetImmediateContext(&d3d_context);
ABI::Windows::Graphics::SizeInt32 new_size;
hr = capture_frame->get_ContentSize(&new_size);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kGetContentSizeFailed);
return hr;
}
// If the size changed, we must resize `mapped_texture_` and `frame_pool_` to
// fit the new size. This must be done before `CopySubresourceRegion` so that
// the textures are the same size.
if (SizeHasChanged(new_size, size_)) {
hr = CreateMappedTexture(texture_2D, new_size.Width, new_size.Height);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kResizeMappedTextureFailed);
return hr;
}
hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat,
kNumBuffers, new_size);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed);
return hr;
}
}
// If the size has changed since the last capture, we must be sure to use
// the smaller dimensions. Otherwise we might overrun our buffer, or
// read stale data from the last frame.
int image_height = std::min(size_.Height, new_size.Height);
int image_width = std::min(size_.Width, new_size.Width);
D3D11_BOX copy_region;
copy_region.left = 0;
copy_region.top = 0;
copy_region.right = image_width;
copy_region.bottom = image_height;
// Our textures are 2D so we just want one "slice" of the box.
copy_region.front = 0;
copy_region.back = 1;
d3d_context->CopySubresourceRegion(mapped_texture_.Get(),
/*dst_subresource_index=*/0, /*dst_x=*/0,
/*dst_y=*/0, /*dst_z=*/0, texture_2D.Get(),
/*src_subresource_index=*/0, &copy_region);
D3D11_MAPPED_SUBRESOURCE map_info;
hr = d3d_context->Map(mapped_texture_.Get(), /*subresource_index=*/0,
D3D11_MAP_READ, /*D3D11_MAP_FLAG_DO_NOT_WAIT=*/0,
&map_info);
if (FAILED(hr)) {
RecordGetFrameResult(GetFrameResult::kMapFrameFailed);
return hr;
}
// Allocate the current frame buffer only if it is not already allocated or
// if the size has changed. Note that we can't reallocate other buffers at
// this point, since the caller may still be reading from them. The queue can
// hold up to two frames.
DesktopSize image_size(image_width, image_height);
if (!queue_.current_frame() ||
!queue_.current_frame()->size().equals(image_size)) {
std::unique_ptr<DesktopFrame> buffer =
std::make_unique<BasicDesktopFrame>(image_size);
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
}
DesktopFrame* current_frame = queue_.current_frame();
DesktopFrame* previous_frame = queue_.previous_frame();
// Will be set to true while copying the frame data to the `current_frame` if
// we can already determine that the content of the new frame differs from the
// previous. The idea is to get a low-complexity indication of if the content
// is static or not without performing a full/deep memory comparison when
// updating the damaged region.
bool frame_content_has_changed = false;
// Check if the queue contains two frames whose content can be compared.
const bool frame_content_can_be_compared = FrameContentCanBeCompared();
// Make a copy of the data pointed to by `map_info.pData` to the preallocated
// `current_frame` so we are free to unmap our texture. If possible, also
// perform a light-weight scan of the vertical line of pixels in the middle
// of the screen. A comparison is performed between two 32-bit pixels (RGBA);
// one from the current frame and one from the previous, and as soon as a
// difference is detected the scan stops and `frame_content_has_changed` is
// set to true.
uint8_t* src_data = static_cast<uint8_t*>(map_info.pData);
uint8_t* dst_data = current_frame->data();
uint8_t* prev_data =
frame_content_can_be_compared ? previous_frame->data() : nullptr;
const int width_in_bytes =
current_frame->size().width() * DesktopFrame::kBytesPerPixel;
RTC_DCHECK_GE(current_frame->stride(), width_in_bytes);
RTC_DCHECK_GE(map_info.RowPitch, width_in_bytes);
const int middle_pixel_offset =
(image_width / 2) * DesktopFrame::kBytesPerPixel;
for (int i = 0; i < image_height; i++) {
memcpy(dst_data, src_data, width_in_bytes);
if (prev_data && !frame_content_has_changed) {
uint8_t* previous_pixel = prev_data + middle_pixel_offset;
uint8_t* current_pixel = dst_data + middle_pixel_offset;
frame_content_has_changed =
memcmp(previous_pixel, current_pixel, DesktopFrame::kBytesPerPixel);
prev_data += current_frame->stride();
}
dst_data += current_frame->stride();
src_data += map_info.RowPitch;
}
d3d_context->Unmap(mapped_texture_.Get(), 0);
if (allow_zero_hertz()) {
if (previous_frame) {
const int previous_frame_size =
previous_frame->stride() * previous_frame->size().height();
const int current_frame_size =
current_frame->stride() * current_frame->size().height();
// Compare the latest frame with the previous and check if the frames are
// equal (both contain the exact same pixel values). Avoid full memory
// comparison if indication of a changed frame already exists from the
// stage above.
if (current_frame_size == previous_frame_size) {
if (frame_content_has_changed) {
// Mark frame as damaged based on existing light-weight indicator.
// Avoids deep memcmp of complete frame and saves resources.
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
} else {
// Perform full memory comparison for all bytes between the current
// and the previous frames.
const bool frames_are_equal =
!memcmp(current_frame->data(), previous_frame->data(),
current_frame_size);
if (!frames_are_equal) {
// TODO(https://crbug.com/1421242): If we had an API to report
// proper damage regions we should be doing AddRect() with a
// SetRect() call on a resize.
damage_region_.SetRect(
DesktopRect::MakeSize(current_frame->size()));
}
}
} else {
// Mark resized frames as damaged.
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
}
}
}
size_ = new_size;
RecordGetFrameResult(GetFrameResult::kSuccess);
return hr;
}
HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender,
IInspectable* event_args) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_LOG(LS_INFO) << "Capture target has been closed.";
item_closed_ = true;
RemoveEventHandler();
// Do not attempt to free resources in the OnItemClosed handler, as this
// causes a race where we try to delete the item that is calling us. Removing
// the event handlers and setting `item_closed_` above is sufficient to ensure
// that the resources are no longer used, and the next time the capturer tries
// to get a frame, we will report a permanent failure and be destroyed.
return S_OK;
}
void WgcCaptureSession::RemoveEventHandler() {
HRESULT hr;
if (item_ && item_closed_token_) {
hr = item_->remove_Closed(*item_closed_token_);
item_closed_token_.reset();
if (FAILED(hr))
RTC_LOG(LS_WARNING) << "Failed to remove Closed event handler: " << hr;
}
}
bool WgcCaptureSession::FrameContentCanBeCompared() {
DesktopFrame* current_frame = queue_.current_frame();
DesktopFrame* previous_frame = queue_.previous_frame();
if (!current_frame || !previous_frame) {
return false;
}
if (current_frame->stride() != previous_frame->stride()) {
return false;
}
return current_frame->size().equals(previous_frame->size());
}
} // namespace webrtc

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
#include <d3d11.h>
#include <windows.graphics.capture.h>
#include <windows.graphics.h>
#include <wrl/client.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/screen_capture_frame_queue.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/wgc_capture_source.h"
#include "rtc_base/event.h"
namespace webrtc {
class WgcCaptureSession final {
public:
WgcCaptureSession(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> item,
ABI::Windows::Graphics::SizeInt32 size);
// Disallow copy and assign.
WgcCaptureSession(const WgcCaptureSession&) = delete;
WgcCaptureSession& operator=(const WgcCaptureSession&) = delete;
~WgcCaptureSession();
HRESULT StartCapture(const DesktopCaptureOptions& options);
// Returns a frame from the local frame queue, if any are present.
bool GetFrame(std::unique_ptr<DesktopFrame>* output_frame,
bool source_should_be_capturable);
bool IsCaptureStarted() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return is_capture_started_;
}
// We keep 2 buffers in the frame pool since it results in a good compromise
// between latency/capture-rate and the rate at which
// Direct3D11CaptureFramePool.TryGetNextFrame returns NULL and we have to fall
// back to providing a copy from our external queue instead.
// We make this public for tests.
static constexpr int kNumBuffers = 2;
private:
// Initializes `mapped_texture_` with the properties of the `src_texture`,
// overrides the values of some necessary properties like the
// D3D11_CPU_ACCESS_READ flag. Also has optional parameters for what size
// `mapped_texture_` should be, if they aren't provided we will use the size
// of `src_texture`.
HRESULT CreateMappedTexture(
Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture,
UINT width = 0,
UINT height = 0);
// Event handler for `item_`'s Closed event.
HRESULT OnItemClosed(
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* sender,
IInspectable* event_args);
// Wraps calls to ProcessFrame and deals with the uniqe start-up phase
// ensuring that we always have one captured frame available.
void EnsureFrame();
// Process the captured frame and copy it to the `queue_`.
HRESULT ProcessFrame();
void RemoveEventHandler();
bool FrameContentCanBeCompared();
bool allow_zero_hertz() const { return allow_zero_hertz_; }
std::unique_ptr<EventRegistrationToken> item_closed_token_;
// A Direct3D11 Device provided by the caller. We use this to create an
// IDirect3DDevice, and also to create textures that will hold the image data.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
// This item represents what we are capturing, we use it to create the
// capture session, and also to listen for the Closed event.
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item_;
// The IDirect3DDevice is necessary to instantiate the frame pool.
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>
direct3d_device_;
// The frame pool is where frames are deposited during capture, we retrieve
// them from here with TryGetNextFrame().
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool>
frame_pool_;
// This texture holds the final image data. We made it a member so we can
// reuse it, instead of having to create a new texture every time we grab a
// frame.
Microsoft::WRL::ComPtr<ID3D11Texture2D> mapped_texture_;
// This is the size of `mapped_texture_` and the buffers in `frame_pool_`. We
// store this as a member so we can compare it to the size of incoming frames
// and resize if necessary.
ABI::Windows::Graphics::SizeInt32 size_;
// The capture session lets us set properties about the capture before it
// starts such as whether to capture the mouse cursor, and it lets us tell WGC
// to start capturing frames.
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureSession>
session_;
// Queue of captured video frames. The queue holds 2 frames and it avoids
// alloc/dealloc per captured frame. Incoming frames from the internal frame
// pool are copied to this queue after required processing in ProcessFrame().
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
bool item_closed_ = false;
bool is_capture_started_ = false;
// Caches the value of DesktopCaptureOptions.allow_wgc_zero_hertz() in
// StartCapture(). Adds 0Hz detection in ProcessFrame() when enabled which
// adds complexity since memcmp() is performed on two successive frames.
bool allow_zero_hertz_ = false;
// Tracks damage region updates that were reported since the last time a frame
// was captured. Currently only supports either the complete rect being
// captured or an empty region. Will always be empty if `allow_zero_hertz_` is
// false.
DesktopRegion damage_region_;
SequenceChecker sequence_checker_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_

View file

@ -0,0 +1,227 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_capture_source.h"
#include <dwmapi.h>
#include <windows.graphics.capture.interop.h>
#include <windows.h>
#include <utility>
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "rtc_base/win/get_activation_factory.h"
using Microsoft::WRL::ComPtr;
namespace WGC = ABI::Windows::Graphics::Capture;
namespace webrtc {
WgcCaptureSource::WgcCaptureSource(DesktopCapturer::SourceId source_id)
: source_id_(source_id) {}
WgcCaptureSource::~WgcCaptureSource() = default;
bool WgcCaptureSource::ShouldBeCapturable() {
return true;
}
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));
}
bool WgcCaptureSource::FocusOnSource() {
return false;
}
ABI::Windows::Graphics::SizeInt32 WgcCaptureSource::GetSize() {
if (!item_)
return {0, 0};
ABI::Windows::Graphics::SizeInt32 item_size;
HRESULT hr = item_->get_Size(&item_size);
if (FAILED(hr))
return {0, 0};
return item_size;
}
HRESULT WgcCaptureSource::GetCaptureItem(
ComPtr<WGC::IGraphicsCaptureItem>* result) {
HRESULT hr = S_OK;
if (!item_)
hr = CreateCaptureItem(&item_);
*result = item_;
return hr;
}
WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default;
WgcWindowSourceFactory::WgcWindowSourceFactory() = default;
WgcWindowSourceFactory::~WgcWindowSourceFactory() = default;
std::unique_ptr<WgcCaptureSource> WgcWindowSourceFactory::CreateCaptureSource(
DesktopCapturer::SourceId source_id) {
return std::make_unique<WgcWindowSource>(source_id);
}
WgcScreenSourceFactory::WgcScreenSourceFactory() = default;
WgcScreenSourceFactory::~WgcScreenSourceFactory() = default;
std::unique_ptr<WgcCaptureSource> WgcScreenSourceFactory::CreateCaptureSource(
DesktopCapturer::SourceId source_id) {
return std::make_unique<WgcScreenSource>(source_id);
}
WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id)
: WgcCaptureSource(source_id) {}
WgcWindowSource::~WgcWindowSource() = default;
DesktopVector WgcWindowSource::GetTopLeft() {
DesktopRect window_rect;
if (!GetWindowRect(reinterpret_cast<HWND>(GetSourceId()), &window_rect))
return DesktopVector();
return window_rect.top_left();
}
ABI::Windows::Graphics::SizeInt32 WgcWindowSource::GetSize() {
RECT window_rect;
HRESULT hr = ::DwmGetWindowAttribute(
reinterpret_cast<HWND>(GetSourceId()), DWMWA_EXTENDED_FRAME_BOUNDS,
reinterpret_cast<void*>(&window_rect), sizeof(window_rect));
if (FAILED(hr))
return WgcCaptureSource::GetSize();
return {window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top};
}
bool WgcWindowSource::ShouldBeCapturable() {
return IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()));
}
bool WgcWindowSource::IsCapturable() {
if (!ShouldBeCapturable()) {
return false;
}
return WgcCaptureSource::IsCapturable();
}
bool WgcWindowSource::FocusOnSource() {
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
return false;
return ::BringWindowToTop(reinterpret_cast<HWND>(GetSourceId())) &&
::SetForegroundWindow(reinterpret_cast<HWND>(GetSourceId()));
}
HRESULT WgcWindowSource::CreateCaptureItem(
ComPtr<WGC::IGraphicsCaptureItem>* result) {
if (!ResolveCoreWinRTDelayload())
return E_FAIL;
ComPtr<IGraphicsCaptureItemInterop> interop;
HRESULT hr = GetActivationFactory<
IGraphicsCaptureItemInterop,
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
if (FAILED(hr))
return hr;
ComPtr<WGC::IGraphicsCaptureItem> item;
hr = interop->CreateForWindow(reinterpret_cast<HWND>(GetSourceId()),
IID_PPV_ARGS(&item));
if (FAILED(hr))
return hr;
if (!item)
return E_HANDLE;
*result = std::move(item);
return hr;
}
WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id)
: WgcCaptureSource(source_id) {
// Getting the HMONITOR could fail if the source_id is invalid. In that case,
// we leave hmonitor_ uninitialized and `IsCapturable()` will fail.
HMONITOR hmon;
if (GetHmonitorFromDeviceIndex(GetSourceId(), &hmon))
hmonitor_ = hmon;
}
WgcScreenSource::~WgcScreenSource() = default;
DesktopVector WgcScreenSource::GetTopLeft() {
if (!hmonitor_)
return DesktopVector();
return GetMonitorRect(*hmonitor_).top_left();
}
ABI::Windows::Graphics::SizeInt32 WgcScreenSource::GetSize() {
ABI::Windows::Graphics::SizeInt32 size = WgcCaptureSource::GetSize();
if (!hmonitor_ || (size.Width != 0 && size.Height != 0))
return size;
DesktopRect rect = GetMonitorRect(*hmonitor_);
return {rect.width(), rect.height()};
}
bool WgcScreenSource::IsCapturable() {
if (!hmonitor_)
return false;
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;
ComPtr<IGraphicsCaptureItemInterop> interop;
HRESULT hr = GetActivationFactory<
IGraphicsCaptureItemInterop,
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
if (FAILED(hr))
return hr;
// Ensure the monitor is still valid (hasn't disconnected) before trying to
// create the item. On versions of Windows before Win11, `CreateForMonitor`
// will crash if no displays are connected.
if (!IsMonitorValid(hmonitor_.value()))
return E_ABORT;
ComPtr<WGC::IGraphicsCaptureItem> item;
hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
if (FAILED(hr))
return hr;
if (!item)
return E_HANDLE;
*result = std::move(item);
return hr;
}
} // namespace webrtc

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
#include <windows.graphics.capture.h>
#include <windows.graphics.h>
#include <wrl/client.h>
#include <memory>
#include "absl/types/optional.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
// Abstract class to represent the source that WGC-based capturers capture
// from. Could represent an application window or a screen. Consumers should use
// the appropriate Wgc*SourceFactory class to create WgcCaptureSource objects
// of the appropriate type.
class WgcCaptureSource {
public:
explicit WgcCaptureSource(DesktopCapturer::SourceId source_id);
virtual ~WgcCaptureSource();
virtual DesktopVector GetTopLeft() = 0;
// Lightweight version of IsCapturable which avoids allocating/deallocating
// COM objects for each call. As such may return a different value than
// IsCapturable.
virtual bool ShouldBeCapturable();
virtual bool IsCapturable();
virtual bool FocusOnSource();
virtual ABI::Windows::Graphics::SizeInt32 GetSize();
HRESULT GetCaptureItem(
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result);
DesktopCapturer::SourceId GetSourceId() { return source_id_; }
protected:
virtual HRESULT CreateCaptureItem(
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) = 0;
private:
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item_;
const DesktopCapturer::SourceId source_id_;
};
class WgcCaptureSourceFactory {
public:
virtual ~WgcCaptureSourceFactory();
virtual std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
DesktopCapturer::SourceId) = 0;
};
class WgcWindowSourceFactory final : public WgcCaptureSourceFactory {
public:
WgcWindowSourceFactory();
// Disallow copy and assign.
WgcWindowSourceFactory(const WgcWindowSourceFactory&) = delete;
WgcWindowSourceFactory& operator=(const WgcWindowSourceFactory&) = delete;
~WgcWindowSourceFactory() override;
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
DesktopCapturer::SourceId) override;
};
class WgcScreenSourceFactory final : public WgcCaptureSourceFactory {
public:
WgcScreenSourceFactory();
WgcScreenSourceFactory(const WgcScreenSourceFactory&) = delete;
WgcScreenSourceFactory& operator=(const WgcScreenSourceFactory&) = delete;
~WgcScreenSourceFactory() override;
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
DesktopCapturer::SourceId) override;
};
// Class for capturing application windows.
class WgcWindowSource final : public WgcCaptureSource {
public:
explicit WgcWindowSource(DesktopCapturer::SourceId source_id);
WgcWindowSource(const WgcWindowSource&) = delete;
WgcWindowSource& operator=(const WgcWindowSource&) = delete;
~WgcWindowSource() override;
DesktopVector GetTopLeft() override;
ABI::Windows::Graphics::SizeInt32 GetSize() override;
bool ShouldBeCapturable() override;
bool IsCapturable() override;
bool FocusOnSource() override;
private:
HRESULT CreateCaptureItem(
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
override;
};
// Class for capturing screens/monitors/displays.
class WgcScreenSource final : public WgcCaptureSource {
public:
explicit WgcScreenSource(DesktopCapturer::SourceId source_id);
WgcScreenSource(const WgcScreenSource&) = delete;
WgcScreenSource& operator=(const WgcScreenSource&) = delete;
~WgcScreenSource() override;
DesktopVector GetTopLeft() override;
ABI::Windows::Graphics::SizeInt32 GetSize() override;
bool IsCapturable() override;
private:
HRESULT CreateCaptureItem(
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
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_capture_source.h"
#include <windows.graphics.capture.h>
#include <wrl/client.h>
#include <utility>
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/test_support/test_window.h"
#include "modules/desktop_capture/win/wgc_capturer_win.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/win/scoped_com_initializer.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
const WCHAR kWindowTitle[] = L"WGC Capture Source Test Window";
const int kFirstXCoord = 25;
const int kFirstYCoord = 50;
const int kSecondXCoord = 50;
const int kSecondYCoord = 75;
} // namespace
class WgcCaptureSourceTest : public ::testing::TestWithParam<CaptureType> {
public:
void SetUp() override {
com_initializer_ =
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
ASSERT_TRUE(com_initializer_->Succeeded());
}
void TearDown() override {
if (window_open_) {
DestroyTestWindow(window_info_);
}
}
void SetUpForWindowSource() {
window_info_ = CreateTestWindow(kWindowTitle);
window_open_ = true;
source_id_ = reinterpret_cast<DesktopCapturer::SourceId>(window_info_.hwnd);
source_factory_ = std::make_unique<WgcWindowSourceFactory>();
}
void SetUpForScreenSource() {
source_id_ = kFullDesktopScreenId;
source_factory_ = std::make_unique<WgcScreenSourceFactory>();
}
protected:
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
std::unique_ptr<WgcCaptureSourceFactory> source_factory_;
std::unique_ptr<WgcCaptureSource> source_;
DesktopCapturer::SourceId source_id_;
WindowInfo window_info_;
bool window_open_ = false;
};
// Window specific test
TEST_F(WgcCaptureSourceTest, WindowPosition) {
if (!IsWgcSupported(CaptureType::kWindow)) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
SetUpForWindowSource();
source_ = source_factory_->CreateCaptureSource(source_id_);
ASSERT_TRUE(source_);
EXPECT_EQ(source_->GetSourceId(), source_id_);
MoveTestWindow(window_info_.hwnd, kFirstXCoord, kFirstYCoord);
DesktopVector source_vector = source_->GetTopLeft();
EXPECT_EQ(source_vector.x(), kFirstXCoord);
EXPECT_EQ(source_vector.y(), kFirstYCoord);
MoveTestWindow(window_info_.hwnd, kSecondXCoord, kSecondYCoord);
source_vector = source_->GetTopLeft();
EXPECT_EQ(source_vector.x(), kSecondXCoord);
EXPECT_EQ(source_vector.y(), kSecondYCoord);
}
// Screen specific test
TEST_F(WgcCaptureSourceTest, ScreenPosition) {
if (!IsWgcSupported(CaptureType::kScreen)) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
SetUpForScreenSource();
source_ = source_factory_->CreateCaptureSource(source_id_);
ASSERT_TRUE(source_);
EXPECT_EQ(source_id_, source_->GetSourceId());
DesktopRect screen_rect = GetFullscreenRect();
DesktopVector source_vector = source_->GetTopLeft();
EXPECT_EQ(source_vector.x(), screen_rect.left());
EXPECT_EQ(source_vector.y(), screen_rect.top());
}
// Source agnostic test
TEST_P(WgcCaptureSourceTest, CreateSource) {
if (!IsWgcSupported(GetParam())) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
if (GetParam() == CaptureType::kWindow) {
SetUpForWindowSource();
} else {
SetUpForScreenSource();
}
source_ = source_factory_->CreateCaptureSource(source_id_);
ASSERT_TRUE(source_);
EXPECT_EQ(source_id_, source_->GetSourceId());
EXPECT_TRUE(source_->IsCapturable());
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item;
EXPECT_TRUE(SUCCEEDED(source_->GetCaptureItem(&item)));
EXPECT_TRUE(item);
}
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
WgcCaptureSourceTest,
::testing::Values(CaptureType::kWindow,
CaptureType::kScreen));
} // namespace webrtc

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_capturer_win.h"
#include <DispatcherQueue.h>
#include <windows.foundation.metadata.h>
#include <windows.graphics.capture.h>
#include <utility>
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/win/get_activation_factory.h"
#include "rtc_base/win/hstring.h"
#include "rtc_base/win/windows_version.h"
#include "system_wrappers/include/metrics.h"
namespace WGC = ABI::Windows::Graphics::Capture;
using Microsoft::WRL::ComPtr;
namespace webrtc {
namespace {
constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll";
constexpr wchar_t kWgcSessionType[] =
L"Windows.Graphics.Capture.GraphicsCaptureSession";
constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract";
constexpr UINT16 kRequiredApiContractVersion = 8;
enum class WgcCapturerResult {
kSuccess = 0,
kNoDirect3dDevice = 1,
kNoSourceSelected = 2,
kItemCreationFailure = 3,
kSessionStartFailure = 4,
kGetFrameFailure = 5,
kFrameDropped = 6,
kCreateDispatcherQueueFailure = 7,
kMaxValue = kCreateDispatcherQueueFailure
};
void RecordWgcCapturerResult(WgcCapturerResult error) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult",
static_cast<int>(error),
static_cast<int>(WgcCapturerResult::kMaxValue));
}
} // namespace
bool IsWgcSupported(CaptureType capture_type) {
if (!HasActiveDisplay()) {
// There is a bug in `CreateForMonitor` that causes a crash if there are no
// active displays. The crash was fixed in Win11, but we are still unable
// to capture screens without an active display.
if (capture_type == CaptureType::kScreen)
return false;
// There is a bug in the DWM (Desktop Window Manager) that prevents it from
// providing image data if there are no displays attached. This was fixed in
// Windows 11.
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11)
return false;
}
// A bug in the WGC API `CreateForMonitor` prevents capturing the entire
// virtual screen (all monitors simultaneously), this was fixed in 20H1. Since
// we can't assert that we won't be asked to capture the entire virtual
// screen, we report unsupported so we can fallback to another capturer.
if (capture_type == CaptureType::kScreen &&
rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1) {
return false;
}
if (!ResolveCoreWinRTDelayload())
return false;
// We need to check if the WGC APIs are presesnt on the system. Certain SKUs
// of Windows ship without these APIs.
ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics>
api_info_statics;
HRESULT hr = GetActivationFactory<
ABI::Windows::Foundation::Metadata::IApiInformationStatics,
RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(
&api_info_statics);
if (FAILED(hr))
return false;
HSTRING api_contract;
hr = webrtc::CreateHstring(kApiContract, wcslen(kApiContract), &api_contract);
if (FAILED(hr))
return false;
boolean is_api_present;
hr = api_info_statics->IsApiContractPresentByMajor(
api_contract, kRequiredApiContractVersion, &is_api_present);
webrtc::DeleteHstring(api_contract);
if (FAILED(hr) || !is_api_present)
return false;
HSTRING wgc_session_type;
hr = webrtc::CreateHstring(kWgcSessionType, wcslen(kWgcSessionType),
&wgc_session_type);
if (FAILED(hr))
return false;
boolean is_type_present;
hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present);
webrtc::DeleteHstring(wgc_session_type);
if (FAILED(hr) || !is_type_present)
return false;
// If the APIs are present, we need to check that they are supported.
ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics;
hr = GetActivationFactory<
WGC::IGraphicsCaptureSessionStatics,
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>(
&capture_session_statics);
if (FAILED(hr))
return false;
boolean is_supported;
hr = capture_session_statics->IsSupported(&is_supported);
if (FAILED(hr) || !is_supported)
return false;
return true;
}
WgcCapturerWin::WgcCapturerWin(
const DesktopCaptureOptions& options,
std::unique_ptr<WgcCaptureSourceFactory> source_factory,
std::unique_ptr<SourceEnumerator> source_enumerator,
bool allow_delayed_capturable_check)
: options_(options),
source_factory_(std::move(source_factory)),
source_enumerator_(std::move(source_enumerator)),
allow_delayed_capturable_check_(allow_delayed_capturable_check) {
if (!core_messaging_library_)
core_messaging_library_ = LoadLibraryW(kCoreMessagingDll);
if (core_messaging_library_) {
create_dispatcher_queue_controller_func_ =
reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress(
core_messaging_library_, "CreateDispatcherQueueController"));
}
}
WgcCapturerWin::~WgcCapturerWin() {
if (core_messaging_library_)
FreeLibrary(core_messaging_library_);
}
// static
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer(
const DesktopCaptureOptions& options,
bool allow_delayed_capturable_check) {
return std::make_unique<WgcCapturerWin>(
options, std::make_unique<WgcWindowSourceFactory>(),
std::make_unique<WindowEnumerator>(
options.enumerate_current_process_windows()),
allow_delayed_capturable_check);
}
// static
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer(
const DesktopCaptureOptions& options) {
return std::make_unique<WgcCapturerWin>(
options, std::make_unique<WgcScreenSourceFactory>(),
std::make_unique<ScreenEnumerator>(), false);
}
bool WgcCapturerWin::GetSourceList(SourceList* sources) {
return source_enumerator_->FindAllSources(sources);
}
bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) {
capture_source_ = source_factory_->CreateCaptureSource(id);
if (allow_delayed_capturable_check_)
return true;
return capture_source_->IsCapturable();
}
bool WgcCapturerWin::FocusOnSelectedSource() {
if (!capture_source_)
return false;
return capture_source_->FocusOnSource();
}
void WgcCapturerWin::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin);
callback_ = callback;
// Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many
// parameters are nullptr as the implemention uses defaults that work well for
// us.
HRESULT hr = D3D11CreateDevice(
/*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE,
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
/*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION,
&d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr);
if (hr == DXGI_ERROR_UNSUPPORTED) {
// If a hardware device could not be created, use WARP which is a high speed
// software device.
hr = D3D11CreateDevice(
/*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP,
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
/*feature_levels=*/nullptr, /*feature_levels_size=*/0,
D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr,
/*device_context=*/nullptr);
}
if (FAILED(hr)) {
RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr;
}
}
void WgcCapturerWin::CaptureFrame() {
RTC_DCHECK(callback_);
if (!capture_source_) {
RTC_LOG(LS_ERROR) << "Source hasn't been selected";
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected);
return;
}
if (!d3d11_device_) {
RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture.";
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice);
return;
}
if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) {
RTC_LOG(LS_ERROR) << "Source is not capturable.";
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
return;
}
HRESULT hr;
if (!dispatcher_queue_created_) {
// Set the apartment type to NONE because this thread should already be COM
// initialized.
DispatcherQueueOptions options{
sizeof(DispatcherQueueOptions),
DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT,
DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE};
ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller;
hr = create_dispatcher_queue_controller_func_(options, &queue_controller);
// If there is already a DispatcherQueue on this thread, that is fine. Its
// lifetime is tied to the thread's, and as long as the thread has one, even
// if we didn't create it, the capture session's events will be delivered on
// this thread.
if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) {
RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure);
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
} else {
dispatcher_queue_created_ = true;
}
}
int64_t capture_start_time_nanos = rtc::TimeNanos();
WgcCaptureSession* capture_session = nullptr;
std::map<SourceId, WgcCaptureSession>::iterator session_iter =
ongoing_captures_.find(capture_source_->GetSourceId());
if (session_iter == ongoing_captures_.end()) {
ComPtr<WGC::IGraphicsCaptureItem> item;
hr = capture_source_->GetCaptureItem(&item);
if (FAILED(hr)) {
RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr;
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure);
return;
}
std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool>
iter_success_pair = ongoing_captures_.emplace(
std::piecewise_construct,
std::forward_as_tuple(capture_source_->GetSourceId()),
std::forward_as_tuple(d3d11_device_, item,
capture_source_->GetSize()));
RTC_DCHECK(iter_success_pair.second);
capture_session = &iter_success_pair.first->second;
} else {
capture_session = &session_iter->second;
}
if (!capture_session->IsCaptureStarted()) {
hr = capture_session->StartCapture(options_);
if (FAILED(hr)) {
RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr;
ongoing_captures_.erase(capture_source_->GetSourceId());
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure);
return;
}
}
std::unique_ptr<DesktopFrame> frame;
if (!capture_session->GetFrame(&frame,
capture_source_->ShouldBeCapturable())) {
RTC_LOG(LS_ERROR) << "GetFrame failed.";
ongoing_captures_.erase(capture_source_->GetSourceId());
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure);
return;
}
if (!frame) {
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
/*frame=*/nullptr);
RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped);
return;
}
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec;
RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime",
capture_time_ms);
frame->set_capture_time_ms(capture_time_ms);
frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin);
frame->set_may_contain_cursor(options_.prefer_cursor_embedded());
frame->set_top_left(capture_source_->GetTopLeft());
RecordWgcCapturerResult(WgcCapturerResult::kSuccess);
callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
std::move(frame));
}
bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) {
std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator
session_iter = ongoing_captures_.find(id);
if (session_iter == ongoing_captures_.end())
return false;
return session_iter->second.IsCaptureStarted();
}
} // namespace webrtc

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_
#include <DispatcherQueue.h>
#include <d3d11.h>
#include <wrl/client.h>
#include <map>
#include <memory>
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/wgc_capture_session.h"
#include "modules/desktop_capture/win/wgc_capture_source.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
namespace webrtc {
// Checks if the WGC API is present and supported on the system.
bool IsWgcSupported(CaptureType capture_type);
// WgcCapturerWin is initialized with an implementation of this base class,
// which it uses to find capturable sources of a particular type. This way,
// WgcCapturerWin can remain source-agnostic.
class SourceEnumerator {
public:
virtual ~SourceEnumerator() = default;
virtual bool FindAllSources(DesktopCapturer::SourceList* sources) = 0;
};
class WindowEnumerator final : public SourceEnumerator {
public:
explicit WindowEnumerator(bool enumerate_current_process_windows)
: enumerate_current_process_windows_(enumerate_current_process_windows) {}
WindowEnumerator(const WindowEnumerator&) = delete;
WindowEnumerator& operator=(const WindowEnumerator&) = delete;
~WindowEnumerator() override = default;
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
// WGC fails to capture windows with the WS_EX_TOOLWINDOW style, so we
// provide it as a filter to ensure windows with the style are not returned.
return window_capture_helper_.EnumerateCapturableWindows(
sources, enumerate_current_process_windows_, WS_EX_TOOLWINDOW);
}
private:
WindowCaptureHelperWin window_capture_helper_;
bool enumerate_current_process_windows_;
};
class ScreenEnumerator final : public SourceEnumerator {
public:
ScreenEnumerator() = default;
ScreenEnumerator(const ScreenEnumerator&) = delete;
ScreenEnumerator& operator=(const ScreenEnumerator&) = delete;
~ScreenEnumerator() override = default;
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
return webrtc::GetScreenList(sources);
}
};
// A capturer that uses the Window.Graphics.Capture APIs. It is suitable for
// both window and screen capture (but only one type per instance). Consumers
// should not instantiate this class directly, instead they should use
// `CreateRawWindowCapturer()` or `CreateRawScreenCapturer()` to receive a
// capturer appropriate for the type of source they want to capture.
class WgcCapturerWin : public DesktopCapturer {
public:
WgcCapturerWin(const DesktopCaptureOptions& options,
std::unique_ptr<WgcCaptureSourceFactory> source_factory,
std::unique_ptr<SourceEnumerator> source_enumerator,
bool allow_delayed_capturable_check);
WgcCapturerWin(const WgcCapturerWin&) = delete;
WgcCapturerWin& operator=(const WgcCapturerWin&) = delete;
~WgcCapturerWin() override;
static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
const DesktopCaptureOptions& options,
bool allow_delayed_capturable_check = false);
static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
const DesktopCaptureOptions& options);
// DesktopCapturer interface.
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
bool FocusOnSelectedSource() override;
void Start(Callback* callback) override;
void CaptureFrame() override;
// Used in WgcCapturerTests.
bool IsSourceBeingCaptured(SourceId id);
private:
typedef HRESULT(WINAPI* CreateDispatcherQueueControllerFunc)(
DispatcherQueueOptions,
ABI::Windows::System::IDispatcherQueueController**);
DesktopCaptureOptions options_;
// We need to either create or ensure that someone else created a
// `DispatcherQueue` on the current thread so that events will be delivered
// on the current thread rather than an arbitrary thread. A
// `DispatcherQueue`'s lifetime is tied to the thread's, and we don't post
// any work to it, so we don't need to hold a reference.
bool dispatcher_queue_created_ = false;
// Statically linking to CoreMessaging.lib is disallowed in Chromium, so we
// load it at runtime.
HMODULE core_messaging_library_ = NULL;
CreateDispatcherQueueControllerFunc create_dispatcher_queue_controller_func_ =
nullptr;
// Factory to create a WgcCaptureSource for us whenever SelectSource is
// called. Initialized at construction with a source-specific implementation.
std::unique_ptr<WgcCaptureSourceFactory> source_factory_;
// The source enumerator helps us find capturable sources of the appropriate
// type. Initialized at construction with a source-specific implementation.
std::unique_ptr<SourceEnumerator> source_enumerator_;
// The WgcCaptureSource represents the source we are capturing. It tells us
// if the source is capturable and it creates the GraphicsCaptureItem for us.
std::unique_ptr<WgcCaptureSource> capture_source_;
// A map of all the sources we are capturing and the associated
// WgcCaptureSession. Frames for the current source (indicated via
// SelectSource) will be retrieved from the appropriate session when
// requested via CaptureFrame.
// This helps us efficiently capture multiple sources (e.g. when consumers
// are trying to display a list of available capture targets with thumbnails).
std::map<SourceId, WgcCaptureSession> ongoing_captures_;
// The callback that we deliver frames to, synchronously, before CaptureFrame
// returns.
Callback* callback_ = nullptr;
// WgcCaptureSource::IsCapturable is expensive to run. So, caller can
// delay capturable check till capture frame is called if the WgcCapturerWin
// is used as a fallback capturer.
bool allow_delayed_capturable_check_ = false;
// A Direct3D11 device that is shared amongst the WgcCaptureSessions, who
// require one to perform the capture.
Microsoft::WRL::ComPtr<::ID3D11Device> d3d11_device_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_

View file

@ -0,0 +1,572 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_capturer_win.h"
#include <string>
#include <utility>
#include <vector>
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/win/test_support/test_window.h"
#include "modules/desktop_capture/win/wgc_capture_session.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/win/scoped_com_initializer.h"
#include "rtc_base/win/windows_version.h"
#include "system_wrappers/include/metrics.h"
#include "system_wrappers/include/sleep.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr char kWindowThreadName[] = "wgc_capturer_test_window_thread";
constexpr WCHAR kWindowTitle[] = L"WGC Capturer Test Window";
constexpr char kCapturerImplHistogram[] =
"WebRTC.DesktopCapture.Win.DesktopCapturerImpl";
constexpr char kCapturerResultHistogram[] =
"WebRTC.DesktopCapture.Win.WgcCapturerResult";
constexpr int kSuccess = 0;
constexpr int kSessionStartFailure = 4;
constexpr char kCaptureSessionResultHistogram[] =
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult";
constexpr int kSourceClosed = 1;
constexpr char kCaptureTimeHistogram[] =
"WebRTC.DesktopCapture.Win.WgcCapturerFrameTime";
// The capturer keeps `kNumBuffers` in its frame pool, so we need to request
// that many frames to clear those out. The next frame will have the new size
// (if the size has changed) so we will resize the frame pool at this point.
// Then, we need to clear any frames that may have delivered to the frame pool
// before the resize. Finally, the next frame will be guaranteed to be the new
// size.
constexpr int kNumCapturesToFlushBuffers =
WgcCaptureSession::kNumBuffers * 2 + 1;
constexpr int kSmallWindowWidth = 200;
constexpr int kSmallWindowHeight = 100;
constexpr int kMediumWindowWidth = 300;
constexpr int kMediumWindowHeight = 200;
constexpr int kLargeWindowWidth = 400;
constexpr int kLargeWindowHeight = 500;
// The size of the image we capture is slightly smaller than the actual size of
// the window.
constexpr int kWindowWidthSubtrahend = 14;
constexpr int kWindowHeightSubtrahend = 7;
// Custom message constants so we can direct our thread to close windows and
// quit running.
constexpr UINT kDestroyWindow = WM_APP;
constexpr UINT kQuitRunning = WM_APP + 1;
// When testing changes to real windows, sometimes the effects (close or resize)
// don't happen immediately, we want to keep trying until we see the effect but
// only for a reasonable amount of time.
constexpr int kMaxTries = 50;
} // namespace
class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>,
public DesktopCapturer::Callback {
public:
void SetUp() override {
com_initializer_ =
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
EXPECT_TRUE(com_initializer_->Succeeded());
if (!IsWgcSupported(GetParam())) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
}
void SetUpForWindowCapture(int window_width = kMediumWindowWidth,
int window_height = kMediumWindowHeight) {
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
DesktopCaptureOptions::CreateDefault());
CreateWindowOnSeparateThread(window_width, window_height);
StartWindowThreadMessageLoop();
source_id_ = GetTestWindowIdFromSourceList();
}
void SetUpForScreenCapture() {
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
DesktopCaptureOptions::CreateDefault());
source_id_ = GetScreenIdFromSourceList();
}
void TearDown() override {
if (window_open_) {
CloseTestWindow();
}
}
// The window must live on a separate thread so that we can run a message pump
// without blocking the test thread. This is necessary if we are interested in
// having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more
// closely resembles how capture works in the wild.
void CreateWindowOnSeparateThread(int window_width, int window_height) {
window_thread_ = rtc::Thread::Create();
window_thread_->SetName(kWindowThreadName, nullptr);
window_thread_->Start();
SendTask(window_thread_.get(), [this, window_width, window_height]() {
window_thread_id_ = GetCurrentThreadId();
window_info_ =
CreateTestWindow(kWindowTitle, window_height, window_width);
window_open_ = true;
while (!IsWindowResponding(window_info_.hwnd)) {
RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in "
"WgcWindowCaptureTest.";
}
while (!IsWindowValidAndVisible(window_info_.hwnd)) {
RTC_LOG(LS_INFO) << "Waiting for test window to be visible in "
"WgcWindowCaptureTest.";
}
});
ASSERT_TRUE(window_thread_->RunningForTest());
ASSERT_FALSE(window_thread_->IsCurrent());
}
void StartWindowThreadMessageLoop() {
window_thread_->PostTask([this]() {
MSG msg;
BOOL gm;
while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
::DispatchMessage(&msg);
if (msg.message == kDestroyWindow) {
DestroyTestWindow(window_info_);
}
if (msg.message == kQuitRunning) {
PostQuitMessage(0);
}
}
});
}
void CloseTestWindow() {
::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0);
::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0);
window_thread_->Stop();
window_open_ = false;
}
DesktopCapturer::SourceId GetTestWindowIdFromSourceList() {
// Frequently, the test window will not show up in GetSourceList because it
// was created too recently. Since we are confident the window will be found
// eventually we loop here until we find it.
intptr_t src_id = 0;
do {
DesktopCapturer::SourceList sources;
EXPECT_TRUE(capturer_->GetSourceList(&sources));
auto it = std::find_if(
sources.begin(), sources.end(),
[&](const DesktopCapturer::Source& src) {
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
});
if (it != sources.end())
src_id = it->id;
} while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd));
return src_id;
}
DesktopCapturer::SourceId GetScreenIdFromSourceList() {
DesktopCapturer::SourceList sources;
EXPECT_TRUE(capturer_->GetSourceList(&sources));
EXPECT_GT(sources.size(), 0ULL);
return sources[0].id;
}
void DoCapture(int num_captures = 1) {
// Capture the requested number of frames. We expect the first capture to
// always succeed. If we're asked for multiple frames, we do expect to see a
// a couple dropped frames due to resizing the window.
const int max_tries = num_captures == 1 ? 1 : kMaxTries;
int success_count = 0;
for (int i = 0; success_count < num_captures && i < max_tries; i++) {
capturer_->CaptureFrame();
if (result_ == DesktopCapturer::Result::ERROR_PERMANENT)
break;
if (result_ == DesktopCapturer::Result::SUCCESS)
success_count++;
}
total_successful_captures_ += success_count;
EXPECT_EQ(success_count, num_captures);
EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
EXPECT_TRUE(frame_);
EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSuccess),
total_successful_captures_);
}
void ValidateFrame(int expected_width, int expected_height) {
EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend);
EXPECT_EQ(frame_->size().height(),
expected_height - kWindowHeightSubtrahend);
// Verify the buffer contains as much data as it should.
int data_length = frame_->stride() * frame_->size().height();
// The first and last pixel should have the same color because they will be
// from the border of the window.
// Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit.
uint32_t first_pixel = static_cast<uint32_t>(*frame_->data());
uint32_t last_pixel = static_cast<uint32_t>(
*(frame_->data() + data_length - DesktopFrame::kBytesPerPixel));
EXPECT_EQ(first_pixel, last_pixel);
// Let's also check a pixel from the middle of the content area, which the
// test window will paint a consistent color for us to verify.
uint8_t* middle_pixel = frame_->data() + (data_length / 2);
int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4;
EXPECT_EQ(*middle_pixel, kTestWindowBValue);
middle_pixel += sub_pixel_offset;
EXPECT_EQ(*middle_pixel, kTestWindowGValue);
middle_pixel += sub_pixel_offset;
EXPECT_EQ(*middle_pixel, kTestWindowRValue);
middle_pixel += sub_pixel_offset;
// The window is opaque so we expect 0xFF for the Alpha channel.
EXPECT_EQ(*middle_pixel, 0xFF);
}
// DesktopCapturer::Callback interface
// The capturer synchronously invokes this method before `CaptureFrame()`
// returns.
void OnCaptureResult(DesktopCapturer::Result result,
std::unique_ptr<DesktopFrame> frame) override {
result_ = result;
frame_ = std::move(frame);
}
protected:
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
DWORD window_thread_id_;
std::unique_ptr<rtc::Thread> window_thread_;
WindowInfo window_info_;
intptr_t source_id_;
bool window_open_ = false;
DesktopCapturer::Result result_;
int total_successful_captures_ = 0;
std::unique_ptr<DesktopFrame> frame_;
std::unique_ptr<DesktopCapturer> capturer_;
};
TEST_P(WgcCapturerWinTest, SelectValidSource) {
if (GetParam() == CaptureType::kWindow) {
SetUpForWindowCapture();
} else {
SetUpForScreenCapture();
}
EXPECT_TRUE(capturer_->SelectSource(source_id_));
}
TEST_P(WgcCapturerWinTest, SelectInvalidSource) {
if (GetParam() == CaptureType::kWindow) {
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
DesktopCaptureOptions::CreateDefault());
source_id_ = kNullWindowId;
} else {
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
DesktopCaptureOptions::CreateDefault());
source_id_ = kInvalidScreenId;
}
EXPECT_FALSE(capturer_->SelectSource(source_id_));
}
TEST_P(WgcCapturerWinTest, Capture) {
if (GetParam() == CaptureType::kWindow) {
SetUpForWindowCapture();
} else {
SetUpForScreenCapture();
}
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram,
DesktopCapturerId::kWgcCapturerWin),
1);
DoCapture();
EXPECT_GT(frame_->size().width(), 0);
EXPECT_GT(frame_->size().height(), 0);
}
TEST_P(WgcCapturerWinTest, CaptureTime) {
if (GetParam() == CaptureType::kWindow) {
SetUpForWindowCapture();
} else {
SetUpForScreenCapture();
}
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
int64_t start_time;
start_time = rtc::TimeNanos();
capturer_->CaptureFrame();
int capture_time_ms =
(rtc::TimeNanos() - start_time) / rtc::kNumNanosecsPerMillisec;
EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
EXPECT_TRUE(frame_);
// The test may measure the time slightly differently than the capturer. So we
// just check if it's within 5 ms.
EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5);
EXPECT_GE(
metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1);
}
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
WgcCapturerWinTest,
::testing::Values(CaptureType::kWindow,
CaptureType::kScreen));
TEST(WgcCapturerNoMonitorTest, NoMonitors) {
ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA);
EXPECT_TRUE(com_initializer.Succeeded());
if (HasActiveDisplay()) {
RTC_LOG(LS_INFO) << "Skip WgcCapturerWinTest designed specifically for "
"systems with no monitors";
GTEST_SKIP();
}
// A bug in `CreateForMonitor` prevents screen capture when no displays are
// attached.
EXPECT_FALSE(IsWgcSupported(CaptureType::kScreen));
// A bug in the DWM (Desktop Window Manager) prevents it from providing image
// data if there are no displays attached. This was fixed in Windows 11.
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11)
EXPECT_FALSE(IsWgcSupported(CaptureType::kWindow));
else
EXPECT_TRUE(IsWgcSupported(CaptureType::kWindow));
}
class WgcCapturerMonitorTest : public WgcCapturerWinTest {
public:
void SetUp() {
com_initializer_ =
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
EXPECT_TRUE(com_initializer_->Succeeded());
if (!IsWgcSupported(CaptureType::kScreen)) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
}
};
TEST_F(WgcCapturerMonitorTest, FocusOnMonitor) {
SetUpForScreenCapture();
EXPECT_TRUE(capturer_->SelectSource(0));
// You can't set focus on a monitor.
EXPECT_FALSE(capturer_->FocusOnSelectedSource());
}
TEST_F(WgcCapturerMonitorTest, CaptureAllMonitors) {
SetUpForScreenCapture();
EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId));
capturer_->Start(this);
DoCapture();
EXPECT_GT(frame_->size().width(), 0);
EXPECT_GT(frame_->size().height(), 0);
}
class WgcCapturerWindowTest : public WgcCapturerWinTest {
public:
void SetUp() {
com_initializer_ =
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
EXPECT_TRUE(com_initializer_->Succeeded());
if (!IsWgcSupported(CaptureType::kWindow)) {
RTC_LOG(LS_INFO)
<< "Skipping WgcCapturerWinTests on unsupported platforms.";
GTEST_SKIP();
}
}
};
TEST_F(WgcCapturerWindowTest, FocusOnWindow) {
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
DesktopCaptureOptions::CreateDefault());
window_info_ = CreateTestWindow(kWindowTitle);
source_id_ = GetScreenIdFromSourceList();
EXPECT_TRUE(capturer_->SelectSource(source_id_));
EXPECT_TRUE(capturer_->FocusOnSelectedSource());
HWND hwnd = reinterpret_cast<HWND>(source_id_);
EXPECT_EQ(hwnd, ::GetActiveWindow());
EXPECT_EQ(hwnd, ::GetForegroundWindow());
EXPECT_EQ(hwnd, ::GetFocus());
DestroyTestWindow(window_info_);
}
TEST_F(WgcCapturerWindowTest, SelectMinimizedWindow) {
SetUpForWindowCapture();
MinimizeTestWindow(reinterpret_cast<HWND>(source_id_));
EXPECT_FALSE(capturer_->SelectSource(source_id_));
UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_));
EXPECT_TRUE(capturer_->SelectSource(source_id_));
}
TEST_F(WgcCapturerWindowTest, SelectClosedWindow) {
SetUpForWindowCapture();
EXPECT_TRUE(capturer_->SelectSource(source_id_));
CloseTestWindow();
EXPECT_FALSE(capturer_->SelectSource(source_id_));
}
TEST_F(WgcCapturerWindowTest, UnsupportedWindowStyle) {
// Create a window with the WS_EX_TOOLWINDOW style, which WGC does not
// support.
window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth,
kMediumWindowHeight, WS_EX_TOOLWINDOW);
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
DesktopCaptureOptions::CreateDefault());
DesktopCapturer::SourceList sources;
EXPECT_TRUE(capturer_->GetSourceList(&sources));
auto it = std::find_if(
sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) {
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
});
// We should not find the window, since we filter for unsupported styles.
EXPECT_EQ(it, sources.end());
DestroyTestWindow(window_info_);
}
TEST_F(WgcCapturerWindowTest, IncreaseWindowSizeMidCapture) {
SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight);
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
DoCapture();
ValidateFrame(kSmallWindowWidth, kSmallWindowHeight);
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
DoCapture(kNumCapturesToFlushBuffers);
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
DoCapture(kNumCapturesToFlushBuffers);
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
}
TEST_F(WgcCapturerWindowTest, ReduceWindowSizeMidCapture) {
SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight);
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
DoCapture();
ValidateFrame(kLargeWindowWidth, kLargeWindowHeight);
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
DoCapture(kNumCapturesToFlushBuffers);
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
DoCapture(kNumCapturesToFlushBuffers);
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
}
TEST_F(WgcCapturerWindowTest, MinimizeWindowMidCapture) {
SetUpForWindowCapture();
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
// Minmize the window and capture should continue but return temporary errors.
MinimizeTestWindow(window_info_.hwnd);
for (int i = 0; i < 5; ++i) {
capturer_->CaptureFrame();
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY);
}
// Reopen the window and the capture should continue normally.
UnminimizeTestWindow(window_info_.hwnd);
DoCapture();
// We can't verify the window size here because the test window does not
// repaint itself after it is unminimized, but capturing successfully is still
// a good test.
}
TEST_F(WgcCapturerWindowTest, CloseWindowMidCapture) {
SetUpForWindowCapture();
EXPECT_TRUE(capturer_->SelectSource(source_id_));
capturer_->Start(this);
DoCapture();
ValidateFrame(kMediumWindowWidth, kMediumWindowHeight);
CloseTestWindow();
// We need to pump our message queue so the Closed event will be delivered to
// the capturer's event handler. If we are too early and the Closed event
// hasn't arrived yet we should keep trying until the capturer receives it and
// stops.
auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get());
MSG msg;
for (int i = 0;
wgc_capturer->IsSourceBeingCaptured(source_id_) && i < kMaxTries; ++i) {
// Unlike GetMessage, PeekMessage will not hang if there are no messages in
// the queue.
PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
SleepMs(1);
}
EXPECT_FALSE(wgc_capturer->IsSourceBeingCaptured(source_id_));
// The frame pool can buffer `kNumBuffers` frames. We must consume these
// and then make one more call to CaptureFrame before we expect to see the
// failure.
int num_tries = 0;
do {
capturer_->CaptureFrame();
} while (result_ == DesktopCapturer::Result::SUCCESS &&
++num_tries <= WgcCaptureSession::kNumBuffers);
EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure),
1);
EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed),
1);
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT);
}
} // namespace webrtc

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
#include <utility>
namespace webrtc {
WgcDesktopFrame::WgcDesktopFrame(DesktopSize size,
int stride,
std::vector<uint8_t>&& image_data)
: DesktopFrame(size, stride, image_data.data(), nullptr),
image_data_(std::move(image_data)) {}
WgcDesktopFrame::~WgcDesktopFrame() = default;
} // namespace webrtc

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_
#include <d3d11.h>
#include <wrl/client.h>
#include <memory>
#include <vector>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
// DesktopFrame implementation used by capturers that use the
// Windows.Graphics.Capture API.
class WgcDesktopFrame final : public DesktopFrame {
public:
// WgcDesktopFrame receives an rvalue reference to the `image_data` vector
// so that it can take ownership of it (and avoid a copy).
WgcDesktopFrame(DesktopSize size,
int stride,
std::vector<uint8_t>&& image_data);
WgcDesktopFrame(const WgcDesktopFrame&) = delete;
WgcDesktopFrame& operator=(const WgcDesktopFrame&) = delete;
~WgcDesktopFrame() override;
private:
std::vector<uint8_t> image_data_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_

View file

@ -0,0 +1,482 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/window_capture_utils.h"
// Just for the DWMWINDOWATTRIBUTE enums (DWMWA_CLOAKED).
#include <dwmapi.h>
#include <algorithm>
#include "modules/desktop_capture/win/scoped_gdi_object.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/win/windows_version.h"
namespace webrtc {
namespace {
struct GetWindowListParams {
GetWindowListParams(int flags,
LONG ex_style_filters,
DesktopCapturer::SourceList* result)
: ignore_untitled(flags & GetWindowListFlags::kIgnoreUntitled),
ignore_unresponsive(flags & GetWindowListFlags::kIgnoreUnresponsive),
ignore_current_process_windows(
flags & GetWindowListFlags::kIgnoreCurrentProcessWindows),
ex_style_filters(ex_style_filters),
result(result) {}
const bool ignore_untitled;
const bool ignore_unresponsive;
const bool ignore_current_process_windows;
const LONG ex_style_filters;
DesktopCapturer::SourceList* const result;
};
bool IsWindowOwnedByCurrentProcess(HWND hwnd) {
DWORD process_id;
GetWindowThreadProcessId(hwnd, &process_id);
return process_id == GetCurrentProcessId();
}
BOOL CALLBACK GetWindowListHandler(HWND hwnd, LPARAM param) {
GetWindowListParams* params = reinterpret_cast<GetWindowListParams*>(param);
DesktopCapturer::SourceList* list = params->result;
// Skip invisible and minimized windows
if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) {
return TRUE;
}
// Skip windows which are not presented in the taskbar,
// namely owned window if they don't have the app window style set
HWND owner = GetWindow(hwnd, GW_OWNER);
LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (owner && !(exstyle & WS_EX_APPWINDOW)) {
return TRUE;
}
// Filter out windows that match the extended styles the caller has specified,
// e.g. WS_EX_TOOLWINDOW for capturers that don't support overlay windows.
if (exstyle & params->ex_style_filters) {
return TRUE;
}
if (params->ignore_unresponsive && !IsWindowResponding(hwnd)) {
return TRUE;
}
DesktopCapturer::Source window;
window.id = reinterpret_cast<WindowId>(hwnd);
// GetWindowText* are potentially blocking operations if `hwnd` is
// owned by the current process. The APIs will send messages to the window's
// message loop, and if the message loop is waiting on this operation we will
// enter a deadlock.
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtexta#remarks
//
// To help consumers avoid this, there is a DesktopCaptureOption to ignore
// windows owned by the current process. Consumers should either ensure that
// the thread running their message loop never waits on this operation, or use
// the option to exclude these windows from the source list.
bool owned_by_current_process = IsWindowOwnedByCurrentProcess(hwnd);
if (owned_by_current_process && params->ignore_current_process_windows) {
return TRUE;
}
// Even if consumers request to enumerate windows owned by the current
// process, we should not call GetWindowText* on unresponsive windows owned by
// the current process because we will hang. Unfortunately, we could still
// hang if the window becomes unresponsive after this check, hence the option
// to avoid these completely.
if (!owned_by_current_process || IsWindowResponding(hwnd)) {
const size_t kTitleLength = 500;
WCHAR window_title[kTitleLength] = L"";
if (GetWindowTextLength(hwnd) != 0 &&
GetWindowTextW(hwnd, window_title, kTitleLength) > 0) {
window.title = rtc::ToUtf8(window_title);
}
}
// Skip windows when we failed to convert the title or it is empty.
if (params->ignore_untitled && window.title.empty())
return TRUE;
// Capture the window class name, to allow specific window classes to be
// skipped.
//
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
// says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't
// need to have a buffer bigger than that.
const size_t kMaxClassNameLength = 256;
WCHAR class_name[kMaxClassNameLength] = L"";
const int class_name_length =
GetClassNameW(hwnd, class_name, kMaxClassNameLength);
if (class_name_length < 1)
return TRUE;
// Skip Program Manager window.
if (wcscmp(class_name, L"Progman") == 0)
return TRUE;
// Skip Start button window on Windows Vista, Windows 7.
// On Windows 8, Windows 8.1, Windows 10 Start button is not a top level
// window, so it will not be examined here.
if (wcscmp(class_name, L"Button") == 0)
return TRUE;
list->push_back(window);
return TRUE;
}
} // namespace
// Prefix used to match the window class for Chrome windows.
const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";
// The hiddgen taskbar will leave a 2 pixel margin on the screen.
const int kHiddenTaskbarMarginOnScreen = 2;
bool GetWindowRect(HWND window, DesktopRect* result) {
RECT rect;
if (!::GetWindowRect(window, &rect)) {
return false;
}
*result = DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
return true;
}
bool GetCroppedWindowRect(HWND window,
bool avoid_cropping_border,
DesktopRect* cropped_rect,
DesktopRect* original_rect) {
DesktopRect window_rect;
if (!GetWindowRect(window, &window_rect)) {
return false;
}
if (original_rect) {
*original_rect = window_rect;
}
*cropped_rect = window_rect;
bool is_maximized = false;
if (!IsWindowMaximized(window, &is_maximized)) {
return false;
}
// As of Windows8, transparent resize borders are added by the OS at
// left/bottom/right sides of a resizeable window. If the cropped window
// doesn't remove these borders, the background will be exposed a bit.
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8 ||
is_maximized) {
// Only apply this cropping to windows with a resize border (otherwise,
// it'd clip the edges of captured pop-up windows without this border).
LONG style = GetWindowLong(window, GWL_STYLE);
if (style & WS_THICKFRAME || style & DS_MODALFRAME) {
int width = GetSystemMetrics(SM_CXSIZEFRAME);
int bottom_height = GetSystemMetrics(SM_CYSIZEFRAME);
const int visible_border_height = GetSystemMetrics(SM_CYBORDER);
int top_height = visible_border_height;
// If requested, avoid cropping the visible window border. This is used
// for pop-up windows to include their border, but not for the outermost
// window (where a partially-transparent border may expose the
// background a bit).
if (avoid_cropping_border) {
width = std::max(0, width - GetSystemMetrics(SM_CXBORDER));
bottom_height = std::max(0, bottom_height - visible_border_height);
top_height = 0;
}
cropped_rect->Extend(-width, -top_height, -width, -bottom_height);
}
}
return true;
}
bool GetWindowContentRect(HWND window, DesktopRect* result) {
if (!GetWindowRect(window, result)) {
return false;
}
RECT rect;
if (!::GetClientRect(window, &rect)) {
return false;
}
const int width = rect.right - rect.left;
// The GetClientRect() is not expected to return a larger area than
// GetWindowRect().
if (width > 0 && width < result->width()) {
// - GetClientRect() always set the left / top of RECT to 0. So we need to
// estimate the border width from GetClientRect() and GetWindowRect().
// - Border width of a window varies according to the window type.
// - GetClientRect() excludes the title bar, which should be considered as
// part of the content and included in the captured frame. So we always
// estimate the border width according to the window width.
// - We assume a window has same border width in each side.
// So we shrink half of the width difference from all four sides.
const int shrink = ((width - result->width()) / 2);
// When `shrink` is negative, DesktopRect::Extend() shrinks itself.
result->Extend(shrink, 0, shrink, 0);
// Usually this should not happen, just in case we have received a strange
// window, which has only left and right borders.
if (result->height() > shrink * 2) {
result->Extend(0, shrink, 0, shrink);
}
RTC_DCHECK(!result->is_empty());
}
return true;
}
int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result) {
win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN>> scoped_hrgn(
CreateRectRgn(0, 0, 0, 0));
const int region_type = GetWindowRgn(window, scoped_hrgn.Get());
if (region_type == SIMPLEREGION) {
RECT rect;
GetRgnBox(scoped_hrgn.Get(), &rect);
*result =
DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}
return region_type;
}
bool GetDcSize(HDC hdc, DesktopSize* size) {
win::ScopedGDIObject<HGDIOBJ, win::DeleteObjectTraits<HGDIOBJ>> scoped_hgdi(
GetCurrentObject(hdc, OBJ_BITMAP));
BITMAP bitmap;
memset(&bitmap, 0, sizeof(BITMAP));
if (GetObject(scoped_hgdi.Get(), sizeof(BITMAP), &bitmap) == 0) {
return false;
}
size->set(bitmap.bmWidth, bitmap.bmHeight);
return true;
}
bool IsWindowMaximized(HWND window, bool* result) {
WINDOWPLACEMENT placement;
memset(&placement, 0, sizeof(WINDOWPLACEMENT));
placement.length = sizeof(WINDOWPLACEMENT);
if (!::GetWindowPlacement(window, &placement)) {
return false;
}
*result = (placement.showCmd == SW_SHOWMAXIMIZED);
return true;
}
bool IsWindowValidAndVisible(HWND window) {
return IsWindow(window) && IsWindowVisible(window) && !IsIconic(window);
}
bool IsWindowResponding(HWND window) {
// 50ms is chosen in case the system is under heavy load, but it's also not
// too long to delay window enumeration considerably.
const UINT uTimeoutMs = 50;
return SendMessageTimeout(window, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, uTimeoutMs,
nullptr);
}
bool GetWindowList(int flags,
DesktopCapturer::SourceList* windows,
LONG ex_style_filters) {
GetWindowListParams params(flags, ex_style_filters, windows);
return ::EnumWindows(&GetWindowListHandler,
reinterpret_cast<LPARAM>(&params)) != 0;
}
// WindowCaptureHelperWin implementation.
WindowCaptureHelperWin::WindowCaptureHelperWin() {
// Try to load dwmapi.dll dynamically since it is not available on XP.
dwmapi_library_ = LoadLibraryW(L"dwmapi.dll");
if (dwmapi_library_) {
func_ = reinterpret_cast<DwmIsCompositionEnabledFunc>(
GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
dwm_get_window_attribute_func_ =
reinterpret_cast<DwmGetWindowAttributeFunc>(
GetProcAddress(dwmapi_library_, "DwmGetWindowAttribute"));
}
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN10) {
if (FAILED(::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr,
CLSCTX_ALL,
IID_PPV_ARGS(&virtual_desktop_manager_)))) {
RTC_LOG(LS_WARNING) << "Fail to create instance of VirtualDesktopManager";
}
}
}
WindowCaptureHelperWin::~WindowCaptureHelperWin() {
if (dwmapi_library_) {
FreeLibrary(dwmapi_library_);
}
}
bool WindowCaptureHelperWin::IsAeroEnabled() {
BOOL result = FALSE;
if (func_) {
func_(&result);
}
return result != FALSE;
}
// This is just a best guess of a notification window. Chrome uses the Windows
// native framework for showing notifications. So far what we know about such a
// window includes: no title, class name with prefix "Chrome_WidgetWin_" and
// with certain extended styles.
bool WindowCaptureHelperWin::IsWindowChromeNotification(HWND hwnd) {
const size_t kTitleLength = 32;
WCHAR window_title[kTitleLength];
GetWindowTextW(hwnd, window_title, kTitleLength);
if (wcsnlen_s(window_title, kTitleLength) != 0) {
return false;
}
const size_t kClassLength = 256;
WCHAR class_name[kClassLength];
const int class_name_length = GetClassNameW(hwnd, class_name, kClassLength);
if (class_name_length < 1 ||
wcsncmp(class_name, kChromeWindowClassPrefix,
wcsnlen_s(kChromeWindowClassPrefix, kClassLength)) != 0) {
return false;
}
const LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if ((exstyle & WS_EX_NOACTIVATE) && (exstyle & WS_EX_TOOLWINDOW) &&
(exstyle & WS_EX_TOPMOST)) {
return true;
}
return false;
}
// `content_rect` is preferred because,
// 1. WindowCapturerWinGdi is using GDI capturer, which cannot capture DX
// output.
// So ScreenCapturer should be used as much as possible to avoid
// uncapturable cases. Note: lots of new applications are using DX output
// (hardware acceleration) to improve the performance which cannot be
// captured by WindowCapturerWinGdi. See bug http://crbug.com/741770.
// 2. WindowCapturerWinGdi is still useful because we do not want to expose the
// content on other windows if the target window is covered by them.
// 3. Shadow and borders should not be considered as "content" on other
// windows because they do not expose any useful information.
//
// So we can bear the false-negative cases (target window is covered by the
// borders or shadow of other windows, but we have not detected it) in favor
// of using ScreenCapturer, rather than let the false-positive cases (target
// windows is only covered by borders or shadow of other windows, but we treat
// it as overlapping) impact the user experience.
bool WindowCaptureHelperWin::AreWindowsOverlapping(
HWND hwnd,
HWND selected_hwnd,
const DesktopRect& selected_window_rect) {
DesktopRect content_rect;
if (!GetWindowContentRect(hwnd, &content_rect)) {
// Bail out if failed to get the window area.
return true;
}
content_rect.IntersectWith(selected_window_rect);
if (content_rect.is_empty()) {
return false;
}
// When the taskbar is automatically hidden, it will leave a 2 pixel margin on
// the screen which will overlap the maximized selected window that will use
// up the full screen area. Since there is no solid way to identify a hidden
// taskbar window, we have to make an exemption here if the overlapping is
// 2 x screen_width/height to a maximized window.
bool is_maximized = false;
IsWindowMaximized(selected_hwnd, &is_maximized);
bool overlaps_hidden_horizontal_taskbar =
selected_window_rect.width() == content_rect.width() &&
content_rect.height() == kHiddenTaskbarMarginOnScreen;
bool overlaps_hidden_vertical_taskbar =
selected_window_rect.height() == content_rect.height() &&
content_rect.width() == kHiddenTaskbarMarginOnScreen;
if (is_maximized && (overlaps_hidden_horizontal_taskbar ||
overlaps_hidden_vertical_taskbar)) {
return false;
}
return true;
}
bool WindowCaptureHelperWin::IsWindowOnCurrentDesktop(HWND hwnd) {
// Make sure the window is on the current virtual desktop.
if (virtual_desktop_manager_) {
BOOL on_current_desktop;
if (SUCCEEDED(virtual_desktop_manager_->IsWindowOnCurrentVirtualDesktop(
hwnd, &on_current_desktop)) &&
!on_current_desktop) {
return false;
}
}
return true;
}
bool WindowCaptureHelperWin::IsWindowVisibleOnCurrentDesktop(HWND hwnd) {
return IsWindowValidAndVisible(hwnd) && IsWindowOnCurrentDesktop(hwnd) &&
!IsWindowCloaked(hwnd);
}
// A cloaked window is composited but not visible to the user.
// Example: Cortana or the Action Center when collapsed.
bool WindowCaptureHelperWin::IsWindowCloaked(HWND hwnd) {
if (!dwm_get_window_attribute_func_) {
// Does not apply.
return false;
}
int res = 0;
if (dwm_get_window_attribute_func_(hwnd, DWMWA_CLOAKED, &res, sizeof(res)) !=
S_OK) {
// Cannot tell so assume not cloaked for backward compatibility.
return false;
}
return res != 0;
}
bool WindowCaptureHelperWin::EnumerateCapturableWindows(
DesktopCapturer::SourceList* results,
bool enumerate_current_process_windows,
LONG ex_style_filters) {
int flags = (GetWindowListFlags::kIgnoreUntitled |
GetWindowListFlags::kIgnoreUnresponsive);
if (!enumerate_current_process_windows) {
flags |= GetWindowListFlags::kIgnoreCurrentProcessWindows;
}
if (!webrtc::GetWindowList(flags, results, ex_style_filters)) {
return false;
}
for (auto it = results->begin(); it != results->end();) {
if (!IsWindowVisibleOnCurrentDesktop(reinterpret_cast<HWND>(it->id))) {
it = results->erase(it);
} else {
++it;
}
}
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_
#include <shlobj.h>
#include <windows.h>
#include <wrl/client.h>
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
// Outputs the window rect. The returned DesktopRect is in system coordinates,
// i.e. the primary monitor on the system always starts from (0, 0). This
// function returns false if native APIs fail.
bool GetWindowRect(HWND window, DesktopRect* result);
// Outputs the window rect, with the left/right/bottom frame border cropped if
// the window is maximized or has a transparent resize border.
// `avoid_cropping_border` may be set to true to avoid cropping the visible
// border when cropping any resize border.
// `cropped_rect` is the cropped rect relative to the
// desktop. `original_rect` is the original rect returned from GetWindowRect.
// Returns true if all API calls succeeded. The returned DesktopRect is in
// system coordinates, i.e. the primary monitor on the system always starts from
// (0, 0). `original_rect` can be nullptr.
//
// TODO(zijiehe): Move this function to CroppingWindowCapturerWin after it has
// been removed from MouseCursorMonitorWin.
// This function should only be used by CroppingWindowCapturerWin. Instead a
// DesktopRect CropWindowRect(const DesktopRect& rect)
// should be added as a utility function to help CroppingWindowCapturerWin and
// WindowCapturerWinGdi to crop out the borders or shadow according to their
// scenarios. But this function is too generic and easy to be misused.
bool GetCroppedWindowRect(HWND window,
bool avoid_cropping_border,
DesktopRect* cropped_rect,
DesktopRect* original_rect);
// Retrieves the rectangle of the content area of `window`. Usually it contains
// title bar and window client area, but borders or shadow are excluded. The
// returned DesktopRect is in system coordinates, i.e. the primary monitor on
// the system always starts from (0, 0). This function returns false if native
// APIs fail.
bool GetWindowContentRect(HWND window, DesktopRect* result);
// Returns the region type of the `window` and fill `rect` with the region of
// `window` if region type is SIMPLEREGION.
int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result);
// Retrieves the size of the `hdc`. This function returns false if native APIs
// fail.
bool GetDcSize(HDC hdc, DesktopSize* size);
// Retrieves whether the `window` is maximized and stores in `result`. This
// function returns false if native APIs fail.
bool IsWindowMaximized(HWND window, bool* result);
// Checks that the HWND is for a valid window, that window's visibility state is
// visible, and that it is not minimized.
bool IsWindowValidAndVisible(HWND window);
// Checks if a window responds to a message within 50ms.
bool IsWindowResponding(HWND window);
enum GetWindowListFlags {
kNone = 0x00,
kIgnoreUntitled = 1 << 0,
kIgnoreUnresponsive = 1 << 1,
kIgnoreCurrentProcessWindows = 1 << 2,
};
// Retrieves the list of top-level windows on the screen.
// Some windows will be ignored:
// - Those that are invisible or minimized.
// - Program Manager & Start menu.
// - [with kIgnoreUntitled] windows with no title.
// - [with kIgnoreUnresponsive] windows that are unresponsive.
// - [with kIgnoreCurrentProcessWindows] windows owned by the current process.
// - Any windows with extended styles that match `ex_style_filters`.
// Returns false if native APIs failed.
bool GetWindowList(int flags,
DesktopCapturer::SourceList* windows,
LONG ex_style_filters = 0);
typedef HRESULT(WINAPI* DwmIsCompositionEnabledFunc)(BOOL* enabled);
typedef HRESULT(WINAPI* DwmGetWindowAttributeFunc)(HWND hwnd,
DWORD flag,
PVOID result_ptr,
DWORD result_size);
class WindowCaptureHelperWin {
public:
WindowCaptureHelperWin();
~WindowCaptureHelperWin();
WindowCaptureHelperWin(const WindowCaptureHelperWin&) = delete;
WindowCaptureHelperWin& operator=(const WindowCaptureHelperWin&) = delete;
bool IsAeroEnabled();
bool IsWindowChromeNotification(HWND hwnd);
bool AreWindowsOverlapping(HWND hwnd,
HWND selected_hwnd,
const DesktopRect& selected_window_rect);
bool IsWindowOnCurrentDesktop(HWND hwnd);
bool IsWindowVisibleOnCurrentDesktop(HWND hwnd);
bool IsWindowCloaked(HWND hwnd);
// The optional `ex_style_filters` parameter allows callers to provide
// extended window styles (e.g. WS_EX_TOOLWINDOW) and prevent windows that
// match from being included in `results`.
bool EnumerateCapturableWindows(DesktopCapturer::SourceList* results,
bool enumerate_current_process_windows,
LONG ex_style_filters = 0);
private:
HMODULE dwmapi_library_ = nullptr;
DwmIsCompositionEnabledFunc func_ = nullptr;
DwmGetWindowAttributeFunc dwm_get_window_attribute_func_ = nullptr;
// Only used on Win10+.
Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/window_capture_utils.h"
#include <winuser.h>
#include <algorithm>
#include <memory>
#include <mutex>
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/win/test_support/test_window.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
const char kWindowThreadName[] = "window_capture_utils_test_thread";
const WCHAR kWindowTitle[] = L"Window Capture Utils Test";
std::unique_ptr<rtc::Thread> SetUpUnresponsiveWindow(std::mutex& mtx,
WindowInfo& info) {
std::unique_ptr<rtc::Thread> window_thread;
window_thread = rtc::Thread::Create();
window_thread->SetName(kWindowThreadName, nullptr);
window_thread->Start();
SendTask(window_thread.get(), [&] { info = CreateTestWindow(kWindowTitle); });
// Intentionally create a deadlock to cause the window to become unresponsive.
mtx.lock();
window_thread->PostTask([&mtx]() {
mtx.lock();
mtx.unlock();
});
return window_thread;
}
} // namespace
TEST(WindowCaptureUtilsTest, GetWindowList) {
WindowInfo info = CreateTestWindow(kWindowTitle);
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list));
EXPECT_GT(window_list.size(), 0ULL);
EXPECT_NE(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
DestroyTestWindow(info);
}
TEST(WindowCaptureUtilsTest, IncludeUnresponsiveWindows) {
std::mutex mtx;
WindowInfo info;
std::unique_ptr<rtc::Thread> window_thread =
SetUpUnresponsiveWindow(mtx, info);
EXPECT_FALSE(IsWindowResponding(info.hwnd));
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list));
EXPECT_GT(window_list.size(), 0ULL);
EXPECT_NE(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
mtx.unlock();
SendTask(window_thread.get(), [&info]() { DestroyTestWindow(info); });
window_thread->Stop();
}
TEST(WindowCaptureUtilsTest, IgnoreUnresponsiveWindows) {
std::mutex mtx;
WindowInfo info;
std::unique_ptr<rtc::Thread> window_thread =
SetUpUnresponsiveWindow(mtx, info);
EXPECT_FALSE(IsWindowResponding(info.hwnd));
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(
GetWindowList(GetWindowListFlags::kIgnoreUnresponsive, &window_list));
EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
mtx.unlock();
SendTask(window_thread.get(), [&info]() { DestroyTestWindow(info); });
window_thread->Stop();
}
TEST(WindowCaptureUtilsTest, IncludeUntitledWindows) {
WindowInfo info = CreateTestWindow(L"");
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list));
EXPECT_GT(window_list.size(), 0ULL);
EXPECT_NE(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
DestroyTestWindow(info);
}
TEST(WindowCaptureUtilsTest, IgnoreUntitledWindows) {
WindowInfo info = CreateTestWindow(L"");
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(GetWindowList(GetWindowListFlags::kIgnoreUntitled, &window_list));
EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
DestroyTestWindow(info);
}
TEST(WindowCaptureUtilsTest, IgnoreCurrentProcessWindows) {
WindowInfo info = CreateTestWindow(kWindowTitle);
DesktopCapturer::SourceList window_list;
ASSERT_TRUE(GetWindowList(GetWindowListFlags::kIgnoreCurrentProcessWindows,
&window_list));
EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(),
[&info](DesktopCapturer::Source window) {
return reinterpret_cast<HWND>(window.id) ==
info.hwnd;
}),
window_list.end());
DestroyTestWindow(info);
}
} // namespace webrtc

View file

@ -0,0 +1,403 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/win/window_capturer_win_gdi.h"
#include <cmath>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "modules/desktop_capture/cropped_desktop_frame.h"
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_frame_win.h"
#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/selected_window_context.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/trace_event.h"
#include "rtc_base/win/windows_version.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
// Used to pass input/output data during the EnumWindows call to collect
// owned/pop-up windows that should be captured.
struct OwnedWindowCollectorContext : public SelectedWindowContext {
OwnedWindowCollectorContext(HWND selected_window,
DesktopRect selected_window_rect,
WindowCaptureHelperWin* window_capture_helper,
std::vector<HWND>* owned_windows)
: SelectedWindowContext(selected_window,
selected_window_rect,
window_capture_helper),
owned_windows(owned_windows) {}
std::vector<HWND>* owned_windows;
};
// Called via EnumWindows for each root window; adds owned/pop-up windows that
// should be captured to a vector it's passed.
BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) {
OwnedWindowCollectorContext* context =
reinterpret_cast<OwnedWindowCollectorContext*>(param);
if (hwnd == context->selected_window()) {
// Windows are enumerated in top-down z-order, so we can stop enumerating
// upon reaching the selected window.
return FALSE;
}
// Skip windows that aren't visible pop-up windows.
if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) ||
!context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop(
hwnd)) {
return TRUE;
}
// Owned windows that intersect the selected window should be captured.
if (context->IsWindowOwnedBySelectedWindow(hwnd) &&
context->IsWindowOverlappingSelectedWindow(hwnd)) {
// Skip windows that draw shadows around menus. These "SysShadow" windows
// would otherwise be captured as solid black bars with no transparency
// gradient (since this capturer doesn't detect / respect variations in the
// window alpha channel). Any other semi-transparent owned windows will be
// captured fully-opaque. This seems preferable to excluding them (at least
// when they have content aside from a solid fill color / visual adornment;
// e.g. some tooltips have the transparent style set).
if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) {
const WCHAR kSysShadow[] = L"SysShadow";
const size_t kClassLength = arraysize(kSysShadow);
WCHAR class_name[kClassLength];
const int class_name_length =
GetClassNameW(hwnd, class_name, kClassLength);
if (class_name_length == kClassLength - 1 &&
wcscmp(class_name, kSysShadow) == 0) {
return TRUE;
}
}
context->owned_windows->push_back(hwnd);
}
return TRUE;
}
WindowCapturerWinGdi::WindowCapturerWinGdi(
bool enumerate_current_process_windows)
: enumerate_current_process_windows_(enumerate_current_process_windows) {}
WindowCapturerWinGdi::~WindowCapturerWinGdi() {}
bool WindowCapturerWinGdi::GetSourceList(SourceList* sources) {
if (!window_capture_helper_.EnumerateCapturableWindows(
sources, enumerate_current_process_windows_))
return false;
std::map<HWND, DesktopSize> new_map;
for (const auto& item : *sources) {
HWND hwnd = reinterpret_cast<HWND>(item.id);
new_map[hwnd] = window_size_map_[hwnd];
}
window_size_map_.swap(new_map);
return true;
}
bool WindowCapturerWinGdi::SelectSource(SourceId id) {
HWND window = reinterpret_cast<HWND>(id);
if (!IsWindowValidAndVisible(window))
return false;
window_ = window;
// When a window is not in the map, window_size_map_[window] will create an
// item with DesktopSize (0, 0).
previous_size_ = window_size_map_[window];
return true;
}
bool WindowCapturerWinGdi::FocusOnSelectedSource() {
if (!window_)
return false;
if (!IsWindowValidAndVisible(window_))
return false;
return BringWindowToTop(window_) && SetForegroundWindow(window_);
}
bool WindowCapturerWinGdi::IsOccluded(const DesktopVector& pos) {
DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left());
HWND hwnd =
reinterpret_cast<HWND>(window_finder_.GetWindowUnderPoint(sys_pos));
return hwnd != window_ &&
std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) ==
owned_windows_.end();
}
void WindowCapturerWinGdi::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
RecordCapturerImpl(DesktopCapturerId::kWindowCapturerWinGdi);
callback_ = callback;
}
void WindowCapturerWinGdi::CaptureFrame() {
RTC_DCHECK(callback_);
int64_t capture_start_time_nanos = rtc::TimeNanos();
CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true);
if (!results.frame) {
// Don't return success if we have no frame.
results.result = results.result == Result::SUCCESS ? Result::ERROR_TEMPORARY
: results.result;
callback_->OnCaptureResult(results.result, nullptr);
return;
}
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
rtc::kNumNanosecsPerMillisec;
RTC_HISTOGRAM_COUNTS_1000(
"WebRTC.DesktopCapture.Win.WindowGdiCapturerFrameTime", capture_time_ms);
results.frame->set_capture_time_ms(capture_time_ms);
results.frame->set_capturer_id(DesktopCapturerId::kWindowCapturerWinGdi);
callback_->OnCaptureResult(results.result, std::move(results.frame));
}
WindowCapturerWinGdi::CaptureResults WindowCapturerWinGdi::CaptureFrame(
bool capture_owned_windows) {
TRACE_EVENT0("webrtc", "WindowCapturerWinGdi::CaptureFrame");
if (!window_) {
RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
return {Result::ERROR_PERMANENT, nullptr};
}
// Stop capturing if the window has been closed.
if (!IsWindow(window_)) {
RTC_LOG(LS_ERROR) << "Target window has been closed.";
return {Result::ERROR_PERMANENT, nullptr};
}
// Determine the window region excluding any resize border, and including
// any visible border if capturing an owned window / dialog. (Don't include
// any visible border for the selected window for consistency with
// CroppingWindowCapturerWin, which would expose a bit of the background
// through the partially-transparent border.)
const bool avoid_cropping_border = !capture_owned_windows;
DesktopRect cropped_rect;
DesktopRect original_rect;
if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect,
&original_rect)) {
RTC_LOG(LS_WARNING) << "Failed to get drawable window area: "
<< GetLastError();
return {Result::ERROR_TEMPORARY, nullptr};
}
// Return a 1x1 black frame if the window is minimized or invisible on current
// desktop, to match behavior on mace. Window can be temporarily invisible
// during the transition of full screen mode on/off.
if (original_rect.is_empty() ||
!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) {
std::unique_ptr<DesktopFrame> frame(
new BasicDesktopFrame(DesktopSize(1, 1)));
previous_size_ = frame->size();
window_size_map_[window_] = previous_size_;
return {Result::SUCCESS, std::move(frame)};
}
HDC window_dc = GetWindowDC(window_);
if (!window_dc) {
RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
return {Result::ERROR_TEMPORARY, nullptr};
}
DesktopRect unscaled_cropped_rect = cropped_rect;
double horizontal_scale = 1.0;
double vertical_scale = 1.0;
DesktopSize window_dc_size;
if (GetDcSize(window_dc, &window_dc_size)) {
// The `window_dc_size` is used to detect the scaling of the original
// window. If the application does not support high-DPI settings, it will
// be scaled by Windows according to the scaling setting.
// https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8
// So the size of the `window_dc`, i.e. the bitmap we can retrieve from
// PrintWindow() or BitBlt() function, will be smaller than
// `original_rect` and `cropped_rect`. Part of the captured desktop frame
// will be black. See
// bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for
// details.
// If `window_dc_size` is smaller than `window_rect`, let's resize both
// `original_rect` and `cropped_rect` according to the scaling factor.
// This will adjust the width and height of the two rects.
horizontal_scale =
static_cast<double>(window_dc_size.width()) / original_rect.width();
vertical_scale =
static_cast<double>(window_dc_size.height()) / original_rect.height();
original_rect.Scale(horizontal_scale, vertical_scale);
cropped_rect.Scale(horizontal_scale, vertical_scale);
// Translate `cropped_rect` to the left so that its position within
// `original_rect` remains accurate after scaling.
// See crbug.com/1083527 for more info.
int translate_left = static_cast<int>(std::round(
(cropped_rect.left() - original_rect.left()) * (horizontal_scale - 1)));
int translate_top = static_cast<int>(std::round(
(cropped_rect.top() - original_rect.top()) * (vertical_scale - 1)));
cropped_rect.Translate(translate_left, translate_top);
}
std::unique_ptr<DesktopFrameWin> frame(
DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc));
if (!frame.get()) {
RTC_LOG(LS_WARNING) << "Failed to create frame.";
ReleaseDC(window_, window_dc);
return {Result::ERROR_TEMPORARY, nullptr};
}
HDC mem_dc = CreateCompatibleDC(window_dc);
HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
BOOL result = FALSE;
// When desktop composition (Aero) is enabled each window is rendered to a
// private buffer allowing BitBlt() to get the window content even if the
// window is occluded. PrintWindow() is slower but lets rendering the window
// contents to an off-screen device context when Aero is not available.
// PrintWindow() is not supported by some applications.
//
// If Aero is enabled, we prefer BitBlt() because it's faster and avoids
// window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
// render occluding windows on top of the desired window.
//
// When composition is enabled the DC returned by GetWindowDC() doesn't always
// have window frame rendered correctly. Windows renders it only once and then
// caches the result between captures. We hack it around by calling
// PrintWindow() whenever window size changes, including the first time of
// capturing - it somehow affects what we get from BitBlt() on the subsequent
// captures.
//
// For Windows 8.1 and later, we want to always use PrintWindow when the
// cropping screen capturer falls back to the window capturer. I.e.
// on Windows 8.1 and later, PrintWindow is only used when the window is
// occluded. When the window is not occluded, it is much faster to capture
// the screen and to crop it to the window position and size.
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
// Special flag that makes PrintWindow to work on Windows 8.1 and later.
// Indeed certain apps (e.g. those using DirectComposition rendering) can't
// be captured using BitBlt or PrintWindow without this flag. Note that on
// Windows 8.0 this flag is not supported so the block below will fallback
// to the other call to PrintWindow. It seems to be very tricky to detect
// Windows 8.0 vs 8.1 so a try/fallback is more approriate here.
const UINT flags = PW_RENDERFULLCONTENT;
result = PrintWindow(window_, mem_dc, flags);
}
if (!result && (!window_capture_helper_.IsAeroEnabled() ||
!previous_size_.equals(frame->size()))) {
result = PrintWindow(window_, mem_dc, 0);
}
// Aero is enabled or PrintWindow() failed, use BitBlt.
if (!result) {
result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
window_dc, 0, 0, SRCCOPY);
}
SelectObject(mem_dc, previous_object);
DeleteDC(mem_dc);
ReleaseDC(window_, window_dc);
previous_size_ = frame->size();
window_size_map_[window_] = previous_size_;
frame->mutable_updated_region()->SetRect(
DesktopRect::MakeSize(frame->size()));
frame->set_top_left(
original_rect.top_left().subtract(GetFullscreenRect().top_left()));
if (!result) {
RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
return {Result::ERROR_TEMPORARY, nullptr};
}
// Rect for the data is relative to the first pixel of the frame.
cropped_rect.Translate(-original_rect.left(), -original_rect.top());
std::unique_ptr<DesktopFrame> cropped_frame =
CreateCroppedDesktopFrame(std::move(frame), cropped_rect);
RTC_DCHECK(cropped_frame);
if (capture_owned_windows) {
// If any owned/pop-up windows overlap the selected window, capture them
// and copy/composite their contents into the frame.
owned_windows_.clear();
OwnedWindowCollectorContext context(window_, unscaled_cropped_rect,
&window_capture_helper_,
&owned_windows_);
if (context.IsSelectedWindowValid()) {
EnumWindows(OwnedWindowCollector, reinterpret_cast<LPARAM>(&context));
if (!owned_windows_.empty()) {
if (!owned_window_capturer_) {
owned_window_capturer_ = std::make_unique<WindowCapturerWinGdi>(
enumerate_current_process_windows_);
}
// Owned windows are stored in top-down z-order, so this iterates in
// reverse to capture / draw them in bottom-up z-order
for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend();
it++) {
HWND hwnd = *it;
if (owned_window_capturer_->SelectSource(
reinterpret_cast<SourceId>(hwnd))) {
CaptureResults results = owned_window_capturer_->CaptureFrame(
/*capture_owned_windows*/ false);
if (results.result != DesktopCapturer::Result::SUCCESS) {
// Simply log any error capturing an owned/pop-up window without
// bubbling it up to the caller (an expected error here is that
// the owned/pop-up window was closed; any unexpected errors won't
// fail the outer capture).
RTC_LOG(LS_INFO) << "Capturing owned window failed (previous "
"error/warning pertained to that)";
} else {
// Copy / composite the captured frame into the outer frame. This
// may no-op if they no longer intersect (if the owned window was
// moved outside the owner bounds since scheduled for capture.)
cropped_frame->CopyIntersectingPixelsFrom(
*results.frame, horizontal_scale, vertical_scale);
}
}
}
}
}
}
return {Result::SUCCESS, std::move(cropped_frame)};
}
// static
std::unique_ptr<DesktopCapturer> WindowCapturerWinGdi::CreateRawWindowCapturer(
const DesktopCaptureOptions& options) {
return std::unique_ptr<DesktopCapturer>(
new WindowCapturerWinGdi(options.enumerate_current_process_windows()));
}
} // namespace webrtc

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_
#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_
#include <map>
#include <memory>
#include <vector>
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "modules/desktop_capture/window_finder_win.h"
namespace webrtc {
class WindowCapturerWinGdi : public DesktopCapturer {
public:
explicit WindowCapturerWinGdi(bool enumerate_current_process_windows);
// Disallow copy and assign
WindowCapturerWinGdi(const WindowCapturerWinGdi&) = delete;
WindowCapturerWinGdi& operator=(const WindowCapturerWinGdi&) = delete;
~WindowCapturerWinGdi() override;
static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
const DesktopCaptureOptions& options);
// DesktopCapturer interface.
void Start(Callback* callback) override;
void CaptureFrame() override;
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
bool FocusOnSelectedSource() override;
bool IsOccluded(const DesktopVector& pos) override;
private:
struct CaptureResults {
Result result;
std::unique_ptr<DesktopFrame> frame;
};
CaptureResults CaptureFrame(bool capture_owned_windows);
Callback* callback_ = nullptr;
// HWND and HDC for the currently selected window or nullptr if window is not
// selected.
HWND window_ = nullptr;
DesktopSize previous_size_;
WindowCaptureHelperWin window_capture_helper_;
bool enumerate_current_process_windows_;
// This map is used to avoid flickering for the case when SelectWindow() calls
// are interleaved with Capture() calls.
std::map<HWND, DesktopSize> window_size_map_;
WindowFinderWin window_finder_;
std::vector<HWND> owned_windows_;
std::unique_ptr<WindowCapturerWinGdi> owned_window_capturer_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_