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 <assert.h> | |
14 #include <string.h> | |
15 | |
16 #include <comdef.h> | |
17 #include <wincodec.h> | |
18 #include <DXGI.h> | |
19 | |
20 #include "webrtc/modules/desktop_capture/desktop_frame_win.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::scoped_ptr; | |
Sergey Ulanov
2016/03/31 18:41:16
scoped_ptr<> is used in only one place, so you can
Hzj_jie
2016/04/05 23:15:17
Done.
| |
29 using std::unique_ptr; | |
Sergey Ulanov
2016/03/31 18:41:17
unique_ptr is not used anywhere in this file.
Hzj_jie
2016/04/05 23:15:17
Done.
| |
30 | |
31 bool ScreenCapturerWinDirectX::kInitialized { false }; | |
Sergey Ulanov
2016/03/31 18:41:16
All of these are not really constants, so you shou
Sergey Ulanov
2016/03/31 18:41:16
= false.
C++11 style initialization syntax is not
Hzj_jie
2016/04/05 23:15:17
Done.
Hzj_jie
2016/04/05 23:15:18
Yes, I have also had the same feeling, but logical
| |
32 bool ScreenCapturerWinDirectX::kInitializeResult { false }; | |
33 ID3D11Device* ScreenCapturerWinDirectX::kD3D11Device { nullptr }; | |
34 ID3D11DeviceContext* ScreenCapturerWinDirectX::kD3D11Context { nullptr }; | |
35 IDXGIOutput1* ScreenCapturerWinDirectX::kDXGIOutput1 { nullptr }; | |
36 ComPtr<IDXGIOutputDuplication> | |
Sergey Ulanov
2016/03/31 18:41:16
Static variables are allowed only for POD types. S
Hzj_jie
2016/04/05 23:15:18
According to MSDN http://shortn/_geEQFIizM3, one a
Sergey Ulanov
2016/04/08 21:22:26
All global variables _must_ be POD. It doesn't mat
| |
37 ScreenCapturerWinDirectX::kDXGIOutputDuplication {}; | |
38 DesktopSize ScreenCapturerWinDirectX::kDesktopSize {}; | |
39 ComPtr<ID3D11Texture2D> ScreenCapturerWinDirectX::kStage {}; | |
40 ComPtr<IDXGISurface> ScreenCapturerWinDirectX::kSurface {}; | |
41 std::vector<BYTE> ScreenCapturerWinDirectX::kMetaDataBuffer {}; | |
42 CriticalSectionWrapper ScreenCapturerWinDirectX::kInitializeLock {}; | |
43 CriticalSectionWrapper ScreenCapturerWinDirectX::kDuplicationLock {}; | |
44 CriticalSectionWrapper ScreenCapturerWinDirectX::kAcquireLock {}; | |
45 | |
46 bool ScreenCapturerWinDirectX::Initialize() { | |
47 if (!kInitialized) { | |
48 CriticalSectionScoped lock(&kInitializeLock); | |
49 if (!kInitialized) { | |
50 kInitializeResult = DoInitialize(); | |
51 kInitialized = true; | |
52 if (kInitializeResult) { | |
53 return true; | |
54 } | |
55 | |
56 // Clean up if DirectX cannot work on the system. | |
57 if (kDXGIOutputDuplication) { | |
58 kDXGIOutputDuplication.Reset(); | |
59 } | |
60 | |
61 if (kDXGIOutput1 != nullptr) { | |
62 kDXGIOutput1->Release(); | |
Sergey Ulanov
2016/03/31 18:41:15
Why do you need this? Doesn't ComPtr<> release the
Hzj_jie
2016/04/05 23:15:18
Yes, ComPtr does, but this instance is a pure poin
| |
63 kDXGIOutput1 = nullptr; | |
64 } | |
65 | |
66 if (kD3D11Context != nullptr) { | |
67 kD3D11Context->Release(); | |
68 kD3D11Context = nullptr; | |
69 } | |
70 | |
71 if (kD3D11Device != nullptr) { | |
72 kD3D11Device->Release(); | |
73 kD3D11Device = nullptr; | |
74 } | |
75 | |
76 return false; | |
77 } | |
78 } | |
79 | |
80 return kInitializeResult; | |
81 } | |
82 | |
83 bool ScreenCapturerWinDirectX::DoInitialize() { | |
84 D3D_FEATURE_LEVEL feature_level; | |
85 _com_error err(D3D11CreateDevice(nullptr, | |
86 D3D_DRIVER_TYPE_HARDWARE, | |
87 nullptr, | |
88 D3D11_CREATE_DEVICE_SINGLETHREADED, | |
Sergey Ulanov
2016/03/31 18:41:17
DirectX may be used from other threads in chrome,
Hzj_jie
2016/04/05 23:15:17
We always have only one thread to access an ID3D11
| |
89 nullptr, | |
90 0, | |
91 D3D11_SDK_VERSION, | |
92 &kD3D11Device, | |
93 &feature_level, | |
94 &kD3D11Context)); | |
95 if (err.Error() != S_OK || | |
96 kD3D11Device == nullptr || | |
97 kD3D11Context == nullptr) { | |
98 LOG(LS_WARNING) << "D3D11CreateDeivce returns error " << err.ErrorMessage() | |
99 << " with code " << err.Error(); | |
100 return false; | |
101 } | |
102 | |
103 if (feature_level < D3D_FEATURE_LEVEL_11_0) { | |
104 LOG(LS_WARNING) << "D3D11CreateDevice returns an instance without DirectX " | |
105 "11 support, level " << feature_level; | |
106 return false; | |
107 } | |
108 | |
109 ComPtr<IDXGIDevice> device; | |
110 err = _com_error(kD3D11Device->QueryInterface( | |
111 __uuidof(IDXGIDevice), | |
112 reinterpret_cast<void**>(device.GetAddressOf()))); | |
113 if (err.Error() != S_OK || !device) { | |
114 LOG(LS_WARNING) << "ID3D11Device is not an implementation of IDXGIDevice, " | |
115 "this usually means the system does not support DirectX " | |
116 "11"; | |
117 return false; | |
118 } | |
119 | |
120 ComPtr<IDXGIAdapter> adapter; | |
121 err = _com_error(device->GetAdapter(adapter.GetAddressOf())); | |
122 if (err.Error() != S_OK || !adapter) { | |
123 LOG(LS_WARNING) << "Failed to get an IDXGIAdapter implementation from " | |
124 "IDXGIDevice."; | |
125 return false; | |
126 } | |
127 | |
128 ComPtr<IDXGIOutput> output; | |
129 for (int i = 0;; i++) { | |
Sergey Ulanov
2016/03/31 18:41:16
while() loop would be more readable here. E.g. see
Hzj_jie
2016/04/05 23:15:17
The scenario is a little bit different, we are loo
| |
130 err = _com_error(adapter->EnumOutputs(i, output.GetAddressOf())); | |
131 if (err.Error() == DXGI_ERROR_NOT_FOUND) { | |
132 LOG(LS_WARNING) << "No output detected."; | |
133 return false; | |
134 } else if (err.Error() == S_OK && output) { | |
Sergey Ulanov
2016/03/31 18:41:15
what if err.Error() is any error other than DXGI_
Sergey Ulanov
2016/03/31 18:41:16
no else after return please: https://www.chromium.
Hzj_jie
2016/04/05 23:15:18
I do not see a statement in MSDN to say this funct
| |
135 DXGI_OUTPUT_DESC desc; | |
136 err = _com_error(output->GetDesc(&desc)); | |
137 if (err.Error() == S_OK) { | |
138 if (desc.AttachedToDesktop) { | |
139 // Current output instance is the device attached to desktop. | |
140 break; | |
141 } | |
142 } else { | |
143 LOG(LS_WARNING) << "Failed to get output description of device " << i | |
144 << ", ignore."; | |
145 } | |
146 } | |
147 } | |
148 | |
149 assert(output); | |
150 err = _com_error(output.CopyTo(__uuidof(IDXGIOutput1), | |
151 reinterpret_cast<void**>(&kDXGIOutput1))); | |
152 if (err.Error() != S_OK || kDXGIOutput1 == nullptr) { | |
153 LOG(LS_WARNING) << "Failed to convert IDXGIOutput to IDXGIOutput1, this " | |
154 "usually means the system does not support DirectX 11"; | |
155 return false; | |
156 } | |
157 | |
158 return DuplicateOutput(); | |
159 } | |
160 | |
161 bool ScreenCapturerWinDirectX::DuplicateOutput() { | |
162 assert(kDXGIOutput1 != nullptr); | |
163 // We are updating the instance. | |
164 CriticalSectionScoped lock(&kDuplicationLock); | |
165 // Make sure nobody is using current instance. | |
166 CriticalSectionScoped lock2(&kAcquireLock); | |
167 if (kDXGIOutputDuplication) { | |
168 kDXGIOutputDuplication.Reset(); | |
169 } | |
170 _com_error err(kDXGIOutput1->DuplicateOutput( | |
171 static_cast<IUnknown*>(kD3D11Device), | |
172 kDXGIOutputDuplication.GetAddressOf())); | |
173 if (err.Error() != S_OK || !kDXGIOutputDuplication) { | |
174 LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error " | |
175 << err.ErrorMessage() << ", with code " << err.Error(); | |
176 return false; | |
177 } | |
178 | |
179 DXGI_OUTDUPL_DESC desc; | |
180 kDXGIOutputDuplication->GetDesc(&desc); | |
181 kDesktopSize.set(desc.ModeDesc.Width, desc.ModeDesc.Height); | |
182 kStage.Reset(); | |
183 kSurface.Reset(); | |
184 return true; | |
185 } | |
186 | |
187 ScreenCapturerWinDirectX::ScreenCapturerWinDirectX( | |
188 const DesktopCaptureOptions& options) : | |
189 callback_(nullptr), | |
190 set_thread_execution_state_failed_(false) { | |
191 assert(kInitialized && kInitializeResult); | |
192 } | |
193 | |
194 ScreenCapturerWinDirectX::~ScreenCapturerWinDirectX() {} | |
195 | |
196 void ScreenCapturerWinDirectX::Start(Callback* callback) { | |
197 assert(callback_ == nullptr); | |
Sergey Ulanov
2016/03/31 18:41:16
Here and everywhere else please use RTC_DCHECK() i
Hzj_jie
2016/04/05 23:15:18
Done.
| |
198 assert(callback != nullptr); | |
199 | |
200 callback_ = callback; | |
201 } | |
202 | |
203 // We do not need to allocate memory in this class. | |
204 void ScreenCapturerWinDirectX::SetSharedMemoryFactory( | |
205 rtc::scoped_ptr<SharedMemoryFactory> shared_memory_factory) {} | |
Sergey Ulanov
2016/03/31 18:41:16
We actually don't want to ignore this call. On win
Hzj_jie
2016/04/05 23:15:18
Yes, done.
| |
206 | |
207 bool ScreenCapturerWinDirectX::CreateTexture(ID3D11Texture2D* texture) { | |
208 assert(texture != nullptr); | |
209 D3D11_TEXTURE2D_DESC desc; | |
210 texture->GetDesc(&desc); | |
211 desc.Usage = D3D11_USAGE_STAGING; | |
212 desc.BindFlags = 0; | |
213 desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; | |
214 desc.MiscFlags = 0; | |
215 if (kStage) { | |
216 { | |
217 ComPtr<IUnknown> left; | |
218 ComPtr<IUnknown> right; | |
219 assert(SUCCEEDED(kStage.CopyTo( | |
Sergey Ulanov
2016/03/31 18:41:16
Don't put any statements with side-effects inside
Hzj_jie
2016/04/05 23:15:18
Done.
| |
220 __uuidof(IUnknown), | |
221 reinterpret_cast<void**>(left.GetAddressOf())))); | |
222 assert(SUCCEEDED(kSurface.CopyTo( | |
223 __uuidof(IUnknown), | |
224 reinterpret_cast<void**>(right.GetAddressOf())))); | |
225 assert(left.Get() == right.Get()); | |
226 } | |
227 _com_error err(kSurface->Unmap()); // This buffer should be used already. | |
228 if (err.Error() == S_OK) { | |
229 D3D11_TEXTURE2D_DESC orgi_desc; | |
230 kStage->GetDesc(&orgi_desc); | |
231 if (memcmp(&desc, &orgi_desc, sizeof(D3D11_TEXTURE2D_DESC)) == 0) { | |
Sergey Ulanov
2016/03/31 18:41:16
You don't need this check. memcmp() is not expecte
Hzj_jie
2016/04/05 23:15:18
This logic is to check whether current buffer (sta
| |
232 return true; | |
233 } | |
234 } else { | |
235 // Let's recreate kSurface later. | |
236 LOG(LS_ERROR) << "Failed to unmap surface, error " << err.ErrorMessage() | |
237 << ", code " << err.Error(); | |
238 } | |
239 kStage.Reset(); | |
240 kSurface.Reset(); | |
241 } | |
242 | |
243 _com_error err = _com_error(kD3D11Device->CreateTexture2D( | |
244 &desc, | |
245 nullptr, | |
246 kStage.GetAddressOf())); | |
247 if (err.Error() != S_OK || !kStage) { | |
248 LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage, " | |
249 "error " << err.ErrorMessage() | |
250 << ", code " << err.Error(); | |
251 return false; | |
252 } | |
253 | |
254 err = _com_error(kStage.CopyTo( | |
255 __uuidof(IDXGISurface), | |
256 reinterpret_cast<void**>(kSurface.GetAddressOf()))); | |
257 if (err.Error() != S_OK || !kSurface) { | |
258 LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface, " | |
259 "error " << err.ErrorMessage() | |
260 << ", code " << err.Error(); | |
261 return false; | |
262 } | |
263 | |
264 return true; | |
265 } | |
266 | |
267 bool ScreenCapturerWinDirectX::DetectUpdatedRegion( | |
268 const DXGI_OUTDUPL_FRAME_INFO& frame_info, | |
Sergey Ulanov
2016/03/31 18:41:16
incorrect indentation. Please use clang-format: ht
Hzj_jie
2016/04/05 23:15:17
Done.
| |
269 DesktopFrame* frame) { | |
270 assert(kDXGIOutputDuplication); | |
271 assert(frame != nullptr); | |
272 DesktopRegion& updated_region = *frame->mutable_updated_region(); | |
273 updated_region.Clear(); | |
274 if (frame_info.TotalMetadataBufferSize == 0) { | |
275 // This should not happen, since frame_info.AccumulatedFrames > 0. | |
276 LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " | |
277 "but TotalMetadataBufferSize == 0"; | |
278 return false; | |
279 } | |
280 | |
281 if (kMetaDataBuffer.size() < frame_info.TotalMetadataBufferSize) { | |
282 kMetaDataBuffer.clear(); // Avoid data copy | |
283 kMetaDataBuffer.reserve(frame_info.TotalMetadataBufferSize); | |
284 } | |
285 | |
286 UINT buff_size = 0; | |
287 DXGI_OUTDUPL_MOVE_RECT* move_rects = nullptr; | |
288 size_t move_rects_count = 0; | |
289 RECT* dirty_rects = nullptr; | |
290 size_t dirty_rects_count = 0; | |
291 for (int i = 0; i < 2; i++) { | |
Sergey Ulanov
2016/03/31 18:41:16
This looks strange. You have a loop that iterates
Hzj_jie
2016/04/05 23:15:17
To share most of the logic below. I agree it looks
| |
292 _com_error err(S_OK); | |
293 if (i == 0) { | |
294 move_rects = | |
295 reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(kMetaDataBuffer.data()); | |
296 err = _com_error(kDXGIOutputDuplication->GetFrameMoveRects( | |
297 kMetaDataBuffer.capacity(), | |
298 move_rects, | |
299 &buff_size)); | |
300 } else { | |
301 dirty_rects = | |
302 reinterpret_cast<RECT*>(kMetaDataBuffer.data() + buff_size); | |
303 err = _com_error(kDXGIOutputDuplication->GetFrameDirtyRects( | |
304 kMetaDataBuffer.capacity() - buff_size, | |
305 dirty_rects, | |
306 &buff_size)); | |
307 } | |
308 if (err.Error() != S_OK) { | |
309 if (err.Error() == DXGI_ERROR_ACCESS_LOST) { | |
310 if (!DuplicateOutput()) { | |
311 LOG(LS_ERROR) << "Failed to regenerate an IDXGIOutputDuplication."; | |
312 } | |
313 } else { | |
314 LOG(LS_ERROR) << "Failed to get " << (i == 0 ? "move" : "dirty") | |
315 << " rectangles, error " << err.ErrorMessage() | |
316 << ", code " << err.Error(); | |
317 } | |
318 // Send whole desktop as we cannot get dirty or move rectangles. | |
319 return false; | |
320 } | |
321 if (i == 0) { | |
322 move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); | |
323 } else { | |
324 dirty_rects_count = buff_size / sizeof(RECT); | |
325 } | |
326 } | |
327 | |
328 while (move_rects_count > 0) { | |
329 updated_region.AddRect(DesktopRect::MakeXYWH( | |
330 move_rects->SourcePoint.x, | |
331 move_rects->SourcePoint.y, | |
332 move_rects->DestinationRect.right - move_rects->DestinationRect.left, | |
333 move_rects->DestinationRect.bottom - move_rects->DestinationRect.top)); | |
334 updated_region.AddRect(DesktopRect::MakeLTRB( | |
335 move_rects->DestinationRect.left, | |
336 move_rects->DestinationRect.top, | |
337 move_rects->DestinationRect.right, | |
338 move_rects->DestinationRect.bottom)); | |
339 move_rects++; | |
340 move_rects_count--; | |
341 } | |
342 | |
343 while (dirty_rects_count > 0) { | |
344 updated_region.AddRect(DesktopRect::MakeLTRB( | |
345 dirty_rects->left, | |
346 dirty_rects->top, | |
347 dirty_rects->right, | |
348 dirty_rects->bottom)); | |
349 dirty_rects++; | |
350 dirty_rects_count--; | |
351 } | |
352 | |
353 return true; | |
354 } | |
355 | |
356 bool ScreenCapturerWinDirectX::ProcessFrame( | |
357 const DXGI_OUTDUPL_FRAME_INFO& frame_info, | |
358 IDXGIResource* resource, | |
359 DesktopFrame** frame) { | |
360 assert(resource != nullptr); | |
361 assert(frame != nullptr); | |
362 assert(frame_info.AccumulatedFrames > 0); | |
363 | |
364 ComPtr<ID3D11Texture2D> texture; | |
365 _com_error err = _com_error(resource->QueryInterface( | |
366 __uuidof(ID3D11Texture2D), | |
367 reinterpret_cast<void**>(texture.GetAddressOf()))); | |
368 if (err.Error() != S_OK || !texture) { | |
369 LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, " | |
370 "error " << err.ErrorMessage() << ", code " | |
371 << err.Error(); | |
372 return false; | |
373 } | |
374 | |
375 // AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to | |
376 // make a copy. | |
377 if (!CreateTexture(texture.Get())) { | |
378 return false; | |
379 } | |
380 | |
381 kD3D11Context->CopyResource(static_cast<ID3D11Resource*>(kStage.Get()), | |
382 static_cast<ID3D11Resource*>(texture.Get())); | |
383 | |
384 DXGI_MAPPED_RECT rect; | |
385 err = _com_error(kSurface->Map(&rect, DXGI_MAP_READ)); | |
386 if (err.Error() != S_OK) { | |
387 LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap, error " | |
388 << err.ErrorMessage() << ", code " << err.Error(); | |
389 return false; | |
390 } | |
391 | |
392 *frame = new DesktopFrameWinDXGI(kDesktopSize, kSurface, rect); | |
393 // kSurface->Unmap will be called next time we capture an image to avoid | |
394 // memory copy. | |
395 if (!DetectUpdatedRegion(frame_info, *frame)) { | |
396 (*frame)->mutable_updated_region()->Clear(); | |
397 (*frame)->mutable_updated_region()->AddRect( | |
398 DesktopRect::MakeSize(kDesktopSize)); | |
399 } | |
400 return true; | |
401 } | |
402 | |
403 void ScreenCapturerWinDirectX::Capture(const DesktopRegion& region) { | |
404 if (!kDXGIOutputDuplication) { | |
405 // Receive a capture request when application is shutting down. | |
406 CallbackError(); | |
407 return; | |
408 } | |
409 | |
410 assert(callback_ != nullptr); | |
411 TickTime capture_start_time = TickTime::Now(); | |
412 | |
413 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { | |
414 if (!set_thread_execution_state_failed_) { | |
415 set_thread_execution_state_failed_ = true; | |
416 LOG(LS_WARNING) << "Failed to make system & display power assertion: " | |
417 << GetLastError(); | |
418 } | |
419 } | |
420 | |
421 DXGI_OUTDUPL_FRAME_INFO frame_info = { 0 }; | |
422 ComPtr<IDXGIResource> resource = nullptr; | |
423 CriticalSectionScoped lock(&kAcquireLock); | |
424 _com_error err(kDXGIOutputDuplication->AcquireNextFrame( | |
425 kAcquireTimeout, | |
426 &frame_info, | |
427 resource.GetAddressOf())); | |
428 if (err.Error() == DXGI_ERROR_ACCESS_LOST) { | |
429 if (DuplicateOutput()) { | |
430 CallbackUnchanged(); | |
431 } else { | |
432 LOG(LS_ERROR) << "Failed to regenerate an IDXGIOutputDuplication"; | |
433 CallbackError(); | |
434 } | |
435 return; | |
436 } else if (err.Error() == DXGI_ERROR_WAIT_TIMEOUT) { | |
Sergey Ulanov
2016/03/31 18:41:17
here and below: no else after return please
Hzj_jie
2016/04/05 23:15:18
Done.
| |
437 // Nothing changed. | |
438 CallbackUnchanged(); | |
439 return; | |
440 } else if (err.Error() != S_OK) { | |
441 CallbackError(); | |
442 return; | |
443 } else { | |
444 if (frame_info.AccumulatedFrames > 0) { | |
445 // Everything looks good so far, build CaptureFrame. | |
446 DesktopFrame* frame = nullptr; | |
447 bool result = ProcessFrame(frame_info, resource.Get(), &frame); | |
448 kDXGIOutputDuplication->ReleaseFrame(); | |
449 if (result) { | |
450 assert(frame != nullptr); | |
451 frame->set_capture_time_ms( | |
452 (TickTime::Now() - capture_start_time).Milliseconds()); | |
453 callback_->OnCaptureCompleted(frame); | |
454 } else { | |
455 assert(frame == nullptr); | |
456 CallbackError(); | |
457 } | |
458 } else { | |
459 // Only mouse cursor moved, ignore. | |
460 CallbackUnchanged(); | |
461 kDXGIOutputDuplication->ReleaseFrame(); | |
462 } | |
463 } | |
464 } | |
465 | |
466 bool ScreenCapturerWinDirectX::GetScreenList(ScreenList* screens) { | |
467 assert(screens != nullptr); | |
468 assert(screens->size() == 0); | |
469 screens->push_back(Screen { 0 }); | |
470 return true; | |
471 } | |
472 | |
473 bool ScreenCapturerWinDirectX::SelectScreen(ScreenId id) { | |
474 return id == 0 || id == kFullDesktopScreenId; | |
475 } | |
476 | |
477 void ScreenCapturerWinDirectX::CallbackUnchanged() { | |
478 callback_->OnCaptureCompleted(new DesktopFrameWinDXGI(kDesktopSize)); | |
Sergey Ulanov
2016/03/31 18:41:16
When nothing is changed we want to emit a frame th
Hzj_jie
2016/04/05 23:15:18
Done.
| |
479 } | |
480 | |
481 void ScreenCapturerWinDirectX::CallbackError() { | |
482 callback_->OnCaptureCompleted(nullptr); | |
483 } | |
484 | |
485 } // namespace webrtc | |
OLD | NEW |