Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Side by Side Diff: webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc

Issue 1845113002: DirectX based screen capturer logic (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Lint errors Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 "webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h"
12
13 #include <string.h>
14
15 #include <comdef.h>
16 #include <wincodec.h>
17 #include <DXGI.h>
18
19 #include "webrtc/base/checks.h"
20 #include "webrtc/base/criticalsection.h"
21 #include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
22 #include "webrtc/system_wrappers/include/logging.h"
23 #include "webrtc/system_wrappers/include/tick_util.h"
24
25 namespace webrtc {
26
27 using Microsoft::WRL::ComPtr;
28 using rtc::CriticalSection;
29 using rtc::CritScope;
30
31 namespace {
32
33 rtc::GlobalLock initialize_lock;
Sergey Ulanov 2016/04/08 21:22:26 non-POD is not allowed as global. What you can do
Hzj_jie 2016/04/11 22:19:16 Changed into rtc::GlobalLockPod, which is a POD sp
34 CriticalSection* duplication_lock;
35 CriticalSection* acquire_lock;
36
37 bool initialized GUARDED_BY(initialize_lock) = false;
38 bool initialize_result GUARDED_BY(initialize_lock) = false;
39 ID3D11Device* d3d11_device GUARDED_BY(initialize_lock) = nullptr;
40 ID3D11DeviceContext* d3d11_context GUARDED_BY(initialize_lock) = nullptr;
41 IDXGIOutput1* dxgi_output1 GUARDED_BY(initialize_lock) = nullptr;
42 ComPtr<IDXGIOutputDuplication> dxgi_output_duplication
43 GUARDED_BY(duplication_lock);
44 DesktopSize desktop_size GUARDED_BY(acquire_lock);
45 std::vector<BYTE> metadata GUARDED_BY(acquire_lock);
46
47 } // namespace
48
49 bool ScreenCapturerWinDirectX::Initialize() {
50 if (!initialized) {
51 rtc::GlobalLockScope lock(&initialize_lock);
52 if (!initialized) {
53 initialize_result = DoInitialize();
54 initialized = true;
55 if (initialize_result) {
56 return true;
57 }
58
59 // Clean up if DirectX cannot work on the system.
60 if (dxgi_output_duplication) {
61 dxgi_output_duplication.Reset();
62 }
63
64 if (dxgi_output1 != nullptr) {
65 dxgi_output1->Release();
66 dxgi_output1 = nullptr;
67 }
68
69 if (d3d11_context != nullptr) {
70 d3d11_context->Release();
71 d3d11_context = nullptr;
72 }
73
74 if (d3d11_device != nullptr) {
75 d3d11_device->Release();
76 d3d11_device = nullptr;
77 }
78
79 return false;
80 }
81 }
82
83 return initialize_result;
84 }
85
86 bool ScreenCapturerWinDirectX::DoInitialize() {
87 D3D_FEATURE_LEVEL feature_level;
88 _com_error err(D3D11CreateDevice(
89 nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
90 D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED,
91 nullptr, 0, D3D11_SDK_VERSION, &d3d11_device, &feature_level,
92 &d3d11_context));
93 if (err.Error() != S_OK || d3d11_device == nullptr ||
94 d3d11_context == nullptr) {
95 LOG(LS_WARNING) << "D3D11CreateDeivce returns error " << err.ErrorMessage()
96 << " with code " << err.Error();
97 return false;
98 }
99
100 if (feature_level < D3D_FEATURE_LEVEL_11_0) {
101 LOG(LS_WARNING) << "D3D11CreateDevice returns an instance without DirectX "
102 "11 support, level "
103 << feature_level;
104 return false;
105 }
106
107 ComPtr<IDXGIDevice> device;
108 err = _com_error(d3d11_device->QueryInterface(
109 __uuidof(IDXGIDevice), reinterpret_cast<void**>(device.GetAddressOf())));
110 if (err.Error() != S_OK || !device) {
111 LOG(LS_WARNING) << "ID3D11Device is not an implementation of IDXGIDevice, "
112 "this usually means the system does not support DirectX "
113 "11";
114 return false;
115 }
116
117 ComPtr<IDXGIAdapter> adapter;
118 err = _com_error(device->GetAdapter(adapter.GetAddressOf()));
119 if (err.Error() != S_OK || !adapter) {
120 LOG(LS_WARNING) << "Failed to get an IDXGIAdapter implementation from "
121 "IDXGIDevice.";
122 return false;
123 }
124
125 ComPtr<IDXGIOutput> output;
126 for (int i = 0;; i++) {
127 err = _com_error(adapter->EnumOutputs(i, output.GetAddressOf()));
128 if (err.Error() == DXGI_ERROR_NOT_FOUND) {
129 LOG(LS_WARNING) << "No output detected.";
130 return false;
131 }
132 if (err.Error() == S_OK && output) {
133 DXGI_OUTPUT_DESC desc;
134 err = _com_error(output->GetDesc(&desc));
135 if (err.Error() == S_OK) {
136 if (desc.AttachedToDesktop) {
137 // Current output instance is the device attached to desktop.
138 break;
139 }
140 } else {
141 LOG(LS_WARNING) << "Failed to get output description of device " << i
142 << ", ignore.";
143 }
144 }
145 }
146
147 RTC_DCHECK(output);
148 err = _com_error(output.CopyTo(__uuidof(IDXGIOutput1),
149 reinterpret_cast<void**>(&dxgi_output1)));
150 if (err.Error() != S_OK || dxgi_output1 == nullptr) {
151 LOG(LS_WARNING) << "Failed to convert IDXGIOutput to IDXGIOutput1, this "
152 "usually means the system does not support DirectX 11";
153 return false;
154 }
155
156 duplication_lock = new CriticalSection();
157 acquire_lock = new CriticalSection();
158 if (DuplicateOutput()) {
159 DesktopFrameWinDxgi::Initialize(d3d11_device, d3d11_context);
160 return true;
161 }
162
163 return false;
164 }
165
166 bool ScreenCapturerWinDirectX::DuplicateOutput() {
167 RTC_DCHECK(dxgi_output1 != nullptr);
168 // We are updating the instance.
169 CritScope lock(duplication_lock);
170 // Make sure nobody is using current instance.
171 CritScope lock2(acquire_lock);
172 if (dxgi_output_duplication) {
173 dxgi_output_duplication->ReleaseFrame();
174 dxgi_output_duplication.Reset();
175 }
176
177 for (int i = 0; i < kDuplicateOutputAttempts; i++) {
178 _com_error err(
179 dxgi_output1->DuplicateOutput(static_cast<IUnknown*>(d3d11_device),
180 dxgi_output_duplication.GetAddressOf()));
181 if (err.Error() == S_OK && dxgi_output_duplication) {
182 DXGI_OUTDUPL_DESC desc;
183 dxgi_output_duplication->GetDesc(&desc);
184 if (desc.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) {
185 LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) "
186 "format, which is required by downstream components, "
187 "format is "
188 << desc.ModeDesc.Format;
189 return false;
190 }
191
192 desktop_size.set(desc.ModeDesc.Width, desc.ModeDesc.Height);
193 return true;
194 } else {
195 // Make sure we have correct signal and duplicate the output next time.
196 dxgi_output_duplication.Reset();
197 LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error "
198 << err.ErrorMessage() << ", with code " << err.Error();
199 Sleep(kDuplicateOutputWait);
200 }
201 }
202
203 return false;
204 }
205
206 ScreenCapturerWinDirectX::ScreenCapturerWinDirectX(
207 const DesktopCaptureOptions& options)
208 : callback_(nullptr), set_thread_execution_state_failed_(false) {
209 RTC_DCHECK(initialized && initialize_result);
210 }
211
212 ScreenCapturerWinDirectX::~ScreenCapturerWinDirectX() {}
213
214 void ScreenCapturerWinDirectX::Start(Callback* callback) {
215 RTC_DCHECK(callback_ == nullptr);
216 RTC_DCHECK(callback != nullptr);
217
218 callback_ = callback;
219 }
220
221 void ScreenCapturerWinDirectX::SetSharedMemoryFactory(
222 rtc::scoped_ptr<SharedMemoryFactory> shared_memory_factory) {
223 shared_memory_factory_ =
224 rtc::ScopedToUnique(std::move(shared_memory_factory));
225 }
226
227 bool ScreenCapturerWinDirectX::DetectUpdatedRegion(
228 const DXGI_OUTDUPL_FRAME_INFO& frame_info,
229 DesktopFrame* frame) {
230 RTC_DCHECK(dxgi_output_duplication);
231 RTC_DCHECK(frame != nullptr);
232 DesktopRegion& updated_region = *frame->mutable_updated_region();
233 updated_region.Clear();
234 if (frame_info.TotalMetadataBufferSize == 0) {
235 // This should not happen, since frame_info.AccumulatedFrames > 0.
236 LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, "
237 "but TotalMetadataBufferSize == 0";
238 return false;
239 }
240
241 if (metadata.size() < frame_info.TotalMetadataBufferSize) {
242 metadata.clear(); // Avoid data copy
243 metadata.reserve(frame_info.TotalMetadataBufferSize);
244 }
245
246 UINT buff_size = 0;
247 DXGI_OUTDUPL_MOVE_RECT* move_rects = nullptr;
248 size_t move_rects_count = 0;
249 RECT* dirty_rects = nullptr;
250 size_t dirty_rects_count = 0;
251 for (int i = 0; i < 2; i++) {
252 _com_error err(S_OK);
253 if (i == 0) {
254 move_rects = reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata.data());
255 err = _com_error(dxgi_output_duplication->GetFrameMoveRects(
256 static_cast<UINT>(metadata.capacity()), move_rects, &buff_size));
257 } else {
258 dirty_rects = reinterpret_cast<RECT*>(metadata.data() + buff_size);
259 err = _com_error(dxgi_output_duplication->GetFrameDirtyRects(
260 static_cast<UINT>(metadata.capacity()) - buff_size, dirty_rects,
261 &buff_size));
262 }
263 if (err.Error() != S_OK) {
264 if (err.Error() == DXGI_ERROR_ACCESS_LOST) {
265 DuplicateOutput();
266 } else {
267 LOG(LS_ERROR) << "Failed to get " << (i == 0 ? "move" : "dirty")
268 << " rectangles, error " << err.ErrorMessage()
269 << ", code " << err.Error();
270 }
271 // Send entire desktop as we cannot get dirty or move rectangles.
272 return false;
273 }
274 if (i == 0) {
275 move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
276 } else {
277 dirty_rects_count = buff_size / sizeof(RECT);
278 }
279 }
280
281 LOG(LS_INFO) << "Found " << move_rects_count << " moved rectangles and "
282 << dirty_rects_count << " dirty rectangles";
283 while (move_rects_count > 0) {
284 updated_region.AddRect(DesktopRect::MakeXYWH(
285 move_rects->SourcePoint.x, move_rects->SourcePoint.y,
286 move_rects->DestinationRect.right - move_rects->DestinationRect.left,
287 move_rects->DestinationRect.bottom - move_rects->DestinationRect.top));
288 updated_region.AddRect(DesktopRect::MakeLTRB(
289 move_rects->DestinationRect.left, move_rects->DestinationRect.top,
290 move_rects->DestinationRect.right, move_rects->DestinationRect.bottom));
291 move_rects++;
292 move_rects_count--;
293 }
294
295 while (dirty_rects_count > 0) {
296 updated_region.AddRect(
297 DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top,
298 dirty_rects->right, dirty_rects->bottom));
299 dirty_rects++;
300 dirty_rects_count--;
301 }
302
303 return true;
304 }
305
306 bool ScreenCapturerWinDirectX::ProcessFrame(
307 const DXGI_OUTDUPL_FRAME_INFO& frame_info,
308 IDXGIResource* resource) {
309 RTC_DCHECK(resource != nullptr);
310 RTC_DCHECK(frame_info.AccumulatedFrames > 0);
311 // We have something to update, so move to next frame.
312 frames_.MoveToNextFrame();
313 DesktopFrameWinDxgi* frame = ReplaceCurrentFrameIfEmpty();
314
315 if (!frame->Capture(frame_info, resource)) {
316 return false;
317 }
318
319 if (!DetectUpdatedRegion(frame_info, frame)) {
320 frame->mutable_updated_region()->Clear();
321 frame->mutable_updated_region()->AddRect(
322 DesktopRect::MakeSize(desktop_size));
323 }
324
325 return true;
326 }
327
328 void ScreenCapturerWinDirectX::Capture(const DesktopRegion& region) {
329 RTC_DCHECK(callback_ != nullptr);
330
331 if (!dxgi_output_duplication && !DuplicateOutput()) {
332 // Receive a capture request when application is shutting down, or between
333 // mode change.
334 callback_->OnCaptureCompleted(nullptr);
335 return;
336 }
337
338 TickTime capture_start_time = TickTime::Now();
339
340 // Make sure even nothing captured, we still have a valid frame to emit.
341 ReplaceCurrentFrameIfEmpty();
342 // Before we move to next frame, current frame cannot be changed, it may be
343 // being used by encoder.
344 // TODO(zijiehe): Use differ to detect changed region.
345
346 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
347 if (!set_thread_execution_state_failed_) {
348 set_thread_execution_state_failed_ = true;
349 LOG(LS_WARNING) << "Failed to make system & display power assertion: "
350 << GetLastError();
351 }
352 }
353
354 DXGI_OUTDUPL_FRAME_INFO frame_info{};
355 ComPtr<IDXGIResource> resource;
356 CritScope lock(acquire_lock);
357 _com_error err(dxgi_output_duplication->AcquireNextFrame(
358 kAcquireTimeout, &frame_info, resource.GetAddressOf()));
359 if (err.Error() == DXGI_ERROR_ACCESS_LOST) {
360 LOG(LS_ERROR) << "Access lost " << err.ErrorMessage();
361 if (DuplicateOutput()) {
362 EmitCurrentFrame();
363 } else {
364 callback_->OnCaptureCompleted(nullptr);
365 }
366 return;
367 }
368
369 if (err.Error() == DXGI_ERROR_WAIT_TIMEOUT) {
370 LOG(LS_INFO) << "Acquire timeout " << err.ErrorMessage();
371 // Nothing changed.
372 EmitCurrentFrame();
373 return;
374 }
375
376 if (err.Error() != S_OK) {
377 LOG(LS_ERROR) << "Failed to capture frame, error " << err.ErrorMessage()
378 << ", code " << err.Error();
379 callback_->OnCaptureCompleted(nullptr);
380 return;
381 }
382
383 if (frame_info.AccumulatedFrames == 0) {
384 LOG(LS_INFO) << "Nothing captured";
385 EmitCurrentFrame();
386 dxgi_output_duplication->ReleaseFrame();
387 return;
388 }
389
390 LOG(LS_INFO) << "Captured " << frame_info.AccumulatedFrames << " frames";
391 // Everything looks good so far, build next frame.
392 bool result = ProcessFrame(frame_info, resource.Get());
393 // DetectUpdatedRegion may release last dxgi_output_duplication. But
394 // DuplicateOutput function will always release last frame, so there is no
395 // potential leak.
396 if (dxgi_output_duplication) {
397 dxgi_output_duplication->ReleaseFrame();
398 }
399 if (result) {
400 frames_.current_frame()->set_capture_time_ms(
401 (TickTime::Now() - capture_start_time).Milliseconds());
402 EmitCurrentFrame();
403 } else {
404 callback_->OnCaptureCompleted(nullptr);
405 }
406 }
407
408 bool ScreenCapturerWinDirectX::GetScreenList(ScreenList* screens) {
409 RTC_DCHECK(screens != nullptr);
410 RTC_DCHECK(screens->size() == 0);
411 screens->push_back(Screen{});
412 return true;
413 }
414
415 bool ScreenCapturerWinDirectX::SelectScreen(ScreenId id) {
416 return id == 0 || id == kFullDesktopScreenId;
417 }
418
419 DesktopFrameWinDxgi* ScreenCapturerWinDirectX::ReplaceCurrentFrameIfEmpty() {
420 if (frames_.current_frame() == nullptr) {
421 std::unique_ptr<DesktopFrameWinDxgi> frame(DesktopFrameWinDxgi::Create(
422 desktop_size, shared_memory_factory_.get()));
423 frames_.ReplaceCurrentFrame(frame.get());
424 return frame.release();
425 }
426
427 return reinterpret_cast<DesktopFrameWinDxgi*>(
428 frames_.current_frame()->GetUnderlyingFrame());
429 }
430
431 void ScreenCapturerWinDirectX::EmitCurrentFrame() {
432 callback_->OnCaptureCompleted(frames_.current_frame()->Share());
433 }
434
435 } // namespace webrtc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698