1 /*
2 * Copyright (c) 2016 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/dxgi_duplicator_controller.h"
12
13 #include <windows.h>
14
15 #include <algorithm>
16 #include <string>
17
18 #include "modules/desktop_capture/desktop_capture_types.h"
19 #include "modules/desktop_capture/win/dxgi_frame.h"
20 #include "modules/desktop_capture/win/screen_capture_utils.h"
21 #include "rtc_base/checks.h"
22 #include "rtc_base/logging.h"
23 #include "rtc_base/time_utils.h"
24 #include "system_wrappers/include/sleep.h"
25
26 namespace webrtc {
27
28 namespace {
29
30 constexpr DWORD kInvalidSessionId = 0xFFFFFFFF;
31
GetCurrentSessionId()32 DWORD GetCurrentSessionId() {
33 DWORD session_id = kInvalidSessionId;
34 if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
35 RTC_LOG(LS_WARNING)
36 << "Failed to retrieve current session Id, current binary "
37 "may not have required priviledge.";
38 }
39 return session_id;
40 }
41
IsConsoleSession()42 bool IsConsoleSession() {
43 return WTSGetActiveConsoleSessionId() == GetCurrentSessionId();
44 }
45
46 } // namespace
47
48 // static
ResultName(DxgiDuplicatorController::Result result)49 std::string DxgiDuplicatorController::ResultName(
50 DxgiDuplicatorController::Result result) {
51 switch (result) {
52 case Result::SUCCEEDED:
53 return "Succeeded";
54 case Result::UNSUPPORTED_SESSION:
55 return "Unsupported session";
56 case Result::FRAME_PREPARE_FAILED:
57 return "Frame preparation failed";
58 case Result::INITIALIZATION_FAILED:
59 return "Initialization failed";
60 case Result::DUPLICATION_FAILED:
61 return "Duplication failed";
62 case Result::INVALID_MONITOR_ID:
63 return "Invalid monitor id";
64 default:
65 return "Unknown error";
66 }
67 }
68
69 // static
70 rtc::scoped_refptr<DxgiDuplicatorController>
Instance()71 DxgiDuplicatorController::Instance() {
72 // The static instance won't be deleted to ensure it can be used by other
73 // threads even during program exiting.
74 static DxgiDuplicatorController* instance = new DxgiDuplicatorController();
75 return rtc::scoped_refptr<DxgiDuplicatorController>(instance);
76 }
77
78 // static
IsCurrentSessionSupported()79 bool DxgiDuplicatorController::IsCurrentSessionSupported() {
80 DWORD current_session_id = GetCurrentSessionId();
81 return current_session_id != kInvalidSessionId && current_session_id != 0;
82 }
83
DxgiDuplicatorController()84 DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {}
85
AddRef()86 void DxgiDuplicatorController::AddRef() {
87 int refcount = (++refcount_);
88 RTC_DCHECK(refcount > 0);
89 }
90
Release()91 void DxgiDuplicatorController::Release() {
92 int refcount = (--refcount_);
93 RTC_DCHECK(refcount >= 0);
94 if (refcount == 0) {
95 RTC_LOG(LS_WARNING) << "Count of references reaches zero, "
96 "DxgiDuplicatorController will be unloaded.";
97 Unload();
98 }
99 }
100
IsSupported()101 bool DxgiDuplicatorController::IsSupported() {
102 MutexLock lock(&mutex_);
103 return Initialize();
104 }
105
RetrieveD3dInfo(D3dInfo * info)106 bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
107 bool result = false;
108 {
109 MutexLock lock(&mutex_);
110 result = Initialize();
111 *info = d3d_info_;
112 }
113 if (!result) {
114 RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo "
115 "retrieved may not accurate or out of date.";
116 }
117 return result;
118 }
119
Duplicate(DxgiFrame * frame)120 DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(
121 DxgiFrame* frame) {
122 return DoDuplicate(frame, -1);
123 }
124
DuplicateMonitor(DxgiFrame * frame,int monitor_id)125 DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(
126 DxgiFrame* frame,
127 int monitor_id) {
128 RTC_DCHECK_GE(monitor_id, 0);
129 return DoDuplicate(frame, monitor_id);
130 }
131
dpi()132 DesktopVector DxgiDuplicatorController::dpi() {
133 MutexLock lock(&mutex_);
134 if (Initialize()) {
135 return dpi_;
136 }
137 return DesktopVector();
138 }
139
ScreenCount()140 int DxgiDuplicatorController::ScreenCount() {
141 MutexLock lock(&mutex_);
142 if (Initialize()) {
143 return ScreenCountUnlocked();
144 }
145 return 0;
146 }
147
GetDeviceNames(std::vector<std::string> * output)148 bool DxgiDuplicatorController::GetDeviceNames(
149 std::vector<std::string>* output) {
150 MutexLock lock(&mutex_);
151 if (Initialize()) {
152 GetDeviceNamesUnlocked(output);
153 return true;
154 }
155 return false;
156 }
157
DoDuplicate(DxgiFrame * frame,int monitor_id)158 DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(
159 DxgiFrame* frame,
160 int monitor_id) {
161 RTC_DCHECK(frame);
162 MutexLock lock(&mutex_);
163
164 // The dxgi components and APIs do not update the screen resolution without
165 // a reinitialization. So we use the GetDC() function to retrieve the screen
166 // resolution to decide whether dxgi components need to be reinitialized.
167 // If the screen resolution changed, it's very likely the next Duplicate()
168 // function call will fail because of a missing monitor or the frame size is
169 // not enough to store the output. So we reinitialize dxgi components in-place
170 // to avoid a capture failure.
171 // But there is no guarantee GetDC() function returns the same resolution as
172 // dxgi APIs, we still rely on dxgi components to return the output frame
173 // size.
174 // TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
175 // IDXGIOutputDuplication::GetDesc() can detect the resolution change without
176 // reinitialization.
177 if (display_configuration_monitor_.IsChanged()) {
178 Deinitialize();
179 }
180
181 if (!Initialize()) {
182 if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) {
183 RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI "
184 "components cannot be initialized.";
185 return Result::UNSUPPORTED_SESSION;
186 }
187
188 // Cannot initialize COM components now, display mode may be changing.
189 return Result::INITIALIZATION_FAILED;
190 }
191
192 if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) {
193 return Result::FRAME_PREPARE_FAILED;
194 }
195
196 frame->frame()->mutable_updated_region()->Clear();
197
198 if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) {
199 succeeded_duplications_++;
200 return Result::SUCCEEDED;
201 }
202 if (monitor_id >= ScreenCountUnlocked()) {
203 // It's a user error to provide a `monitor_id` larger than screen count. We
204 // do not need to deinitialize.
205 return Result::INVALID_MONITOR_ID;
206 }
207
208 // If the `monitor_id` is valid, but DoDuplicateUnlocked() failed, something
209 // must be wrong from capturer APIs. We should Deinitialize().
210 Deinitialize();
211 return Result::DUPLICATION_FAILED;
212 }
213
Unload()214 void DxgiDuplicatorController::Unload() {
215 MutexLock lock(&mutex_);
216 Deinitialize();
217 }
218
Unregister(const Context * const context)219 void DxgiDuplicatorController::Unregister(const Context* const context) {
220 MutexLock lock(&mutex_);
221 if (ContextExpired(context)) {
222 // The Context has not been setup after a recent initialization, so it
223 // should not been registered in duplicators.
224 return;
225 }
226 for (size_t i = 0; i < duplicators_.size(); i++) {
227 duplicators_[i].Unregister(&context->contexts[i]);
228 }
229 }
230
Initialize()231 bool DxgiDuplicatorController::Initialize() {
232 if (!duplicators_.empty()) {
233 return true;
234 }
235
236 if (DoInitialize()) {
237 return true;
238 }
239 Deinitialize();
240 return false;
241 }
242
DoInitialize()243 bool DxgiDuplicatorController::DoInitialize() {
244 RTC_DCHECK(desktop_rect_.is_empty());
245 RTC_DCHECK(duplicators_.empty());
246
247 d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
248 d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
249
250 std::vector<D3dDevice> devices = D3dDevice::EnumDevices();
251 if (devices.empty()) {
252 RTC_LOG(LS_WARNING) << "No D3dDevice found.";
253 return false;
254 }
255
256 for (size_t i = 0; i < devices.size(); i++) {
257 D3D_FEATURE_LEVEL feature_level =
258 devices[i].d3d_device()->GetFeatureLevel();
259 if (d3d_info_.max_feature_level == 0 ||
260 feature_level > d3d_info_.max_feature_level) {
261 d3d_info_.max_feature_level = feature_level;
262 }
263 if (d3d_info_.min_feature_level == 0 ||
264 feature_level < d3d_info_.min_feature_level) {
265 d3d_info_.min_feature_level = feature_level;
266 }
267
268 DxgiAdapterDuplicator duplicator(devices[i]);
269 // There may be several video cards on the system, some of them may not
270 // support IDXGOutputDuplication. But they should not impact others from
271 // taking effect, so we should continually try other adapters. This usually
272 // happens when a non-official virtual adapter is installed on the system.
273 if (!duplicator.Initialize()) {
274 RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on "
275 "adapter "
276 << i;
277 continue;
278 }
279 RTC_DCHECK(!duplicator.desktop_rect().is_empty());
280 duplicators_.push_back(std::move(duplicator));
281
282 desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
283 }
284 TranslateRect();
285
286 HDC hdc = GetDC(nullptr);
287 // Use old DPI value if failed.
288 if (hdc) {
289 dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY));
290 ReleaseDC(nullptr, hdc);
291 }
292
293 identity_++;
294
295 if (duplicators_.empty()) {
296 RTC_LOG(LS_WARNING)
297 << "Cannot initialize any DxgiAdapterDuplicator instance.";
298 }
299
300 return !duplicators_.empty();
301 }
302
Deinitialize()303 void DxgiDuplicatorController::Deinitialize() {
304 desktop_rect_ = DesktopRect();
305 duplicators_.clear();
306 display_configuration_monitor_.Reset();
307 }
308
ContextExpired(const Context * const context) const309 bool DxgiDuplicatorController::ContextExpired(
310 const Context* const context) const {
311 RTC_DCHECK(context);
312 return context->controller_id != identity_ ||
313 context->contexts.size() != duplicators_.size();
314 }
315
Setup(Context * context)316 void DxgiDuplicatorController::Setup(Context* context) {
317 if (ContextExpired(context)) {
318 RTC_DCHECK(context);
319 context->contexts.clear();
320 context->contexts.resize(duplicators_.size());
321 for (size_t i = 0; i < duplicators_.size(); i++) {
322 duplicators_[i].Setup(&context->contexts[i]);
323 }
324 context->controller_id = identity_;
325 }
326 }
327
DoDuplicateUnlocked(Context * context,int monitor_id,SharedDesktopFrame * target)328 bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
329 int monitor_id,
330 SharedDesktopFrame* target) {
331 Setup(context);
332
333 if (!EnsureFrameCaptured(context, target)) {
334 return false;
335 }
336
337 bool result = false;
338 if (monitor_id < 0) {
339 // Capture entire screen.
340 result = DoDuplicateAll(context, target);
341 } else {
342 result = DoDuplicateOne(context, monitor_id, target);
343 }
344
345 if (result) {
346 target->set_dpi(dpi_);
347 return true;
348 }
349
350 return false;
351 }
352
DoDuplicateAll(Context * context,SharedDesktopFrame * target)353 bool DxgiDuplicatorController::DoDuplicateAll(Context* context,
354 SharedDesktopFrame* target) {
355 for (size_t i = 0; i < duplicators_.size(); i++) {
356 if (!duplicators_[i].Duplicate(&context->contexts[i], target)) {
357 return false;
358 }
359 }
360 return true;
361 }
362
DoDuplicateOne(Context * context,int monitor_id,SharedDesktopFrame * target)363 bool DxgiDuplicatorController::DoDuplicateOne(Context* context,
364 int monitor_id,
365 SharedDesktopFrame* target) {
366 RTC_DCHECK(monitor_id >= 0);
367 for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size();
368 i++) {
369 if (monitor_id >= duplicators_[i].screen_count()) {
370 monitor_id -= duplicators_[i].screen_count();
371 } else {
372 if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id,
373 target)) {
374 target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left());
375 return true;
376 }
377 return false;
378 }
379 }
380 return false;
381 }
382
GetNumFramesCaptured() const383 int64_t DxgiDuplicatorController::GetNumFramesCaptured() const {
384 int64_t min = INT64_MAX;
385 for (const auto& duplicator : duplicators_) {
386 min = std::min(min, duplicator.GetNumFramesCaptured());
387 }
388
389 return min;
390 }
391
desktop_size() const392 DesktopSize DxgiDuplicatorController::desktop_size() const {
393 return desktop_rect_.size();
394 }
395
ScreenRect(int id) const396 DesktopRect DxgiDuplicatorController::ScreenRect(int id) const {
397 RTC_DCHECK(id >= 0);
398 for (size_t i = 0; i < duplicators_.size(); i++) {
399 if (id >= duplicators_[i].screen_count()) {
400 id -= duplicators_[i].screen_count();
401 } else {
402 return duplicators_[i].ScreenRect(id);
403 }
404 }
405 return DesktopRect();
406 }
407
ScreenCountUnlocked() const408 int DxgiDuplicatorController::ScreenCountUnlocked() const {
409 int result = 0;
410 for (auto& duplicator : duplicators_) {
411 result += duplicator.screen_count();
412 }
413 return result;
414 }
415
GetDeviceNamesUnlocked(std::vector<std::string> * output) const416 void DxgiDuplicatorController::GetDeviceNamesUnlocked(
417 std::vector<std::string>* output) const {
418 RTC_DCHECK(output);
419 for (auto& duplicator : duplicators_) {
420 for (int i = 0; i < duplicator.screen_count(); i++) {
421 output->push_back(duplicator.GetDeviceName(i));
422 }
423 }
424 }
425
SelectedDesktopSize(int monitor_id) const426 DesktopSize DxgiDuplicatorController::SelectedDesktopSize(
427 int monitor_id) const {
428 if (monitor_id < 0) {
429 return desktop_size();
430 }
431
432 return ScreenRect(monitor_id).size();
433 }
434
EnsureFrameCaptured(Context * context,SharedDesktopFrame * target)435 bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
436 SharedDesktopFrame* target) {
437 // On a modern system, the FPS / monitor refresh rate is usually larger than
438 // or equal to 60. So 17 milliseconds is enough to capture at least one frame.
439 const int64_t ms_per_frame = 17;
440 // Skip frames to ensure a full frame refresh has occurred and the DXGI
441 // machinery is producing frames before this function returns.
442 int64_t frames_to_skip = 1;
443 // The total time out milliseconds for this function. If we cannot get enough
444 // frames during this time interval, this function returns false, and cause
445 // the DXGI components to be reinitialized. This usually should not happen
446 // unless the system is switching display mode when this function is being
447 // called. 500 milliseconds should be enough for ~30 frames.
448 const int64_t timeout_ms = 500;
449
450 if (GetNumFramesCaptured() == 0 && !IsConsoleSession()) {
451 // When capturing a console session, waiting for a single frame is
452 // sufficient to ensure that DXGI output duplication is working. When the
453 // session is not attached to the console, it has been observed that DXGI
454 // may produce up to 4 frames (typically 1-2 though) before stopping. When
455 // this condition occurs, no errors are returned from the output duplication
456 // API, it simply appears that nothing is changing on the screen. Thus for
457 // detached sessions, we need to capture a few extra frames before we can be
458 // confident that output duplication was initialized properly.
459 frames_to_skip = 5;
460 }
461
462 if (GetNumFramesCaptured() >= frames_to_skip) {
463 return true;
464 }
465
466 std::unique_ptr<SharedDesktopFrame> fallback_frame;
467 SharedDesktopFrame* shared_frame = nullptr;
468 if (target->size().width() >= desktop_size().width() &&
469 target->size().height() >= desktop_size().height()) {
470 // `target` is large enough to cover entire screen, we do not need to use
471 // `fallback_frame`.
472 shared_frame = target;
473 } else {
474 fallback_frame = SharedDesktopFrame::Wrap(
475 std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(desktop_size())));
476 shared_frame = fallback_frame.get();
477 }
478
479 const int64_t start_ms = rtc::TimeMillis();
480 while (GetNumFramesCaptured() < frames_to_skip) {
481 if (!DoDuplicateAll(context, shared_frame)) {
482 return false;
483 }
484
485 // Calling DoDuplicateAll() may change the number of frames captured.
486 if (GetNumFramesCaptured() >= frames_to_skip) {
487 break;
488 }
489
490 if (rtc::TimeMillis() - start_ms > timeout_ms) {
491 RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip
492 << " frames "
493 "within "
494 << timeout_ms << " milliseconds.";
495 return false;
496 }
497
498 // Sleep `ms_per_frame` before attempting to capture the next frame to
499 // ensure the video adapter has time to update the screen.
500 webrtc::SleepMs(ms_per_frame);
501 }
502 return true;
503 }
504
TranslateRect()505 void DxgiDuplicatorController::TranslateRect() {
506 const DesktopVector position =
507 DesktopVector().subtract(desktop_rect_.top_left());
508 desktop_rect_.Translate(position);
509 for (auto& duplicator : duplicators_) {
510 duplicator.TranslateRect(position);
511 }
512 }
513
514 } // namespace webrtc
515