// 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 #include #include #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "remoting/base/types.h" #include "remoting/host/capturer_helper.h" #include "remoting/host/x_server_pixel_buffer.h" namespace remoting { namespace { // A class to perform capturing for Linux. class CapturerLinux : public Capturer { public: CapturerLinux(); virtual ~CapturerLinux(); // 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: bool Init(); // TODO(ajwong): Do we really want this to be synchronous? void CalculateInvalidRects(); void CaptureRects(const InvalidRects& rects, Capturer::CaptureCompletedCallback* callback); void DeinitXlib(); // We expose two forms of blitting to handle variations in the pixel format. // In FastBlit, the operation is effectively a memcpy. void FastBlit(uint8* image, const gfx::Rect& rect, CaptureData* capture_data); void SlowBlit(uint8* image, const gfx::Rect& rect, CaptureData* capture_data); static const int kBytesPerPixel = 4; // X11 graphics context. Display* display_; GC gc_; Window root_window_; int width_; int height_; // XDamage information. Damage damage_handle_; int damage_event_base_; int damage_error_base_; // Access to the X Server's pixel buffer. XServerPixelBuffer x_server_pixel_buffer_; // A thread-safe list of invalid rectangles, and the size of the most // recently captured screen. CapturerHelper helper_; // Capture state. static const int kNumBuffers = 2; uint8* buffers_[kNumBuffers]; int current_buffer_; int stride_; bool capture_fullscreen_; // Format of pixels returned in buffer. media::VideoFrame::Format pixel_format_; // Invalid rects in the last capture. This is used to synchronize current with // the previous buffer used. InvalidRects last_invalid_rects_; // Last capture buffer used. uint8* last_buffer_; DISALLOW_COPY_AND_ASSIGN(CapturerLinux); }; CapturerLinux::CapturerLinux() : display_(NULL), gc_(NULL), root_window_(BadValue), width_(0), height_(0), damage_handle_(BadValue), damage_event_base_(-1), damage_error_base_(-1), current_buffer_(0), stride_(0), capture_fullscreen_(true), pixel_format_(media::VideoFrame::RGB32), last_buffer_(NULL) { for (int i = 0; i < kNumBuffers; i++) { buffers_[i] = NULL; } CHECK(Init()); } CapturerLinux::~CapturerLinux() { DeinitXlib(); for (int i = 0; i < kNumBuffers; i++) { delete [] buffers_[i]; buffers_[i] = NULL; } } bool CapturerLinux::Init() { // TODO(ajwong): We should specify the display string we are attaching to // in the constructor. display_ = XOpenDisplay(NULL); if (!display_) { LOG(ERROR) << "Unable to open display"; return false; } x_server_pixel_buffer_.Init(display_); root_window_ = RootWindow(display_, DefaultScreen(display_)); if (root_window_ == BadValue) { LOG(ERROR) << "Unable to get the root window"; DeinitXlib(); return false; } gc_ = XCreateGC(display_, root_window_, 0, NULL); if (gc_ == NULL) { LOG(ERROR) << "Unable to get graphics context"; DeinitXlib(); return false; } // Setup XDamage to report changes in the damage window. Mark the whole // window as invalid. if (!XDamageQueryExtension(display_, &damage_event_base_, &damage_error_base_)) { LOG(ERROR) << "Server does not support XDamage."; DeinitXlib(); return false; } damage_handle_ = XDamageCreate(display_, root_window_, XDamageReportDeltaRectangles); if (damage_handle_ == BadValue) { LOG(ERROR) << "Unable to create damage handle."; DeinitXlib(); return false; } // TODO(ajwong): We should be able to replace this with a XDamageAdd(). capture_fullscreen_ = true; // Set up the dimensions of the catpure framebuffer. XWindowAttributes root_attr; XGetWindowAttributes(display_, root_window_, &root_attr); width_ = root_attr.width; height_ = root_attr.height; stride_ = width_ * kBytesPerPixel; VLOG(1) << "Initialized with Geometry: " << width_ << "x" << height_; // Allocate the screen buffers. for (int i = 0; i < kNumBuffers; i++) { buffers_[i] = new uint8[width_ * height_ * kBytesPerPixel]; } return true; } void CapturerLinux::ScreenConfigurationChanged() { // TODO(ajwong): Support resolution changes. NOTIMPLEMENTED(); } media::VideoFrame::Format CapturerLinux::pixel_format() const { return pixel_format_; } void CapturerLinux::ClearInvalidRects() { helper_.ClearInvalidRects(); } void CapturerLinux::InvalidateRects(const InvalidRects& inval_rects) { helper_.InvalidateRects(inval_rects); } void CapturerLinux::InvalidateScreen(const gfx::Size& size) { helper_.InvalidateScreen(size); } void CapturerLinux::InvalidateFullScreen() { helper_.InvalidateFullScreen(); } void CapturerLinux::CaptureInvalidRects( CaptureCompletedCallback* callback) { CalculateInvalidRects(); InvalidRects rects; helper_.SwapInvalidRects(rects); CaptureRects(rects, callback); } void CapturerLinux::CalculateInvalidRects() { if (helper_.IsCaptureFullScreen(gfx::Size(width_, height_))) capture_fullscreen_ = true; // TODO(ajwong): The capture_fullscreen_ logic here is very ugly. Refactor. // Find the number of events that are outstanding "now." We don't just loop // on XPending because we want to guarantee this terminates. int events_to_process = XPending(display_); XEvent e; InvalidRects invalid_rects; for (int i = 0; i < events_to_process; i++) { XNextEvent(display_, &e); if (e.type == damage_event_base_ + XDamageNotify) { // If we're doing a full screen capture, we should just drain the events. if (!capture_fullscreen_) { XDamageNotifyEvent *event = reinterpret_cast(&e); gfx::Rect damage_rect(event->area.x, event->area.y, event->area.width, event->area.height); // TODO(hclam): Perform more checks on the rect. if (damage_rect.width() <= 0 && damage_rect.height() <= 0) continue; invalid_rects.insert(damage_rect); VLOG(3) << "Damage received for rect at (" << damage_rect.x() << "," << damage_rect.y() << ") size (" << damage_rect.width() << "," << damage_rect.height() << ")"; } } else { LOG(WARNING) << "Got unknown event type: " << e.type; } } if (capture_fullscreen_) { // TODO(hclam): Check the new dimension again. helper_.InvalidateScreen(gfx::Size(width_, height_)); capture_fullscreen_ = false; } else { helper_.InvalidateRects(invalid_rects); } } void CapturerLinux::CaptureRects( const InvalidRects& rects, Capturer::CaptureCompletedCallback* callback) { scoped_ptr callback_deleter(callback); uint8* buffer = buffers_[current_buffer_]; DataPlanes planes; planes.data[0] = buffer; planes.strides[0] = stride_; scoped_refptr capture_data(new CaptureData( planes, gfx::Size(width_, height_), media::VideoFrame::RGB32)); // Synchronize the current buffer with the last one since we do not capture // the entire desktop. Note that encoder may be reading from the previous // buffer at this time so thread access complaints are false positives. // TODO(hclam): We can reduce the amount of copying here by subtracting // |rects| from |last_invalid_rects_|. for (InvalidRects::const_iterator it = last_invalid_rects_.begin(); last_buffer_ && it != last_invalid_rects_.end(); ++it) { int offset = it->y() * stride_ + it->x() * kBytesPerPixel; for (int i = 0; i < it->height(); ++i) { memcpy(buffer + offset, last_buffer_ + offset, it->width() * kBytesPerPixel); offset += width_ * kBytesPerPixel; } } for (InvalidRects::const_iterator it = rects.begin(); it != rects.end(); ++it) { uint8* image = x_server_pixel_buffer_.CaptureRect(*it); // Check if we can fastpath the blit. int depth = x_server_pixel_buffer_.GetDepth(); int bpp = x_server_pixel_buffer_.GetBitsPerPixel(); bool is_rgb = x_server_pixel_buffer_.IsRgb(); if ((depth == 24 || depth == 32) && bpp == 32 && is_rgb) { VLOG(3) << "Fast blitting"; FastBlit(image, *it, capture_data); } else { VLOG(3) << "Slow blitting"; SlowBlit(image, *it, capture_data); } } // TODO(ajwong): We should only repair the rects that were copied! XDamageSubtract(display_, damage_handle_, None, None); capture_data->mutable_dirty_rects() = rects; last_invalid_rects_ = rects; last_buffer_ = buffer; current_buffer_ = (current_buffer_ + 1) % kNumBuffers; helper_.set_size_most_recent(capture_data->size()); callback->Run(capture_data); } void CapturerLinux::DeinitXlib() { if (gc_) { XFreeGC(display_, gc_); gc_ = NULL; } if (display_) { XCloseDisplay(display_); display_ = NULL; } } void CapturerLinux::FastBlit(uint8* image, const gfx::Rect& rect, CaptureData* capture_data) { uint8* src_pos = image; int src_stride = x_server_pixel_buffer_.GetStride(); int dst_x = rect.x(), dst_y = rect.y(); DataPlanes planes = capture_data->data_planes(); uint8* dst_buffer = planes.data[0]; const int dst_stride = planes.strides[0]; uint8* dst_pos = dst_buffer + dst_stride * dst_y; dst_pos += dst_x * kBytesPerPixel; int height = rect.height(), row_bytes = rect.width() * kBytesPerPixel; for (int y = 0; y < height; ++y) { memcpy(dst_pos, src_pos, row_bytes); src_pos += src_stride; dst_pos += dst_stride; } } void CapturerLinux::SlowBlit(uint8* image, const gfx::Rect& rect, CaptureData* capture_data) { DataPlanes planes = capture_data->data_planes(); uint8* dst_buffer = planes.data[0]; const int dst_stride = planes.strides[0]; int src_stride = x_server_pixel_buffer_.GetStride(); int dst_x = rect.x(), dst_y = rect.y(); int width = rect.width(), height = rect.height(); unsigned int red_mask = x_server_pixel_buffer_.GetRedMask(); unsigned int blue_mask = x_server_pixel_buffer_.GetBlueMask(); unsigned int green_mask = x_server_pixel_buffer_.GetGreenMask(); unsigned int red_shift = x_server_pixel_buffer_.GetRedShift(); unsigned int blue_shift = x_server_pixel_buffer_.GetBlueShift(); unsigned int green_shift = x_server_pixel_buffer_.GetGreenShift(); unsigned int max_red = red_mask >> red_shift; unsigned int max_blue = blue_mask >> blue_shift; unsigned int max_green = green_mask >> green_shift; unsigned int bits_per_pixel = x_server_pixel_buffer_.GetBitsPerPixel(); uint8* dst_pos = dst_buffer + dst_stride * dst_y; uint8* src_pos = image; dst_pos += dst_x * kBytesPerPixel; // TODO(hclam): Optimize, perhaps using MMX code or by converting to // YUV directly for (int y = 0; y < height; y++) { uint32_t* dst_pos_32 = reinterpret_cast(dst_pos); uint32_t* src_pos_32 = reinterpret_cast(src_pos); uint16_t* src_pos_16 = reinterpret_cast(src_pos); for (int x = 0; x < width; x++) { // Dereference through an appropriately-aligned pointer. uint32_t pixel; if (bits_per_pixel == 32) pixel = src_pos_32[x]; else if (bits_per_pixel == 16) pixel = src_pos_16[x]; else pixel = src_pos[x]; uint32_t r = (((pixel & red_mask) >> red_shift) * 255) / max_red; uint32_t b = (((pixel & blue_mask) >> blue_shift) * 255) / max_blue; uint32_t g = (((pixel & green_mask) >> green_shift) * 255) / max_green; // Write as 32-bit RGB. dst_pos_32[x] = r << 16 | g << 8 | b; } dst_pos += dst_stride; src_pos += src_stride; } } const gfx::Size& CapturerLinux::size_most_recent() const { return helper_.size_most_recent(); } } // namespace // static Capturer* Capturer::Create() { return new CapturerLinux(); } } // namespace remoting