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