OLD | NEW |
---|---|
(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 | |
OLD | NEW |