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