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