summaryrefslogtreecommitdiffstats
path: root/remoting/host/capturer_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'remoting/host/capturer_win.cc')
-rw-r--r--remoting/host/capturer_win.cc337
1 files changed, 337 insertions, 0 deletions
diff --git a/remoting/host/capturer_win.cc b/remoting/host/capturer_win.cc
new file mode 100644
index 0000000..b5fe81e
--- /dev/null
+++ b/remoting/host/capturer_win.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/capturer.h"
+
+#include <windows.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "remoting/host/capturer_helper.h"
+#include "remoting/host/differ.h"
+#include "ui/gfx/rect.h"
+
+namespace remoting {
+
+namespace {
+
+// CapturerGdi captures 32bit RGB using GDI.
+//
+// CapturerGdi is double-buffered as required by Capturer. See
+// remoting/host/capturer.h.
+class CapturerGdi : public Capturer {
+ public:
+ CapturerGdi();
+ virtual ~CapturerGdi();
+
+ // Capturer interface.
+ virtual void ScreenConfigurationChanged() OVERRIDE;
+ virtual media::VideoFrame::Format pixel_format() const OVERRIDE;
+ virtual void ClearInvalidRects() OVERRIDE;
+ virtual void InvalidateRects(const InvalidRects& inval_rects) OVERRIDE;
+ virtual void InvalidateScreen(const gfx::Size& size) OVERRIDE;
+ virtual void InvalidateFullScreen() OVERRIDE;
+ virtual void CaptureInvalidRects(CaptureCompletedCallback* callback) OVERRIDE;
+ virtual const gfx::Size& size_most_recent() const OVERRIDE;
+
+ private:
+ struct VideoFrameBuffer {
+ VideoFrameBuffer(void* data, const gfx::Size& size, int bytes_per_pixel,
+ int bytes_per_row)
+ : data(data), size(size), bytes_per_pixel(bytes_per_pixel),
+ bytes_per_row(bytes_per_row) {
+ }
+ VideoFrameBuffer() {
+ data = 0;
+ size = gfx::Size(0, 0);
+ bytes_per_pixel = 0;
+ bytes_per_row = 0;
+ }
+ void* data;
+ gfx::Size size;
+ int bytes_per_pixel;
+ int bytes_per_row;
+ };
+
+ // Make sure that the current buffer has the same size as the screen.
+ void UpdateBufferCapture(const gfx::Size& size);
+
+ // Allocate memory for a buffer of a given size, freeing any memory previously
+ // allocated for that buffer.
+ void ReallocateBuffer(int buffer_index, const gfx::Size& size);
+
+ void CalculateInvalidRects();
+ void CaptureRects(const InvalidRects& rects,
+ CaptureCompletedCallback* callback);
+
+ void ReleaseBuffers();
+ // Generates an image in the current buffer.
+ void CaptureImage();
+
+ // Gets the current screen size and calls ScreenConfigurationChanged
+ // if the screen size has changed.
+ void MaybeChangeScreenConfiguration();
+
+ // Gets the screen size.
+ gfx::Size GetScreenSize();
+
+ // A thread-safe list of invalid rectangles, and the size of the most
+ // recently captured screen.
+ CapturerHelper helper;
+
+ // There are two buffers for the screen images, as required by Capturer.
+ static const int kNumBuffers = 2;
+ VideoFrameBuffer buffers_[kNumBuffers];
+
+ // Gdi specific information about screen.
+ HDC desktop_dc_;
+ HDC memory_dc_;
+ HBITMAP target_bitmap_[kNumBuffers];
+
+ // The screen size attached to the device contexts through which the screen
+ // is captured.
+ gfx::Size dc_size_;
+
+ // The current buffer with valid data for reading.
+ int current_buffer_;
+
+ // Format of pixels returned in buffer.
+ media::VideoFrame::Format pixel_format_;
+
+ // Class to calculate the difference between two screen bitmaps.
+ scoped_ptr<Differ> differ_;
+
+ // True if we should force a fullscreen capture.
+ bool capture_fullscreen_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturerGdi);
+};
+
+// 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors.
+static const int kPixelsPerMeter = 3780;
+// 32 bit RGBA is 4 bytes per pixel.
+static const int kBytesPerPixel = 4;
+
+CapturerGdi::CapturerGdi()
+ : desktop_dc_(NULL),
+ memory_dc_(NULL),
+ dc_size_(0, 0),
+ current_buffer_(0),
+ pixel_format_(media::VideoFrame::RGB32),
+ capture_fullscreen_(true) {
+ memset(target_bitmap_, 0, sizeof(target_bitmap_));
+ memset(buffers_, 0, sizeof(buffers_));
+ ScreenConfigurationChanged();
+}
+
+CapturerGdi::~CapturerGdi() {
+ ReleaseBuffers();
+}
+
+media::VideoFrame::Format CapturerGdi::pixel_format() const {
+ return pixel_format_;
+}
+
+void CapturerGdi::ClearInvalidRects() {
+ helper.ClearInvalidRects();
+}
+
+void CapturerGdi::InvalidateRects(const InvalidRects& inval_rects) {
+ helper.InvalidateRects(inval_rects);
+}
+
+void CapturerGdi::InvalidateScreen(const gfx::Size& size) {
+ helper.InvalidateScreen(size);
+}
+
+void CapturerGdi::InvalidateFullScreen() {
+ helper.InvalidateFullScreen();
+}
+
+void CapturerGdi::CaptureInvalidRects(CaptureCompletedCallback* callback) {
+ CalculateInvalidRects();
+ InvalidRects inval_rects;
+ helper.SwapInvalidRects(inval_rects);
+ CaptureRects(inval_rects, callback);
+}
+
+const gfx::Size& CapturerGdi::size_most_recent() const {
+ return helper.size_most_recent();
+}
+
+void CapturerGdi::ReleaseBuffers() {
+ for (int i = kNumBuffers - 1; i >= 0; i--) {
+ if (target_bitmap_[i]) {
+ DeleteObject(target_bitmap_[i]);
+ target_bitmap_[i] = NULL;
+ }
+ if (buffers_[i].data) {
+ DeleteObject(buffers_[i].data);
+ buffers_[i].data = NULL;
+ }
+ }
+
+ desktop_dc_ = NULL;
+ if (memory_dc_) {
+ DeleteDC(memory_dc_);
+ memory_dc_ = NULL;
+ }
+}
+
+void CapturerGdi::ScreenConfigurationChanged() {
+ // We poll for screen configuration changes, so ignore notifications.
+}
+
+void CapturerGdi::UpdateBufferCapture(const gfx::Size& size) {
+ // Make sure the DCs have the correct dimensions.
+ if (size != dc_size_) {
+ // TODO(simonmorris): screen dimensions changing isn't equivalent to needing
+ // a new DC, but it's good enough for now.
+ desktop_dc_ = GetDC(GetDesktopWindow());
+ if (memory_dc_)
+ DeleteDC(memory_dc_);
+ memory_dc_ = CreateCompatibleDC(desktop_dc_);
+ dc_size_ = size;
+ }
+
+ // Make sure the current bitmap has the correct dimensions.
+ if (size != buffers_[current_buffer_].size) {
+ ReallocateBuffer(current_buffer_, size);
+ capture_fullscreen_ = true;
+ }
+}
+
+void CapturerGdi::ReallocateBuffer(int buffer_index, const gfx::Size& size) {
+ // Delete any previously constructed bitmap.
+ if (target_bitmap_[buffer_index]) {
+ DeleteObject(target_bitmap_[buffer_index]);
+ target_bitmap_[buffer_index] = NULL;
+ }
+ if (buffers_[buffer_index].data) {
+ DeleteObject(buffers_[buffer_index].data);
+ buffers_[buffer_index].data = NULL;
+ }
+
+ // Create a bitmap to keep the desktop image.
+ int rounded_width = (size.width() + 3) & (~3);
+
+ // Dimensions of screen.
+ pixel_format_ = media::VideoFrame::RGB32;
+ int bytes_per_row = rounded_width * kBytesPerPixel;
+
+ // Create a device independent bitmap (DIB) that is the same size.
+ BITMAPINFO bmi;
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biHeight = -size.height();
+ bmi.bmiHeader.biWidth = size.width();
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = kBytesPerPixel * 8;
+ bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
+ bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
+ bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter;
+ bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter;
+
+ // Create memory for the buffers.
+ target_bitmap_[buffer_index] =
+ CreateDIBSection(desktop_dc_, &bmi, DIB_RGB_COLORS,
+ static_cast<void**>(&buffers_[buffer_index].data),
+ NULL, 0);
+ buffers_[buffer_index].size = gfx::Size(bmi.bmiHeader.biWidth,
+ std::abs(bmi.bmiHeader.biHeight));
+ buffers_[buffer_index].bytes_per_pixel = bmi.bmiHeader.biBitCount / 8;
+ buffers_[buffer_index].bytes_per_row =
+ bmi.bmiHeader.biSizeImage / std::abs(bmi.bmiHeader.biHeight);
+}
+
+void CapturerGdi::CalculateInvalidRects() {
+ CaptureImage();
+
+ const VideoFrameBuffer& current = buffers_[current_buffer_];
+ if (helper.IsCaptureFullScreen(current.size))
+ capture_fullscreen_ = true;
+
+ if (capture_fullscreen_) {
+ InvalidateScreen(current.size);
+ capture_fullscreen_ = false;
+ return;
+ }
+
+ // Find the previous and current screens.
+ int prev_buffer_id = current_buffer_ - 1;
+ if (prev_buffer_id < 0) {
+ prev_buffer_id = kNumBuffers - 1;
+ }
+ const VideoFrameBuffer& prev = buffers_[prev_buffer_id];
+
+ // Maybe the previous and current screens can't be differenced.
+ if ((current.size != prev.size) ||
+ (current.bytes_per_pixel != prev.bytes_per_pixel) ||
+ (current.bytes_per_row != prev.bytes_per_row)) {
+ InvalidateScreen(current.size);
+ return;
+ }
+
+ // Make sure the differencer is set up correctly for these previous and
+ // current screens.
+ if (!differ_.get() ||
+ (differ_->width() != current.size.width()) ||
+ (differ_->height() != current.size.height()) ||
+ (differ_->bytes_per_pixel() != current.bytes_per_pixel) ||
+ (differ_->bytes_per_row() != current.bytes_per_row)) {
+ differ_.reset(new Differ(current.size.width(), current.size.height(),
+ current.bytes_per_pixel, current.bytes_per_row));
+ }
+
+ InvalidRects rects;
+ differ_->CalcDirtyRects(prev.data, current.data, &rects);
+
+ InvalidateRects(rects);
+}
+
+void CapturerGdi::CaptureRects(const InvalidRects& rects,
+ CaptureCompletedCallback* callback) {
+ scoped_ptr<CaptureCompletedCallback> callback_deleter(callback);
+
+ const VideoFrameBuffer& buffer = buffers_[current_buffer_];
+ current_buffer_ = (current_buffer_ + 1) % kNumBuffers;
+
+ DataPlanes planes;
+ planes.data[0] = static_cast<uint8*>(buffer.data);
+ planes.strides[0] = buffer.bytes_per_row;
+
+ scoped_refptr<CaptureData> data(new CaptureData(planes,
+ buffer.size,
+ pixel_format_));
+ data->mutable_dirty_rects() = rects;
+
+ helper.set_size_most_recent(data->size());
+
+ callback->Run(data);
+}
+
+void CapturerGdi::CaptureImage() {
+ // Make sure the structures we use to capture the image have the correct size.
+ UpdateBufferCapture(GetScreenSize());
+
+ // Select the target bitmap into the memory dc.
+ SelectObject(memory_dc_, target_bitmap_[current_buffer_]);
+
+ // And then copy the rect from desktop to memory.
+ BitBlt(memory_dc_, 0, 0, buffers_[current_buffer_].size.width(),
+ buffers_[current_buffer_].size.height(), desktop_dc_, 0, 0,
+ SRCCOPY | CAPTUREBLT);
+}
+
+gfx::Size CapturerGdi::GetScreenSize() {
+ return gfx::Size(GetSystemMetrics(SM_CXSCREEN),
+ GetSystemMetrics(SM_CYSCREEN));
+}
+
+} // namespace
+
+// static
+Capturer* Capturer::Create() {
+ return new CapturerGdi();
+}
+
+} // namespace remoting