diff options
Diffstat (limited to 'remoting/host/capturer_win.cc')
-rw-r--r-- | remoting/host/capturer_win.cc | 337 |
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 |