blob: 6aaff4e3d0d5f50b2970678ee7188b6746d112a0 [file] [log] [blame]
// 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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xdamage.h>
#include <set>
#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<XDamageNotifyEvent*>(&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<CaptureCompletedCallback> callback_deleter(callback);
uint8* buffer = buffers_[current_buffer_];
DataPlanes planes;
planes.data[0] = buffer;
planes.strides[0] = stride_;
scoped_refptr<CaptureData> 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<uint32_t*>(dst_pos);
uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(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