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

Side by Side Diff: media/video/capture/screen/screen_capturer_win.cc

Issue 15692018: Remove screen capturers from media/video/capture/screen. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 6 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/video/capture/screen/screen_capturer.h"
6
7 #include <windows.h>
8
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/time.h"
12 #include "media/video/capture/screen/differ.h"
13 #include "media/video/capture/screen/mouse_cursor_shape.h"
14 #include "media/video/capture/screen/screen_capture_frame_queue.h"
15 #include "media/video/capture/screen/screen_capturer_helper.h"
16 #include "media/video/capture/screen/win/desktop.h"
17 #include "media/video/capture/screen/win/scoped_thread_desktop.h"
18 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
19 #include "third_party/webrtc/modules/desktop_capture/desktop_frame_win.h"
20 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
21
22 namespace media {
23
24 namespace {
25
26 // Constants from dwmapi.h.
27 const UINT DWM_EC_DISABLECOMPOSITION = 0;
28 const UINT DWM_EC_ENABLECOMPOSITION = 1;
29
30 typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
31
32 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
33
34 // Pixel colors used when generating cursor outlines.
35 const uint32 kPixelBgraBlack = 0xff000000;
36 const uint32 kPixelBgraWhite = 0xffffffff;
37 const uint32 kPixelBgraTransparent = 0x00000000;
38
39 uint8_t AlphaMul(uint8_t v, uint8_t alpha) {
40 return (static_cast<uint16_t>(v) * alpha) >> 8;
41 }
42
43 // ScreenCapturerWin captures 32bit RGB using GDI.
44 //
45 // ScreenCapturerWin is double-buffered as required by ScreenCapturer.
46 class ScreenCapturerWin : public ScreenCapturer {
47 public:
48 ScreenCapturerWin(bool disable_aero);
49 virtual ~ScreenCapturerWin();
50
51 // Overridden from ScreenCapturer:
52 virtual void Start(Callback* callback) OVERRIDE;
53 virtual void Capture(const webrtc::DesktopRegion& region) OVERRIDE;
54 virtual void SetMouseShapeObserver(
55 MouseShapeObserver* mouse_shape_observer) OVERRIDE;
56
57 private:
58 // Make sure that the device contexts match the screen configuration.
59 void PrepareCaptureResources();
60
61 // Captures the current screen contents into the current buffer.
62 void CaptureImage();
63
64 // Expand the cursor shape to add a white outline for visibility against
65 // dark backgrounds.
66 void AddCursorOutline(int width, int height, uint32* dst);
67
68 // Capture the current cursor shape.
69 void CaptureCursor();
70
71 Callback* callback_;
72 MouseShapeObserver* mouse_shape_observer_;
73
74 // A thread-safe list of invalid rectangles, and the size of the most
75 // recently captured screen.
76 ScreenCapturerHelper helper_;
77
78 // Snapshot of the last cursor bitmap we sent to the client. This is used
79 // to diff against the current cursor so we only send a cursor-change
80 // message when the shape has changed.
81 MouseCursorShape last_cursor_;
82
83 ScopedThreadDesktop desktop_;
84
85 // GDI resources used for screen capture.
86 HDC desktop_dc_;
87 HDC memory_dc_;
88
89 // Queue of the frames buffers.
90 ScreenCaptureFrameQueue queue_;
91
92 // Rectangle describing the bounds of the desktop device context.
93 webrtc::DesktopRect desktop_dc_rect_;
94
95 // Class to calculate the difference between two screen bitmaps.
96 scoped_ptr<Differ> differ_;
97
98 HMODULE dwmapi_library_;
99 DwmEnableCompositionFunc composition_func_;
100
101 // Used to suppress duplicate logging of SetThreadExecutionState errors.
102 bool set_thread_execution_state_failed_;
103
104 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin);
105 };
106
107 ScreenCapturerWin::ScreenCapturerWin(bool disable_aero)
108 : callback_(NULL),
109 mouse_shape_observer_(NULL),
110 desktop_dc_(NULL),
111 memory_dc_(NULL),
112 dwmapi_library_(NULL),
113 composition_func_(NULL),
114 set_thread_execution_state_failed_(false) {
115 if (disable_aero) {
116 // Load dwmapi.dll dynamically since it is not available on XP.
117 if (!dwmapi_library_)
118 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
119
120 if (dwmapi_library_) {
121 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
122 GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
123 }
124 }
125 }
126
127 ScreenCapturerWin::~ScreenCapturerWin() {
128 if (desktop_dc_)
129 ReleaseDC(NULL, desktop_dc_);
130 if (memory_dc_)
131 DeleteDC(memory_dc_);
132
133 // Restore Aero.
134 if (composition_func_)
135 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
136
137 if (dwmapi_library_)
138 FreeLibrary(dwmapi_library_);
139 }
140
141 void ScreenCapturerWin::Capture(const webrtc::DesktopRegion& region) {
142 base::Time capture_start_time = base::Time::Now();
143
144 queue_.MoveToNextFrame();
145
146 // Request that the system not power-down the system, or the display hardware.
147 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
148 if (!set_thread_execution_state_failed_) {
149 set_thread_execution_state_failed_ = true;
150 LOG_GETLASTERROR(WARNING)
151 << "Failed to make system & display power assertion";
152 }
153 }
154
155 // Make sure the GDI capture resources are up-to-date.
156 PrepareCaptureResources();
157
158 // Copy screen bits to the current buffer.
159 CaptureImage();
160
161 const webrtc::DesktopFrame* current_frame = queue_.current_frame();
162 const webrtc::DesktopFrame* last_frame = queue_.previous_frame();
163 if (last_frame) {
164 // Make sure the differencer is set up correctly for these previous and
165 // current screens.
166 if (!differ_.get() ||
167 (differ_->width() != current_frame->size().width()) ||
168 (differ_->height() != current_frame->size().height()) ||
169 (differ_->bytes_per_row() != current_frame->stride())) {
170 differ_.reset(new Differ(current_frame->size().width(),
171 current_frame->size().height(),
172 webrtc::DesktopFrame::kBytesPerPixel,
173 current_frame->stride()));
174 }
175
176 // Calculate difference between the two last captured frames.
177 webrtc::DesktopRegion region;
178 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
179 &region);
180 helper_.InvalidateRegion(region);
181 } else {
182 // No previous frame is available. Invalidate the whole screen.
183 helper_.InvalidateScreen(current_frame->size());
184 }
185
186 helper_.set_size_most_recent(current_frame->size());
187
188 // Emit the current frame.
189 webrtc::DesktopFrame* frame = queue_.current_frame()->Share();
190 frame->set_dpi(webrtc::DesktopVector(
191 GetDeviceCaps(desktop_dc_, LOGPIXELSX),
192 GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
193 frame->mutable_updated_region()->Clear();
194 helper_.TakeInvalidRegion(frame->mutable_updated_region());
195 frame->set_capture_time_ms(
196 (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
197 callback_->OnCaptureCompleted(frame);
198
199 // Check for cursor shape update.
200 CaptureCursor();
201 }
202
203 void ScreenCapturerWin::SetMouseShapeObserver(
204 MouseShapeObserver* mouse_shape_observer) {
205 DCHECK(!mouse_shape_observer_);
206 DCHECK(mouse_shape_observer);
207
208 mouse_shape_observer_ = mouse_shape_observer;
209 }
210
211 void ScreenCapturerWin::Start(Callback* callback) {
212 DCHECK(!callback_);
213 DCHECK(callback);
214
215 callback_ = callback;
216
217 // Vote to disable Aero composited desktop effects while capturing. Windows
218 // will restore Aero automatically if the process exits. This has no effect
219 // under Windows 8 or higher. See crbug.com/124018.
220 if (composition_func_)
221 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
222 }
223
224 void ScreenCapturerWin::PrepareCaptureResources() {
225 // Switch to the desktop receiving user input if different from the current
226 // one.
227 scoped_ptr<Desktop> input_desktop = Desktop::GetInputDesktop();
228 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
229 // Release GDI resources otherwise SetThreadDesktop will fail.
230 if (desktop_dc_) {
231 ReleaseDC(NULL, desktop_dc_);
232 desktop_dc_ = NULL;
233 }
234
235 if (memory_dc_) {
236 DeleteDC(memory_dc_);
237 memory_dc_ = NULL;
238 }
239
240 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
241 // So we can continue capture screen bits, just from the wrong desktop.
242 desktop_.SetThreadDesktop(input_desktop.Pass());
243
244 // Re-assert our vote to disable Aero.
245 // See crbug.com/124018 and crbug.com/129906.
246 if (composition_func_ != NULL) {
247 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
248 }
249 }
250
251 // If the display bounds have changed then recreate GDI resources.
252 // TODO(wez): Also check for pixel format changes.
253 webrtc::DesktopRect screen_rect(webrtc::DesktopRect::MakeXYWH(
254 GetSystemMetrics(SM_XVIRTUALSCREEN),
255 GetSystemMetrics(SM_YVIRTUALSCREEN),
256 GetSystemMetrics(SM_CXVIRTUALSCREEN),
257 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
258 if (!screen_rect.equals(desktop_dc_rect_)) {
259 if (desktop_dc_) {
260 ReleaseDC(NULL, desktop_dc_);
261 desktop_dc_ = NULL;
262 }
263 if (memory_dc_) {
264 DeleteDC(memory_dc_);
265 memory_dc_ = NULL;
266 }
267 desktop_dc_rect_ = webrtc::DesktopRect();
268 }
269
270 if (desktop_dc_ == NULL) {
271 DCHECK(memory_dc_ == NULL);
272
273 // Create GDI device contexts to capture from the desktop into memory.
274 desktop_dc_ = GetDC(NULL);
275 CHECK(desktop_dc_);
276 memory_dc_ = CreateCompatibleDC(desktop_dc_);
277 CHECK(memory_dc_);
278 desktop_dc_rect_ = screen_rect;
279
280 // Make sure the frame buffers will be reallocated.
281 queue_.Reset();
282
283 helper_.ClearInvalidRegion();
284 }
285 }
286
287 void ScreenCapturerWin::CaptureImage() {
288 // If the current buffer is from an older generation then allocate a new one.
289 // Note that we can't reallocate other buffers at this point, since the caller
290 // may still be reading from them.
291 if (!queue_.current_frame()) {
292 DCHECK(desktop_dc_ != NULL);
293 DCHECK(memory_dc_ != NULL);
294
295 webrtc::DesktopSize size = webrtc::DesktopSize(
296 desktop_dc_rect_.width(), desktop_dc_rect_.height());
297
298 size_t buffer_size = size.width() * size.height() *
299 webrtc::DesktopFrame::kBytesPerPixel;
300 webrtc::SharedMemory* shared_memory =
301 callback_->CreateSharedMemory(buffer_size);
302 scoped_ptr<webrtc::DesktopFrameWin> buffer(
303 webrtc::DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
304 queue_.ReplaceCurrentFrame(buffer.PassAs<webrtc::DesktopFrame>());
305 }
306
307 // Select the target bitmap into the memory dc and copy the rect from desktop
308 // to memory.
309 webrtc::DesktopFrameWin* current = static_cast<webrtc::DesktopFrameWin*>(
310 queue_.current_frame()->GetUnderlyingFrame());
311 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
312 if (previous_object != NULL) {
313 BitBlt(memory_dc_,
314 0, 0, desktop_dc_rect_.width(), desktop_dc_rect_.height(),
315 desktop_dc_,
316 desktop_dc_rect_.left(), desktop_dc_rect_.top(),
317 SRCCOPY | CAPTUREBLT);
318
319 // Select back the previously selected object to that the device contect
320 // could be destroyed independently of the bitmap if needed.
321 SelectObject(memory_dc_, previous_object);
322 }
323 }
324
325 void ScreenCapturerWin::AddCursorOutline(int width,
326 int height,
327 uint32* dst) {
328 for (int y = 0; y < height; y++) {
329 for (int x = 0; x < width; x++) {
330 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
331 // neighbor pixels to see if this should be changed to an outline pixel.
332 if (*dst == kPixelBgraTransparent) {
333 // Change to white pixel if any neighbors (top, bottom, left, right)
334 // are black.
335 if ((y > 0 && dst[-width] == kPixelBgraBlack) ||
336 (y < height - 1 && dst[width] == kPixelBgraBlack) ||
337 (x > 0 && dst[-1] == kPixelBgraBlack) ||
338 (x < width - 1 && dst[1] == kPixelBgraBlack)) {
339 *dst = kPixelBgraWhite;
340 }
341 }
342 dst++;
343 }
344 }
345 }
346
347 void ScreenCapturerWin::CaptureCursor() {
348 CURSORINFO cursor_info;
349 cursor_info.cbSize = sizeof(CURSORINFO);
350 if (!GetCursorInfo(&cursor_info)) {
351 VLOG(3) << "Unable to get cursor info. Error = " << GetLastError();
352 return;
353 }
354
355 // Note that this does not need to be freed.
356 HCURSOR hcursor = cursor_info.hCursor;
357 ICONINFO iinfo;
358 if (!GetIconInfo(hcursor, &iinfo)) {
359 VLOG(3) << "Unable to get cursor icon info. Error = " << GetLastError();
360 return;
361 }
362 int hotspot_x = iinfo.xHotspot;
363 int hotspot_y = iinfo.yHotspot;
364
365 // Get the cursor bitmap.
366 HBITMAP hbitmap;
367 BITMAP bitmap;
368 bool color_bitmap;
369 if (iinfo.hbmColor) {
370 // Color cursor bitmap.
371 color_bitmap = true;
372 hbitmap = reinterpret_cast<HBITMAP>(
373 CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION));
374 if (!hbitmap) {
375 VLOG(3) << "Unable to copy color cursor image. Error = "
376 << GetLastError();
377 return;
378 }
379
380 // Free the color and mask bitmaps since we only need our copy.
381 DeleteObject(iinfo.hbmColor);
382 DeleteObject(iinfo.hbmMask);
383 } else {
384 // Black and white (xor) cursor.
385 color_bitmap = false;
386 hbitmap = iinfo.hbmMask;
387 }
388
389 if (!GetObject(hbitmap, sizeof(BITMAP), &bitmap)) {
390 VLOG(3) << "Unable to get cursor bitmap. Error = " << GetLastError();
391 DeleteObject(hbitmap);
392 return;
393 }
394
395 int width = bitmap.bmWidth;
396 int height = bitmap.bmHeight;
397 // For non-color cursors, the mask contains both an AND and an XOR mask and
398 // the height includes both. Thus, the width is correct, but we need to
399 // divide by 2 to get the correct mask height.
400 if (!color_bitmap) {
401 height /= 2;
402 }
403 int data_size = height * width * webrtc::DesktopFrame::kBytesPerPixel;
404
405 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape());
406 cursor->data.resize(data_size);
407 uint8* cursor_dst_data =
408 reinterpret_cast<uint8*>(&*(cursor->data.begin()));
409
410 // Copy/convert cursor bitmap into format needed by chromotocol.
411 int row_bytes = bitmap.bmWidthBytes;
412 if (color_bitmap) {
413 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) {
414 VLOG(3) << "Unsupported color cursor format. Error = " << GetLastError();
415 DeleteObject(hbitmap);
416 return;
417 }
418
419 // Copy across colour cursor imagery.
420 // MouseCursorShape stores imagery top-down, and premultiplied
421 // by the alpha channel, whereas windows stores them bottom-up
422 // and not premultiplied.
423 uint8* cursor_src_data = reinterpret_cast<uint8*>(bitmap.bmBits);
424 uint8* src = cursor_src_data + ((height - 1) * row_bytes);
425 uint8* dst = cursor_dst_data;
426 for (int row = 0; row < height; ++row) {
427 for (int column = 0; column < width; ++column) {
428 dst[0] = AlphaMul(src[0], src[3]);
429 dst[1] = AlphaMul(src[1], src[3]);
430 dst[2] = AlphaMul(src[2], src[3]);
431 dst[3] = src[3];
432 dst += webrtc::DesktopFrame::kBytesPerPixel;
433 src += webrtc::DesktopFrame::kBytesPerPixel;
434 }
435 src -= row_bytes + (width * webrtc::DesktopFrame::kBytesPerPixel);
436 }
437 } else {
438 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) {
439 VLOG(3) << "Unsupported cursor mask format. Error = " << GetLastError();
440 DeleteObject(hbitmap);
441 return;
442 }
443
444 // x2 because there are 2 masks in the bitmap: AND and XOR.
445 int mask_bytes = height * row_bytes * 2;
446 scoped_ptr<uint8[]> mask(new uint8[mask_bytes]);
447 if (!GetBitmapBits(hbitmap, mask_bytes, mask.get())) {
448 VLOG(3) << "Unable to get cursor mask bits. Error = " << GetLastError();
449 DeleteObject(hbitmap);
450 return;
451 }
452 uint8* and_mask = mask.get();
453 uint8* xor_mask = mask.get() + height * row_bytes;
454 uint8* dst = cursor_dst_data;
455 bool add_outline = false;
456 for (int y = 0; y < height; y++) {
457 for (int x = 0; x < width; x++) {
458 int byte = y * row_bytes + x / 8;
459 int bit = 7 - x % 8;
460 int and_bit = and_mask[byte] & (1 << bit);
461 int xor_bit = xor_mask[byte] & (1 << bit);
462
463 // The two cursor masks combine as follows:
464 // AND XOR Windows Result Our result RGB Alpha
465 // 0 0 Black Black 00 ff
466 // 0 1 White White ff ff
467 // 1 0 Screen Transparent 00 00
468 // 1 1 Reverse-screen Black 00 ff
469 // Since we don't support XOR cursors, we replace the "Reverse Screen"
470 // with black. In this case, we also add an outline around the cursor
471 // so that it is visible against a dark background.
472 int rgb = (!and_bit && xor_bit) ? 0xff : 0x00;
473 int alpha = (and_bit && !xor_bit) ? 0x00 : 0xff;
474 *dst++ = rgb;
475 *dst++ = rgb;
476 *dst++ = rgb;
477 *dst++ = alpha;
478 if (and_bit && xor_bit) {
479 add_outline = true;
480 }
481 }
482 }
483 if (add_outline) {
484 AddCursorOutline(width, height,
485 reinterpret_cast<uint32*>(cursor_dst_data));
486 }
487 }
488
489 DeleteObject(hbitmap);
490
491 cursor->size.set(width, height);
492 cursor->hotspot.set(hotspot_x, hotspot_y);
493
494 // Compare the current cursor with the last one we sent to the client. If
495 // they're the same, then don't bother sending the cursor again.
496 if (last_cursor_.size.equals(cursor->size) &&
497 last_cursor_.hotspot.equals(cursor->hotspot) &&
498 last_cursor_.data == cursor->data) {
499 return;
500 }
501
502 VLOG(3) << "Sending updated cursor: " << width << "x" << height;
503
504 // Record the last cursor image that we sent to the client.
505 last_cursor_ = *cursor;
506
507 if (mouse_shape_observer_)
508 mouse_shape_observer_->OnCursorShapeChanged(cursor.Pass());
509 }
510
511 } // namespace
512
513 // static
514 scoped_ptr<ScreenCapturer> ScreenCapturer::Create() {
515 return CreateWithDisableAero(true);
516 }
517
518 // static
519 scoped_ptr<ScreenCapturer> ScreenCapturer::CreateWithDisableAero(
520 bool disable_aero) {
521 return scoped_ptr<ScreenCapturer>(new ScreenCapturerWin(disable_aero));
522 }
523
524 } // namespace media
OLDNEW
« no previous file with comments | « media/video/capture/screen/screen_capturer_unittest.cc ('k') | media/video/capture/screen/screen_capturer_x11.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698