/* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/desktop_capture/win/dxgi_duplicator_controller.h" #include #include #include #include "modules/desktop_capture/desktop_capture_types.h" #include "modules/desktop_capture/win/dxgi_frame.h" #include "modules/desktop_capture/win/screen_capture_utils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/timeutils.h" #include "system_wrappers/include/sleep.h" namespace webrtc { // static std::string DxgiDuplicatorController::ResultName( DxgiDuplicatorController::Result result) { switch (result) { case Result::SUCCEEDED: return "Succeeded"; case Result::UNSUPPORTED_SESSION: return "Unsupported session"; case Result::FRAME_PREPARE_FAILED: return "Frame preparation failed"; case Result::INITIALIZATION_FAILED: return "Initialization failed"; case Result::DUPLICATION_FAILED: return "Duplication failed"; case Result::INVALID_MONITOR_ID: return "Invalid monitor id"; default: return "Unknown error"; } } // static rtc::scoped_refptr DxgiDuplicatorController::Instance() { // The static instance won't be deleted to ensure it can be used by other // threads even during program exiting. static DxgiDuplicatorController* instance = new DxgiDuplicatorController(); return rtc::scoped_refptr(instance); } // static bool DxgiDuplicatorController::IsCurrentSessionSupported() { DWORD session_id = 0; if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) { RTC_LOG(LS_WARNING) << "Failed to retrieve current session Id, current binary " "may not have required priviledge."; return false; } return session_id != 0; } DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {} void DxgiDuplicatorController::AddRef() { int refcount = (++refcount_); RTC_DCHECK(refcount > 0); } void DxgiDuplicatorController::Release() { int refcount = (--refcount_); RTC_DCHECK(refcount >= 0); if (refcount == 0) { RTC_LOG(LS_WARNING) << "Count of references reaches zero, " "DxgiDuplicatorController will be unloaded."; Unload(); } } bool DxgiDuplicatorController::IsSupported() { rtc::CritScope lock(&lock_); return Initialize(); } bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) { bool result = false; { rtc::CritScope lock(&lock_); result = Initialize(); *info = d3d_info_; } if (!result) { RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo " "retrieved may not accurate or out of date."; } return result; } DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(DxgiFrame* frame) { return DoDuplicate(frame, -1); } DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(DxgiFrame* frame, int monitor_id) { RTC_DCHECK_GE(monitor_id, 0); return DoDuplicate(frame, monitor_id); } DesktopVector DxgiDuplicatorController::dpi() { rtc::CritScope lock(&lock_); if (Initialize()) { return dpi_; } return DesktopVector(); } int DxgiDuplicatorController::ScreenCount() { rtc::CritScope lock(&lock_); if (Initialize()) { return ScreenCountUnlocked(); } return 0; } bool DxgiDuplicatorController::GetDeviceNames( std::vector* output) { rtc::CritScope lock(&lock_); if (Initialize()) { GetDeviceNamesUnlocked(output); return true; } return false; } DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(DxgiFrame* frame, int monitor_id) { RTC_DCHECK(frame); rtc::CritScope lock(&lock_); // The dxgi components and APIs do not update the screen resolution without // a reinitialization. So we use the GetDC() function to retrieve the screen // resolution to decide whether dxgi components need to be reinitialized. // If the screen resolution changed, it's very likely the next Duplicate() // function call will fail because of a missing monitor or the frame size is // not enough to store the output. So we reinitialize dxgi components in-place // to avoid a capture failure. // But there is no guarantee GetDC() function returns the same resolution as // dxgi APIs, we still rely on dxgi components to return the output frame // size. // TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and // IDXGIOutputDuplication::GetDesc() can detect the resolution change without // reinitialization. if (display_configuration_monitor_.IsChanged()) { Deinitialize(); } if (!Initialize()) { if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) { RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI " "components cannot be initialized."; return Result::UNSUPPORTED_SESSION; } // Cannot initialize COM components now, display mode may be changing. return Result::INITIALIZATION_FAILED; } if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) { return Result::FRAME_PREPARE_FAILED; } frame->frame()->mutable_updated_region()->Clear(); if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) { succeeded_duplications_++; return Result::SUCCEEDED; } if (monitor_id >= ScreenCountUnlocked()) { // It's a user error to provide a |monitor_id| larger than screen count. We // do not need to deinitialize. return Result::INVALID_MONITOR_ID; } // If the |monitor_id| is valid, but DoDuplicateUnlocked() failed, something // must be wrong from capturer APIs. We should Deinitialize(). Deinitialize(); return Result::DUPLICATION_FAILED; } void DxgiDuplicatorController::Unload() { rtc::CritScope lock(&lock_); Deinitialize(); } void DxgiDuplicatorController::Unregister(const Context* const context) { rtc::CritScope lock(&lock_); if (ContextExpired(context)) { // The Context has not been setup after a recent initialization, so it // should not been registered in duplicators. return; } for (size_t i = 0; i < duplicators_.size(); i++) { duplicators_[i].Unregister(&context->contexts[i]); } } bool DxgiDuplicatorController::Initialize() { if (!duplicators_.empty()) { return true; } if (DoInitialize()) { return true; } Deinitialize(); return false; } bool DxgiDuplicatorController::DoInitialize() { RTC_DCHECK(desktop_rect_.is_empty()); RTC_DCHECK(duplicators_.empty()); d3d_info_.min_feature_level = static_cast(0); d3d_info_.max_feature_level = static_cast(0); std::vector devices = D3dDevice::EnumDevices(); if (devices.empty()) { RTC_LOG(LS_WARNING) << "No D3dDevice found."; return false; } for (size_t i = 0; i < devices.size(); i++) { D3D_FEATURE_LEVEL feature_level = devices[i].d3d_device()->GetFeatureLevel(); if (d3d_info_.max_feature_level == 0 || feature_level > d3d_info_.max_feature_level) { d3d_info_.max_feature_level = feature_level; } if (d3d_info_.min_feature_level == 0 || feature_level < d3d_info_.min_feature_level) { d3d_info_.min_feature_level = feature_level; } DxgiAdapterDuplicator duplicator(devices[i]); // There may be several video cards on the system, some of them may not // support IDXGOutputDuplication. But they should not impact others from // taking effect, so we should continually try other adapters. This usually // happens when a non-official virtual adapter is installed on the system. if (!duplicator.Initialize()) { RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on " "adapter " << i; continue; } RTC_DCHECK(!duplicator.desktop_rect().is_empty()); duplicators_.push_back(std::move(duplicator)); desktop_rect_.UnionWith(duplicators_.back().desktop_rect()); } TranslateRect(); HDC hdc = GetDC(nullptr); // Use old DPI value if failed. if (hdc) { dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); ReleaseDC(nullptr, hdc); } identity_++; if (duplicators_.empty()) { RTC_LOG(LS_WARNING) << "Cannot initialize any DxgiAdapterDuplicator instance."; } return !duplicators_.empty(); } void DxgiDuplicatorController::Deinitialize() { desktop_rect_ = DesktopRect(); duplicators_.clear(); display_configuration_monitor_.Reset(); } bool DxgiDuplicatorController::ContextExpired( const Context* const context) const { RTC_DCHECK(context); return context->controller_id != identity_ || context->contexts.size() != duplicators_.size(); } void DxgiDuplicatorController::Setup(Context* context) { if (ContextExpired(context)) { RTC_DCHECK(context); context->contexts.clear(); context->contexts.resize(duplicators_.size()); for (size_t i = 0; i < duplicators_.size(); i++) { duplicators_[i].Setup(&context->contexts[i]); } context->controller_id = identity_; } } bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context, int monitor_id, SharedDesktopFrame* target) { Setup(context); if (!EnsureFrameCaptured(context, target)) { return false; } bool result = false; if (monitor_id < 0) { // Capture entire screen. result = DoDuplicateAll(context, target); } else { result = DoDuplicateOne(context, monitor_id, target); } if (result) { target->set_dpi(dpi_); return true; } return false; } bool DxgiDuplicatorController::DoDuplicateAll(Context* context, SharedDesktopFrame* target) { for (size_t i = 0; i < duplicators_.size(); i++) { if (!duplicators_[i].Duplicate(&context->contexts[i], target)) { return false; } } return true; } bool DxgiDuplicatorController::DoDuplicateOne(Context* context, int monitor_id, SharedDesktopFrame* target) { RTC_DCHECK(monitor_id >= 0); for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size(); i++) { if (monitor_id >= duplicators_[i].screen_count()) { monitor_id -= duplicators_[i].screen_count(); } else { if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id, target)) { target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left()); return true; } return false; } } return false; } int64_t DxgiDuplicatorController::GetNumFramesCaptured() const { int64_t min = INT64_MAX; for (const auto& duplicator : duplicators_) { min = std::min(min, duplicator.GetNumFramesCaptured()); } return min; } DesktopSize DxgiDuplicatorController::desktop_size() const { return desktop_rect_.size(); } DesktopRect DxgiDuplicatorController::ScreenRect(int id) const { RTC_DCHECK(id >= 0); for (size_t i = 0; i < duplicators_.size(); i++) { if (id >= duplicators_[i].screen_count()) { id -= duplicators_[i].screen_count(); } else { return duplicators_[i].ScreenRect(id); } } return DesktopRect(); } int DxgiDuplicatorController::ScreenCountUnlocked() const { int result = 0; for (auto& duplicator : duplicators_) { result += duplicator.screen_count(); } return result; } void DxgiDuplicatorController::GetDeviceNamesUnlocked( std::vector* output) const { RTC_DCHECK(output); for (auto& duplicator : duplicators_) { for (int i = 0; i < duplicator.screen_count(); i++) { output->push_back(duplicator.GetDeviceName(i)); } } } DesktopSize DxgiDuplicatorController::SelectedDesktopSize( int monitor_id) const { if (monitor_id < 0) { return desktop_size(); } return ScreenRect(monitor_id).size(); } bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context, SharedDesktopFrame* target) { // On a modern system, the FPS / monitor refresh rate is usually larger than // or equal to 60. So 17 milliseconds is enough to capture at least one frame. const int64_t ms_per_frame = 17; // Skips the first frame to ensure a full frame refresh has happened before // this function returns. const int64_t frames_to_skip = 1; // The total time out milliseconds for this function. If we cannot get enough // frames during this time interval, this function returns false, and cause // the DXGI components to be reinitialized. This usually should not happen // unless the system is switching display mode when this function is being // called. 500 milliseconds should be enough for ~30 frames. const int64_t timeout_ms = 500; if (GetNumFramesCaptured() >= frames_to_skip) { return true; } std::unique_ptr fallback_frame; SharedDesktopFrame* shared_frame = nullptr; if (target->size().width() >= desktop_size().width() && target->size().height() >= desktop_size().height()) { // |target| is large enough to cover entire screen, we do not need to use // |fallback_frame|. shared_frame = target; } else { fallback_frame = SharedDesktopFrame::Wrap(std::unique_ptr( new BasicDesktopFrame(desktop_size()))); shared_frame = fallback_frame.get(); } const int64_t start_ms = rtc::TimeMillis(); int64_t last_frame_start_ms = 0; while (GetNumFramesCaptured() < frames_to_skip) { if (GetNumFramesCaptured() > 0) { // Sleep |ms_per_frame| before capturing next frame to ensure the screen // has been updated by the video adapter. webrtc::SleepMs( ms_per_frame - (rtc::TimeMillis() - last_frame_start_ms)); } last_frame_start_ms = rtc::TimeMillis(); if (!DoDuplicateAll(context, shared_frame)) { return false; } if (rtc::TimeMillis() - start_ms > timeout_ms) { RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip << " frames " "within " << timeout_ms << " milliseconds."; return false; } } return true; } void DxgiDuplicatorController::TranslateRect() { const DesktopVector position = DesktopVector().subtract(desktop_rect_.top_left()); desktop_rect_.Translate(position); for (auto& duplicator : duplicators_) { duplicator.TranslateRect(position); } } } // namespace webrtc