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/dxgi_output_duplicator.h" | |
12 | |
13 #include <string.h> | |
14 | |
15 #include <unknwn.h> | |
16 #include <DXGIFormat.h> | |
17 #include <Windows.h> | |
18 | |
19 #include "webrtc/base/checks.h" | |
20 #include "webrtc/base/logging.h" | |
21 #include "webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h" | |
22 #include "webrtc/modules/desktop_capture/win/dxgi_texture_staging.h" | |
23 | |
24 namespace webrtc { | |
25 | |
26 using Microsoft::WRL::ComPtr; | |
27 | |
28 namespace { | |
29 | |
30 // Timeout for AcquireNextFrame() call. | |
31 const int kAcquireTimeoutMs = 10; | |
32 | |
33 DesktopRect RECTToDesktopRect(const RECT& rect) { | |
34 return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); | |
35 } | |
36 | |
37 } // namespace | |
38 | |
39 DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device, | |
40 const ComPtr<IDXGIOutput1>& output, | |
41 const DXGI_OUTPUT_DESC& desc) | |
42 : device_(device), | |
43 output_(output), | |
44 desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) { | |
45 RTC_DCHECK(output_); | |
46 RTC_DCHECK(!desktop_rect_.is_empty()); | |
47 RTC_DCHECK(desktop_rect_.left() >= 0 && desktop_rect_.top() >= 0); | |
48 } | |
49 | |
50 DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) = | |
51 default; | |
52 | |
53 DxgiOutputDuplicator::~DxgiOutputDuplicator() { | |
54 if (duplication_) { | |
55 duplication_->ReleaseFrame(); | |
56 } | |
57 texture_.reset(); | |
58 } | |
59 | |
60 bool DxgiOutputDuplicator::Initialize() { | |
61 if (DuplicateOutput()) { | |
62 if (desc_.DesktopImageInSystemMemory) { | |
63 texture_.reset(new DxgiTextureMapping(desktop_rect_, duplication_.Get())); | |
64 } else { | |
65 texture_.reset(new DxgiTextureStaging(desktop_rect_, device_)); | |
66 } | |
67 return true; | |
68 } else { | |
69 duplication_.Reset(); | |
70 return false; | |
71 } | |
72 } | |
73 | |
74 bool DxgiOutputDuplicator::DuplicateOutput() { | |
75 RTC_DCHECK(!duplication_); | |
76 _com_error error = | |
77 output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()), | |
78 duplication_.GetAddressOf()); | |
79 if (error.Error() != S_OK || !duplication_) { | |
80 LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error " | |
81 << error.ErrorMessage() << ", with code " << error.Error(); | |
82 return false; | |
83 } | |
84 | |
85 memset(&desc_, 0, sizeof(desc_)); | |
86 duplication_->GetDesc(&desc_); | |
87 if (desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { | |
88 LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " | |
89 "format, which is required by downstream components, " | |
90 "format is " | |
91 << desc_.ModeDesc.Format; | |
92 return false; | |
93 } | |
94 | |
95 if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() || | |
96 static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) { | |
97 LOG(LS_ERROR) << "IDXGIDuplicateOutput does not return a same size as its " | |
98 "IDXGIOutput1, size returned by IDXGIDuplicateOutput is " | |
99 << desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height | |
100 << ", size returned by IDXGIOutput1 is " | |
101 << desktop_rect_.width() << " x " << desktop_rect_.height(); | |
102 return false; | |
103 } | |
104 | |
105 return true; | |
106 } | |
107 | |
108 bool DxgiOutputDuplicator::ReleaseFrame() { | |
109 RTC_DCHECK(duplication_); | |
110 _com_error error = duplication_->ReleaseFrame(); | |
111 if (error.Error() != S_OK) { | |
112 LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication, " | |
113 "error" | |
114 << error.ErrorMessage() << ", code " << error.Error(); | |
115 return false; | |
116 } | |
117 return true; | |
118 } | |
119 | |
120 bool DxgiOutputDuplicator::Duplicate(DxgiOutputContext* context, | |
121 const DesktopFrame* last_frame, | |
122 const DesktopVector offset, | |
123 DesktopFrame* target) { | |
124 RTC_DCHECK(duplication_); | |
125 RTC_DCHECK(texture_); | |
126 RTC_DCHECK(target); | |
127 DXGI_OUTDUPL_FRAME_INFO frame_info; | |
128 memset(&frame_info, 0, sizeof(frame_info)); | |
129 ComPtr<IDXGIResource> resource; | |
130 _com_error error = duplication_->AcquireNextFrame( | |
131 kAcquireTimeoutMs, &frame_info, resource.GetAddressOf()); | |
132 if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) { | |
133 LOG(LS_ERROR) << "Failed to capture frame, error " << error.ErrorMessage() | |
134 << ", code " << error.Error(); | |
135 return false; | |
136 } | |
137 | |
138 // We need to merge updated region with the one from last frame, since current | |
139 // frame contains the content one frame before. Note, this is for double | |
140 // buffering implementation, as what we have in ScreenCapturerWinDirectx. If | |
141 // a consumer uses single buffering, we should clear context->updated_region | |
142 // after it has been merged to updated_region. | |
143 DesktopRegion updated_region = context->updated_region; | |
144 if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0) { | |
145 DetectUpdatedRegion(frame_info, offset, &context->updated_region); | |
146 SpreadContextChange(context); | |
147 updated_region.AddRegion(context->updated_region); | |
148 if (!texture_->CopyFrom(frame_info, resource.Get(), updated_region)) { | |
149 return false; | |
150 } | |
151 | |
152 const DesktopFrame& source = texture_->AsDesktopFrame(); | |
153 DesktopRect target_rect(DesktopRect::MakeSize(target->size())); | |
154 for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); | |
155 it.Advance()) { | |
156 if (!target_rect.ContainsRect(it.rect())) { | |
157 // target size is not large enough to copy the pixel from texture. | |
158 return false; | |
159 } | |
160 target->CopyPixelsFrom(source, it.rect().top_left().subtract(offset), | |
161 it.rect()); | |
162 } | |
163 target->mutable_updated_region()->AddRegion(updated_region); | |
164 return texture_->Release() && ReleaseFrame(); | |
165 } | |
166 | |
167 if (last_frame != nullptr) { | |
168 // DxgiOutputDuplicatorContainer::Duplicate() makes sure target size and | |
169 // last frame size are consistent. | |
170 RTC_DCHECK(target->size().equals(last_frame->size())); | |
171 // No change since last frame or AcquireNextFrame() timed out, we will | |
172 // export last frame to the target. | |
173 context->updated_region.Clear(); | |
174 for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); | |
175 it.Advance()) { | |
176 target->CopyPixelsFrom(*last_frame, it.rect().top_left(), it.rect()); | |
177 } | |
178 target->mutable_updated_region()->AddRegion(updated_region); | |
179 } | |
180 // If AcquireNextFrame() failed with timeout error, we do not need to release | |
181 // the frame. | |
182 return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame(); | |
183 } | |
184 | |
185 DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect( | |
186 const DesktopVector offset) { | |
187 DesktopRect result(DesktopRect::MakeSize(desktop_rect_.size())); | |
188 result.Translate(offset); | |
189 return result; | |
190 } | |
191 | |
192 void DxgiOutputDuplicator::DetectUpdatedRegion( | |
193 const DXGI_OUTDUPL_FRAME_INFO& frame_info, | |
194 const DesktopVector offset, | |
195 DesktopRegion* updated_region) { | |
196 if (DoDetectUpdatedRegion(frame_info, updated_region)) { | |
197 updated_region->Translate(offset.x(), offset.y()); | |
198 // Make sure even a region returned by Windows API is out of the scope of | |
199 // desktop_rect_, we still won't export it to the target DesktopFrame. | |
200 updated_region->IntersectWith(TranslatedDesktopRect(offset)); | |
201 } else { | |
202 updated_region->SetRect(TranslatedDesktopRect(offset)); | |
203 } | |
204 } | |
205 | |
206 bool DxgiOutputDuplicator::DoDetectUpdatedRegion( | |
207 const DXGI_OUTDUPL_FRAME_INFO& frame_info, | |
208 DesktopRegion* updated_region) { | |
209 RTC_DCHECK(updated_region); | |
210 updated_region->Clear(); | |
211 if (frame_info.TotalMetadataBufferSize == 0) { | |
212 // This should not happen, since frame_info.AccumulatedFrames > 0. | |
213 LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " | |
214 "but TotalMetadataBufferSize == 0"; | |
215 return false; | |
216 } | |
217 | |
218 if (metadata.capacity() < frame_info.TotalMetadataBufferSize) { | |
219 metadata.clear(); // Avoid data copy | |
220 metadata.reserve(frame_info.TotalMetadataBufferSize); | |
221 } | |
222 | |
223 UINT buff_size = 0; | |
224 DXGI_OUTDUPL_MOVE_RECT* move_rects = | |
225 reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata.data()); | |
226 size_t move_rects_count = 0; | |
227 _com_error error = _com_error(duplication_->GetFrameMoveRects( | |
228 static_cast<UINT>(metadata.capacity()), move_rects, &buff_size)); | |
229 if (error.Error() != S_OK) { | |
230 LOG(LS_ERROR) << "Failed to get move rectangles, error " | |
231 << error.ErrorMessage() << ", code " << error.Error(); | |
232 return false; | |
233 } | |
234 move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); | |
235 | |
236 RECT* dirty_rects = reinterpret_cast<RECT*>(metadata.data() + buff_size); | |
237 size_t dirty_rects_count = 0; | |
238 error = _com_error(duplication_->GetFrameDirtyRects( | |
239 static_cast<UINT>(metadata.capacity()) - buff_size, dirty_rects, | |
240 &buff_size)); | |
241 if (error.Error() != S_OK) { | |
242 LOG(LS_ERROR) << "Failed to get dirty rectangles, error " | |
243 << error.ErrorMessage() << ", code " << error.Error(); | |
244 return false; | |
245 } | |
246 dirty_rects_count = buff_size / sizeof(RECT); | |
247 | |
248 while (move_rects_count > 0) { | |
249 updated_region->AddRect(DesktopRect::MakeXYWH( | |
250 move_rects->SourcePoint.x, move_rects->SourcePoint.y, | |
251 move_rects->DestinationRect.right - move_rects->DestinationRect.left, | |
252 move_rects->DestinationRect.bottom - move_rects->DestinationRect.top)); | |
253 updated_region->AddRect(DesktopRect::MakeLTRB( | |
254 move_rects->DestinationRect.left, move_rects->DestinationRect.top, | |
255 move_rects->DestinationRect.right, move_rects->DestinationRect.bottom)); | |
256 move_rects++; | |
257 move_rects_count--; | |
258 } | |
259 | |
260 while (dirty_rects_count > 0) { | |
261 updated_region->AddRect( | |
262 DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top, | |
263 dirty_rects->right, dirty_rects->bottom)); | |
264 dirty_rects++; | |
265 dirty_rects_count--; | |
266 } | |
267 | |
268 return true; | |
269 } | |
270 | |
271 void DxgiOutputDuplicator::Setup(DxgiOutputContext* context) { | |
272 RTC_DCHECK(context->updated_region.is_empty()); | |
273 // Always copy entire monitor during the first Duplicate() function call. | |
274 context->updated_region.AddRect(desktop_rect_); | |
275 for (size_t i = 0; i < contexts_.size(); i++) { | |
276 if (contexts_[i] == nullptr) { | |
277 contexts_[i] = context; | |
278 return; | |
279 } | |
280 } | |
281 | |
282 contexts_.push_back(context); | |
283 } | |
284 | |
285 void DxgiOutputDuplicator::Unregister(const DxgiOutputContext* const context) { | |
286 for (size_t i = 0; i < contexts_.size(); i++) { | |
287 if (contexts_[i] == context) { | |
288 contexts_[i] = nullptr; | |
289 return; | |
290 } | |
291 } | |
292 | |
293 RTC_DCHECK(false && "Unregistered DxgiOutputContext"); | |
Sergey Ulanov
2016/08/06 01:27:57
RTC_NOTREACHED();
BTW with DCHECK you can also pr
Hzj_jie
2016/08/08 00:16:09
Done. RTC_NOTREACHED is enough to explain the issu
| |
294 } | |
295 | |
296 void DxgiOutputDuplicator::SpreadContextChange( | |
297 const DxgiOutputContext* const source) { | |
298 for (DxgiOutputContext* dest : contexts_) { | |
299 if (dest != source) { | |
300 dest->updated_region.AddRegion(source->updated_region); | |
301 } | |
302 } | |
303 } | |
304 | |
305 } // namespace webrtc | |
OLD | NEW |