diff options
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/win')
70 files changed, 9010 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc b/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc new file mode 100644 index 0000000000..1d645098e2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor.h b/third_party/libwebrtc/modules/desktop_capture/win/cursor.h new file mode 100644 index 0000000000..54d78164a3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur Binary files differnew file mode 100644 index 0000000000..27702b825c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur Binary files differnew file mode 100644 index 0000000000..7e0d8596da --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur Binary files differnew file mode 100644 index 0000000000..fefb09e1a1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur Binary files differnew file mode 100644 index 0000000000..4f8a094f31 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur Binary files differnew file mode 100644 index 0000000000..ac9cdbfbb3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur Binary files differnew file mode 100644 index 0000000000..efdbee5415 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur Binary files differnew file mode 100644 index 0000000000..9678d55446 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc new file mode 100644 index 0000000000..23f5d89571 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h new file mode 100644 index 0000000000..f583554d68 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc new file mode 100644 index 0000000000..90073791c9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc @@ -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" diff --git a/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc new file mode 100644 index 0000000000..3d46117501 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h new file mode 100644 index 0000000000..aeb9d1823a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc b/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc new file mode 100644 index 0000000000..4a671dd9ae --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop.h b/third_party/libwebrtc/modules/desktop_capture/win/desktop.h new file mode 100644 index 0000000000..01bed8592d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc new file mode 100644 index 0000000000..476ddc4aba --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h new file mode 100644 index 0000000000..ebf31419ce --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc new file mode 100644 index 0000000000..52d89214d8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc @@ -0,0 +1,37 @@ +/* + * 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 "modules/desktop_capture/win/screen_capture_utils.h" + +namespace webrtc { + +bool DisplayConfigurationMonitor::IsChanged() { + DesktopRect rect = GetFullscreenRect(); + if (!initialized_) { + initialized_ = true; + rect_ = rect; + return false; + } + + if (rect.equals(rect_)) { + return false; + } + + rect_ = rect; + return true; +} + +void DisplayConfigurationMonitor::Reset() { + initialized_ = false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h new file mode 100644 index 0000000000..39c211cfbe --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h @@ -0,0 +1,38 @@ +/* + * 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_geometry.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 change of display configuration has happened after last + // IsChanged() call. This function won't return true for the first time after + // constructor or Reset() call. + bool IsChanged(); + + // Resets to the initial state. + void Reset(); + + private: + DesktopRect rect_; + bool initialized_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc new file mode 100644 index 0000000000..88ec4e25bf --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h new file mode 100644 index 0000000000..5931b51f9e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc new file mode 100644 index 0000000000..c18b238f03 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h new file mode 100644 index 0000000000..59c2112db5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc new file mode 100644 index 0000000000..a776896f6c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc @@ -0,0 +1,514 @@ +/* + * 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::dpi() { + MutexLock lock(&mutex_); + if (Initialize()) { + return 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()) { + 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) { + 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(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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h new file mode 100644 index 0000000000..88c2939187 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h @@ -0,0 +1,253 @@ +/* + * 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 underyling 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 on, 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. + }; + + enum class Result { + SUCCEEDED, + UNSUPPORTED_SESSION, + FRAME_PREPARE_FAILED, + INITIALIZATION_FAILED, + DUPLICATION_FAILED, + 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` should >= 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 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 rtc::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 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 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 succeeded duplications have been performed. + uint32_t succeeded_duplications_ RTC_GUARDED_BY(mutex_) = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc new file mode 100644 index 0000000000..13d5b4b62e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h new file mode 100644 index 0000000000..6a9ce868a7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc new file mode 100644 index 0000000000..caad6f43d1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc @@ -0,0 +1,390 @@ +/* + * 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" + +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_); + if (desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { + RTC_LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " + << "format, 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::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; + } + + // 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); + 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); + } 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h new file mode 100644 index 0000000000..df15fe566e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h @@ -0,0 +1,149 @@ +/* + * 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); + + 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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc new file mode 100644 index 0000000000..b8f5b81f90 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h new file mode 100644 index 0000000000..a663b95a04 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc new file mode 100644 index 0000000000..7ecf1adc61 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h new file mode 100644 index 0000000000..71f00b99ab --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc new file mode 100644 index 0000000000..17e8518a7d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc @@ -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(¤t_desc); + const bool recreate_needed = + (memcmp(&desc, ¤t_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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h new file mode 100644 index 0000000000..e8c2af6662 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc new file mode 100644 index 0000000000..4222dfc01e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h new file mode 100644 index 0000000000..c97cbe252b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h @@ -0,0 +1,24 @@ +/* + * 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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h b/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h new file mode 100644 index 0000000000..2b01941e20 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc new file mode 100644 index 0000000000..22e8e7bc8f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h new file mode 100644 index 0000000000..98f151a46c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc new file mode 100644 index 0000000000..1dc2918d08 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc @@ -0,0 +1,184 @@ +/* + * 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 <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, 0, 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)); +} + +DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen, + const std::wstring& device_key) { + RTC_DCHECK(IsGUIThread(false)); + 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h new file mode 100644 index 0000000000..97bfe816d8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h @@ -0,0 +1,75 @@ +/* + * 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 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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc new file mode 100644 index 0000000000..2e58c6b164 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc new file mode 100644 index 0000000000..efa763993a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc @@ -0,0 +1,230 @@ +/* + * 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() = 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); + } + 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); + + // 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h new file mode 100644 index 0000000000..801a0632fc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h @@ -0,0 +1,105 @@ +/* + * 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); + + explicit ScreenCapturerWinDirectx(); + + ~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_; + + // 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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc new file mode 100644 index 0000000000..c9f46f782c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc new file mode 100644 index 0000000000..c6d4a75931 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc @@ -0,0 +1,241 @@ +/* + * 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, ¤t_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()) { + 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() { + RTC_DCHECK(IsGUIThread(false)); + 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h new file mode 100644 index 0000000000..7c3977ed42 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 0000000000..ce747e0141 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,398 @@ +/* + * 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_); + host_window_ = NULL; + } + + if (magnifier_initialized_) { + mag_uninitialize_func_(); + magnifier_initialized_ = false; + } + + if (mag_lib_handle_) { + FreeLibrary(mag_lib_handle_); + mag_lib_handle_ = NULL; + } + + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } +} + +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, ¤t_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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h new file mode 100644 index 0000000000..07c5b1e9e6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc new file mode 100644 index 0000000000..398ea1e53a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h new file mode 100644 index 0000000000..99e38e3fa2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc new file mode 100644 index 0000000000..c07ff74aa5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h new file mode 100644 index 0000000000..b055da7ccd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc new file mode 100644 index 0000000000..ea5565c89c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc @@ -0,0 +1,429 @@ +/* + * 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 <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" + +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; + +// The maximum time `GetFrame` will wait for a frame to arrive, if we don't have +// any in the pool. +constexpr TimeDelta kMaxWaitForFrame = TimeDelta::Millis(50); +constexpr TimeDelta kMaxWaitForFirstFrame = TimeDelta::Millis(500); + +// 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, + kMaxValue = kRecreateFramePoolFailed +}; + +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)); +} + +} // 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() { + RemoveEventHandlers(); +} + +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; + } + + frames_in_pool_ = 0; + + // Because `WgcCapturerWin` created a `DispatcherQueue`, and we created + // `frame_pool_` via `Create`, the `FrameArrived` event will be delivered on + // the current thread. + frame_arrived_token_ = std::make_unique<EventRegistrationToken>(); + auto frame_arrived_handler = + Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler< + WGC::Direct3D11CaptureFramePool*, IInspectable*>>( + this, &WgcCaptureSession::OnFrameArrived); + hr = frame_pool_->add_FrameArrived(frame_arrived_handler.Get(), + frame_arrived_token_.get()); + + 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); + } + } + + 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; +} + +HRESULT WgcCaptureSession::GetFrame( + std::unique_ptr<DesktopFrame>* output_frame) { + 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_); + + if (frames_in_pool_ < 1) + wait_for_frame_event_.Wait(first_frame_ ? kMaxWaitForFirstFrame + : kMaxWaitForFrame); + + 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) { + RecordGetFrameResult(GetFrameResult::kFrameDropped); + return hr; + } + + first_frame_ = false; + --frames_in_pool_; + + // 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 (size_.Height != new_size.Height || size_.Width != new_size.Width) { + 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, ©_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; + } + + int row_data_length = image_width * DesktopFrame::kBytesPerPixel; + + // Make a copy of the data pointed to by `map_info.pData` so we are free to + // unmap our texture. + uint8_t* src_data = static_cast<uint8_t*>(map_info.pData); + std::vector<uint8_t> image_data; + image_data.resize(image_height * row_data_length); + uint8_t* image_data_ptr = image_data.data(); + for (int i = 0; i < image_height; i++) { + memcpy(image_data_ptr, src_data, row_data_length); + image_data_ptr += row_data_length; + src_data += map_info.RowPitch; + } + + d3d_context->Unmap(mapped_texture_.Get(), 0); + + // Transfer ownership of `image_data` to the output_frame. + DesktopSize size(image_width, image_height); + *output_frame = std::make_unique<WgcDesktopFrame>(size, row_data_length, + std::move(image_data)); + + size_ = new_size; + RecordGetFrameResult(GetFrameResult::kSuccess); + return hr; +} + +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::OnFrameArrived( + WGC::IDirect3D11CaptureFramePool* sender, + IInspectable* event_args) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK_LT(frames_in_pool_, kNumBuffers); + ++frames_in_pool_; + wait_for_frame_event_.Set(); + return S_OK; +} + +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; + + RemoveEventHandlers(); + + // 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::RemoveEventHandlers() { + HRESULT hr; + if (frame_pool_ && frame_arrived_token_) { + hr = frame_pool_->remove_FrameArrived(*frame_arrived_token_); + frame_arrived_token_.reset(); + if (FAILED(hr)) { + RTC_LOG(LS_WARNING) << "Failed to remove FrameArrived event handler: " + << 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; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h new file mode 100644 index 0000000000..dfe1fa60bb --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h @@ -0,0 +1,137 @@ +/* + * 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/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 frame pool, if any are present. + HRESULT GetFrame(std::unique_ptr<DesktopFrame>* output_frame); + + bool IsCaptureStarted() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return is_capture_started_; + } + + // We keep 2 buffers in the frame pool to balance the staleness of the frame + // with having to wait for frames to arrive too frequently. Too many buffers + // will lead to a high latency, and too few will lead to poor performance. + // 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); + + // Event handler for `frame_pool_`'s FrameArrived event. + HRESULT OnFrameArrived( + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* sender, + IInspectable* event_args); + + void RemoveEventHandlers(); + + // We wait on this event in `GetFrame` if there are no frames in the pool. + // `OnFrameArrived` will set the event so we can proceed. + rtc::Event wait_for_frame_event_; + int frames_in_pool_; + + // We're willing to wait for a frame a little longer if it's the first one. + bool first_frame_ = true; + + std::unique_ptr<EventRegistrationToken> frame_arrived_token_; + 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_; + + bool item_closed_ = false; + bool is_capture_started_ = false; + + SequenceChecker sequence_checker_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc new file mode 100644 index 0000000000..24e6129ec7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc @@ -0,0 +1,218 @@ +/* + * 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::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::IsCapturable() { + if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()))) + 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h new file mode 100644 index 0000000000..d1275b6168 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h @@ -0,0 +1,141 @@ +/* + * 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; + 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 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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc new file mode 100644 index 0000000000..dc37ec2e0d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc new file mode 100644 index 0000000000..8ec6a29f23 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc @@ -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; + hr = capture_session->GetFrame(&frame); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "GetFrame failed: " << hr; + 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h new file mode 100644 index 0000000000..30253d9db6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc new file mode 100644 index 0000000000..a7b656fcfc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc new file mode 100644 index 0000000000..dd9009120b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h new file mode 100644 index 0000000000..0a671cf2f8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc new file mode 100644 index 0000000000..d58c02e17c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc @@ -0,0 +1,486 @@ +/* + * 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); + + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + window.pid = static_cast<pid_t>(pid); + + // 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>(¶ms)) != 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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h new file mode 100644 index 0000000000..caea07958d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h @@ -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_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc new file mode 100644 index 0000000000..137440b09e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc new file mode 100644 index 0000000000..bc3a762264 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc @@ -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 diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h new file mode 100644 index 0000000000..bf94dfe192 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h @@ -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_ |