Chromium Code Reviews| 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 |