• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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