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/screen_capturer_win_directx.h"
12 
13 #include <algorithm>
14 #include <memory>
15 #include <string>
16 #include <utility>
17 #include <vector>
18 
19 #include "modules/desktop_capture/desktop_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 "rtc_base/trace_event.h"
25 
26 namespace webrtc {
27 
28 using Microsoft::WRL::ComPtr;
29 
30 // static
IsSupported()31 bool ScreenCapturerWinDirectx::IsSupported() {
32   // Forwards IsSupported() function call to DxgiDuplicatorController.
33   return DxgiDuplicatorController::Instance()->IsSupported();
34 }
35 
36 // static
RetrieveD3dInfo(D3dInfo * info)37 bool ScreenCapturerWinDirectx::RetrieveD3dInfo(D3dInfo* info) {
38   // Forwards SupportedFeatureLevels() function call to
39   // DxgiDuplicatorController.
40   return DxgiDuplicatorController::Instance()->RetrieveD3dInfo(info);
41 }
42 
43 // static
IsCurrentSessionSupported()44 bool ScreenCapturerWinDirectx::IsCurrentSessionSupported() {
45   return DxgiDuplicatorController::IsCurrentSessionSupported();
46 }
47 
48 // static
GetScreenListFromDeviceNames(const std::vector<std::string> & device_names,DesktopCapturer::SourceList * screens)49 bool ScreenCapturerWinDirectx::GetScreenListFromDeviceNames(
50     const std::vector<std::string>& device_names,
51     DesktopCapturer::SourceList* screens) {
52   RTC_DCHECK(screens->empty());
53 
54   DesktopCapturer::SourceList gdi_screens;
55   std::vector<std::string> gdi_names;
56   if (!GetScreenList(&gdi_screens, &gdi_names)) {
57     return false;
58   }
59 
60   RTC_DCHECK_EQ(gdi_screens.size(), gdi_names.size());
61 
62   ScreenId max_screen_id = -1;
63   for (const DesktopCapturer::Source& screen : gdi_screens) {
64     max_screen_id = std::max(max_screen_id, screen.id);
65   }
66 
67   for (const auto& device_name : device_names) {
68     const auto it = std::find(gdi_names.begin(), gdi_names.end(), device_name);
69     if (it == gdi_names.end()) {
70       // devices_names[i] has not been found in gdi_names, so use max_screen_id.
71       max_screen_id++;
72       screens->push_back({max_screen_id});
73     } else {
74       screens->push_back({gdi_screens[it - gdi_names.begin()]});
75     }
76   }
77 
78   return true;
79 }
80 
81 // static
GetIndexFromScreenId(ScreenId id,const std::vector<std::string> & device_names)82 int ScreenCapturerWinDirectx::GetIndexFromScreenId(
83     ScreenId id,
84     const std::vector<std::string>& device_names) {
85   DesktopCapturer::SourceList screens;
86   if (!GetScreenListFromDeviceNames(device_names, &screens)) {
87     return -1;
88   }
89 
90   RTC_DCHECK_EQ(device_names.size(), screens.size());
91 
92   for (size_t i = 0; i < screens.size(); i++) {
93     if (screens[i].id == id) {
94       return static_cast<int>(i);
95     }
96   }
97 
98   return -1;
99 }
100 
ScreenCapturerWinDirectx()101 ScreenCapturerWinDirectx::ScreenCapturerWinDirectx()
102     : controller_(DxgiDuplicatorController::Instance()) {}
103 
104 ScreenCapturerWinDirectx::~ScreenCapturerWinDirectx() = default;
105 
Start(Callback * callback)106 void ScreenCapturerWinDirectx::Start(Callback* callback) {
107   RTC_DCHECK(!callback_);
108   RTC_DCHECK(callback);
109 
110   callback_ = callback;
111 }
112 
SetSharedMemoryFactory(std::unique_ptr<SharedMemoryFactory> shared_memory_factory)113 void ScreenCapturerWinDirectx::SetSharedMemoryFactory(
114     std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
115   shared_memory_factory_ = std::move(shared_memory_factory);
116 }
117 
CaptureFrame()118 void ScreenCapturerWinDirectx::CaptureFrame() {
119   RTC_DCHECK(callback_);
120   TRACE_EVENT0("webrtc", "ScreenCapturerWinDirectx::CaptureFrame");
121 
122   int64_t capture_start_time_nanos = rtc::TimeNanos();
123 
124   frames_.MoveToNextFrame();
125   if (!frames_.current_frame()) {
126     frames_.ReplaceCurrentFrame(
127         std::make_unique<DxgiFrame>(shared_memory_factory_.get()));
128   }
129 
130   DxgiDuplicatorController::Result result;
131   if (current_screen_id_ == kFullDesktopScreenId) {
132     result = controller_->Duplicate(frames_.current_frame());
133   } else {
134     result = controller_->DuplicateMonitor(frames_.current_frame(),
135                                            current_screen_id_);
136   }
137 
138   using DuplicateResult = DxgiDuplicatorController::Result;
139   if (result != DuplicateResult::SUCCEEDED) {
140     RTC_LOG(LS_ERROR) << "DxgiDuplicatorController failed to capture desktop, "
141                          "error code "
142                       << DxgiDuplicatorController::ResultName(result);
143   }
144   switch (result) {
145     case DuplicateResult::UNSUPPORTED_SESSION: {
146       RTC_LOG(LS_ERROR)
147           << "Current binary is running on a session not supported "
148              "by DirectX screen capturer.";
149       callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
150       break;
151     }
152     case DuplicateResult::FRAME_PREPARE_FAILED: {
153       RTC_LOG(LS_ERROR) << "Failed to allocate a new DesktopFrame.";
154       // This usually means we do not have enough memory or SharedMemoryFactory
155       // cannot work correctly.
156       callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
157       break;
158     }
159     case DuplicateResult::INVALID_MONITOR_ID: {
160       RTC_LOG(LS_ERROR) << "Invalid monitor id " << current_screen_id_;
161       callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
162       break;
163     }
164     case DuplicateResult::INITIALIZATION_FAILED:
165     case DuplicateResult::DUPLICATION_FAILED: {
166       callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
167       break;
168     }
169     case DuplicateResult::SUCCEEDED: {
170       std::unique_ptr<DesktopFrame> frame =
171           frames_.current_frame()->frame()->Share();
172       frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
173                                  rtc::kNumNanosecsPerMillisec);
174       frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinDirectx);
175 
176       // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
177       // the frame, see WindowCapturerMac::CaptureFrame.
178 
179       callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
180       break;
181     }
182   }
183 }
184 
GetSourceList(SourceList * sources)185 bool ScreenCapturerWinDirectx::GetSourceList(SourceList* sources) {
186   std::vector<std::string> device_names;
187   if (!controller_->GetDeviceNames(&device_names)) {
188     return false;
189   }
190 
191   return GetScreenListFromDeviceNames(device_names, sources);
192 }
193 
SelectSource(SourceId id)194 bool ScreenCapturerWinDirectx::SelectSource(SourceId id) {
195   if (id == kFullDesktopScreenId) {
196     current_screen_id_ = id;
197     return true;
198   }
199 
200   std::vector<std::string> device_names;
201   if (!controller_->GetDeviceNames(&device_names)) {
202     return false;
203   }
204 
205   int index;
206   index = GetIndexFromScreenId(id, device_names);
207   if (index == -1) {
208     return false;
209   }
210 
211   current_screen_id_ = index;
212   return true;
213 }
214 
215 }  // namespace webrtc
216