• 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 // 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() : refcount_(0) {}
71 
AddRef()72 void DxgiDuplicatorController::AddRef() {
73   int refcount = (++refcount_);
74   RTC_DCHECK(refcount > 0);
75 }
76 
Release()77 void DxgiDuplicatorController::Release() {
78   int refcount = (--refcount_);
79   RTC_DCHECK(refcount >= 0);
80   if (refcount == 0) {
81     RTC_LOG(LS_WARNING) << "Count of references reaches zero, "
82                            "DxgiDuplicatorController will be unloaded.";
83     Unload();
84   }
85 }
86 
IsSupported()87 bool DxgiDuplicatorController::IsSupported() {
88   rtc::CritScope lock(&lock_);
89   return Initialize();
90 }
91 
RetrieveD3dInfo(D3dInfo * info)92 bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
93   bool result = false;
94   {
95     rtc::CritScope lock(&lock_);
96     result = Initialize();
97     *info = d3d_info_;
98   }
99   if (!result) {
100     RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo "
101                            "retrieved may not accurate or out of date.";
102   }
103   return result;
104 }
105 
Duplicate(DxgiFrame * frame)106 DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(
107     DxgiFrame* frame) {
108   return DoDuplicate(frame, -1);
109 }
110 
DuplicateMonitor(DxgiFrame * frame,int monitor_id)111 DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(
112     DxgiFrame* frame,
113     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 
DoDuplicate(DxgiFrame * frame,int monitor_id)144 DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(
145     DxgiFrame* frame,
146     int monitor_id) {
147   RTC_DCHECK(frame);
148   rtc::CritScope lock(&lock_);
149 
150   // The dxgi components and APIs do not update the screen resolution without
151   // a reinitialization. So we use the GetDC() function to retrieve the screen
152   // resolution to decide whether dxgi components need to be reinitialized.
153   // If the screen resolution changed, it's very likely the next Duplicate()
154   // function call will fail because of a missing monitor or the frame size is
155   // not enough to store the output. So we reinitialize dxgi components in-place
156   // to avoid a capture failure.
157   // But there is no guarantee GetDC() function returns the same resolution as
158   // dxgi APIs, we still rely on dxgi components to return the output frame
159   // size.
160   // TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
161   // IDXGIOutputDuplication::GetDesc() can detect the resolution change without
162   // reinitialization.
163   if (display_configuration_monitor_.IsChanged()) {
164     Deinitialize();
165   }
166 
167   if (!Initialize()) {
168     if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) {
169       RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI "
170                              "components cannot be initialized.";
171       return Result::UNSUPPORTED_SESSION;
172     }
173 
174     // Cannot initialize COM components now, display mode may be changing.
175     return Result::INITIALIZATION_FAILED;
176   }
177 
178   if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) {
179     return Result::FRAME_PREPARE_FAILED;
180   }
181 
182   frame->frame()->mutable_updated_region()->Clear();
183 
184   if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) {
185     succeeded_duplications_++;
186     return Result::SUCCEEDED;
187   }
188   if (monitor_id >= ScreenCountUnlocked()) {
189     // It's a user error to provide a |monitor_id| larger than screen count. We
190     // do not need to deinitialize.
191     return Result::INVALID_MONITOR_ID;
192   }
193 
194   // If the |monitor_id| is valid, but DoDuplicateUnlocked() failed, something
195   // must be wrong from capturer APIs. We should Deinitialize().
196   Deinitialize();
197   return Result::DUPLICATION_FAILED;
198 }
199 
Unload()200 void DxgiDuplicatorController::Unload() {
201   rtc::CritScope lock(&lock_);
202   Deinitialize();
203 }
204 
Unregister(const Context * const context)205 void DxgiDuplicatorController::Unregister(const Context* const context) {
206   rtc::CritScope lock(&lock_);
207   if (ContextExpired(context)) {
208     // The Context has not been setup after a recent initialization, so it
209     // should not been registered in duplicators.
210     return;
211   }
212   for (size_t i = 0; i < duplicators_.size(); i++) {
213     duplicators_[i].Unregister(&context->contexts[i]);
214   }
215 }
216 
Initialize()217 bool DxgiDuplicatorController::Initialize() {
218   if (!duplicators_.empty()) {
219     return true;
220   }
221 
222   if (DoInitialize()) {
223     return true;
224   }
225   Deinitialize();
226   return false;
227 }
228 
DoInitialize()229 bool DxgiDuplicatorController::DoInitialize() {
230   RTC_DCHECK(desktop_rect_.is_empty());
231   RTC_DCHECK(duplicators_.empty());
232 
233   d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
234   d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
235 
236   std::vector<D3dDevice> devices = D3dDevice::EnumDevices();
237   if (devices.empty()) {
238     RTC_LOG(LS_WARNING) << "No D3dDevice found.";
239     return false;
240   }
241 
242   for (size_t i = 0; i < devices.size(); i++) {
243     D3D_FEATURE_LEVEL feature_level =
244         devices[i].d3d_device()->GetFeatureLevel();
245     if (d3d_info_.max_feature_level == 0 ||
246         feature_level > d3d_info_.max_feature_level) {
247       d3d_info_.max_feature_level = feature_level;
248     }
249     if (d3d_info_.min_feature_level == 0 ||
250         feature_level < d3d_info_.min_feature_level) {
251       d3d_info_.min_feature_level = feature_level;
252     }
253 
254     DxgiAdapterDuplicator duplicator(devices[i]);
255     // There may be several video cards on the system, some of them may not
256     // support IDXGOutputDuplication. But they should not impact others from
257     // taking effect, so we should continually try other adapters. This usually
258     // happens when a non-official virtual adapter is installed on the system.
259     if (!duplicator.Initialize()) {
260       RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on "
261                              "adapter "
262                           << i;
263       continue;
264     }
265     RTC_DCHECK(!duplicator.desktop_rect().is_empty());
266     duplicators_.push_back(std::move(duplicator));
267 
268     desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
269   }
270   TranslateRect();
271 
272   HDC hdc = GetDC(nullptr);
273   // Use old DPI value if failed.
274   if (hdc) {
275     dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY));
276     ReleaseDC(nullptr, hdc);
277   }
278 
279   identity_++;
280 
281   if (duplicators_.empty()) {
282     RTC_LOG(LS_WARNING)
283         << "Cannot initialize any DxgiAdapterDuplicator instance.";
284   }
285 
286   return !duplicators_.empty();
287 }
288 
Deinitialize()289 void DxgiDuplicatorController::Deinitialize() {
290   desktop_rect_ = DesktopRect();
291   duplicators_.clear();
292   display_configuration_monitor_.Reset();
293 }
294 
ContextExpired(const Context * const context) const295 bool DxgiDuplicatorController::ContextExpired(
296     const Context* const context) const {
297   RTC_DCHECK(context);
298   return context->controller_id != identity_ ||
299          context->contexts.size() != duplicators_.size();
300 }
301 
Setup(Context * context)302 void DxgiDuplicatorController::Setup(Context* context) {
303   if (ContextExpired(context)) {
304     RTC_DCHECK(context);
305     context->contexts.clear();
306     context->contexts.resize(duplicators_.size());
307     for (size_t i = 0; i < duplicators_.size(); i++) {
308       duplicators_[i].Setup(&context->contexts[i]);
309     }
310     context->controller_id = identity_;
311   }
312 }
313 
DoDuplicateUnlocked(Context * context,int monitor_id,SharedDesktopFrame * target)314 bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
315                                                    int monitor_id,
316                                                    SharedDesktopFrame* target) {
317   Setup(context);
318 
319   if (!EnsureFrameCaptured(context, target)) {
320     return false;
321   }
322 
323   bool result = false;
324   if (monitor_id < 0) {
325     // Capture entire screen.
326     result = DoDuplicateAll(context, target);
327   } else {
328     result = DoDuplicateOne(context, monitor_id, target);
329   }
330 
331   if (result) {
332     target->set_dpi(dpi_);
333     return true;
334   }
335 
336   return false;
337 }
338 
DoDuplicateAll(Context * context,SharedDesktopFrame * target)339 bool DxgiDuplicatorController::DoDuplicateAll(Context* context,
340                                               SharedDesktopFrame* target) {
341   for (size_t i = 0; i < duplicators_.size(); i++) {
342     if (!duplicators_[i].Duplicate(&context->contexts[i], target)) {
343       return false;
344     }
345   }
346   return true;
347 }
348 
DoDuplicateOne(Context * context,int monitor_id,SharedDesktopFrame * target)349 bool DxgiDuplicatorController::DoDuplicateOne(Context* context,
350                                               int monitor_id,
351                                               SharedDesktopFrame* target) {
352   RTC_DCHECK(monitor_id >= 0);
353   for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size();
354        i++) {
355     if (monitor_id >= duplicators_[i].screen_count()) {
356       monitor_id -= duplicators_[i].screen_count();
357     } else {
358       if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id,
359                                            target)) {
360         target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left());
361         return true;
362       }
363       return false;
364     }
365   }
366   return false;
367 }
368 
GetNumFramesCaptured() const369 int64_t DxgiDuplicatorController::GetNumFramesCaptured() const {
370   int64_t min = INT64_MAX;
371   for (const auto& duplicator : duplicators_) {
372     min = std::min(min, duplicator.GetNumFramesCaptured());
373   }
374 
375   return min;
376 }
377 
desktop_size() const378 DesktopSize DxgiDuplicatorController::desktop_size() const {
379   return desktop_rect_.size();
380 }
381 
ScreenRect(int id) const382 DesktopRect DxgiDuplicatorController::ScreenRect(int id) const {
383   RTC_DCHECK(id >= 0);
384   for (size_t i = 0; i < duplicators_.size(); i++) {
385     if (id >= duplicators_[i].screen_count()) {
386       id -= duplicators_[i].screen_count();
387     } else {
388       return duplicators_[i].ScreenRect(id);
389     }
390   }
391   return DesktopRect();
392 }
393 
ScreenCountUnlocked() const394 int DxgiDuplicatorController::ScreenCountUnlocked() const {
395   int result = 0;
396   for (auto& duplicator : duplicators_) {
397     result += duplicator.screen_count();
398   }
399   return result;
400 }
401 
GetDeviceNamesUnlocked(std::vector<std::string> * output) const402 void DxgiDuplicatorController::GetDeviceNamesUnlocked(
403     std::vector<std::string>* output) const {
404   RTC_DCHECK(output);
405   for (auto& duplicator : duplicators_) {
406     for (int i = 0; i < duplicator.screen_count(); i++) {
407       output->push_back(duplicator.GetDeviceName(i));
408     }
409   }
410 }
411 
SelectedDesktopSize(int monitor_id) const412 DesktopSize DxgiDuplicatorController::SelectedDesktopSize(
413     int monitor_id) const {
414   if (monitor_id < 0) {
415     return desktop_size();
416   }
417 
418   return ScreenRect(monitor_id).size();
419 }
420 
EnsureFrameCaptured(Context * context,SharedDesktopFrame * target)421 bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
422                                                    SharedDesktopFrame* target) {
423   // On a modern system, the FPS / monitor refresh rate is usually larger than
424   // or equal to 60. So 17 milliseconds is enough to capture at least one frame.
425   const int64_t ms_per_frame = 17;
426   // Skips the first frame to ensure a full frame refresh has happened before
427   // this function returns.
428   const int64_t frames_to_skip = 1;
429   // The total time out milliseconds for this function. If we cannot get enough
430   // frames during this time interval, this function returns false, and cause
431   // the DXGI components to be reinitialized. This usually should not happen
432   // unless the system is switching display mode when this function is being
433   // called. 500 milliseconds should be enough for ~30 frames.
434   const int64_t timeout_ms = 500;
435   if (GetNumFramesCaptured() >= frames_to_skip) {
436     return true;
437   }
438 
439   std::unique_ptr<SharedDesktopFrame> fallback_frame;
440   SharedDesktopFrame* shared_frame = nullptr;
441   if (target->size().width() >= desktop_size().width() &&
442       target->size().height() >= desktop_size().height()) {
443     // |target| is large enough to cover entire screen, we do not need to use
444     // |fallback_frame|.
445     shared_frame = target;
446   } else {
447     fallback_frame = SharedDesktopFrame::Wrap(
448         std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(desktop_size())));
449     shared_frame = fallback_frame.get();
450   }
451 
452   const int64_t start_ms = rtc::TimeMillis();
453   int64_t last_frame_start_ms = 0;
454   while (GetNumFramesCaptured() < frames_to_skip) {
455     if (GetNumFramesCaptured() > 0) {
456       // Sleep |ms_per_frame| before capturing next frame to ensure the screen
457       // has been updated by the video adapter.
458       webrtc::SleepMs(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