xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/win/wgc_capturer_win.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/desktop_capture/win/wgc_capturer_win.h"
12 
13 #include <DispatcherQueue.h>
14 #include <windows.foundation.metadata.h>
15 #include <windows.graphics.capture.h>
16 
17 #include <utility>
18 
19 #include "modules/desktop_capture/desktop_capture_metrics_helper.h"
20 #include "modules/desktop_capture/desktop_capture_types.h"
21 #include "modules/desktop_capture/win/wgc_desktop_frame.h"
22 #include "rtc_base/logging.h"
23 #include "rtc_base/time_utils.h"
24 #include "rtc_base/win/get_activation_factory.h"
25 #include "rtc_base/win/hstring.h"
26 #include "rtc_base/win/windows_version.h"
27 #include "system_wrappers/include/metrics.h"
28 
29 namespace WGC = ABI::Windows::Graphics::Capture;
30 using Microsoft::WRL::ComPtr;
31 
32 namespace webrtc {
33 
34 namespace {
35 
36 constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll";
37 
38 constexpr wchar_t kWgcSessionType[] =
39     L"Windows.Graphics.Capture.GraphicsCaptureSession";
40 constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract";
41 constexpr UINT16 kRequiredApiContractVersion = 8;
42 
43 enum class WgcCapturerResult {
44   kSuccess = 0,
45   kNoDirect3dDevice = 1,
46   kNoSourceSelected = 2,
47   kItemCreationFailure = 3,
48   kSessionStartFailure = 4,
49   kGetFrameFailure = 5,
50   kFrameDropped = 6,
51   kCreateDispatcherQueueFailure = 7,
52   kMaxValue = kCreateDispatcherQueueFailure
53 };
54 
RecordWgcCapturerResult(WgcCapturerResult error)55 void RecordWgcCapturerResult(WgcCapturerResult error) {
56   RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult",
57                             static_cast<int>(error),
58                             static_cast<int>(WgcCapturerResult::kMaxValue));
59 }
60 
61 }  // namespace
62 
IsWgcSupported(CaptureType capture_type)63 bool IsWgcSupported(CaptureType capture_type) {
64   if (!HasActiveDisplay()) {
65     // There is a bug in `CreateForMonitor` that causes a crash if there are no
66     // active displays. The crash was fixed in Win11, but we are still unable
67     // to capture screens without an active display.
68     if (capture_type == CaptureType::kScreen)
69       return false;
70 
71     // There is a bug in the DWM (Desktop Window Manager) that prevents it from
72     // providing image data if there are no displays attached. This was fixed in
73     // Windows 11.
74     if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11)
75       return false;
76   }
77 
78   // A bug in the WGC API `CreateForMonitor` prevents capturing the entire
79   // virtual screen (all monitors simultaneously), this was fixed in 20H1. Since
80   // we can't assert that we won't be asked to capture the entire virtual
81   // screen, we report unsupported so we can fallback to another capturer.
82   if (capture_type == CaptureType::kScreen &&
83       rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1) {
84     return false;
85   }
86 
87   if (!ResolveCoreWinRTDelayload())
88     return false;
89 
90   // We need to check if the WGC APIs are presesnt on the system. Certain SKUs
91   // of Windows ship without these APIs.
92   ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics>
93       api_info_statics;
94   HRESULT hr = GetActivationFactory<
95       ABI::Windows::Foundation::Metadata::IApiInformationStatics,
96       RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(
97       &api_info_statics);
98   if (FAILED(hr))
99     return false;
100 
101   HSTRING api_contract;
102   hr = webrtc::CreateHstring(kApiContract, wcslen(kApiContract), &api_contract);
103   if (FAILED(hr))
104     return false;
105 
106   boolean is_api_present;
107   hr = api_info_statics->IsApiContractPresentByMajor(
108       api_contract, kRequiredApiContractVersion, &is_api_present);
109   webrtc::DeleteHstring(api_contract);
110   if (FAILED(hr) || !is_api_present)
111     return false;
112 
113   HSTRING wgc_session_type;
114   hr = webrtc::CreateHstring(kWgcSessionType, wcslen(kWgcSessionType),
115                              &wgc_session_type);
116   if (FAILED(hr))
117     return false;
118 
119   boolean is_type_present;
120   hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present);
121   webrtc::DeleteHstring(wgc_session_type);
122   if (FAILED(hr) || !is_type_present)
123     return false;
124 
125   // If the APIs are present, we need to check that they are supported.
126   ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics;
127   hr = GetActivationFactory<
128       WGC::IGraphicsCaptureSessionStatics,
129       RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>(
130       &capture_session_statics);
131   if (FAILED(hr))
132     return false;
133 
134   boolean is_supported;
135   hr = capture_session_statics->IsSupported(&is_supported);
136   if (FAILED(hr) || !is_supported)
137     return false;
138 
139   return true;
140 }
141 
WgcCapturerWin(const DesktopCaptureOptions & options,std::unique_ptr<WgcCaptureSourceFactory> source_factory,std::unique_ptr<SourceEnumerator> source_enumerator,bool allow_delayed_capturable_check)142 WgcCapturerWin::WgcCapturerWin(
143     const DesktopCaptureOptions& options,
144     std::unique_ptr<WgcCaptureSourceFactory> source_factory,
145     std::unique_ptr<SourceEnumerator> source_enumerator,
146     bool allow_delayed_capturable_check)
147     : options_(options),
148       source_factory_(std::move(source_factory)),
149       source_enumerator_(std::move(source_enumerator)),
150       allow_delayed_capturable_check_(allow_delayed_capturable_check) {
151   if (!core_messaging_library_)
152     core_messaging_library_ = LoadLibraryW(kCoreMessagingDll);
153 
154   if (core_messaging_library_) {
155     create_dispatcher_queue_controller_func_ =
156         reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress(
157             core_messaging_library_, "CreateDispatcherQueueController"));
158   }
159 }
160 
~WgcCapturerWin()161 WgcCapturerWin::~WgcCapturerWin() {
162   if (core_messaging_library_)
163     FreeLibrary(core_messaging_library_);
164 }
165 
166 // static
CreateRawWindowCapturer(const DesktopCaptureOptions & options,bool allow_delayed_capturable_check)167 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer(
168     const DesktopCaptureOptions& options,
169     bool allow_delayed_capturable_check) {
170   return std::make_unique<WgcCapturerWin>(
171       options, std::make_unique<WgcWindowSourceFactory>(),
172       std::make_unique<WindowEnumerator>(
173           options.enumerate_current_process_windows()),
174       allow_delayed_capturable_check);
175 }
176 
177 // static
CreateRawScreenCapturer(const DesktopCaptureOptions & options)178 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer(
179     const DesktopCaptureOptions& options) {
180   return std::make_unique<WgcCapturerWin>(
181       options, std::make_unique<WgcScreenSourceFactory>(),
182       std::make_unique<ScreenEnumerator>(), false);
183 }
184 
GetSourceList(SourceList * sources)185 bool WgcCapturerWin::GetSourceList(SourceList* sources) {
186   return source_enumerator_->FindAllSources(sources);
187 }
188 
SelectSource(DesktopCapturer::SourceId id)189 bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) {
190   capture_source_ = source_factory_->CreateCaptureSource(id);
191   if (allow_delayed_capturable_check_)
192     return true;
193 
194   return capture_source_->IsCapturable();
195 }
196 
FocusOnSelectedSource()197 bool WgcCapturerWin::FocusOnSelectedSource() {
198   if (!capture_source_)
199     return false;
200 
201   return capture_source_->FocusOnSource();
202 }
203 
Start(Callback * callback)204 void WgcCapturerWin::Start(Callback* callback) {
205   RTC_DCHECK(!callback_);
206   RTC_DCHECK(callback);
207   RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin);
208 
209   callback_ = callback;
210 
211   // Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many
212   // parameters are nullptr as the implemention uses defaults that work well for
213   // us.
214   HRESULT hr = D3D11CreateDevice(
215       /*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE,
216       /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
217       /*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION,
218       &d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr);
219   if (hr == DXGI_ERROR_UNSUPPORTED) {
220     // If a hardware device could not be created, use WARP which is a high speed
221     // software device.
222     hr = D3D11CreateDevice(
223         /*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP,
224         /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
225         /*feature_levels=*/nullptr, /*feature_levels_size=*/0,
226         D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr,
227         /*device_context=*/nullptr);
228   }
229 
230   if (FAILED(hr)) {
231     RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr;
232   }
233 }
234 
CaptureFrame()235 void WgcCapturerWin::CaptureFrame() {
236   RTC_DCHECK(callback_);
237 
238   if (!capture_source_) {
239     RTC_LOG(LS_ERROR) << "Source hasn't been selected";
240     callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
241                                /*frame=*/nullptr);
242     RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected);
243     return;
244   }
245 
246   if (!d3d11_device_) {
247     RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture.";
248     callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
249                                /*frame=*/nullptr);
250     RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice);
251     return;
252   }
253 
254   if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) {
255     RTC_LOG(LS_ERROR) << "Source is not capturable.";
256     callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
257                                /*frame=*/nullptr);
258     return;
259   }
260 
261   HRESULT hr;
262   if (!dispatcher_queue_created_) {
263     // Set the apartment type to NONE because this thread should already be COM
264     // initialized.
265     DispatcherQueueOptions options{
266         sizeof(DispatcherQueueOptions),
267         DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT,
268         DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE};
269     ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller;
270     hr = create_dispatcher_queue_controller_func_(options, &queue_controller);
271 
272     // If there is already a DispatcherQueue on this thread, that is fine. Its
273     // lifetime is tied to the thread's, and as long as the thread has one, even
274     // if we didn't create it, the capture session's events will be delivered on
275     // this thread.
276     if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) {
277       RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure);
278       callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
279                                  /*frame=*/nullptr);
280     } else {
281       dispatcher_queue_created_ = true;
282     }
283   }
284 
285   int64_t capture_start_time_nanos = rtc::TimeNanos();
286 
287   WgcCaptureSession* capture_session = nullptr;
288   std::map<SourceId, WgcCaptureSession>::iterator session_iter =
289       ongoing_captures_.find(capture_source_->GetSourceId());
290   if (session_iter == ongoing_captures_.end()) {
291     ComPtr<WGC::IGraphicsCaptureItem> item;
292     hr = capture_source_->GetCaptureItem(&item);
293     if (FAILED(hr)) {
294       RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr;
295       callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
296                                  /*frame=*/nullptr);
297       RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure);
298       return;
299     }
300 
301     std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool>
302         iter_success_pair = ongoing_captures_.emplace(
303             std::piecewise_construct,
304             std::forward_as_tuple(capture_source_->GetSourceId()),
305             std::forward_as_tuple(d3d11_device_, item,
306                                   capture_source_->GetSize()));
307     RTC_DCHECK(iter_success_pair.second);
308     capture_session = &iter_success_pair.first->second;
309   } else {
310     capture_session = &session_iter->second;
311   }
312 
313   if (!capture_session->IsCaptureStarted()) {
314     hr = capture_session->StartCapture(options_);
315     if (FAILED(hr)) {
316       RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr;
317       ongoing_captures_.erase(capture_source_->GetSourceId());
318       callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
319                                  /*frame=*/nullptr);
320       RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure);
321       return;
322     }
323   }
324 
325   std::unique_ptr<DesktopFrame> frame;
326   hr = capture_session->GetFrame(&frame);
327   if (FAILED(hr)) {
328     RTC_LOG(LS_ERROR) << "GetFrame failed: " << hr;
329     ongoing_captures_.erase(capture_source_->GetSourceId());
330     callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
331                                /*frame=*/nullptr);
332     RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure);
333     return;
334   }
335 
336   if (!frame) {
337     callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
338                                /*frame=*/nullptr);
339     RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped);
340     return;
341   }
342 
343   int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
344                         rtc::kNumNanosecsPerMillisec;
345   RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime",
346                             capture_time_ms);
347   frame->set_capture_time_ms(capture_time_ms);
348   frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin);
349   frame->set_may_contain_cursor(options_.prefer_cursor_embedded());
350   frame->set_top_left(capture_source_->GetTopLeft());
351   RecordWgcCapturerResult(WgcCapturerResult::kSuccess);
352   callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
353                              std::move(frame));
354 }
355 
IsSourceBeingCaptured(DesktopCapturer::SourceId id)356 bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) {
357   std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator
358       session_iter = ongoing_captures_.find(id);
359   if (session_iter == ongoing_captures_.end())
360     return false;
361 
362   return session_iter->second.IsCaptureStarted();
363 }
364 
365 }  // namespace webrtc
366