Repo created
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 766 B |
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/display_configuration_monitor.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
bool DisplayConfigurationMonitor::IsChanged(
|
||||
DesktopCapturer::SourceId source_id) {
|
||||
DesktopRect rect = GetFullscreenRect();
|
||||
DesktopVector dpi = GetDpiForSourceId(source_id);
|
||||
|
||||
if (!initialized_) {
|
||||
initialized_ = true;
|
||||
rect_ = rect;
|
||||
source_dpis_.emplace(source_id, std::move(dpi));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!source_dpis_.contains(source_id)) {
|
||||
// If this is the first time we've seen this source_id, use the current DPI
|
||||
// so the monitor does not indicate a change and possibly get reset.
|
||||
source_dpis_.emplace(source_id, dpi);
|
||||
}
|
||||
|
||||
bool has_changed = false;
|
||||
if (!rect.equals(rect_) || !source_dpis_.at(source_id).equals(dpi)) {
|
||||
has_changed = true;
|
||||
rect_ = rect;
|
||||
source_dpis_.emplace(source_id, std::move(dpi));
|
||||
}
|
||||
|
||||
return has_changed;
|
||||
}
|
||||
|
||||
void DisplayConfigurationMonitor::Reset() {
|
||||
initialized_ = false;
|
||||
source_dpis_.clear();
|
||||
rect_ = {};
|
||||
}
|
||||
|
||||
DesktopVector DisplayConfigurationMonitor::GetDpiForSourceId(
|
||||
DesktopCapturer::SourceId source_id) {
|
||||
HMONITOR monitor = 0;
|
||||
if (source_id == kFullDesktopScreenId) {
|
||||
// Get a handle to the primary monitor when capturing the full desktop.
|
||||
monitor = MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY);
|
||||
} else if (!GetHmonitorFromDeviceIndex(source_id, &monitor)) {
|
||||
RTC_LOG(LS_WARNING) << "GetHmonitorFromDeviceIndex failed.";
|
||||
}
|
||||
return GetDpiForMonitor(monitor);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/containers/flat_map.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A passive monitor to detect the change of display configuration on a Windows
|
||||
// system.
|
||||
// TODO(zijiehe): Also check for pixel format changes.
|
||||
class DisplayConfigurationMonitor {
|
||||
public:
|
||||
// Checks whether the display configuration has changed since the last time
|
||||
// IsChanged() was called. |source_id| is used to observe changes for a
|
||||
// specific display or all displays if kFullDesktopScreenId is passed in.
|
||||
// Returns false if object was Reset() or if IsChanged() has not been called.
|
||||
bool IsChanged(DesktopCapturer::SourceId source_id);
|
||||
|
||||
// Resets to the initial state.
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
DesktopVector GetDpiForSourceId(DesktopCapturer::SourceId source_id);
|
||||
|
||||
// Represents the size of the desktop which includes all displays.
|
||||
DesktopRect rect_;
|
||||
|
||||
// Tracks the DPI for each display being captured. We need to track for each
|
||||
// display as each one can be configured to use a different DPI which will not
|
||||
// be reflected in calls to get the system DPI.
|
||||
flat_map<DesktopCapturer::SourceId, DesktopVector> source_dpis_;
|
||||
|
||||
// Indicates whether |rect_| and |source_dpis_| have been initialized. This is
|
||||
// used to prevent the monitor instance from signaling 'IsChanged()' before
|
||||
// the initial values have been set.
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,515 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/win/dxgi_frame.h"
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "system_wrappers/include/sleep.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr DWORD kInvalidSessionId = 0xFFFFFFFF;
|
||||
|
||||
DWORD GetCurrentSessionId() {
|
||||
DWORD session_id = kInvalidSessionId;
|
||||
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Failed to retrieve current session Id, current binary "
|
||||
"may not have required priviledge.";
|
||||
}
|
||||
return session_id;
|
||||
}
|
||||
|
||||
bool IsConsoleSession() {
|
||||
return WTSGetActiveConsoleSessionId() == GetCurrentSessionId();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::string DxgiDuplicatorController::ResultName(
|
||||
DxgiDuplicatorController::Result result) {
|
||||
switch (result) {
|
||||
case Result::SUCCEEDED:
|
||||
return "Succeeded";
|
||||
case Result::UNSUPPORTED_SESSION:
|
||||
return "Unsupported session";
|
||||
case Result::FRAME_PREPARE_FAILED:
|
||||
return "Frame preparation failed";
|
||||
case Result::INITIALIZATION_FAILED:
|
||||
return "Initialization failed";
|
||||
case Result::DUPLICATION_FAILED:
|
||||
return "Duplication failed";
|
||||
case Result::INVALID_MONITOR_ID:
|
||||
return "Invalid monitor id";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
rtc::scoped_refptr<DxgiDuplicatorController>
|
||||
DxgiDuplicatorController::Instance() {
|
||||
// The static instance won't be deleted to ensure it can be used by other
|
||||
// threads even during program exiting.
|
||||
static DxgiDuplicatorController* instance = new DxgiDuplicatorController();
|
||||
return rtc::scoped_refptr<DxgiDuplicatorController>(instance);
|
||||
}
|
||||
|
||||
// static
|
||||
bool DxgiDuplicatorController::IsCurrentSessionSupported() {
|
||||
DWORD current_session_id = GetCurrentSessionId();
|
||||
return current_session_id != kInvalidSessionId && current_session_id != 0;
|
||||
}
|
||||
|
||||
DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {}
|
||||
|
||||
void DxgiDuplicatorController::AddRef() {
|
||||
int refcount = (++refcount_);
|
||||
RTC_DCHECK(refcount > 0);
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::Release() {
|
||||
int refcount = (--refcount_);
|
||||
RTC_DCHECK(refcount >= 0);
|
||||
if (refcount == 0) {
|
||||
RTC_LOG(LS_WARNING) << "Count of references reaches zero, "
|
||||
"DxgiDuplicatorController will be unloaded.";
|
||||
Unload();
|
||||
}
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::IsSupported() {
|
||||
MutexLock lock(&mutex_);
|
||||
return Initialize();
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
|
||||
bool result = false;
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
result = Initialize();
|
||||
*info = d3d_info_;
|
||||
}
|
||||
if (!result) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo "
|
||||
"retrieved may not accurate or out of date.";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(
|
||||
DxgiFrame* frame) {
|
||||
return DoDuplicate(frame, -1);
|
||||
}
|
||||
|
||||
DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(
|
||||
DxgiFrame* frame,
|
||||
int monitor_id) {
|
||||
RTC_DCHECK_GE(monitor_id, 0);
|
||||
return DoDuplicate(frame, monitor_id);
|
||||
}
|
||||
|
||||
DesktopVector DxgiDuplicatorController::system_dpi() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (Initialize()) {
|
||||
return system_dpi_;
|
||||
}
|
||||
return DesktopVector();
|
||||
}
|
||||
|
||||
int DxgiDuplicatorController::ScreenCount() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (Initialize()) {
|
||||
return ScreenCountUnlocked();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::GetDeviceNames(
|
||||
std::vector<std::string>* output) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (Initialize()) {
|
||||
GetDeviceNamesUnlocked(output);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(
|
||||
DxgiFrame* frame,
|
||||
int monitor_id) {
|
||||
RTC_DCHECK(frame);
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// The dxgi components and APIs do not update the screen resolution without
|
||||
// a reinitialization. So we use the GetDC() function to retrieve the screen
|
||||
// resolution to decide whether dxgi components need to be reinitialized.
|
||||
// If the screen resolution changed, it's very likely the next Duplicate()
|
||||
// function call will fail because of a missing monitor or the frame size is
|
||||
// not enough to store the output. So we reinitialize dxgi components in-place
|
||||
// to avoid a capture failure.
|
||||
// But there is no guarantee GetDC() function returns the same resolution as
|
||||
// dxgi APIs, we still rely on dxgi components to return the output frame
|
||||
// size.
|
||||
// TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
|
||||
// IDXGIOutputDuplication::GetDesc() can detect the resolution change without
|
||||
// reinitialization.
|
||||
if (display_configuration_monitor_.IsChanged(frame->source_id_)) {
|
||||
Deinitialize();
|
||||
}
|
||||
|
||||
if (!Initialize()) {
|
||||
if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) {
|
||||
RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI "
|
||||
"components cannot be initialized.";
|
||||
return Result::UNSUPPORTED_SESSION;
|
||||
}
|
||||
|
||||
// Cannot initialize COM components now, display mode may be changing.
|
||||
return Result::INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) {
|
||||
return Result::FRAME_PREPARE_FAILED;
|
||||
}
|
||||
|
||||
frame->frame()->mutable_updated_region()->Clear();
|
||||
|
||||
if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) {
|
||||
succeeded_duplications_++;
|
||||
return Result::SUCCEEDED;
|
||||
}
|
||||
if (monitor_id >= ScreenCountUnlocked()) {
|
||||
// It's a user error to provide a `monitor_id` larger than screen count. We
|
||||
// do not need to deinitialize.
|
||||
return Result::INVALID_MONITOR_ID;
|
||||
}
|
||||
|
||||
// If the `monitor_id` is valid, but DoDuplicateUnlocked() failed, something
|
||||
// must be wrong from capturer APIs. We should Deinitialize().
|
||||
Deinitialize();
|
||||
return Result::DUPLICATION_FAILED;
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::Unload() {
|
||||
MutexLock lock(&mutex_);
|
||||
Deinitialize();
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::Unregister(const Context* const context) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (ContextExpired(context)) {
|
||||
// The Context has not been setup after a recent initialization, so it
|
||||
// should not been registered in duplicators.
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < duplicators_.size(); i++) {
|
||||
duplicators_[i].Unregister(&context->contexts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::Initialize() {
|
||||
if (!duplicators_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DoInitialize()) {
|
||||
return true;
|
||||
}
|
||||
Deinitialize();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::DoInitialize() {
|
||||
RTC_DCHECK(desktop_rect_.is_empty());
|
||||
RTC_DCHECK(duplicators_.empty());
|
||||
|
||||
d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
|
||||
d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
|
||||
|
||||
std::vector<D3dDevice> devices = D3dDevice::EnumDevices();
|
||||
if (devices.empty()) {
|
||||
RTC_LOG(LS_WARNING) << "No D3dDevice found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < devices.size(); i++) {
|
||||
D3D_FEATURE_LEVEL feature_level =
|
||||
devices[i].d3d_device()->GetFeatureLevel();
|
||||
if (d3d_info_.max_feature_level == 0 ||
|
||||
feature_level > d3d_info_.max_feature_level) {
|
||||
d3d_info_.max_feature_level = feature_level;
|
||||
}
|
||||
if (d3d_info_.min_feature_level == 0 ||
|
||||
feature_level < d3d_info_.min_feature_level) {
|
||||
d3d_info_.min_feature_level = feature_level;
|
||||
}
|
||||
|
||||
DxgiAdapterDuplicator duplicator(devices[i]);
|
||||
// There may be several video cards on the system, some of them may not
|
||||
// support IDXGOutputDuplication. But they should not impact others from
|
||||
// taking effect, so we should continually try other adapters. This usually
|
||||
// happens when a non-official virtual adapter is installed on the system.
|
||||
if (!duplicator.Initialize()) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on "
|
||||
"adapter "
|
||||
<< i;
|
||||
continue;
|
||||
}
|
||||
RTC_DCHECK(!duplicator.desktop_rect().is_empty());
|
||||
duplicators_.push_back(std::move(duplicator));
|
||||
|
||||
desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
|
||||
}
|
||||
TranslateRect();
|
||||
|
||||
HDC hdc = GetDC(nullptr);
|
||||
// Use old DPI value if failed.
|
||||
if (hdc) {
|
||||
system_dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX),
|
||||
GetDeviceCaps(hdc, LOGPIXELSY));
|
||||
ReleaseDC(nullptr, hdc);
|
||||
}
|
||||
|
||||
identity_++;
|
||||
|
||||
if (duplicators_.empty()) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Cannot initialize any DxgiAdapterDuplicator instance.";
|
||||
}
|
||||
|
||||
return !duplicators_.empty();
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::Deinitialize() {
|
||||
desktop_rect_ = DesktopRect();
|
||||
duplicators_.clear();
|
||||
display_configuration_monitor_.Reset();
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::ContextExpired(
|
||||
const Context* const context) const {
|
||||
RTC_DCHECK(context);
|
||||
return context->controller_id != identity_ ||
|
||||
context->contexts.size() != duplicators_.size();
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::Setup(Context* context) {
|
||||
if (ContextExpired(context)) {
|
||||
RTC_DCHECK(context);
|
||||
context->contexts.clear();
|
||||
context->contexts.resize(duplicators_.size());
|
||||
for (size_t i = 0; i < duplicators_.size(); i++) {
|
||||
duplicators_[i].Setup(&context->contexts[i]);
|
||||
}
|
||||
context->controller_id = identity_;
|
||||
}
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
|
||||
int monitor_id,
|
||||
SharedDesktopFrame* target) {
|
||||
Setup(context);
|
||||
|
||||
if (!EnsureFrameCaptured(context, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (monitor_id < 0) {
|
||||
// Capture entire screen.
|
||||
result = DoDuplicateAll(context, target);
|
||||
} else {
|
||||
result = DoDuplicateOne(context, monitor_id, target);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
target->set_dpi(system_dpi_);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::DoDuplicateAll(Context* context,
|
||||
SharedDesktopFrame* target) {
|
||||
for (size_t i = 0; i < duplicators_.size(); i++) {
|
||||
if (!duplicators_[i].Duplicate(&context->contexts[i], target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::DoDuplicateOne(Context* context,
|
||||
int monitor_id,
|
||||
SharedDesktopFrame* target) {
|
||||
RTC_DCHECK(monitor_id >= 0);
|
||||
for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size();
|
||||
i++) {
|
||||
if (monitor_id >= duplicators_[i].screen_count()) {
|
||||
monitor_id -= duplicators_[i].screen_count();
|
||||
} else {
|
||||
if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id,
|
||||
target)) {
|
||||
target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t DxgiDuplicatorController::GetNumFramesCaptured() const {
|
||||
int64_t min = INT64_MAX;
|
||||
for (const auto& duplicator : duplicators_) {
|
||||
min = std::min(min, duplicator.GetNumFramesCaptured());
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
DesktopSize DxgiDuplicatorController::desktop_size() const {
|
||||
return desktop_rect_.size();
|
||||
}
|
||||
|
||||
DesktopRect DxgiDuplicatorController::ScreenRect(int id) const {
|
||||
RTC_DCHECK(id >= 0);
|
||||
for (size_t i = 0; i < duplicators_.size(); i++) {
|
||||
if (id >= duplicators_[i].screen_count()) {
|
||||
id -= duplicators_[i].screen_count();
|
||||
} else {
|
||||
return duplicators_[i].ScreenRect(id);
|
||||
}
|
||||
}
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
int DxgiDuplicatorController::ScreenCountUnlocked() const {
|
||||
int result = 0;
|
||||
for (auto& duplicator : duplicators_) {
|
||||
result += duplicator.screen_count();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::GetDeviceNamesUnlocked(
|
||||
std::vector<std::string>* output) const {
|
||||
RTC_DCHECK(output);
|
||||
for (auto& duplicator : duplicators_) {
|
||||
for (int i = 0; i < duplicator.screen_count(); i++) {
|
||||
output->push_back(duplicator.GetDeviceName(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DesktopSize DxgiDuplicatorController::SelectedDesktopSize(
|
||||
int monitor_id) const {
|
||||
if (monitor_id < 0) {
|
||||
return desktop_size();
|
||||
}
|
||||
|
||||
return ScreenRect(monitor_id).size();
|
||||
}
|
||||
|
||||
bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
|
||||
SharedDesktopFrame* target) {
|
||||
// On a modern system, the FPS / monitor refresh rate is usually larger than
|
||||
// or equal to 60. So 17 milliseconds is enough to capture at least one frame.
|
||||
const int64_t ms_per_frame = 17;
|
||||
// Skip frames to ensure a full frame refresh has occurred and the DXGI
|
||||
// machinery is producing frames before this function returns.
|
||||
int64_t frames_to_skip = 1;
|
||||
// The total time out milliseconds for this function. If we cannot get enough
|
||||
// frames during this time interval, this function returns false, and cause
|
||||
// the DXGI components to be reinitialized. This usually should not happen
|
||||
// unless the system is switching display mode when this function is being
|
||||
// called. 500 milliseconds should be enough for ~30 frames.
|
||||
const int64_t timeout_ms = 500;
|
||||
|
||||
if (GetNumFramesCaptured() == 0 && !IsConsoleSession()) {
|
||||
// When capturing a console session, waiting for a single frame is
|
||||
// sufficient to ensure that DXGI output duplication is working. When the
|
||||
// session is not attached to the console, it has been observed that DXGI
|
||||
// may produce up to 4 frames (typically 1-2 though) before stopping. When
|
||||
// this condition occurs, no errors are returned from the output duplication
|
||||
// API, it simply appears that nothing is changing on the screen. Thus for
|
||||
// detached sessions, we need to capture a few extra frames before we can be
|
||||
// confident that output duplication was initialized properly.
|
||||
frames_to_skip = 5;
|
||||
}
|
||||
|
||||
if (GetNumFramesCaptured() >= frames_to_skip) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<SharedDesktopFrame> fallback_frame;
|
||||
SharedDesktopFrame* shared_frame = nullptr;
|
||||
if (target->size().width() >= desktop_size().width() &&
|
||||
target->size().height() >= desktop_size().height()) {
|
||||
// `target` is large enough to cover entire screen, we do not need to use
|
||||
// `fallback_frame`.
|
||||
shared_frame = target;
|
||||
} else {
|
||||
fallback_frame = SharedDesktopFrame::Wrap(
|
||||
std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(desktop_size())));
|
||||
shared_frame = fallback_frame.get();
|
||||
}
|
||||
|
||||
const int64_t start_ms = rtc::TimeMillis();
|
||||
while (GetNumFramesCaptured() < frames_to_skip) {
|
||||
if (!DoDuplicateAll(context, shared_frame)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calling DoDuplicateAll() may change the number of frames captured.
|
||||
if (GetNumFramesCaptured() >= frames_to_skip) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rtc::TimeMillis() - start_ms > timeout_ms) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip
|
||||
<< " frames "
|
||||
"within "
|
||||
<< timeout_ms << " milliseconds.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sleep `ms_per_frame` before attempting to capture the next frame to
|
||||
// ensure the video adapter has time to update the screen.
|
||||
webrtc::SleepMs(ms_per_frame);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DxgiDuplicatorController::TranslateRect() {
|
||||
const DesktopVector position =
|
||||
DesktopVector().subtract(desktop_rect_.top_left());
|
||||
desktop_rect_.Translate(position);
|
||||
for (auto& duplicator : duplicators_) {
|
||||
duplicator.TranslateRect(position);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
|
||||
|
||||
#include <d3dcommon.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "modules/desktop_capture/win/d3d_device.h"
|
||||
#include "modules/desktop_capture/win/display_configuration_monitor.h"
|
||||
#include "modules/desktop_capture/win/dxgi_adapter_duplicator.h"
|
||||
#include "modules/desktop_capture/win/dxgi_context.h"
|
||||
#include "modules/desktop_capture/win/dxgi_frame.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A controller for all the objects we need to call Windows DirectX capture APIs
|
||||
// It's a singleton because only one IDXGIOutputDuplication instance per monitor
|
||||
// is allowed per application.
|
||||
//
|
||||
// Consumers should create a DxgiDuplicatorController::Context and keep it
|
||||
// throughout their lifetime, and pass it when calling Duplicate(). Consumers
|
||||
// can also call IsSupported() to determine whether the system supports DXGI
|
||||
// duplicator or not. If a previous IsSupported() function call returns true,
|
||||
// but a later Duplicate() returns false, this usually means the display mode is
|
||||
// changing. Consumers should retry after a while. (Typically 50 milliseconds,
|
||||
// but according to hardware performance, this time may vary.)
|
||||
// The underlying DxgiOutputDuplicators may take an additional reference on the
|
||||
// frame passed in to the Duplicate methods so that they can guarantee delivery
|
||||
// of new frames when requested; since if there have been no updates to the
|
||||
// surface, they may be unable to capture a frame.
|
||||
class RTC_EXPORT DxgiDuplicatorController {
|
||||
public:
|
||||
using Context = DxgiFrameContext;
|
||||
|
||||
// A collection of D3d information we are interested in, which may impact
|
||||
// capturer performance or reliability.
|
||||
struct D3dInfo {
|
||||
// Each video adapter has its own D3D_FEATURE_LEVEL, so this structure
|
||||
// contains the minimum and maximium D3D_FEATURE_LEVELs current system
|
||||
// supports.
|
||||
// Both fields can be 0, which is the default value to indicate no valid
|
||||
// D3D_FEATURE_LEVEL has been retrieved from underlying OS APIs.
|
||||
D3D_FEATURE_LEVEL min_feature_level;
|
||||
D3D_FEATURE_LEVEL max_feature_level;
|
||||
|
||||
// TODO(zijiehe): Add more fields, such as manufacturer name, mode, driver
|
||||
// version.
|
||||
};
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered or
|
||||
// reordered and numeric values should never be reused. This enum corresponds
|
||||
// to WebRtcDirectXCapturerResult in tools/metrics/histograms/enums.xml.
|
||||
enum class Result {
|
||||
SUCCEEDED = 0,
|
||||
UNSUPPORTED_SESSION = 1,
|
||||
FRAME_PREPARE_FAILED = 2,
|
||||
INITIALIZATION_FAILED = 3,
|
||||
DUPLICATION_FAILED = 4,
|
||||
INVALID_MONITOR_ID = 5,
|
||||
MAX_VALUE = INVALID_MONITOR_ID
|
||||
};
|
||||
|
||||
// Converts `result` into user-friendly string representation. The return
|
||||
// value should not be used to identify error types.
|
||||
static std::string ResultName(Result result);
|
||||
|
||||
// Returns the singleton instance of DxgiDuplicatorController.
|
||||
static rtc::scoped_refptr<DxgiDuplicatorController> Instance();
|
||||
|
||||
// See ScreenCapturerWinDirectx::IsCurrentSessionSupported().
|
||||
static bool IsCurrentSessionSupported();
|
||||
|
||||
// All the following public functions implicitly call Initialize() function.
|
||||
|
||||
// Detects whether the system supports DXGI based capturer.
|
||||
bool IsSupported();
|
||||
|
||||
// Returns a copy of D3dInfo composed by last Initialize() function call. This
|
||||
// function always copies the latest information into `info`. But once the
|
||||
// function returns false, the information in `info` may not accurate.
|
||||
bool RetrieveD3dInfo(D3dInfo* info);
|
||||
|
||||
// Captures current screen and writes into `frame`. May retain a reference to
|
||||
// `frame`'s underlying |SharedDesktopFrame|.
|
||||
// TODO(zijiehe): Windows cannot guarantee the frames returned by each
|
||||
// IDXGIOutputDuplication are synchronized. But we are using a totally
|
||||
// different threading model than the way Windows suggested, it's hard to
|
||||
// synchronize them manually. We should find a way to do it.
|
||||
Result Duplicate(DxgiFrame* frame);
|
||||
|
||||
// Captures one monitor and writes into target. `monitor_id` must be >= 0. If
|
||||
// `monitor_id` is greater than the total screen count of all the Duplicators,
|
||||
// this function returns false. May retain a reference to `frame`'s underlying
|
||||
// |SharedDesktopFrame|.
|
||||
Result DuplicateMonitor(DxgiFrame* frame, int monitor_id);
|
||||
|
||||
// Returns dpi of current system. Returns an empty DesktopVector if system
|
||||
// does not support DXGI based capturer.
|
||||
DesktopVector system_dpi();
|
||||
|
||||
// Returns the count of screens on the system. These screens can be retrieved
|
||||
// by an integer in the range of [0, ScreenCount()). If system does not
|
||||
// support DXGI based capturer, this function returns 0.
|
||||
int ScreenCount();
|
||||
|
||||
// Returns the device names of all screens on the system in utf8 encoding.
|
||||
// These screens can be retrieved by an integer in the range of
|
||||
// [0, output->size()). If system does not support DXGI based capturer, this
|
||||
// function returns false.
|
||||
bool GetDeviceNames(std::vector<std::string>* output);
|
||||
|
||||
private:
|
||||
// DxgiFrameContext calls private Unregister(Context*) function in Reset().
|
||||
friend void DxgiFrameContext::Reset();
|
||||
|
||||
// scoped_refptr<DxgiDuplicatorController> accesses private AddRef() and
|
||||
// Release() functions.
|
||||
friend class webrtc::scoped_refptr<DxgiDuplicatorController>;
|
||||
|
||||
// A private constructor to ensure consumers to use
|
||||
// DxgiDuplicatorController::Instance().
|
||||
DxgiDuplicatorController();
|
||||
|
||||
// Not implemented: The singleton DxgiDuplicatorController instance should not
|
||||
// be deleted.
|
||||
~DxgiDuplicatorController();
|
||||
|
||||
// RefCountedInterface implementations.
|
||||
void AddRef();
|
||||
void Release();
|
||||
|
||||
// Does the real duplication work. Setting `monitor_id` < 0 to capture entire
|
||||
// screen. This function calls Initialize(). And if the duplication failed,
|
||||
// this function calls Deinitialize() to ensure the Dxgi components can be
|
||||
// reinitialized next time.
|
||||
Result DoDuplicate(DxgiFrame* frame, int monitor_id);
|
||||
|
||||
// Unload all the DXGI components and releases the resources. This function
|
||||
// wraps Deinitialize() with `mutex_`.
|
||||
void Unload();
|
||||
|
||||
// Unregisters Context from this instance and all DxgiAdapterDuplicator(s)
|
||||
// it owns.
|
||||
void Unregister(const Context* const context);
|
||||
|
||||
// All functions below should be called in `mutex_` locked scope and should be
|
||||
// after a successful Initialize().
|
||||
|
||||
// If current instance has not been initialized, executes DoInitialize()
|
||||
// function, and returns initialize result. Otherwise directly returns true.
|
||||
// This function may calls Deinitialize() if initialization failed.
|
||||
bool Initialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Does the real initialization work, this function should only be called in
|
||||
// Initialize().
|
||||
bool DoInitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Clears all COM components referred to by this instance. So next Duplicate()
|
||||
// call will eventually initialize this instance again.
|
||||
void Deinitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// A helper function to check whether a Context has been expired.
|
||||
bool ContextExpired(const Context* const context) const
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Updates Context if needed.
|
||||
void Setup(Context* context) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
bool DoDuplicateUnlocked(Context* context,
|
||||
int monitor_id,
|
||||
SharedDesktopFrame* target)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Captures all monitors.
|
||||
bool DoDuplicateAll(Context* context, SharedDesktopFrame* target)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Captures one monitor.
|
||||
bool DoDuplicateOne(Context* context,
|
||||
int monitor_id,
|
||||
SharedDesktopFrame* target)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// The minimum GetNumFramesCaptured() returned by `duplicators_`.
|
||||
int64_t GetNumFramesCaptured() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns a DesktopSize to cover entire `desktop_rect_`.
|
||||
DesktopSize desktop_size() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns the size of one screen. `id` should be >= 0. If system does not
|
||||
// support DXGI based capturer, or `id` is greater than the total screen count
|
||||
// of all the Duplicators, this function returns an empty DesktopRect.
|
||||
DesktopRect ScreenRect(int id) const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
int ScreenCountUnlocked() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void GetDeviceNamesUnlocked(std::vector<std::string>* output) const
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns the desktop size of the selected screen `monitor_id`. Setting
|
||||
// `monitor_id` < 0 to return the entire screen size.
|
||||
DesktopSize SelectedDesktopSize(int monitor_id) const
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Retries DoDuplicateAll() for several times until GetNumFramesCaptured() is
|
||||
// large enough. Returns false if DoDuplicateAll() returns false, or
|
||||
// GetNumFramesCaptured() has never reached the requirement.
|
||||
// According to http://crbug.com/682112, dxgi capturer returns a black frame
|
||||
// during first several capture attempts.
|
||||
bool EnsureFrameCaptured(Context* context, SharedDesktopFrame* target)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Moves `desktop_rect_` and all underlying `duplicators_`, putting top left
|
||||
// corner of the desktop at (0, 0). This is necessary because DXGI_OUTPUT_DESC
|
||||
// may return negative coordinates. Called from DoInitialize() after all
|
||||
// DxgiAdapterDuplicator and DxgiOutputDuplicator instances are initialized.
|
||||
void TranslateRect() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// The count of references which are now "living".
|
||||
std::atomic_int refcount_;
|
||||
|
||||
// This lock must be locked whenever accessing any of the following objects.
|
||||
Mutex mutex_;
|
||||
|
||||
// A self-incremented integer to compare with the one in Context. It ensures
|
||||
// a Context instance is always initialized after DxgiDuplicatorController.
|
||||
int identity_ RTC_GUARDED_BY(mutex_) = 0;
|
||||
DesktopRect desktop_rect_ RTC_GUARDED_BY(mutex_);
|
||||
DesktopVector system_dpi_ RTC_GUARDED_BY(mutex_);
|
||||
std::vector<DxgiAdapterDuplicator> duplicators_ RTC_GUARDED_BY(mutex_);
|
||||
D3dInfo d3d_info_ RTC_GUARDED_BY(mutex_);
|
||||
DisplayConfigurationMonitor display_configuration_monitor_
|
||||
RTC_GUARDED_BY(mutex_);
|
||||
// A number to indicate how many successful duplications have been performed.
|
||||
uint32_t succeeded_duplications_ RTC_GUARDED_BY(mutex_) = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/dxgi_output_duplicator.h"
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgiformat.h>
|
||||
#include <string.h>
|
||||
#include <unknwn.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/desktop_capture/win/desktop_capture_utils.h"
|
||||
#include "modules/desktop_capture/win/dxgi_texture_mapping.h"
|
||||
#include "modules/desktop_capture/win/dxgi_texture_staging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/string_utils.h"
|
||||
#include "rtc_base/win32.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace {
|
||||
|
||||
// Timeout for AcquireNextFrame() call.
|
||||
// DxgiDuplicatorController leverages external components to do the capture
|
||||
// scheduling. So here DxgiOutputDuplicator does not need to actively wait for a
|
||||
// new frame.
|
||||
const int kAcquireTimeoutMs = 0;
|
||||
|
||||
DesktopRect RECTToDesktopRect(const RECT& rect) {
|
||||
return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
|
||||
Rotation DxgiRotationToRotation(DXGI_MODE_ROTATION rotation) {
|
||||
switch (rotation) {
|
||||
case DXGI_MODE_ROTATION_IDENTITY:
|
||||
case DXGI_MODE_ROTATION_UNSPECIFIED:
|
||||
return Rotation::CLOCK_WISE_0;
|
||||
case DXGI_MODE_ROTATION_ROTATE90:
|
||||
return Rotation::CLOCK_WISE_90;
|
||||
case DXGI_MODE_ROTATION_ROTATE180:
|
||||
return Rotation::CLOCK_WISE_180;
|
||||
case DXGI_MODE_ROTATION_ROTATE270:
|
||||
return Rotation::CLOCK_WISE_270;
|
||||
}
|
||||
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return Rotation::CLOCK_WISE_0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device,
|
||||
const ComPtr<IDXGIOutput1>& output,
|
||||
const DXGI_OUTPUT_DESC& desc)
|
||||
: device_(device),
|
||||
output_(output),
|
||||
device_name_(rtc::ToUtf8(desc.DeviceName)),
|
||||
desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) {
|
||||
RTC_DCHECK(output_);
|
||||
RTC_DCHECK(!desktop_rect_.is_empty());
|
||||
RTC_DCHECK_GT(desktop_rect_.width(), 0);
|
||||
RTC_DCHECK_GT(desktop_rect_.height(), 0);
|
||||
}
|
||||
|
||||
DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) =
|
||||
default;
|
||||
|
||||
DxgiOutputDuplicator::~DxgiOutputDuplicator() {
|
||||
if (duplication_) {
|
||||
duplication_->ReleaseFrame();
|
||||
}
|
||||
texture_.reset();
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::Initialize() {
|
||||
if (DuplicateOutput()) {
|
||||
if (desc_.DesktopImageInSystemMemory) {
|
||||
texture_.reset(new DxgiTextureMapping(duplication_.Get()));
|
||||
} else {
|
||||
texture_.reset(new DxgiTextureStaging(device_));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
duplication_.Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::DuplicateOutput() {
|
||||
RTC_DCHECK(!duplication_);
|
||||
_com_error error =
|
||||
output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()),
|
||||
duplication_.GetAddressOf());
|
||||
if (error.Error() != S_OK || !duplication_) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1: "
|
||||
<< desktop_capture::utils::ComErrorToString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&desc_, 0, sizeof(desc_));
|
||||
duplication_->GetDesc(&desc_);
|
||||
|
||||
// DXGI_FORMAT_R16G16B16A16_FLOAT is returned for HDR monitor,
|
||||
// DXGI_FORMAT_B8G8R8A8_UNORM for others.
|
||||
if ((desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) &&
|
||||
(desc_.ModeDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT)) {
|
||||
RTC_LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8, 16 bit)"
|
||||
<< "which is required by downstream components"
|
||||
<< "format is " << desc_.ModeDesc.Format;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() ||
|
||||
static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "IDXGIDuplicateOutput does not return a same size as its "
|
||||
<< "IDXGIOutput1, size returned by IDXGIDuplicateOutput is "
|
||||
<< desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height
|
||||
<< ", size returned by IDXGIOutput1 is " << desktop_rect_.width()
|
||||
<< " x " << desktop_rect_.height();
|
||||
return false;
|
||||
}
|
||||
|
||||
rotation_ = DxgiRotationToRotation(desc_.Rotation);
|
||||
unrotated_size_ = RotateSize(desktop_size(), ReverseRotation(rotation_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::ReleaseFrame() {
|
||||
RTC_DCHECK(duplication_);
|
||||
_com_error error = duplication_->ReleaseFrame();
|
||||
if (error.Error() != S_OK) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication: "
|
||||
<< desktop_capture::utils::ComErrorToString(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::ContainsMouseCursor(
|
||||
const DXGI_OUTDUPL_FRAME_INFO& frame_info) {
|
||||
// The DXGI_OUTDUPL_POINTER_POSITION structure that describes the most recent
|
||||
// mouse position is only valid if the LastMouseUpdateTime member is a non-
|
||||
// zero value.
|
||||
if (frame_info.LastMouseUpdateTime.QuadPart == 0)
|
||||
return false;
|
||||
|
||||
// Ignore cases when the mouse shape has changed and not the position.
|
||||
const bool new_pointer_shape = (frame_info.PointerShapeBufferSize != 0);
|
||||
if (new_pointer_shape)
|
||||
return false;
|
||||
|
||||
// The mouse cursor has moved and we can now query if the mouse pointer is
|
||||
// drawn onto the desktop image or not to decide if we must draw the mouse
|
||||
// pointer shape onto the desktop image (always done by default currently).
|
||||
// Either the mouse pointer is already drawn onto the desktop image that
|
||||
// IDXGIOutputDuplication::AcquireNextFrame provides or the mouse pointer is
|
||||
// separate from the desktop image. If the mouse pointer is drawn onto the
|
||||
// desktop image, the pointer position data that is reported by
|
||||
// AcquireNextFrame indicates that a separate pointer isn’t visible, hence
|
||||
// `frame_info.PointerPosition.Visible` is false.
|
||||
const bool cursor_embedded_in_frame = !frame_info.PointerPosition.Visible;
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.Win.DirectXCursorEmbedded",
|
||||
cursor_embedded_in_frame);
|
||||
return cursor_embedded_in_frame;
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::Duplicate(Context* context,
|
||||
DesktopVector offset,
|
||||
SharedDesktopFrame* target) {
|
||||
RTC_DCHECK(duplication_);
|
||||
RTC_DCHECK(texture_);
|
||||
RTC_DCHECK(target);
|
||||
if (!DesktopRect::MakeSize(target->size())
|
||||
.ContainsRect(GetTranslatedDesktopRect(offset))) {
|
||||
// target size is not large enough to cover current output region.
|
||||
return false;
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
memset(&frame_info, 0, sizeof(frame_info));
|
||||
ComPtr<IDXGIResource> resource;
|
||||
_com_error error = duplication_->AcquireNextFrame(
|
||||
kAcquireTimeoutMs, &frame_info, resource.GetAddressOf());
|
||||
if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to capture frame: "
|
||||
<< desktop_capture::utils::ComErrorToString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool cursor_embedded_in_frame = ContainsMouseCursor(frame_info);
|
||||
|
||||
// We need to merge updated region with the one from context, but only spread
|
||||
// updated region from current frame. So keeps a copy of updated region from
|
||||
// context here. The `updated_region` always starts from (0, 0).
|
||||
DesktopRegion updated_region;
|
||||
updated_region.Swap(&context->updated_region);
|
||||
if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0 && resource) {
|
||||
DetectUpdatedRegion(frame_info, &context->updated_region);
|
||||
SpreadContextChange(context);
|
||||
if (!texture_->CopyFrom(frame_info, resource.Get())) {
|
||||
return false;
|
||||
}
|
||||
updated_region.AddRegion(context->updated_region);
|
||||
// TODO(zijiehe): Figure out why clearing context->updated_region() here
|
||||
// triggers screen flickering?
|
||||
|
||||
const DesktopFrame& source = texture_->AsDesktopFrame();
|
||||
if (rotation_ != Rotation::CLOCK_WISE_0) {
|
||||
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
// The `updated_region` returned by Windows is rotated, but the `source`
|
||||
// frame is not. So we need to rotate it reversely.
|
||||
const DesktopRect source_rect =
|
||||
RotateRect(it.rect(), desktop_size(), ReverseRotation(rotation_));
|
||||
RotateDesktopFrame(source, source_rect, rotation_, offset, target);
|
||||
}
|
||||
} else {
|
||||
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
// The DesktopRect in `target`, starts from offset.
|
||||
DesktopRect dest_rect = it.rect();
|
||||
dest_rect.Translate(offset);
|
||||
target->CopyPixelsFrom(source, it.rect().top_left(), dest_rect);
|
||||
}
|
||||
}
|
||||
last_frame_ = target->Share();
|
||||
last_frame_offset_ = offset;
|
||||
updated_region.Translate(offset.x(), offset.y());
|
||||
target->mutable_updated_region()->AddRegion(updated_region);
|
||||
target->set_may_contain_cursor(cursor_embedded_in_frame);
|
||||
num_frames_captured_++;
|
||||
return texture_->Release() && ReleaseFrame();
|
||||
}
|
||||
|
||||
if (last_frame_) {
|
||||
// No change since last frame or AcquireNextFrame() timed out, we will
|
||||
// export last frame to the target.
|
||||
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
// The DesktopRect in `source`, starts from last_frame_offset_.
|
||||
DesktopRect source_rect = it.rect();
|
||||
// The DesktopRect in `target`, starts from offset.
|
||||
DesktopRect target_rect = source_rect;
|
||||
source_rect.Translate(last_frame_offset_);
|
||||
target_rect.Translate(offset);
|
||||
target->CopyPixelsFrom(*last_frame_, source_rect.top_left(), target_rect);
|
||||
}
|
||||
updated_region.Translate(offset.x(), offset.y());
|
||||
target->mutable_updated_region()->AddRegion(updated_region);
|
||||
target->set_may_contain_cursor(cursor_embedded_in_frame);
|
||||
} else {
|
||||
// If we were at the very first frame, and capturing failed, the
|
||||
// context->updated_region should be kept unchanged for next attempt.
|
||||
context->updated_region.Swap(&updated_region);
|
||||
}
|
||||
// If AcquireNextFrame() failed with timeout error, we do not need to release
|
||||
// the frame.
|
||||
return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame();
|
||||
}
|
||||
|
||||
DesktopRect DxgiOutputDuplicator::GetTranslatedDesktopRect(
|
||||
DesktopVector offset) const {
|
||||
DesktopRect result(DesktopRect::MakeSize(desktop_size()));
|
||||
result.Translate(offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
DesktopRect DxgiOutputDuplicator::GetUntranslatedDesktopRect() const {
|
||||
return DesktopRect::MakeSize(desktop_size());
|
||||
}
|
||||
|
||||
void DxgiOutputDuplicator::DetectUpdatedRegion(
|
||||
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
|
||||
DesktopRegion* updated_region) {
|
||||
if (DoDetectUpdatedRegion(frame_info, updated_region)) {
|
||||
// Make sure even a region returned by Windows API is out of the scope of
|
||||
// desktop_rect_, we still won't export it to the target DesktopFrame.
|
||||
updated_region->IntersectWith(GetUntranslatedDesktopRect());
|
||||
} else {
|
||||
updated_region->SetRect(GetUntranslatedDesktopRect());
|
||||
}
|
||||
}
|
||||
|
||||
bool DxgiOutputDuplicator::DoDetectUpdatedRegion(
|
||||
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
|
||||
DesktopRegion* updated_region) {
|
||||
RTC_DCHECK(updated_region);
|
||||
updated_region->Clear();
|
||||
if (frame_info.TotalMetadataBufferSize == 0) {
|
||||
// This should not happen, since frame_info.AccumulatedFrames > 0.
|
||||
RTC_LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, "
|
||||
<< "but TotalMetadataBufferSize == 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (metadata_.size() < frame_info.TotalMetadataBufferSize) {
|
||||
metadata_.clear(); // Avoid data copy
|
||||
metadata_.resize(frame_info.TotalMetadataBufferSize);
|
||||
}
|
||||
|
||||
UINT buff_size = 0;
|
||||
DXGI_OUTDUPL_MOVE_RECT* move_rects =
|
||||
reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata_.data());
|
||||
size_t move_rects_count = 0;
|
||||
_com_error error = duplication_->GetFrameMoveRects(
|
||||
static_cast<UINT>(metadata_.size()), move_rects, &buff_size);
|
||||
if (error.Error() != S_OK) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to get move rectangles: "
|
||||
<< desktop_capture::utils::ComErrorToString(error);
|
||||
return false;
|
||||
}
|
||||
move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
|
||||
|
||||
RECT* dirty_rects = reinterpret_cast<RECT*>(metadata_.data() + buff_size);
|
||||
size_t dirty_rects_count = 0;
|
||||
error = duplication_->GetFrameDirtyRects(
|
||||
static_cast<UINT>(metadata_.size()) - buff_size, dirty_rects, &buff_size);
|
||||
if (error.Error() != S_OK) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to get dirty rectangles: "
|
||||
<< desktop_capture::utils::ComErrorToString(error);
|
||||
return false;
|
||||
}
|
||||
dirty_rects_count = buff_size / sizeof(RECT);
|
||||
|
||||
while (move_rects_count > 0) {
|
||||
// DirectX capturer API may randomly return unmoved move_rects, which should
|
||||
// be skipped to avoid unnecessary wasting of differing and encoding
|
||||
// resources.
|
||||
// By using testing application it2me_standalone_host_main, this check
|
||||
// reduces average capture time by 0.375% (4.07 -> 4.055), and average
|
||||
// encode time by 0.313% (8.042 -> 8.016) without other impacts.
|
||||
if (move_rects->SourcePoint.x != move_rects->DestinationRect.left ||
|
||||
move_rects->SourcePoint.y != move_rects->DestinationRect.top) {
|
||||
updated_region->AddRect(
|
||||
RotateRect(DesktopRect::MakeXYWH(move_rects->SourcePoint.x,
|
||||
move_rects->SourcePoint.y,
|
||||
move_rects->DestinationRect.right -
|
||||
move_rects->DestinationRect.left,
|
||||
move_rects->DestinationRect.bottom -
|
||||
move_rects->DestinationRect.top),
|
||||
unrotated_size_, rotation_));
|
||||
updated_region->AddRect(
|
||||
RotateRect(DesktopRect::MakeLTRB(move_rects->DestinationRect.left,
|
||||
move_rects->DestinationRect.top,
|
||||
move_rects->DestinationRect.right,
|
||||
move_rects->DestinationRect.bottom),
|
||||
unrotated_size_, rotation_));
|
||||
} else {
|
||||
RTC_LOG(LS_INFO) << "Unmoved move_rect detected, ["
|
||||
<< move_rects->DestinationRect.left << ", "
|
||||
<< move_rects->DestinationRect.top << "] - ["
|
||||
<< move_rects->DestinationRect.right << ", "
|
||||
<< move_rects->DestinationRect.bottom << "].";
|
||||
}
|
||||
move_rects++;
|
||||
move_rects_count--;
|
||||
}
|
||||
|
||||
while (dirty_rects_count > 0) {
|
||||
updated_region->AddRect(RotateRect(
|
||||
DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top,
|
||||
dirty_rects->right, dirty_rects->bottom),
|
||||
unrotated_size_, rotation_));
|
||||
dirty_rects++;
|
||||
dirty_rects_count--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DxgiOutputDuplicator::Setup(Context* context) {
|
||||
RTC_DCHECK(context->updated_region.is_empty());
|
||||
// Always copy entire monitor during the first Duplicate() function call.
|
||||
context->updated_region.AddRect(GetUntranslatedDesktopRect());
|
||||
RTC_DCHECK(std::find(contexts_.begin(), contexts_.end(), context) ==
|
||||
contexts_.end());
|
||||
contexts_.push_back(context);
|
||||
}
|
||||
|
||||
void DxgiOutputDuplicator::Unregister(const Context* const context) {
|
||||
auto it = std::find(contexts_.begin(), contexts_.end(), context);
|
||||
RTC_DCHECK(it != contexts_.end());
|
||||
contexts_.erase(it);
|
||||
}
|
||||
|
||||
void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) {
|
||||
for (Context* dest : contexts_) {
|
||||
RTC_DCHECK(dest);
|
||||
if (dest != source) {
|
||||
dest->updated_region.AddRegion(source->updated_region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DesktopSize DxgiOutputDuplicator::desktop_size() const {
|
||||
return desktop_rect_.size();
|
||||
}
|
||||
|
||||
int64_t DxgiOutputDuplicator::num_frames_captured() const {
|
||||
#if !defined(NDEBUG)
|
||||
RTC_DCHECK_EQ(!!last_frame_, num_frames_captured_ > 0);
|
||||
#endif
|
||||
return num_frames_captured_;
|
||||
}
|
||||
|
||||
void DxgiOutputDuplicator::TranslateRect(const DesktopVector& position) {
|
||||
desktop_rect_.Translate(position);
|
||||
RTC_DCHECK_GE(desktop_rect_.left(), 0);
|
||||
RTC_DCHECK_GE(desktop_rect_.top(), 0);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
|
||||
|
||||
#include <comdef.h>
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame_rotation.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "modules/desktop_capture/win/d3d_device.h"
|
||||
#include "modules/desktop_capture/win/dxgi_context.h"
|
||||
#include "modules/desktop_capture/win/dxgi_texture.h"
|
||||
#include "rtc_base/thread_annotations.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Duplicates the content on one IDXGIOutput, i.e. one monitor attached to one
|
||||
// video card. None of functions in this class is thread-safe.
|
||||
class DxgiOutputDuplicator {
|
||||
public:
|
||||
using Context = DxgiOutputContext;
|
||||
|
||||
// Creates an instance of DxgiOutputDuplicator from a D3dDevice and one of its
|
||||
// IDXGIOutput1. Caller must maintain the lifetime of device, to make sure it
|
||||
// outlives this instance. Only DxgiAdapterDuplicator can create an instance.
|
||||
DxgiOutputDuplicator(const D3dDevice& device,
|
||||
const Microsoft::WRL::ComPtr<IDXGIOutput1>& output,
|
||||
const DXGI_OUTPUT_DESC& desc);
|
||||
|
||||
// To allow this class to work with vector.
|
||||
DxgiOutputDuplicator(DxgiOutputDuplicator&& other);
|
||||
|
||||
// Destructs this instance. We need to make sure texture_ has been released
|
||||
// before duplication_.
|
||||
~DxgiOutputDuplicator();
|
||||
|
||||
// Initializes duplication_ object.
|
||||
bool Initialize();
|
||||
|
||||
// Copies the content of current IDXGIOutput to the `target`. To improve the
|
||||
// performance, this function copies only regions merged from
|
||||
// `context`->updated_region and DetectUpdatedRegion(). The `offset` decides
|
||||
// the offset in the `target` where the content should be copied to. i.e. this
|
||||
// function copies the content to the rectangle of (offset.x(), offset.y()) to
|
||||
// (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()).
|
||||
// Returns false in case of a failure.
|
||||
// May retain a reference to `target` so that a "captured" frame can be
|
||||
// returned in the event that a new frame is not ready to be captured yet.
|
||||
// (Or in other words, if the call to IDXGIOutputDuplication::AcquireNextFrame
|
||||
// indicates that there is not yet a new frame, this is usually because no
|
||||
// updates have occurred to the frame).
|
||||
bool Duplicate(Context* context,
|
||||
DesktopVector offset,
|
||||
SharedDesktopFrame* target);
|
||||
|
||||
// Returns the desktop rect covered by this DxgiOutputDuplicator.
|
||||
DesktopRect desktop_rect() const { return desktop_rect_; }
|
||||
|
||||
// Returns the device name from DXGI_OUTPUT_DESC in utf8 encoding.
|
||||
const std::string& device_name() const { return device_name_; }
|
||||
|
||||
void Setup(Context* context);
|
||||
|
||||
void Unregister(const Context* const context);
|
||||
|
||||
// How many frames have been captured by this DxigOutputDuplicator.
|
||||
int64_t num_frames_captured() const;
|
||||
|
||||
// Moves `desktop_rect_`. See DxgiDuplicatorController::TranslateRect().
|
||||
void TranslateRect(const DesktopVector& position);
|
||||
|
||||
private:
|
||||
// Calls DoDetectUpdatedRegion(). If it fails, this function sets the
|
||||
// `updated_region` as entire UntranslatedDesktopRect().
|
||||
void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
|
||||
DesktopRegion* updated_region);
|
||||
|
||||
// Returns untranslated updated region, which are directly returned by Windows
|
||||
// APIs. Returns false in case of a failure.
|
||||
bool DoDetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
|
||||
DesktopRegion* updated_region);
|
||||
|
||||
// Returns true if the mouse cursor is embedded in the captured frame and
|
||||
// false if not. Also logs the same boolean as
|
||||
// WebRTC.DesktopCapture.Win.DirectXCursorEmbedded UMA.
|
||||
bool ContainsMouseCursor(const DXGI_OUTDUPL_FRAME_INFO& frame_info);
|
||||
|
||||
bool ReleaseFrame();
|
||||
|
||||
// Initializes duplication_ instance. Expects duplication_ is in empty status.
|
||||
// Returns false if system does not support IDXGIOutputDuplication.
|
||||
bool DuplicateOutput();
|
||||
|
||||
// Returns a DesktopRect with the same size of desktop_size(), but translated
|
||||
// by offset.
|
||||
DesktopRect GetTranslatedDesktopRect(DesktopVector offset) const;
|
||||
|
||||
// Returns a DesktopRect with the same size of desktop_size(), but starts from
|
||||
// (0, 0).
|
||||
DesktopRect GetUntranslatedDesktopRect() const;
|
||||
|
||||
// Spreads changes from `context` to other registered Context(s) in
|
||||
// contexts_.
|
||||
void SpreadContextChange(const Context* const context);
|
||||
|
||||
// Returns the size of desktop rectangle current instance representing.
|
||||
DesktopSize desktop_size() const;
|
||||
|
||||
const D3dDevice device_;
|
||||
const Microsoft::WRL::ComPtr<IDXGIOutput1> output_;
|
||||
const std::string device_name_;
|
||||
DesktopRect desktop_rect_;
|
||||
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
|
||||
DXGI_OUTDUPL_DESC desc_;
|
||||
std::vector<uint8_t> metadata_;
|
||||
std::unique_ptr<DxgiTexture> texture_;
|
||||
Rotation rotation_;
|
||||
DesktopSize unrotated_size_;
|
||||
|
||||
// After each AcquireNextFrame() function call, updated_region_(s) of all
|
||||
// active Context(s) need to be updated. Since they have missed the
|
||||
// change this time. And during next Duplicate() function call, their
|
||||
// updated_region_ will be merged and copied.
|
||||
std::vector<Context*> contexts_;
|
||||
|
||||
// The last full frame of this output and its offset. If on AcquireNextFrame()
|
||||
// failed because of timeout, i.e. no update, we can copy content from
|
||||
// `last_frame_`.
|
||||
std::unique_ptr<SharedDesktopFrame> last_frame_;
|
||||
DesktopVector last_frame_offset_;
|
||||
|
||||
int64_t num_frames_captured_ = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/full_screen_application_handler.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::unique_ptr<FullScreenApplicationHandler>
|
||||
CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId sourceId);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
|
||||
#include <shellscalingapi.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/string_utils.h"
|
||||
#include "rtc_base/win32.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
bool HasActiveDisplay() {
|
||||
DesktopCapturer::SourceList screens;
|
||||
|
||||
return GetScreenList(&screens) && !screens.empty();
|
||||
}
|
||||
|
||||
bool GetScreenList(DesktopCapturer::SourceList* screens,
|
||||
std::vector<std::string>* device_names /* = nullptr */) {
|
||||
RTC_DCHECK(screens->empty());
|
||||
RTC_DCHECK(!device_names || device_names->empty());
|
||||
|
||||
BOOL enum_result = TRUE;
|
||||
for (int device_index = 0;; ++device_index) {
|
||||
DISPLAY_DEVICEW device;
|
||||
device.cb = sizeof(device);
|
||||
enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0);
|
||||
|
||||
// `enum_result` is 0 if we have enumerated all devices.
|
||||
if (!enum_result) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We only care about active displays.
|
||||
if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
screens->push_back({device_index, std::string()});
|
||||
if (device_names) {
|
||||
device_names->push_back(rtc::ToUtf8(device.DeviceName));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index,
|
||||
HMONITOR* hmonitor) {
|
||||
// A device index of `kFullDesktopScreenId` or -1 represents all screens, an
|
||||
// HMONITOR of 0 indicates the same.
|
||||
if (device_index == kFullDesktopScreenId) {
|
||||
*hmonitor = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring device_key;
|
||||
if (!IsScreenValid(device_index, &device_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DesktopRect screen_rect = GetScreenRect(device_index, device_key);
|
||||
if (screen_rect.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT rect = {screen_rect.left(), screen_rect.top(), screen_rect.right(),
|
||||
screen_rect.bottom()};
|
||||
|
||||
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
|
||||
if (monitor == NULL) {
|
||||
RTC_LOG(LS_WARNING) << "No HMONITOR found for supplied device index.";
|
||||
return false;
|
||||
}
|
||||
|
||||
*hmonitor = monitor;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsMonitorValid(const HMONITOR monitor) {
|
||||
// An HMONITOR of 0 refers to a virtual monitor that spans all physical
|
||||
// monitors.
|
||||
if (monitor == 0) {
|
||||
// There is a bug in a Windows OS API that causes a crash when capturing if
|
||||
// there are no active displays. We must ensure there is an active display
|
||||
// before returning true.
|
||||
if (!HasActiveDisplay())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MONITORINFO monitor_info;
|
||||
monitor_info.cbSize = sizeof(MONITORINFO);
|
||||
return GetMonitorInfoA(monitor, &monitor_info);
|
||||
}
|
||||
|
||||
DesktopRect GetMonitorRect(const HMONITOR monitor) {
|
||||
MONITORINFO monitor_info;
|
||||
monitor_info.cbSize = sizeof(MONITORINFO);
|
||||
if (!GetMonitorInfoA(monitor, &monitor_info)) {
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
return DesktopRect::MakeLTRB(
|
||||
monitor_info.rcMonitor.left, monitor_info.rcMonitor.top,
|
||||
monitor_info.rcMonitor.right, monitor_info.rcMonitor.bottom);
|
||||
}
|
||||
|
||||
bool IsScreenValid(const DesktopCapturer::SourceId screen,
|
||||
std::wstring* device_key) {
|
||||
if (screen == kFullDesktopScreenId) {
|
||||
*device_key = L"";
|
||||
return true;
|
||||
}
|
||||
|
||||
DISPLAY_DEVICEW device;
|
||||
device.cb = sizeof(device);
|
||||
BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0);
|
||||
if (enum_result) {
|
||||
*device_key = device.DeviceKey;
|
||||
}
|
||||
|
||||
return !!enum_result;
|
||||
}
|
||||
|
||||
DesktopRect GetFullscreenRect() {
|
||||
return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_YVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_CYVIRTUALSCREEN));
|
||||
}
|
||||
|
||||
DesktopVector GetDpiForMonitor(HMONITOR monitor) {
|
||||
UINT dpi_x, dpi_y;
|
||||
// MDT_EFFECTIVE_DPI includes the scale factor as well as the system DPI.
|
||||
HRESULT hr = ::GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y);
|
||||
if (SUCCEEDED(hr)) {
|
||||
return {static_cast<INT>(dpi_x), static_cast<INT>(dpi_y)};
|
||||
}
|
||||
RTC_LOG_GLE_EX(LS_WARNING, hr) << "GetDpiForMonitor() failed";
|
||||
|
||||
// If we can't get the per-monitor DPI, then return the system DPI.
|
||||
HDC hdc = GetDC(nullptr);
|
||||
if (hdc) {
|
||||
DesktopVector dpi{GetDeviceCaps(hdc, LOGPIXELSX),
|
||||
GetDeviceCaps(hdc, LOGPIXELSY)};
|
||||
ReleaseDC(nullptr, hdc);
|
||||
return dpi;
|
||||
}
|
||||
|
||||
// If everything fails, then return the default DPI for Windows.
|
||||
return {96, 96};
|
||||
}
|
||||
|
||||
DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
|
||||
const std::wstring& device_key) {
|
||||
if (screen == kFullDesktopScreenId) {
|
||||
return GetFullscreenRect();
|
||||
}
|
||||
|
||||
DISPLAY_DEVICEW device;
|
||||
device.cb = sizeof(device);
|
||||
BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0);
|
||||
if (!result) {
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
// Verifies the device index still maps to the same display device, to make
|
||||
// sure we are capturing the same device when devices are added or removed.
|
||||
// DeviceKey is documented as reserved, but it actually contains the registry
|
||||
// key for the device and is unique for each monitor, while DeviceID is not.
|
||||
if (device_key != device.DeviceKey) {
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
DEVMODEW device_mode;
|
||||
device_mode.dmSize = sizeof(device_mode);
|
||||
device_mode.dmDriverExtra = 0;
|
||||
result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS,
|
||||
&device_mode, 0);
|
||||
if (!result) {
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
return DesktopRect::MakeXYWH(
|
||||
device_mode.dmPosition.x, device_mode.dmPosition.y,
|
||||
device_mode.dmPelsWidth, device_mode.dmPelsHeight);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
|
||||
|
||||
#if defined(WEBRTC_WIN)
|
||||
// Forward declare HMONITOR in a windows.h compatible way so that we can avoid
|
||||
// including windows.h.
|
||||
#define WEBRTC_DECLARE_HANDLE(name) \
|
||||
struct name##__; \
|
||||
typedef struct name##__* name
|
||||
WEBRTC_DECLARE_HANDLE(HMONITOR);
|
||||
#undef WEBRTC_DECLARE_HANDLE
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Returns true if the system has at least one active display.
|
||||
bool HasActiveDisplay();
|
||||
|
||||
// Output the list of active screens into `screens`. Returns true if succeeded,
|
||||
// or false if it fails to enumerate the display devices. If the `device_names`
|
||||
// is provided, it will be filled with the DISPLAY_DEVICE.DeviceName in UTF-8
|
||||
// encoding. If this function returns true, consumers can always assume that
|
||||
// `screens`[i] and `device_names`[i] indicate the same monitor on the system.
|
||||
bool GetScreenList(DesktopCapturer::SourceList* screens,
|
||||
std::vector<std::string>* device_names = nullptr);
|
||||
|
||||
// Converts a device index (which are returned by `GetScreenList`) into an
|
||||
// HMONITOR.
|
||||
bool GetHmonitorFromDeviceIndex(DesktopCapturer::SourceId device_index,
|
||||
HMONITOR* hmonitor);
|
||||
|
||||
// Returns true if `monitor` represents a valid display
|
||||
// monitor. Consumers should recheck the validity of HMONITORs before use if a
|
||||
// WM_DISPLAYCHANGE message has been received.
|
||||
bool IsMonitorValid(HMONITOR monitor);
|
||||
|
||||
// Returns the rect of the monitor identified by `monitor`, relative to the
|
||||
// primary display's top-left. On failure, returns an empty rect.
|
||||
DesktopRect GetMonitorRect(HMONITOR monitor);
|
||||
|
||||
// Returns the DPI for the specified monitor. On failure, returns the system DPI
|
||||
// or the Windows default DPI (96x96) if the system DPI can't be retrieved.
|
||||
DesktopVector GetDpiForMonitor(HMONITOR monitor);
|
||||
|
||||
// Returns true if `screen` is a valid screen. The screen device key is
|
||||
// returned through `device_key` if the screen is valid. The device key can be
|
||||
// used in GetScreenRect to verify the screen matches the previously obtained
|
||||
// id.
|
||||
bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key);
|
||||
|
||||
// Get the rect of the entire system in system coordinate system. I.e. the
|
||||
// primary monitor always starts from (0, 0).
|
||||
DesktopRect GetFullscreenRect();
|
||||
|
||||
// Get the rect of the screen identified by `screen`, relative to the primary
|
||||
// display's top-left. If the screen device key does not match `device_key`, or
|
||||
// the screen does not exist, or any error happens, an empty rect is returned.
|
||||
RTC_EXPORT DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
|
||||
const std::wstring& device_key);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capturer_win_directx.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
// static
|
||||
bool ScreenCapturerWinDirectx::IsSupported() {
|
||||
// Forwards IsSupported() function call to DxgiDuplicatorController.
|
||||
return DxgiDuplicatorController::Instance()->IsSupported();
|
||||
}
|
||||
|
||||
// static
|
||||
bool ScreenCapturerWinDirectx::RetrieveD3dInfo(D3dInfo* info) {
|
||||
// Forwards SupportedFeatureLevels() function call to
|
||||
// DxgiDuplicatorController.
|
||||
return DxgiDuplicatorController::Instance()->RetrieveD3dInfo(info);
|
||||
}
|
||||
|
||||
// static
|
||||
bool ScreenCapturerWinDirectx::IsCurrentSessionSupported() {
|
||||
return DxgiDuplicatorController::IsCurrentSessionSupported();
|
||||
}
|
||||
|
||||
// static
|
||||
bool ScreenCapturerWinDirectx::GetScreenListFromDeviceNames(
|
||||
const std::vector<std::string>& device_names,
|
||||
DesktopCapturer::SourceList* screens) {
|
||||
RTC_DCHECK(screens->empty());
|
||||
|
||||
DesktopCapturer::SourceList gdi_screens;
|
||||
std::vector<std::string> gdi_names;
|
||||
if (!GetScreenList(&gdi_screens, &gdi_names)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RTC_DCHECK_EQ(gdi_screens.size(), gdi_names.size());
|
||||
|
||||
ScreenId max_screen_id = -1;
|
||||
for (const DesktopCapturer::Source& screen : gdi_screens) {
|
||||
max_screen_id = std::max(max_screen_id, screen.id);
|
||||
}
|
||||
|
||||
for (const auto& device_name : device_names) {
|
||||
const auto it = std::find(gdi_names.begin(), gdi_names.end(), device_name);
|
||||
if (it == gdi_names.end()) {
|
||||
// devices_names[i] has not been found in gdi_names, so use max_screen_id.
|
||||
max_screen_id++;
|
||||
screens->push_back({max_screen_id});
|
||||
} else {
|
||||
screens->push_back({gdi_screens[it - gdi_names.begin()]});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
int ScreenCapturerWinDirectx::GetIndexFromScreenId(
|
||||
ScreenId id,
|
||||
const std::vector<std::string>& device_names) {
|
||||
DesktopCapturer::SourceList screens;
|
||||
if (!GetScreenListFromDeviceNames(device_names, &screens)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RTC_DCHECK_EQ(device_names.size(), screens.size());
|
||||
|
||||
for (size_t i = 0; i < screens.size(); i++) {
|
||||
if (screens[i].id == id) {
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ScreenCapturerWinDirectx::ScreenCapturerWinDirectx()
|
||||
: controller_(DxgiDuplicatorController::Instance()) {}
|
||||
|
||||
ScreenCapturerWinDirectx::ScreenCapturerWinDirectx(
|
||||
const DesktopCaptureOptions& options)
|
||||
: ScreenCapturerWinDirectx() {
|
||||
options_ = options;
|
||||
}
|
||||
|
||||
ScreenCapturerWinDirectx::~ScreenCapturerWinDirectx() = default;
|
||||
|
||||
void ScreenCapturerWinDirectx::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinDirectx);
|
||||
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
void ScreenCapturerWinDirectx::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
shared_memory_factory_ = std::move(shared_memory_factory);
|
||||
}
|
||||
|
||||
void ScreenCapturerWinDirectx::CaptureFrame() {
|
||||
RTC_DCHECK(callback_);
|
||||
TRACE_EVENT0("webrtc", "ScreenCapturerWinDirectx::CaptureFrame");
|
||||
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
|
||||
// Note that the [] operator will create the ScreenCaptureFrameQueue if it
|
||||
// doesn't exist, so this is safe.
|
||||
ScreenCaptureFrameQueue<DxgiFrame>& frames =
|
||||
frame_queue_map_[current_screen_id_];
|
||||
|
||||
frames.MoveToNextFrame();
|
||||
|
||||
if (!frames.current_frame()) {
|
||||
frames.ReplaceCurrentFrame(
|
||||
std::make_unique<DxgiFrame>(shared_memory_factory_.get()));
|
||||
}
|
||||
|
||||
DxgiDuplicatorController::Result result;
|
||||
if (current_screen_id_ == kFullDesktopScreenId) {
|
||||
result = controller_->Duplicate(frames.current_frame());
|
||||
} else {
|
||||
result = controller_->DuplicateMonitor(frames.current_frame(),
|
||||
current_screen_id_);
|
||||
}
|
||||
|
||||
using DuplicateResult = DxgiDuplicatorController::Result;
|
||||
if (result != DuplicateResult::SUCCEEDED) {
|
||||
RTC_LOG(LS_ERROR) << "DxgiDuplicatorController failed to capture desktop, "
|
||||
"error code "
|
||||
<< DxgiDuplicatorController::ResultName(result);
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.DesktopCapture.Win.DirectXCapturerResult",
|
||||
static_cast<int>(result),
|
||||
static_cast<int>(DxgiDuplicatorController::Result::MAX_VALUE));
|
||||
switch (result) {
|
||||
case DuplicateResult::UNSUPPORTED_SESSION: {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Current binary is running on a session not supported "
|
||||
"by DirectX screen capturer.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
break;
|
||||
}
|
||||
case DuplicateResult::FRAME_PREPARE_FAILED: {
|
||||
RTC_LOG(LS_ERROR) << "Failed to allocate a new DesktopFrame.";
|
||||
// This usually means we do not have enough memory or SharedMemoryFactory
|
||||
// cannot work correctly.
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
break;
|
||||
}
|
||||
case DuplicateResult::INVALID_MONITOR_ID: {
|
||||
RTC_LOG(LS_ERROR) << "Invalid monitor id " << current_screen_id_;
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
break;
|
||||
}
|
||||
case DuplicateResult::INITIALIZATION_FAILED:
|
||||
case DuplicateResult::DUPLICATION_FAILED: {
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
break;
|
||||
}
|
||||
case DuplicateResult::SUCCEEDED: {
|
||||
std::unique_ptr<DesktopFrame> frame =
|
||||
frames.current_frame()->frame()->Share();
|
||||
|
||||
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
RTC_HISTOGRAM_COUNTS_1000(
|
||||
"WebRTC.DesktopCapture.Win.DirectXCapturerFrameTime",
|
||||
capture_time_ms);
|
||||
frame->set_capture_time_ms(capture_time_ms);
|
||||
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinDirectx);
|
||||
// The DXGI Output Duplicator supports embedding the cursor but it is
|
||||
// only supported on very few display adapters. This switch allows us
|
||||
// to exclude an integrated cursor for all captured frames.
|
||||
if (!options_.prefer_cursor_embedded()) {
|
||||
frame->set_may_contain_cursor(false);
|
||||
}
|
||||
|
||||
// TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
|
||||
// the frame, see WindowCapturerMac::CaptureFrame.
|
||||
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinDirectx::GetSourceList(SourceList* sources) {
|
||||
std::vector<std::string> device_names;
|
||||
if (!controller_->GetDeviceNames(&device_names)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetScreenListFromDeviceNames(device_names, sources);
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinDirectx::SelectSource(SourceId id) {
|
||||
if (id == kFullDesktopScreenId) {
|
||||
current_screen_id_ = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> device_names;
|
||||
if (!controller_->GetDeviceNames(&device_names)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index;
|
||||
index = GetIndexFromScreenId(id, device_names);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_screen_id_ = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|
||||
|
||||
#include <d3dcommon.h>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
||||
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
|
||||
#include "modules/desktop_capture/win/dxgi_frame.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// ScreenCapturerWinDirectx captures 32bit RGBA using DirectX.
|
||||
class RTC_EXPORT ScreenCapturerWinDirectx : public DesktopCapturer {
|
||||
public:
|
||||
using D3dInfo = DxgiDuplicatorController::D3dInfo;
|
||||
|
||||
// Whether the system supports DirectX based capturing.
|
||||
static bool IsSupported();
|
||||
|
||||
// Returns a most recent D3dInfo composed by
|
||||
// DxgiDuplicatorController::Initialize() function. This function implicitly
|
||||
// calls DxgiDuplicatorController::Initialize() if it has not been
|
||||
// initialized. This function returns false and output parameter is kept
|
||||
// unchanged if DxgiDuplicatorController::Initialize() failed.
|
||||
// The D3dInfo may change based on hardware configuration even without
|
||||
// restarting the hardware and software. Refer to https://goo.gl/OOCppq. So
|
||||
// consumers should not cache the result returned by this function.
|
||||
static bool RetrieveD3dInfo(D3dInfo* info);
|
||||
|
||||
// Whether current process is running in a Windows session which is supported
|
||||
// by ScreenCapturerWinDirectx.
|
||||
// Usually using ScreenCapturerWinDirectx in unsupported sessions will fail.
|
||||
// But this behavior may vary on different Windows version. So consumers can
|
||||
// always try IsSupported() function.
|
||||
static bool IsCurrentSessionSupported();
|
||||
|
||||
// Maps `device_names` with the result from GetScreenList() and creates a new
|
||||
// SourceList to include only the ones in `device_names`. If this function
|
||||
// returns true, consumers can always assume `device_names`.size() equals to
|
||||
// `screens`->size(), meanwhile `device_names`[i] and `screens`[i] indicate
|
||||
// the same monitor on the system.
|
||||
// Public for test only.
|
||||
static bool GetScreenListFromDeviceNames(
|
||||
const std::vector<std::string>& device_names,
|
||||
DesktopCapturer::SourceList* screens);
|
||||
|
||||
// Maps `id` with the result from GetScreenListFromDeviceNames() and returns
|
||||
// the index of the entity in `device_names`. This function returns -1 if `id`
|
||||
// cannot be found.
|
||||
// Public for test only.
|
||||
static int GetIndexFromScreenId(ScreenId id,
|
||||
const std::vector<std::string>& device_names);
|
||||
|
||||
// This constructor is deprecated. Please don't use it in new implementations.
|
||||
ScreenCapturerWinDirectx();
|
||||
explicit ScreenCapturerWinDirectx(const DesktopCaptureOptions& options);
|
||||
|
||||
~ScreenCapturerWinDirectx() override;
|
||||
|
||||
ScreenCapturerWinDirectx(const ScreenCapturerWinDirectx&) = delete;
|
||||
ScreenCapturerWinDirectx& operator=(const ScreenCapturerWinDirectx&) = delete;
|
||||
|
||||
// DesktopCapturer implementation.
|
||||
void Start(Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
|
||||
private:
|
||||
const rtc::scoped_refptr<DxgiDuplicatorController> controller_;
|
||||
DesktopCaptureOptions options_;
|
||||
|
||||
// The underlying DxgiDuplicators may retain a reference to the frames that
|
||||
// we ask them to duplicate so that they can continue returning valid frames
|
||||
// in the event that the target has not been updated. Thus, we need to ensure
|
||||
// that we have a separate frame queue for each source id, so that these held
|
||||
// frames don't get overwritten with the data from another Duplicator/monitor.
|
||||
std::unordered_map<SourceId, ScreenCaptureFrameQueue<DxgiFrame>>
|
||||
frame_queue_map_;
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
|
||||
Callback* callback_ = nullptr;
|
||||
SourceId current_screen_id_ = kFullDesktopScreenId;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capturer_win_gdi.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_frame_win.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/win/cursor.h"
|
||||
#include "modules/desktop_capture/win/desktop.h"
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Constants from dwmapi.h.
|
||||
const UINT DWM_EC_DISABLECOMPOSITION = 0;
|
||||
const UINT DWM_EC_ENABLECOMPOSITION = 1;
|
||||
|
||||
const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
|
||||
|
||||
} // namespace
|
||||
|
||||
ScreenCapturerWinGdi::ScreenCapturerWinGdi(
|
||||
const DesktopCaptureOptions& options) {
|
||||
if (options.disable_effects()) {
|
||||
// Load dwmapi.dll dynamically since it is not available on XP.
|
||||
if (!dwmapi_library_)
|
||||
dwmapi_library_ = LoadLibraryW(kDwmapiLibraryName);
|
||||
|
||||
if (dwmapi_library_) {
|
||||
composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
|
||||
GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
|
||||
if (desktop_dc_)
|
||||
ReleaseDC(NULL, desktop_dc_);
|
||||
if (memory_dc_)
|
||||
DeleteDC(memory_dc_);
|
||||
|
||||
// Restore Aero.
|
||||
if (composition_func_)
|
||||
(*composition_func_)(DWM_EC_ENABLECOMPOSITION);
|
||||
|
||||
if (dwmapi_library_)
|
||||
FreeLibrary(dwmapi_library_);
|
||||
}
|
||||
|
||||
void ScreenCapturerWinGdi::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
shared_memory_factory_ = std::move(shared_memory_factory);
|
||||
}
|
||||
|
||||
void ScreenCapturerWinGdi::CaptureFrame() {
|
||||
TRACE_EVENT0("webrtc", "ScreenCapturerWinGdi::CaptureFrame");
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
|
||||
queue_.MoveToNextFrame();
|
||||
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
|
||||
RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
|
||||
}
|
||||
|
||||
// Make sure the GDI capture resources are up-to-date.
|
||||
PrepareCaptureResources();
|
||||
|
||||
if (!CaptureImage()) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to capture screen by GDI.";
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit the current frame.
|
||||
std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
|
||||
frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
|
||||
GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
|
||||
frame->mutable_updated_region()->SetRect(
|
||||
DesktopRect::MakeSize(frame->size()));
|
||||
|
||||
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
RTC_HISTOGRAM_COUNTS_1000(
|
||||
"WebRTC.DesktopCapture.Win.ScreenGdiCapturerFrameTime", capture_time_ms);
|
||||
frame->set_capture_time_ms(capture_time_ms);
|
||||
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinGdi);
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinGdi::GetSourceList(SourceList* sources) {
|
||||
return webrtc::GetScreenList(sources);
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinGdi::SelectSource(SourceId id) {
|
||||
bool valid = IsScreenValid(id, ¤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(kFullDesktopScreenId)) {
|
||||
if (desktop_dc_) {
|
||||
ReleaseDC(NULL, desktop_dc_);
|
||||
desktop_dc_ = nullptr;
|
||||
}
|
||||
if (memory_dc_) {
|
||||
DeleteDC(memory_dc_);
|
||||
memory_dc_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!desktop_dc_) {
|
||||
RTC_DCHECK(!memory_dc_);
|
||||
|
||||
// Create GDI device contexts to capture from the desktop into memory.
|
||||
desktop_dc_ = GetDC(nullptr);
|
||||
RTC_CHECK(desktop_dc_);
|
||||
memory_dc_ = CreateCompatibleDC(desktop_dc_);
|
||||
RTC_CHECK(memory_dc_);
|
||||
|
||||
// Make sure the frame buffers will be reallocated.
|
||||
queue_.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinGdi::CaptureImage() {
|
||||
DesktopRect screen_rect =
|
||||
GetScreenRect(current_screen_id_, current_device_key_);
|
||||
if (screen_rect.is_empty()) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to get screen rect.";
|
||||
return false;
|
||||
}
|
||||
|
||||
DesktopSize size = screen_rect.size();
|
||||
// If the current buffer is from an older generation then allocate a new one.
|
||||
// Note that we can't reallocate other buffers at this point, since the caller
|
||||
// may still be reading from them.
|
||||
if (!queue_.current_frame() ||
|
||||
!queue_.current_frame()->size().equals(screen_rect.size())) {
|
||||
RTC_DCHECK(desktop_dc_);
|
||||
RTC_DCHECK(memory_dc_);
|
||||
|
||||
std::unique_ptr<DesktopFrame> buffer = DesktopFrameWin::Create(
|
||||
size, shared_memory_factory_.get(), desktop_dc_);
|
||||
if (!buffer) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to create frame buffer.";
|
||||
return false;
|
||||
}
|
||||
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
|
||||
}
|
||||
queue_.current_frame()->set_top_left(
|
||||
screen_rect.top_left().subtract(GetFullscreenRect().top_left()));
|
||||
|
||||
// Select the target bitmap into the memory dc and copy the rect from desktop
|
||||
// to memory.
|
||||
DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
|
||||
queue_.current_frame()->GetUnderlyingFrame());
|
||||
HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
|
||||
if (!previous_object || previous_object == HGDI_ERROR) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to select current bitmap into memery dc.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = (BitBlt(memory_dc_, 0, 0, screen_rect.width(),
|
||||
screen_rect.height(), desktop_dc_, screen_rect.left(),
|
||||
screen_rect.top(), SRCCOPY | CAPTUREBLT) != FALSE);
|
||||
if (!result) {
|
||||
RTC_LOG_GLE(LS_WARNING) << "BitBlt failed";
|
||||
}
|
||||
|
||||
// Select back the previously selected object to that the device contect
|
||||
// could be destroyed independently of the bitmap if needed.
|
||||
SelectObject(memory_dc_, previous_object);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capturer_win_magnifier.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_frame_win.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/win/cursor.h"
|
||||
#include "modules/desktop_capture/win/desktop.h"
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
DWORD GetTlsIndex() {
|
||||
static const DWORD tls_index = TlsAlloc();
|
||||
RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES);
|
||||
return tls_index;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// kMagnifierWindowClass has to be "Magnifier" according to the Magnification
|
||||
// API. The other strings can be anything.
|
||||
static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost";
|
||||
static wchar_t kHostWindowName[] = L"MagnifierHost";
|
||||
static wchar_t kMagnifierWindowClass[] = L"Magnifier";
|
||||
static wchar_t kMagnifierWindowName[] = L"MagnifierWindow";
|
||||
|
||||
ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default;
|
||||
ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
|
||||
// DestroyWindow must be called before MagUninitialize. magnifier_window_ is
|
||||
// destroyed automatically when host_window_ is destroyed.
|
||||
if (host_window_)
|
||||
DestroyWindow(host_window_);
|
||||
|
||||
if (magnifier_initialized_)
|
||||
mag_uninitialize_func_();
|
||||
|
||||
if (mag_lib_handle_)
|
||||
FreeLibrary(mag_lib_handle_);
|
||||
|
||||
if (desktop_dc_)
|
||||
ReleaseDC(NULL, desktop_dc_);
|
||||
}
|
||||
|
||||
void ScreenCapturerWinMagnifier::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinMagnifier);
|
||||
|
||||
callback_ = callback;
|
||||
|
||||
if (!InitializeMagnifier()) {
|
||||
RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerWinMagnifier::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
shared_memory_factory_ = std::move(shared_memory_factory);
|
||||
}
|
||||
|
||||
void ScreenCapturerWinMagnifier::CaptureFrame() {
|
||||
RTC_DCHECK(callback_);
|
||||
if (!magnifier_initialized_) {
|
||||
RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
|
||||
// Switch to the desktop receiving user input if different from the current
|
||||
// one.
|
||||
std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
|
||||
if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
|
||||
// Release GDI resources otherwise SetThreadDesktop will fail.
|
||||
if (desktop_dc_) {
|
||||
ReleaseDC(NULL, desktop_dc_);
|
||||
desktop_dc_ = NULL;
|
||||
}
|
||||
// If SetThreadDesktop() fails, the thread is still assigned a desktop.
|
||||
// So we can continue capture screen bits, just from the wrong desktop.
|
||||
desktop_.SetThreadDesktop(input_desktop.release());
|
||||
}
|
||||
|
||||
DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
|
||||
queue_.MoveToNextFrame();
|
||||
CreateCurrentFrameIfNecessary(rect.size());
|
||||
// CaptureImage may fail in some situations, e.g. windows8 metro mode. So
|
||||
// defer to the fallback capturer if magnifier capturer did not work.
|
||||
if (!CaptureImage(rect)) {
|
||||
RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit the current frame.
|
||||
std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
|
||||
frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
|
||||
GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
|
||||
frame->mutable_updated_region()->SetRect(
|
||||
DesktopRect::MakeSize(frame->size()));
|
||||
|
||||
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
RTC_HISTOGRAM_COUNTS_1000(
|
||||
"WebRTC.DesktopCapture.Win.MagnifierCapturerFrameTime", capture_time_ms);
|
||||
frame->set_capture_time_ms(capture_time_ms);
|
||||
frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinMagnifier);
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) {
|
||||
return webrtc::GetScreenList(sources);
|
||||
}
|
||||
|
||||
bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) {
|
||||
if (IsScreenValid(id, ¤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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,585 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/wgc_capture_session.h"
|
||||
|
||||
#include <DispatcherQueue.h>
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <windows.graphics.directX.direct3d11.interop.h>
|
||||
#include <windows.graphics.h>
|
||||
#include <wrl/client.h>
|
||||
#include <wrl/event.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/win/create_direct3d_device.h"
|
||||
#include "rtc_base/win/get_activation_factory.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
#include "system_wrappers/include/sleep.h"
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
namespace WGC = ABI::Windows::Graphics::Capture;
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// We must use a BGRA pixel format that has 4 bytes per pixel, as required by
|
||||
// the DesktopFrame interface.
|
||||
constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX::
|
||||
DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum class StartCaptureResult {
|
||||
kSuccess = 0,
|
||||
kSourceClosed = 1,
|
||||
kAddClosedFailed = 2,
|
||||
kDxgiDeviceCastFailed = 3,
|
||||
kD3dDelayLoadFailed = 4,
|
||||
kD3dDeviceCreationFailed = 5,
|
||||
kFramePoolActivationFailed = 6,
|
||||
// kFramePoolCastFailed = 7, (deprecated)
|
||||
// kGetItemSizeFailed = 8, (deprecated)
|
||||
kCreateFramePoolFailed = 9,
|
||||
kCreateCaptureSessionFailed = 10,
|
||||
kStartCaptureFailed = 11,
|
||||
kMaxValue = kStartCaptureFailed
|
||||
};
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum class GetFrameResult {
|
||||
kSuccess = 0,
|
||||
kItemClosed = 1,
|
||||
kTryGetNextFrameFailed = 2,
|
||||
kFrameDropped = 3,
|
||||
kGetSurfaceFailed = 4,
|
||||
kDxgiInterfaceAccessFailed = 5,
|
||||
kTexture2dCastFailed = 6,
|
||||
kCreateMappedTextureFailed = 7,
|
||||
kMapFrameFailed = 8,
|
||||
kGetContentSizeFailed = 9,
|
||||
kResizeMappedTextureFailed = 10,
|
||||
kRecreateFramePoolFailed = 11,
|
||||
kFramePoolEmpty = 12,
|
||||
kMaxValue = kFramePoolEmpty
|
||||
};
|
||||
|
||||
void RecordStartCaptureResult(StartCaptureResult error) {
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult",
|
||||
static_cast<int>(error), static_cast<int>(StartCaptureResult::kMaxValue));
|
||||
}
|
||||
|
||||
void RecordGetFrameResult(GetFrameResult error) {
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.DesktopCapture.Win.WgcCaptureSessionGetFrameResult",
|
||||
static_cast<int>(error), static_cast<int>(GetFrameResult::kMaxValue));
|
||||
}
|
||||
|
||||
bool SizeHasChanged(ABI::Windows::Graphics::SizeInt32 size_new,
|
||||
ABI::Windows::Graphics::SizeInt32 size_old) {
|
||||
return (size_new.Height != size_old.Height ||
|
||||
size_new.Width != size_old.Width);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WgcCaptureSession::WgcCaptureSession(ComPtr<ID3D11Device> d3d11_device,
|
||||
ComPtr<WGC::IGraphicsCaptureItem> item,
|
||||
ABI::Windows::Graphics::SizeInt32 size)
|
||||
: d3d11_device_(std::move(d3d11_device)),
|
||||
item_(std::move(item)),
|
||||
size_(size) {}
|
||||
|
||||
WgcCaptureSession::~WgcCaptureSession() {
|
||||
RemoveEventHandler();
|
||||
}
|
||||
|
||||
HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
RTC_DCHECK(!is_capture_started_);
|
||||
|
||||
if (item_closed_) {
|
||||
RTC_LOG(LS_ERROR) << "The target source has been closed.";
|
||||
RecordStartCaptureResult(StartCaptureResult::kSourceClosed);
|
||||
return E_ABORT;
|
||||
}
|
||||
|
||||
RTC_DCHECK(d3d11_device_);
|
||||
RTC_DCHECK(item_);
|
||||
|
||||
// Listen for the Closed event, to detect if the source we are capturing is
|
||||
// closed (e.g. application window is closed or monitor is disconnected). If
|
||||
// it is, we should abort the capture.
|
||||
item_closed_token_ = std::make_unique<EventRegistrationToken>();
|
||||
auto closed_handler =
|
||||
Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
||||
WGC::GraphicsCaptureItem*, IInspectable*>>(
|
||||
this, &WgcCaptureSession::OnItemClosed);
|
||||
HRESULT hr =
|
||||
item_->add_Closed(closed_handler.Get(), item_closed_token_.get());
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kAddClosedFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComPtr<IDXGIDevice> dxgi_device;
|
||||
hr = d3d11_device_->QueryInterface(IID_PPV_ARGS(&dxgi_device));
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kDxgiDeviceCastFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (!ResolveCoreWinRTDirect3DDelayload()) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kD3dDelayLoadFailed);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
hr = CreateDirect3DDeviceFromDXGIDevice(dxgi_device.Get(), &direct3d_device_);
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kD3dDeviceCreationFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComPtr<WGC::IDirect3D11CaptureFramePoolStatics> frame_pool_statics;
|
||||
hr = GetActivationFactory<
|
||||
WGC::IDirect3D11CaptureFramePoolStatics,
|
||||
RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool>(
|
||||
&frame_pool_statics);
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kFramePoolActivationFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = frame_pool_statics->Create(direct3d_device_.Get(), kPixelFormat,
|
||||
kNumBuffers, size_, &frame_pool_);
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kCreateFramePoolFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_);
|
||||
if (FAILED(hr)) {
|
||||
RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (!options.prefer_cursor_embedded()) {
|
||||
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession2> session2;
|
||||
if (SUCCEEDED(session_->QueryInterface(
|
||||
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession2,
|
||||
&session2))) {
|
||||
session2->put_IsCursorCaptureEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
// By default, the WGC capture API adds a yellow border around the captured
|
||||
// window or display to indicate that a capture is in progress. The section
|
||||
// below is an attempt to remove this yellow border to make the capture
|
||||
// experience more inline with the DXGI capture path.
|
||||
// This requires 10.0.20348.0 or later, which practically means Windows 11.
|
||||
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession3> session3;
|
||||
if (SUCCEEDED(session_->QueryInterface(
|
||||
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession3,
|
||||
&session3))) {
|
||||
session3->put_IsBorderRequired(false);
|
||||
}
|
||||
|
||||
allow_zero_hertz_ = options.allow_wgc_zero_hertz();
|
||||
|
||||
hr = session_->StartCapture();
|
||||
if (FAILED(hr)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to start CaptureSession: " << hr;
|
||||
RecordStartCaptureResult(StartCaptureResult::kStartCaptureFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
RecordStartCaptureResult(StartCaptureResult::kSuccess);
|
||||
|
||||
is_capture_started_ = true;
|
||||
return hr;
|
||||
}
|
||||
|
||||
void WgcCaptureSession::EnsureFrame() {
|
||||
// Try to process the captured frame and copy it to the `queue_`.
|
||||
HRESULT hr = ProcessFrame();
|
||||
if (SUCCEEDED(hr)) {
|
||||
RTC_CHECK(queue_.current_frame());
|
||||
return;
|
||||
}
|
||||
|
||||
// We failed to process the frame, but we do have a frame so just return that.
|
||||
if (queue_.current_frame()) {
|
||||
RTC_LOG(LS_ERROR) << "ProcessFrame failed, using existing frame: " << hr;
|
||||
return;
|
||||
}
|
||||
|
||||
// ProcessFrame failed and we don't have a current frame. This could indicate
|
||||
// a startup path where we may need to try/wait a few times to ensure that we
|
||||
// have a frame. We try to get a new frame from the frame pool for a maximum
|
||||
// of 10 times after sleeping for 20ms. We choose 20ms as it's just a bit
|
||||
// longer than 17ms (for 60fps*) and hopefully avoids unlucky timing causing
|
||||
// us to wait two frames when we mostly seem to only need to wait for one.
|
||||
// This approach should ensure that GetFrame() always delivers a valid frame
|
||||
// with a max latency of 200ms and often after sleeping only once.
|
||||
// The scheme is heuristic and based on manual testing.
|
||||
// (*) On a modern system, the FPS / monitor refresh rate is usually larger
|
||||
// than or equal to 60.
|
||||
|
||||
const int max_sleep_count = 10;
|
||||
const int sleep_time_ms = 20;
|
||||
|
||||
int sleep_count = 0;
|
||||
while (!queue_.current_frame() && sleep_count < max_sleep_count) {
|
||||
sleep_count++;
|
||||
webrtc::SleepMs(sleep_time_ms);
|
||||
hr = ProcessFrame();
|
||||
if (FAILED(hr)) {
|
||||
RTC_DLOG(LS_WARNING) << "ProcessFrame failed during startup: " << hr;
|
||||
}
|
||||
}
|
||||
RTC_LOG_IF(LS_ERROR, !queue_.current_frame())
|
||||
<< "Unable to process a valid frame even after trying 10 times.";
|
||||
}
|
||||
|
||||
bool WgcCaptureSession::GetFrame(std::unique_ptr<DesktopFrame>* output_frame,
|
||||
bool source_should_be_capturable) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
|
||||
// Try to process the captured frame and wait some if needed. Avoid trying
|
||||
// if we know that the source will not be capturable. This can happen e.g.
|
||||
// when captured window is minimized and if EnsureFrame() was called in this
|
||||
// state a large amount of kFrameDropped errors would be logged.
|
||||
if (source_should_be_capturable)
|
||||
EnsureFrame();
|
||||
|
||||
// Return a NULL frame and false as `result` if we still don't have a valid
|
||||
// frame. This will lead to a DesktopCapturer::Result::ERROR_PERMANENT being
|
||||
// posted by the WGC capturer.
|
||||
DesktopFrame* current_frame = queue_.current_frame();
|
||||
if (!current_frame) {
|
||||
RTC_LOG(LS_ERROR) << "GetFrame failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Swap in the DesktopRegion in `damage_region_` which is updated in
|
||||
// ProcessFrame(). The updated region is either empty or the full rect being
|
||||
// captured where an empty damage region corresponds to "no change in content
|
||||
// since last frame".
|
||||
current_frame->mutable_updated_region()->Swap(&damage_region_);
|
||||
damage_region_.Clear();
|
||||
|
||||
// Emit the current frame.
|
||||
std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share();
|
||||
*output_frame = std::move(new_frame);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HRESULT WgcCaptureSession::CreateMappedTexture(
|
||||
ComPtr<ID3D11Texture2D> src_texture,
|
||||
UINT width,
|
||||
UINT height) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
|
||||
D3D11_TEXTURE2D_DESC src_desc;
|
||||
src_texture->GetDesc(&src_desc);
|
||||
D3D11_TEXTURE2D_DESC map_desc;
|
||||
map_desc.Width = width == 0 ? src_desc.Width : width;
|
||||
map_desc.Height = height == 0 ? src_desc.Height : height;
|
||||
map_desc.MipLevels = src_desc.MipLevels;
|
||||
map_desc.ArraySize = src_desc.ArraySize;
|
||||
map_desc.Format = src_desc.Format;
|
||||
map_desc.SampleDesc = src_desc.SampleDesc;
|
||||
map_desc.Usage = D3D11_USAGE_STAGING;
|
||||
map_desc.BindFlags = 0;
|
||||
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
map_desc.MiscFlags = 0;
|
||||
return d3d11_device_->CreateTexture2D(&map_desc, nullptr, &mapped_texture_);
|
||||
}
|
||||
|
||||
HRESULT WgcCaptureSession::ProcessFrame() {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
|
||||
if (item_closed_) {
|
||||
RTC_LOG(LS_ERROR) << "The target source has been closed.";
|
||||
RecordGetFrameResult(GetFrameResult::kItemClosed);
|
||||
return E_ABORT;
|
||||
}
|
||||
|
||||
RTC_DCHECK(is_capture_started_);
|
||||
|
||||
ComPtr<WGC::IDirect3D11CaptureFrame> capture_frame;
|
||||
HRESULT hr = frame_pool_->TryGetNextFrame(&capture_frame);
|
||||
if (FAILED(hr)) {
|
||||
RTC_LOG(LS_ERROR) << "TryGetNextFrame failed: " << hr;
|
||||
RecordGetFrameResult(GetFrameResult::kTryGetNextFrameFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (!capture_frame) {
|
||||
if (!queue_.current_frame()) {
|
||||
// The frame pool was empty and so is the external queue.
|
||||
RTC_DLOG(LS_ERROR) << "Frame pool was empty => kFrameDropped.";
|
||||
RecordGetFrameResult(GetFrameResult::kFrameDropped);
|
||||
} else {
|
||||
// The frame pool was empty but there is still one old frame available in
|
||||
// external the queue.
|
||||
RTC_DLOG(LS_WARNING) << "Frame pool was empty => kFramePoolEmpty.";
|
||||
RecordGetFrameResult(GetFrameResult::kFramePoolEmpty);
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
queue_.MoveToNextFrame();
|
||||
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
|
||||
RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared.";
|
||||
}
|
||||
|
||||
// We need to get `capture_frame` as an `ID3D11Texture2D` so that we can get
|
||||
// the raw image data in the format required by the `DesktopFrame` interface.
|
||||
ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>
|
||||
d3d_surface;
|
||||
hr = capture_frame->get_Surface(&d3d_surface);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kGetSurfaceFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
|
||||
direct3DDxgiInterfaceAccess;
|
||||
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kDxgiInterfaceAccessFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComPtr<ID3D11Texture2D> texture_2D;
|
||||
hr = direct3DDxgiInterfaceAccess->GetInterface(IID_PPV_ARGS(&texture_2D));
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kTexture2dCastFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (!mapped_texture_) {
|
||||
hr = CreateMappedTexture(texture_2D);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed);
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to copy `texture_2D` into `mapped_texture_` as the latter has the
|
||||
// D3D11_CPU_ACCESS_READ flag set, which lets us access the image data.
|
||||
// Otherwise it would only be readable by the GPU.
|
||||
ComPtr<ID3D11DeviceContext> d3d_context;
|
||||
d3d11_device_->GetImmediateContext(&d3d_context);
|
||||
|
||||
ABI::Windows::Graphics::SizeInt32 new_size;
|
||||
hr = capture_frame->get_ContentSize(&new_size);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kGetContentSizeFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// If the size changed, we must resize `mapped_texture_` and `frame_pool_` to
|
||||
// fit the new size. This must be done before `CopySubresourceRegion` so that
|
||||
// the textures are the same size.
|
||||
if (SizeHasChanged(new_size, size_)) {
|
||||
hr = CreateMappedTexture(texture_2D, new_size.Width, new_size.Height);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kResizeMappedTextureFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat,
|
||||
kNumBuffers, new_size);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed);
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
// If the size has changed since the last capture, we must be sure to use
|
||||
// the smaller dimensions. Otherwise we might overrun our buffer, or
|
||||
// read stale data from the last frame.
|
||||
int image_height = std::min(size_.Height, new_size.Height);
|
||||
int image_width = std::min(size_.Width, new_size.Width);
|
||||
|
||||
D3D11_BOX copy_region;
|
||||
copy_region.left = 0;
|
||||
copy_region.top = 0;
|
||||
copy_region.right = image_width;
|
||||
copy_region.bottom = image_height;
|
||||
// Our textures are 2D so we just want one "slice" of the box.
|
||||
copy_region.front = 0;
|
||||
copy_region.back = 1;
|
||||
d3d_context->CopySubresourceRegion(mapped_texture_.Get(),
|
||||
/*dst_subresource_index=*/0, /*dst_x=*/0,
|
||||
/*dst_y=*/0, /*dst_z=*/0, texture_2D.Get(),
|
||||
/*src_subresource_index=*/0, ©_region);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE map_info;
|
||||
hr = d3d_context->Map(mapped_texture_.Get(), /*subresource_index=*/0,
|
||||
D3D11_MAP_READ, /*D3D11_MAP_FLAG_DO_NOT_WAIT=*/0,
|
||||
&map_info);
|
||||
if (FAILED(hr)) {
|
||||
RecordGetFrameResult(GetFrameResult::kMapFrameFailed);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Allocate the current frame buffer only if it is not already allocated or
|
||||
// if the size has changed. Note that we can't reallocate other buffers at
|
||||
// this point, since the caller may still be reading from them. The queue can
|
||||
// hold up to two frames.
|
||||
DesktopSize image_size(image_width, image_height);
|
||||
if (!queue_.current_frame() ||
|
||||
!queue_.current_frame()->size().equals(image_size)) {
|
||||
std::unique_ptr<DesktopFrame> buffer =
|
||||
std::make_unique<BasicDesktopFrame>(image_size);
|
||||
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
|
||||
}
|
||||
|
||||
DesktopFrame* current_frame = queue_.current_frame();
|
||||
DesktopFrame* previous_frame = queue_.previous_frame();
|
||||
|
||||
// Will be set to true while copying the frame data to the `current_frame` if
|
||||
// we can already determine that the content of the new frame differs from the
|
||||
// previous. The idea is to get a low-complexity indication of if the content
|
||||
// is static or not without performing a full/deep memory comparison when
|
||||
// updating the damaged region.
|
||||
bool frame_content_has_changed = false;
|
||||
|
||||
// Check if the queue contains two frames whose content can be compared.
|
||||
const bool frame_content_can_be_compared = FrameContentCanBeCompared();
|
||||
|
||||
// Make a copy of the data pointed to by `map_info.pData` to the preallocated
|
||||
// `current_frame` so we are free to unmap our texture. If possible, also
|
||||
// perform a light-weight scan of the vertical line of pixels in the middle
|
||||
// of the screen. A comparison is performed between two 32-bit pixels (RGBA);
|
||||
// one from the current frame and one from the previous, and as soon as a
|
||||
// difference is detected the scan stops and `frame_content_has_changed` is
|
||||
// set to true.
|
||||
uint8_t* src_data = static_cast<uint8_t*>(map_info.pData);
|
||||
uint8_t* dst_data = current_frame->data();
|
||||
uint8_t* prev_data =
|
||||
frame_content_can_be_compared ? previous_frame->data() : nullptr;
|
||||
|
||||
const int width_in_bytes =
|
||||
current_frame->size().width() * DesktopFrame::kBytesPerPixel;
|
||||
RTC_DCHECK_GE(current_frame->stride(), width_in_bytes);
|
||||
RTC_DCHECK_GE(map_info.RowPitch, width_in_bytes);
|
||||
const int middle_pixel_offset =
|
||||
(image_width / 2) * DesktopFrame::kBytesPerPixel;
|
||||
for (int i = 0; i < image_height; i++) {
|
||||
memcpy(dst_data, src_data, width_in_bytes);
|
||||
if (prev_data && !frame_content_has_changed) {
|
||||
uint8_t* previous_pixel = prev_data + middle_pixel_offset;
|
||||
uint8_t* current_pixel = dst_data + middle_pixel_offset;
|
||||
frame_content_has_changed =
|
||||
memcmp(previous_pixel, current_pixel, DesktopFrame::kBytesPerPixel);
|
||||
prev_data += current_frame->stride();
|
||||
}
|
||||
dst_data += current_frame->stride();
|
||||
src_data += map_info.RowPitch;
|
||||
}
|
||||
|
||||
d3d_context->Unmap(mapped_texture_.Get(), 0);
|
||||
|
||||
if (allow_zero_hertz()) {
|
||||
if (previous_frame) {
|
||||
const int previous_frame_size =
|
||||
previous_frame->stride() * previous_frame->size().height();
|
||||
const int current_frame_size =
|
||||
current_frame->stride() * current_frame->size().height();
|
||||
|
||||
// Compare the latest frame with the previous and check if the frames are
|
||||
// equal (both contain the exact same pixel values). Avoid full memory
|
||||
// comparison if indication of a changed frame already exists from the
|
||||
// stage above.
|
||||
if (current_frame_size == previous_frame_size) {
|
||||
if (frame_content_has_changed) {
|
||||
// Mark frame as damaged based on existing light-weight indicator.
|
||||
// Avoids deep memcmp of complete frame and saves resources.
|
||||
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
|
||||
} else {
|
||||
// Perform full memory comparison for all bytes between the current
|
||||
// and the previous frames.
|
||||
const bool frames_are_equal =
|
||||
!memcmp(current_frame->data(), previous_frame->data(),
|
||||
current_frame_size);
|
||||
if (!frames_are_equal) {
|
||||
// TODO(https://crbug.com/1421242): If we had an API to report
|
||||
// proper damage regions we should be doing AddRect() with a
|
||||
// SetRect() call on a resize.
|
||||
damage_region_.SetRect(
|
||||
DesktopRect::MakeSize(current_frame->size()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Mark resized frames as damaged.
|
||||
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_ = new_size;
|
||||
RecordGetFrameResult(GetFrameResult::kSuccess);
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender,
|
||||
IInspectable* event_args) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Capture target has been closed.";
|
||||
item_closed_ = true;
|
||||
|
||||
RemoveEventHandler();
|
||||
|
||||
// Do not attempt to free resources in the OnItemClosed handler, as this
|
||||
// causes a race where we try to delete the item that is calling us. Removing
|
||||
// the event handlers and setting `item_closed_` above is sufficient to ensure
|
||||
// that the resources are no longer used, and the next time the capturer tries
|
||||
// to get a frame, we will report a permanent failure and be destroyed.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void WgcCaptureSession::RemoveEventHandler() {
|
||||
HRESULT hr;
|
||||
if (item_ && item_closed_token_) {
|
||||
hr = item_->remove_Closed(*item_closed_token_);
|
||||
item_closed_token_.reset();
|
||||
if (FAILED(hr))
|
||||
RTC_LOG(LS_WARNING) << "Failed to remove Closed event handler: " << hr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WgcCaptureSession::FrameContentCanBeCompared() {
|
||||
DesktopFrame* current_frame = queue_.current_frame();
|
||||
DesktopFrame* previous_frame = queue_.previous_frame();
|
||||
if (!current_frame || !previous_frame) {
|
||||
return false;
|
||||
}
|
||||
if (current_frame->stride() != previous_frame->stride()) {
|
||||
return false;
|
||||
}
|
||||
return current_frame->size().equals(previous_frame->size());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <windows.graphics.capture.h>
|
||||
#include <windows.graphics.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/sequence_checker.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "modules/desktop_capture/win/wgc_capture_source.h"
|
||||
#include "rtc_base/event.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class WgcCaptureSession final {
|
||||
public:
|
||||
WgcCaptureSession(
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> item,
|
||||
ABI::Windows::Graphics::SizeInt32 size);
|
||||
|
||||
// Disallow copy and assign.
|
||||
WgcCaptureSession(const WgcCaptureSession&) = delete;
|
||||
WgcCaptureSession& operator=(const WgcCaptureSession&) = delete;
|
||||
|
||||
~WgcCaptureSession();
|
||||
|
||||
HRESULT StartCapture(const DesktopCaptureOptions& options);
|
||||
|
||||
// Returns a frame from the local frame queue, if any are present.
|
||||
bool GetFrame(std::unique_ptr<DesktopFrame>* output_frame,
|
||||
bool source_should_be_capturable);
|
||||
|
||||
bool IsCaptureStarted() const {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
return is_capture_started_;
|
||||
}
|
||||
|
||||
// We keep 2 buffers in the frame pool since it results in a good compromise
|
||||
// between latency/capture-rate and the rate at which
|
||||
// Direct3D11CaptureFramePool.TryGetNextFrame returns NULL and we have to fall
|
||||
// back to providing a copy from our external queue instead.
|
||||
// We make this public for tests.
|
||||
static constexpr int kNumBuffers = 2;
|
||||
|
||||
private:
|
||||
// Initializes `mapped_texture_` with the properties of the `src_texture`,
|
||||
// overrides the values of some necessary properties like the
|
||||
// D3D11_CPU_ACCESS_READ flag. Also has optional parameters for what size
|
||||
// `mapped_texture_` should be, if they aren't provided we will use the size
|
||||
// of `src_texture`.
|
||||
HRESULT CreateMappedTexture(
|
||||
Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture,
|
||||
UINT width = 0,
|
||||
UINT height = 0);
|
||||
|
||||
// Event handler for `item_`'s Closed event.
|
||||
HRESULT OnItemClosed(
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* sender,
|
||||
IInspectable* event_args);
|
||||
|
||||
// Wraps calls to ProcessFrame and deals with the uniqe start-up phase
|
||||
// ensuring that we always have one captured frame available.
|
||||
void EnsureFrame();
|
||||
|
||||
// Process the captured frame and copy it to the `queue_`.
|
||||
HRESULT ProcessFrame();
|
||||
|
||||
void RemoveEventHandler();
|
||||
|
||||
bool FrameContentCanBeCompared();
|
||||
|
||||
bool allow_zero_hertz() const { return allow_zero_hertz_; }
|
||||
|
||||
std::unique_ptr<EventRegistrationToken> item_closed_token_;
|
||||
|
||||
// A Direct3D11 Device provided by the caller. We use this to create an
|
||||
// IDirect3DDevice, and also to create textures that will hold the image data.
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
|
||||
|
||||
// This item represents what we are capturing, we use it to create the
|
||||
// capture session, and also to listen for the Closed event.
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
|
||||
item_;
|
||||
|
||||
// The IDirect3DDevice is necessary to instantiate the frame pool.
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>
|
||||
direct3d_device_;
|
||||
|
||||
// The frame pool is where frames are deposited during capture, we retrieve
|
||||
// them from here with TryGetNextFrame().
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool>
|
||||
frame_pool_;
|
||||
|
||||
// This texture holds the final image data. We made it a member so we can
|
||||
// reuse it, instead of having to create a new texture every time we grab a
|
||||
// frame.
|
||||
Microsoft::WRL::ComPtr<ID3D11Texture2D> mapped_texture_;
|
||||
|
||||
// This is the size of `mapped_texture_` and the buffers in `frame_pool_`. We
|
||||
// store this as a member so we can compare it to the size of incoming frames
|
||||
// and resize if necessary.
|
||||
ABI::Windows::Graphics::SizeInt32 size_;
|
||||
|
||||
// The capture session lets us set properties about the capture before it
|
||||
// starts such as whether to capture the mouse cursor, and it lets us tell WGC
|
||||
// to start capturing frames.
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureSession>
|
||||
session_;
|
||||
|
||||
// Queue of captured video frames. The queue holds 2 frames and it avoids
|
||||
// alloc/dealloc per captured frame. Incoming frames from the internal frame
|
||||
// pool are copied to this queue after required processing in ProcessFrame().
|
||||
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
|
||||
|
||||
bool item_closed_ = false;
|
||||
bool is_capture_started_ = false;
|
||||
|
||||
// Caches the value of DesktopCaptureOptions.allow_wgc_zero_hertz() in
|
||||
// StartCapture(). Adds 0Hz detection in ProcessFrame() when enabled which
|
||||
// adds complexity since memcmp() is performed on two successive frames.
|
||||
bool allow_zero_hertz_ = false;
|
||||
|
||||
// Tracks damage region updates that were reported since the last time a frame
|
||||
// was captured. Currently only supports either the complete rect being
|
||||
// captured or an empty region. Will always be empty if `allow_zero_hertz_` is
|
||||
// false.
|
||||
DesktopRegion damage_region_;
|
||||
|
||||
SequenceChecker sequence_checker_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/wgc_capture_source.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||
#include "rtc_base/win/get_activation_factory.h"
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
namespace WGC = ABI::Windows::Graphics::Capture;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
WgcCaptureSource::WgcCaptureSource(DesktopCapturer::SourceId source_id)
|
||||
: source_id_(source_id) {}
|
||||
WgcCaptureSource::~WgcCaptureSource() = default;
|
||||
|
||||
bool WgcCaptureSource::ShouldBeCapturable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WgcCaptureSource::IsCapturable() {
|
||||
// If we can create a capture item, then we can capture it. Unfortunately,
|
||||
// we can't cache this item because it may be created in a different COM
|
||||
// apartment than where capture will eventually start from.
|
||||
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||
return SUCCEEDED(CreateCaptureItem(&item));
|
||||
}
|
||||
|
||||
bool WgcCaptureSource::FocusOnSource() {
|
||||
return false;
|
||||
}
|
||||
|
||||
ABI::Windows::Graphics::SizeInt32 WgcCaptureSource::GetSize() {
|
||||
if (!item_)
|
||||
return {0, 0};
|
||||
|
||||
ABI::Windows::Graphics::SizeInt32 item_size;
|
||||
HRESULT hr = item_->get_Size(&item_size);
|
||||
if (FAILED(hr))
|
||||
return {0, 0};
|
||||
|
||||
return item_size;
|
||||
}
|
||||
|
||||
HRESULT WgcCaptureSource::GetCaptureItem(
|
||||
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||
HRESULT hr = S_OK;
|
||||
if (!item_)
|
||||
hr = CreateCaptureItem(&item_);
|
||||
|
||||
*result = item_;
|
||||
return hr;
|
||||
}
|
||||
|
||||
WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default;
|
||||
|
||||
WgcWindowSourceFactory::WgcWindowSourceFactory() = default;
|
||||
WgcWindowSourceFactory::~WgcWindowSourceFactory() = default;
|
||||
|
||||
std::unique_ptr<WgcCaptureSource> WgcWindowSourceFactory::CreateCaptureSource(
|
||||
DesktopCapturer::SourceId source_id) {
|
||||
return std::make_unique<WgcWindowSource>(source_id);
|
||||
}
|
||||
|
||||
WgcScreenSourceFactory::WgcScreenSourceFactory() = default;
|
||||
WgcScreenSourceFactory::~WgcScreenSourceFactory() = default;
|
||||
|
||||
std::unique_ptr<WgcCaptureSource> WgcScreenSourceFactory::CreateCaptureSource(
|
||||
DesktopCapturer::SourceId source_id) {
|
||||
return std::make_unique<WgcScreenSource>(source_id);
|
||||
}
|
||||
|
||||
WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id)
|
||||
: WgcCaptureSource(source_id) {}
|
||||
WgcWindowSource::~WgcWindowSource() = default;
|
||||
|
||||
DesktopVector WgcWindowSource::GetTopLeft() {
|
||||
DesktopRect window_rect;
|
||||
if (!GetWindowRect(reinterpret_cast<HWND>(GetSourceId()), &window_rect))
|
||||
return DesktopVector();
|
||||
|
||||
return window_rect.top_left();
|
||||
}
|
||||
|
||||
ABI::Windows::Graphics::SizeInt32 WgcWindowSource::GetSize() {
|
||||
RECT window_rect;
|
||||
HRESULT hr = ::DwmGetWindowAttribute(
|
||||
reinterpret_cast<HWND>(GetSourceId()), DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
reinterpret_cast<void*>(&window_rect), sizeof(window_rect));
|
||||
if (FAILED(hr))
|
||||
return WgcCaptureSource::GetSize();
|
||||
|
||||
return {window_rect.right - window_rect.left,
|
||||
window_rect.bottom - window_rect.top};
|
||||
}
|
||||
|
||||
bool WgcWindowSource::ShouldBeCapturable() {
|
||||
return IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()));
|
||||
}
|
||||
|
||||
bool WgcWindowSource::IsCapturable() {
|
||||
if (!ShouldBeCapturable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WgcCaptureSource::IsCapturable();
|
||||
}
|
||||
|
||||
bool WgcWindowSource::FocusOnSource() {
|
||||
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
|
||||
return false;
|
||||
|
||||
return ::BringWindowToTop(reinterpret_cast<HWND>(GetSourceId())) &&
|
||||
::SetForegroundWindow(reinterpret_cast<HWND>(GetSourceId()));
|
||||
}
|
||||
|
||||
HRESULT WgcWindowSource::CreateCaptureItem(
|
||||
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||
if (!ResolveCoreWinRTDelayload())
|
||||
return E_FAIL;
|
||||
|
||||
ComPtr<IGraphicsCaptureItemInterop> interop;
|
||||
HRESULT hr = GetActivationFactory<
|
||||
IGraphicsCaptureItemInterop,
|
||||
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||
hr = interop->CreateForWindow(reinterpret_cast<HWND>(GetSourceId()),
|
||||
IID_PPV_ARGS(&item));
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (!item)
|
||||
return E_HANDLE;
|
||||
|
||||
*result = std::move(item);
|
||||
return hr;
|
||||
}
|
||||
|
||||
WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id)
|
||||
: WgcCaptureSource(source_id) {
|
||||
// Getting the HMONITOR could fail if the source_id is invalid. In that case,
|
||||
// we leave hmonitor_ uninitialized and `IsCapturable()` will fail.
|
||||
HMONITOR hmon;
|
||||
if (GetHmonitorFromDeviceIndex(GetSourceId(), &hmon))
|
||||
hmonitor_ = hmon;
|
||||
}
|
||||
|
||||
WgcScreenSource::~WgcScreenSource() = default;
|
||||
|
||||
DesktopVector WgcScreenSource::GetTopLeft() {
|
||||
if (!hmonitor_)
|
||||
return DesktopVector();
|
||||
|
||||
return GetMonitorRect(*hmonitor_).top_left();
|
||||
}
|
||||
|
||||
ABI::Windows::Graphics::SizeInt32 WgcScreenSource::GetSize() {
|
||||
ABI::Windows::Graphics::SizeInt32 size = WgcCaptureSource::GetSize();
|
||||
if (!hmonitor_ || (size.Width != 0 && size.Height != 0))
|
||||
return size;
|
||||
|
||||
DesktopRect rect = GetMonitorRect(*hmonitor_);
|
||||
return {rect.width(), rect.height()};
|
||||
}
|
||||
|
||||
bool WgcScreenSource::IsCapturable() {
|
||||
if (!hmonitor_)
|
||||
return false;
|
||||
|
||||
if (!IsMonitorValid(*hmonitor_))
|
||||
return false;
|
||||
|
||||
return WgcCaptureSource::IsCapturable();
|
||||
}
|
||||
|
||||
HRESULT WgcScreenSource::CreateCaptureItem(
|
||||
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||
if (!hmonitor_)
|
||||
return E_ABORT;
|
||||
|
||||
if (!ResolveCoreWinRTDelayload())
|
||||
return E_FAIL;
|
||||
|
||||
ComPtr<IGraphicsCaptureItemInterop> interop;
|
||||
HRESULT hr = GetActivationFactory<
|
||||
IGraphicsCaptureItemInterop,
|
||||
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
// Ensure the monitor is still valid (hasn't disconnected) before trying to
|
||||
// create the item. On versions of Windows before Win11, `CreateForMonitor`
|
||||
// will crash if no displays are connected.
|
||||
if (!IsMonitorValid(hmonitor_.value()))
|
||||
return E_ABORT;
|
||||
|
||||
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||
hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (!item)
|
||||
return E_HANDLE;
|
||||
|
||||
*result = std::move(item);
|
||||
return hr;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||
|
||||
#include <windows.graphics.capture.h>
|
||||
#include <windows.graphics.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Abstract class to represent the source that WGC-based capturers capture
|
||||
// from. Could represent an application window or a screen. Consumers should use
|
||||
// the appropriate Wgc*SourceFactory class to create WgcCaptureSource objects
|
||||
// of the appropriate type.
|
||||
class WgcCaptureSource {
|
||||
public:
|
||||
explicit WgcCaptureSource(DesktopCapturer::SourceId source_id);
|
||||
virtual ~WgcCaptureSource();
|
||||
|
||||
virtual DesktopVector GetTopLeft() = 0;
|
||||
// Lightweight version of IsCapturable which avoids allocating/deallocating
|
||||
// COM objects for each call. As such may return a different value than
|
||||
// IsCapturable.
|
||||
virtual bool ShouldBeCapturable();
|
||||
virtual bool IsCapturable();
|
||||
virtual bool FocusOnSource();
|
||||
virtual ABI::Windows::Graphics::SizeInt32 GetSize();
|
||||
HRESULT GetCaptureItem(
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result);
|
||||
DesktopCapturer::SourceId GetSourceId() { return source_id_; }
|
||||
|
||||
protected:
|
||||
virtual HRESULT CreateCaptureItem(
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) = 0;
|
||||
|
||||
private:
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
|
||||
item_;
|
||||
const DesktopCapturer::SourceId source_id_;
|
||||
};
|
||||
|
||||
class WgcCaptureSourceFactory {
|
||||
public:
|
||||
virtual ~WgcCaptureSourceFactory();
|
||||
|
||||
virtual std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||
DesktopCapturer::SourceId) = 0;
|
||||
};
|
||||
|
||||
class WgcWindowSourceFactory final : public WgcCaptureSourceFactory {
|
||||
public:
|
||||
WgcWindowSourceFactory();
|
||||
|
||||
// Disallow copy and assign.
|
||||
WgcWindowSourceFactory(const WgcWindowSourceFactory&) = delete;
|
||||
WgcWindowSourceFactory& operator=(const WgcWindowSourceFactory&) = delete;
|
||||
|
||||
~WgcWindowSourceFactory() override;
|
||||
|
||||
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||
DesktopCapturer::SourceId) override;
|
||||
};
|
||||
|
||||
class WgcScreenSourceFactory final : public WgcCaptureSourceFactory {
|
||||
public:
|
||||
WgcScreenSourceFactory();
|
||||
|
||||
WgcScreenSourceFactory(const WgcScreenSourceFactory&) = delete;
|
||||
WgcScreenSourceFactory& operator=(const WgcScreenSourceFactory&) = delete;
|
||||
|
||||
~WgcScreenSourceFactory() override;
|
||||
|
||||
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||
DesktopCapturer::SourceId) override;
|
||||
};
|
||||
|
||||
// Class for capturing application windows.
|
||||
class WgcWindowSource final : public WgcCaptureSource {
|
||||
public:
|
||||
explicit WgcWindowSource(DesktopCapturer::SourceId source_id);
|
||||
|
||||
WgcWindowSource(const WgcWindowSource&) = delete;
|
||||
WgcWindowSource& operator=(const WgcWindowSource&) = delete;
|
||||
|
||||
~WgcWindowSource() override;
|
||||
|
||||
DesktopVector GetTopLeft() override;
|
||||
ABI::Windows::Graphics::SizeInt32 GetSize() override;
|
||||
bool ShouldBeCapturable() override;
|
||||
bool IsCapturable() override;
|
||||
bool FocusOnSource() override;
|
||||
|
||||
private:
|
||||
HRESULT CreateCaptureItem(
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
|
||||
override;
|
||||
};
|
||||
|
||||
// Class for capturing screens/monitors/displays.
|
||||
class WgcScreenSource final : public WgcCaptureSource {
|
||||
public:
|
||||
explicit WgcScreenSource(DesktopCapturer::SourceId source_id);
|
||||
|
||||
WgcScreenSource(const WgcScreenSource&) = delete;
|
||||
WgcScreenSource& operator=(const WgcScreenSource&) = delete;
|
||||
|
||||
~WgcScreenSource() override;
|
||||
|
||||
DesktopVector GetTopLeft() override;
|
||||
ABI::Windows::Graphics::SizeInt32 GetSize() override;
|
||||
bool IsCapturable() override;
|
||||
|
||||
private:
|
||||
HRESULT CreateCaptureItem(
|
||||
Microsoft::WRL::ComPtr<
|
||||
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
|
||||
override;
|
||||
|
||||
// To maintain compatibility with other capturers, this class accepts a
|
||||
// device index as it's SourceId. However, WGC requires we use an HMONITOR to
|
||||
// describe which screen to capture. So, we internally convert the supplied
|
||||
// device index into an HMONITOR when `IsCapturable()` is called.
|
||||
absl::optional<HMONITOR> hmonitor_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/wgc_capturer_win.h"
|
||||
|
||||
#include <DispatcherQueue.h>
|
||||
#include <windows.foundation.metadata.h>
|
||||
#include <windows.graphics.capture.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/win/get_activation_factory.h"
|
||||
#include "rtc_base/win/hstring.h"
|
||||
#include "rtc_base/win/windows_version.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace WGC = ABI::Windows::Graphics::Capture;
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll";
|
||||
|
||||
constexpr wchar_t kWgcSessionType[] =
|
||||
L"Windows.Graphics.Capture.GraphicsCaptureSession";
|
||||
constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract";
|
||||
constexpr UINT16 kRequiredApiContractVersion = 8;
|
||||
|
||||
enum class WgcCapturerResult {
|
||||
kSuccess = 0,
|
||||
kNoDirect3dDevice = 1,
|
||||
kNoSourceSelected = 2,
|
||||
kItemCreationFailure = 3,
|
||||
kSessionStartFailure = 4,
|
||||
kGetFrameFailure = 5,
|
||||
kFrameDropped = 6,
|
||||
kCreateDispatcherQueueFailure = 7,
|
||||
kMaxValue = kCreateDispatcherQueueFailure
|
||||
};
|
||||
|
||||
void RecordWgcCapturerResult(WgcCapturerResult error) {
|
||||
RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult",
|
||||
static_cast<int>(error),
|
||||
static_cast<int>(WgcCapturerResult::kMaxValue));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsWgcSupported(CaptureType capture_type) {
|
||||
if (!HasActiveDisplay()) {
|
||||
// There is a bug in `CreateForMonitor` that causes a crash if there are no
|
||||
// active displays. The crash was fixed in Win11, but we are still unable
|
||||
// to capture screens without an active display.
|
||||
if (capture_type == CaptureType::kScreen)
|
||||
return false;
|
||||
|
||||
// There is a bug in the DWM (Desktop Window Manager) that prevents it from
|
||||
// providing image data if there are no displays attached. This was fixed in
|
||||
// Windows 11.
|
||||
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11)
|
||||
return false;
|
||||
}
|
||||
|
||||
// A bug in the WGC API `CreateForMonitor` prevents capturing the entire
|
||||
// virtual screen (all monitors simultaneously), this was fixed in 20H1. Since
|
||||
// we can't assert that we won't be asked to capture the entire virtual
|
||||
// screen, we report unsupported so we can fallback to another capturer.
|
||||
if (capture_type == CaptureType::kScreen &&
|
||||
rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveCoreWinRTDelayload())
|
||||
return false;
|
||||
|
||||
// We need to check if the WGC APIs are presesnt on the system. Certain SKUs
|
||||
// of Windows ship without these APIs.
|
||||
ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics>
|
||||
api_info_statics;
|
||||
HRESULT hr = GetActivationFactory<
|
||||
ABI::Windows::Foundation::Metadata::IApiInformationStatics,
|
||||
RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(
|
||||
&api_info_statics);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
HSTRING api_contract;
|
||||
hr = webrtc::CreateHstring(kApiContract, wcslen(kApiContract), &api_contract);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
boolean is_api_present;
|
||||
hr = api_info_statics->IsApiContractPresentByMajor(
|
||||
api_contract, kRequiredApiContractVersion, &is_api_present);
|
||||
webrtc::DeleteHstring(api_contract);
|
||||
if (FAILED(hr) || !is_api_present)
|
||||
return false;
|
||||
|
||||
HSTRING wgc_session_type;
|
||||
hr = webrtc::CreateHstring(kWgcSessionType, wcslen(kWgcSessionType),
|
||||
&wgc_session_type);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
boolean is_type_present;
|
||||
hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present);
|
||||
webrtc::DeleteHstring(wgc_session_type);
|
||||
if (FAILED(hr) || !is_type_present)
|
||||
return false;
|
||||
|
||||
// If the APIs are present, we need to check that they are supported.
|
||||
ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics;
|
||||
hr = GetActivationFactory<
|
||||
WGC::IGraphicsCaptureSessionStatics,
|
||||
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>(
|
||||
&capture_session_statics);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
boolean is_supported;
|
||||
hr = capture_session_statics->IsSupported(&is_supported);
|
||||
if (FAILED(hr) || !is_supported)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WgcCapturerWin::WgcCapturerWin(
|
||||
const DesktopCaptureOptions& options,
|
||||
std::unique_ptr<WgcCaptureSourceFactory> source_factory,
|
||||
std::unique_ptr<SourceEnumerator> source_enumerator,
|
||||
bool allow_delayed_capturable_check)
|
||||
: options_(options),
|
||||
source_factory_(std::move(source_factory)),
|
||||
source_enumerator_(std::move(source_enumerator)),
|
||||
allow_delayed_capturable_check_(allow_delayed_capturable_check) {
|
||||
if (!core_messaging_library_)
|
||||
core_messaging_library_ = LoadLibraryW(kCoreMessagingDll);
|
||||
|
||||
if (core_messaging_library_) {
|
||||
create_dispatcher_queue_controller_func_ =
|
||||
reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress(
|
||||
core_messaging_library_, "CreateDispatcherQueueController"));
|
||||
}
|
||||
}
|
||||
|
||||
WgcCapturerWin::~WgcCapturerWin() {
|
||||
if (core_messaging_library_)
|
||||
FreeLibrary(core_messaging_library_);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer(
|
||||
const DesktopCaptureOptions& options,
|
||||
bool allow_delayed_capturable_check) {
|
||||
return std::make_unique<WgcCapturerWin>(
|
||||
options, std::make_unique<WgcWindowSourceFactory>(),
|
||||
std::make_unique<WindowEnumerator>(
|
||||
options.enumerate_current_process_windows()),
|
||||
allow_delayed_capturable_check);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
return std::make_unique<WgcCapturerWin>(
|
||||
options, std::make_unique<WgcScreenSourceFactory>(),
|
||||
std::make_unique<ScreenEnumerator>(), false);
|
||||
}
|
||||
|
||||
bool WgcCapturerWin::GetSourceList(SourceList* sources) {
|
||||
return source_enumerator_->FindAllSources(sources);
|
||||
}
|
||||
|
||||
bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) {
|
||||
capture_source_ = source_factory_->CreateCaptureSource(id);
|
||||
if (allow_delayed_capturable_check_)
|
||||
return true;
|
||||
|
||||
return capture_source_->IsCapturable();
|
||||
}
|
||||
|
||||
bool WgcCapturerWin::FocusOnSelectedSource() {
|
||||
if (!capture_source_)
|
||||
return false;
|
||||
|
||||
return capture_source_->FocusOnSource();
|
||||
}
|
||||
|
||||
void WgcCapturerWin::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin);
|
||||
|
||||
callback_ = callback;
|
||||
|
||||
// Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many
|
||||
// parameters are nullptr as the implemention uses defaults that work well for
|
||||
// us.
|
||||
HRESULT hr = D3D11CreateDevice(
|
||||
/*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE,
|
||||
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
/*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION,
|
||||
&d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr);
|
||||
if (hr == DXGI_ERROR_UNSUPPORTED) {
|
||||
// If a hardware device could not be created, use WARP which is a high speed
|
||||
// software device.
|
||||
hr = D3D11CreateDevice(
|
||||
/*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP,
|
||||
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
/*feature_levels=*/nullptr, /*feature_levels_size=*/0,
|
||||
D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr,
|
||||
/*device_context=*/nullptr);
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr;
|
||||
}
|
||||
}
|
||||
|
||||
void WgcCapturerWin::CaptureFrame() {
|
||||
RTC_DCHECK(callback_);
|
||||
|
||||
if (!capture_source_) {
|
||||
RTC_LOG(LS_ERROR) << "Source hasn't been selected";
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d3d11_device_) {
|
||||
RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture.";
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) {
|
||||
RTC_LOG(LS_ERROR) << "Source is not capturable.";
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr;
|
||||
if (!dispatcher_queue_created_) {
|
||||
// Set the apartment type to NONE because this thread should already be COM
|
||||
// initialized.
|
||||
DispatcherQueueOptions options{
|
||||
sizeof(DispatcherQueueOptions),
|
||||
DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT,
|
||||
DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE};
|
||||
ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller;
|
||||
hr = create_dispatcher_queue_controller_func_(options, &queue_controller);
|
||||
|
||||
// If there is already a DispatcherQueue on this thread, that is fine. Its
|
||||
// lifetime is tied to the thread's, and as long as the thread has one, even
|
||||
// if we didn't create it, the capture session's events will be delivered on
|
||||
// this thread.
|
||||
if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) {
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure);
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
} else {
|
||||
dispatcher_queue_created_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
|
||||
WgcCaptureSession* capture_session = nullptr;
|
||||
std::map<SourceId, WgcCaptureSession>::iterator session_iter =
|
||||
ongoing_captures_.find(capture_source_->GetSourceId());
|
||||
if (session_iter == ongoing_captures_.end()) {
|
||||
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||
hr = capture_source_->GetCaptureItem(&item);
|
||||
if (FAILED(hr)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr;
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool>
|
||||
iter_success_pair = ongoing_captures_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(capture_source_->GetSourceId()),
|
||||
std::forward_as_tuple(d3d11_device_, item,
|
||||
capture_source_->GetSize()));
|
||||
RTC_DCHECK(iter_success_pair.second);
|
||||
capture_session = &iter_success_pair.first->second;
|
||||
} else {
|
||||
capture_session = &session_iter->second;
|
||||
}
|
||||
|
||||
if (!capture_session->IsCaptureStarted()) {
|
||||
hr = capture_session->StartCapture(options_);
|
||||
if (FAILED(hr)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr;
|
||||
ongoing_captures_.erase(capture_source_->GetSourceId());
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> frame;
|
||||
if (!capture_session->GetFrame(&frame,
|
||||
capture_source_->ShouldBeCapturable())) {
|
||||
RTC_LOG(LS_ERROR) << "GetFrame failed.";
|
||||
ongoing_captures_.erase(capture_source_->GetSourceId());
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
|
||||
/*frame=*/nullptr);
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped);
|
||||
return;
|
||||
}
|
||||
|
||||
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime",
|
||||
capture_time_ms);
|
||||
frame->set_capture_time_ms(capture_time_ms);
|
||||
frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin);
|
||||
frame->set_may_contain_cursor(options_.prefer_cursor_embedded());
|
||||
frame->set_top_left(capture_source_->GetTopLeft());
|
||||
RecordWgcCapturerResult(WgcCapturerResult::kSuccess);
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
|
||||
std::move(frame));
|
||||
}
|
||||
|
||||
bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) {
|
||||
std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator
|
||||
session_iter = ongoing_captures_.find(id);
|
||||
if (session_iter == ongoing_captures_.end())
|
||||
return false;
|
||||
|
||||
return session_iter->second.IsCaptureStarted();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,482 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||
|
||||
// Just for the DWMWINDOWATTRIBUTE enums (DWMWA_CLOAKED).
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/desktop_capture/win/scoped_gdi_object.h"
|
||||
#include "rtc_base/arraysize.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/string_utils.h"
|
||||
#include "rtc_base/win/windows_version.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
struct GetWindowListParams {
|
||||
GetWindowListParams(int flags,
|
||||
LONG ex_style_filters,
|
||||
DesktopCapturer::SourceList* result)
|
||||
: ignore_untitled(flags & GetWindowListFlags::kIgnoreUntitled),
|
||||
ignore_unresponsive(flags & GetWindowListFlags::kIgnoreUnresponsive),
|
||||
ignore_current_process_windows(
|
||||
flags & GetWindowListFlags::kIgnoreCurrentProcessWindows),
|
||||
ex_style_filters(ex_style_filters),
|
||||
result(result) {}
|
||||
const bool ignore_untitled;
|
||||
const bool ignore_unresponsive;
|
||||
const bool ignore_current_process_windows;
|
||||
const LONG ex_style_filters;
|
||||
DesktopCapturer::SourceList* const result;
|
||||
};
|
||||
|
||||
bool IsWindowOwnedByCurrentProcess(HWND hwnd) {
|
||||
DWORD process_id;
|
||||
GetWindowThreadProcessId(hwnd, &process_id);
|
||||
return process_id == GetCurrentProcessId();
|
||||
}
|
||||
|
||||
BOOL CALLBACK GetWindowListHandler(HWND hwnd, LPARAM param) {
|
||||
GetWindowListParams* params = reinterpret_cast<GetWindowListParams*>(param);
|
||||
DesktopCapturer::SourceList* list = params->result;
|
||||
|
||||
// Skip invisible and minimized windows
|
||||
if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Skip windows which are not presented in the taskbar,
|
||||
// namely owned window if they don't have the app window style set
|
||||
HWND owner = GetWindow(hwnd, GW_OWNER);
|
||||
LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
if (owner && !(exstyle & WS_EX_APPWINDOW)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Filter out windows that match the extended styles the caller has specified,
|
||||
// e.g. WS_EX_TOOLWINDOW for capturers that don't support overlay windows.
|
||||
if (exstyle & params->ex_style_filters) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (params->ignore_unresponsive && !IsWindowResponding(hwnd)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
DesktopCapturer::Source window;
|
||||
window.id = reinterpret_cast<WindowId>(hwnd);
|
||||
|
||||
// GetWindowText* are potentially blocking operations if `hwnd` is
|
||||
// owned by the current process. The APIs will send messages to the window's
|
||||
// message loop, and if the message loop is waiting on this operation we will
|
||||
// enter a deadlock.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtexta#remarks
|
||||
//
|
||||
// To help consumers avoid this, there is a DesktopCaptureOption to ignore
|
||||
// windows owned by the current process. Consumers should either ensure that
|
||||
// the thread running their message loop never waits on this operation, or use
|
||||
// the option to exclude these windows from the source list.
|
||||
bool owned_by_current_process = IsWindowOwnedByCurrentProcess(hwnd);
|
||||
if (owned_by_current_process && params->ignore_current_process_windows) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Even if consumers request to enumerate windows owned by the current
|
||||
// process, we should not call GetWindowText* on unresponsive windows owned by
|
||||
// the current process because we will hang. Unfortunately, we could still
|
||||
// hang if the window becomes unresponsive after this check, hence the option
|
||||
// to avoid these completely.
|
||||
if (!owned_by_current_process || IsWindowResponding(hwnd)) {
|
||||
const size_t kTitleLength = 500;
|
||||
WCHAR window_title[kTitleLength] = L"";
|
||||
if (GetWindowTextLength(hwnd) != 0 &&
|
||||
GetWindowTextW(hwnd, window_title, kTitleLength) > 0) {
|
||||
window.title = rtc::ToUtf8(window_title);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip windows when we failed to convert the title or it is empty.
|
||||
if (params->ignore_untitled && window.title.empty())
|
||||
return TRUE;
|
||||
|
||||
// Capture the window class name, to allow specific window classes to be
|
||||
// skipped.
|
||||
//
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
|
||||
// says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't
|
||||
// need to have a buffer bigger than that.
|
||||
const size_t kMaxClassNameLength = 256;
|
||||
WCHAR class_name[kMaxClassNameLength] = L"";
|
||||
const int class_name_length =
|
||||
GetClassNameW(hwnd, class_name, kMaxClassNameLength);
|
||||
if (class_name_length < 1)
|
||||
return TRUE;
|
||||
|
||||
// Skip Program Manager window.
|
||||
if (wcscmp(class_name, L"Progman") == 0)
|
||||
return TRUE;
|
||||
|
||||
// Skip Start button window on Windows Vista, Windows 7.
|
||||
// On Windows 8, Windows 8.1, Windows 10 Start button is not a top level
|
||||
// window, so it will not be examined here.
|
||||
if (wcscmp(class_name, L"Button") == 0)
|
||||
return TRUE;
|
||||
|
||||
list->push_back(window);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Prefix used to match the window class for Chrome windows.
|
||||
const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";
|
||||
|
||||
// The hiddgen taskbar will leave a 2 pixel margin on the screen.
|
||||
const int kHiddenTaskbarMarginOnScreen = 2;
|
||||
|
||||
bool GetWindowRect(HWND window, DesktopRect* result) {
|
||||
RECT rect;
|
||||
if (!::GetWindowRect(window, &rect)) {
|
||||
return false;
|
||||
}
|
||||
*result = DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetCroppedWindowRect(HWND window,
|
||||
bool avoid_cropping_border,
|
||||
DesktopRect* cropped_rect,
|
||||
DesktopRect* original_rect) {
|
||||
DesktopRect window_rect;
|
||||
if (!GetWindowRect(window, &window_rect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (original_rect) {
|
||||
*original_rect = window_rect;
|
||||
}
|
||||
*cropped_rect = window_rect;
|
||||
|
||||
bool is_maximized = false;
|
||||
if (!IsWindowMaximized(window, &is_maximized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// As of Windows8, transparent resize borders are added by the OS at
|
||||
// left/bottom/right sides of a resizeable window. If the cropped window
|
||||
// doesn't remove these borders, the background will be exposed a bit.
|
||||
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8 ||
|
||||
is_maximized) {
|
||||
// Only apply this cropping to windows with a resize border (otherwise,
|
||||
// it'd clip the edges of captured pop-up windows without this border).
|
||||
LONG style = GetWindowLong(window, GWL_STYLE);
|
||||
if (style & WS_THICKFRAME || style & DS_MODALFRAME) {
|
||||
int width = GetSystemMetrics(SM_CXSIZEFRAME);
|
||||
int bottom_height = GetSystemMetrics(SM_CYSIZEFRAME);
|
||||
const int visible_border_height = GetSystemMetrics(SM_CYBORDER);
|
||||
int top_height = visible_border_height;
|
||||
|
||||
// If requested, avoid cropping the visible window border. This is used
|
||||
// for pop-up windows to include their border, but not for the outermost
|
||||
// window (where a partially-transparent border may expose the
|
||||
// background a bit).
|
||||
if (avoid_cropping_border) {
|
||||
width = std::max(0, width - GetSystemMetrics(SM_CXBORDER));
|
||||
bottom_height = std::max(0, bottom_height - visible_border_height);
|
||||
top_height = 0;
|
||||
}
|
||||
cropped_rect->Extend(-width, -top_height, -width, -bottom_height);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetWindowContentRect(HWND window, DesktopRect* result) {
|
||||
if (!GetWindowRect(window, result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT rect;
|
||||
if (!::GetClientRect(window, &rect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int width = rect.right - rect.left;
|
||||
// The GetClientRect() is not expected to return a larger area than
|
||||
// GetWindowRect().
|
||||
if (width > 0 && width < result->width()) {
|
||||
// - GetClientRect() always set the left / top of RECT to 0. So we need to
|
||||
// estimate the border width from GetClientRect() and GetWindowRect().
|
||||
// - Border width of a window varies according to the window type.
|
||||
// - GetClientRect() excludes the title bar, which should be considered as
|
||||
// part of the content and included in the captured frame. So we always
|
||||
// estimate the border width according to the window width.
|
||||
// - We assume a window has same border width in each side.
|
||||
// So we shrink half of the width difference from all four sides.
|
||||
const int shrink = ((width - result->width()) / 2);
|
||||
// When `shrink` is negative, DesktopRect::Extend() shrinks itself.
|
||||
result->Extend(shrink, 0, shrink, 0);
|
||||
// Usually this should not happen, just in case we have received a strange
|
||||
// window, which has only left and right borders.
|
||||
if (result->height() > shrink * 2) {
|
||||
result->Extend(0, shrink, 0, shrink);
|
||||
}
|
||||
RTC_DCHECK(!result->is_empty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result) {
|
||||
win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN>> scoped_hrgn(
|
||||
CreateRectRgn(0, 0, 0, 0));
|
||||
const int region_type = GetWindowRgn(window, scoped_hrgn.Get());
|
||||
|
||||
if (region_type == SIMPLEREGION) {
|
||||
RECT rect;
|
||||
GetRgnBox(scoped_hrgn.Get(), &rect);
|
||||
*result =
|
||||
DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
return region_type;
|
||||
}
|
||||
|
||||
bool GetDcSize(HDC hdc, DesktopSize* size) {
|
||||
win::ScopedGDIObject<HGDIOBJ, win::DeleteObjectTraits<HGDIOBJ>> scoped_hgdi(
|
||||
GetCurrentObject(hdc, OBJ_BITMAP));
|
||||
BITMAP bitmap;
|
||||
memset(&bitmap, 0, sizeof(BITMAP));
|
||||
if (GetObject(scoped_hgdi.Get(), sizeof(BITMAP), &bitmap) == 0) {
|
||||
return false;
|
||||
}
|
||||
size->set(bitmap.bmWidth, bitmap.bmHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsWindowMaximized(HWND window, bool* result) {
|
||||
WINDOWPLACEMENT placement;
|
||||
memset(&placement, 0, sizeof(WINDOWPLACEMENT));
|
||||
placement.length = sizeof(WINDOWPLACEMENT);
|
||||
if (!::GetWindowPlacement(window, &placement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = (placement.showCmd == SW_SHOWMAXIMIZED);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsWindowValidAndVisible(HWND window) {
|
||||
return IsWindow(window) && IsWindowVisible(window) && !IsIconic(window);
|
||||
}
|
||||
|
||||
bool IsWindowResponding(HWND window) {
|
||||
// 50ms is chosen in case the system is under heavy load, but it's also not
|
||||
// too long to delay window enumeration considerably.
|
||||
const UINT uTimeoutMs = 50;
|
||||
return SendMessageTimeout(window, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, uTimeoutMs,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool GetWindowList(int flags,
|
||||
DesktopCapturer::SourceList* windows,
|
||||
LONG ex_style_filters) {
|
||||
GetWindowListParams params(flags, ex_style_filters, windows);
|
||||
return ::EnumWindows(&GetWindowListHandler,
|
||||
reinterpret_cast<LPARAM>(¶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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||