// 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 #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_; // 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(&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 callback_deleter(callback); const VideoFrameBuffer& buffer = buffers_[current_buffer_]; current_buffer_ = (current_buffer_ + 1) % kNumBuffers; DataPlanes planes; planes.data[0] = static_cast(buffer.data); planes.strides[0] = buffer.bytes_per_row; scoped_refptr 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