blob: 825e724c936d9d6d9fed1ad993396a860a08881f [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/host/capturer_helper.h"
#include "remoting/host/differ.h"
#include "remoting/host/x_server_pixel_buffer.h"
namespace remoting {
namespace {
static const int kBytesPerPixel = 4;
static bool ShouldUseXDamage() {
// For now, always use full-screen polling instead of the DAMAGE extension,
// as this extension is broken on many current systems OOTB.
return false;
}
// A class representing a full-frame pixel buffer
class VideoFrameBuffer {
public:
VideoFrameBuffer() : bytes_per_row_(0), needs_update_(true) {}
void Update(Display* display, Window root_window) {
if (needs_update_) {
needs_update_ = false;
XWindowAttributes root_attr;
XGetWindowAttributes(display, root_window, &root_attr);
if (root_attr.width != size_.width() ||
root_attr.height != size_.height()) {
size_.SetSize(root_attr.width, root_attr.height);
bytes_per_row_ = size_.width() * kBytesPerPixel;
size_t buffer_size = size_.width() * size_.height() * kBytesPerPixel;
ptr_.reset(new uint8[buffer_size]);
}
}
}
gfx::Size size() const { return size_; }
int bytes_per_row() const { return bytes_per_row_; }
uint8* ptr() const { return ptr_.get(); }
void set_needs_update() { needs_update_ = true; }
private:
gfx::Size size_;
int bytes_per_row_;
scoped_array<uint8> ptr_;
bool needs_update_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameBuffer);
};
// A class to perform capturing for Linux.
class CapturerLinux : public Capturer {
public:
CapturerLinux();
virtual ~CapturerLinux();
bool Init(); // TODO(ajwong): Do we really want this to be synchronous?
// Capturer interface.
virtual void ScreenConfigurationChanged() OVERRIDE;
virtual media::VideoFrame::Format pixel_format() const OVERRIDE;
virtual void ClearInvalidRegion() OVERRIDE;
virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
virtual void InvalidateScreen(const gfx::Size& size) OVERRIDE;
virtual void InvalidateFullScreen() OVERRIDE;
virtual void CaptureInvalidRegion(CaptureCompletedCallback* callback)
OVERRIDE;
virtual const gfx::Size& size_most_recent() const OVERRIDE;
private:
void InitXDamage();
// Read and handle all currently-pending XEvents.
// In the DAMAGE case, process the XDamage events and store the resulting
// damage rectangles in the CapturerHelper.
// In all cases, call ScreenConfigurationChanged() in response to any
// ConfigNotify events.
void ProcessPendingXEvents();
// Capture screen pixels, and return the data in a new CaptureData object,
// to be freed by the caller.
// In the DAMAGE case, the CapturerHelper already holds the list of invalid
// rectangles from ProcessPendingXEvents().
// In the non-DAMAGE case, this captures the whole screen, then calculates
// some invalid rectangles that include any differences between this and the
// previous capture.
CaptureData* CaptureFrame();
// Synchronize the current buffer with |last_buffer_|, by copying pixels from
// the area of |last_invalid_rects|.
// Note this only works on the assumption that kNumBuffers == 2, as
// |last_invalid_rects| holds the differences from the previous buffer and
// the one prior to that (which will then be the current buffer).
void SynchronizeFrame();
void DeinitXlib();
// Capture a rectangle from |x_server_pixel_buffer_|, and copy the data into
// |capture_data|.
void CaptureRect(const SkIRect& rect, CaptureData* capture_data);
// 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 SkIRect& rect, CaptureData* capture_data);
void SlowBlit(uint8* image, const SkIRect& rect, CaptureData* capture_data);
// X11 graphics context.
Display* display_;
GC gc_;
Window root_window_;
// XDamage information.
bool use_damage_;
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;
VideoFrameBuffer buffers_[kNumBuffers];
int current_buffer_;
// Format of pixels returned in buffer.
media::VideoFrame::Format pixel_format_;
// Invalid region from the previous capture. This is used to synchronize the
// current with the last buffer used.
SkRegion last_invalid_region_;
// Last capture buffer used.
uint8* last_buffer_;
// |Differ| for use when polling for changes.
scoped_ptr<Differ> differ_;
DISALLOW_COPY_AND_ASSIGN(CapturerLinux);
};
CapturerLinux::CapturerLinux()
: display_(NULL),
gc_(NULL),
root_window_(BadValue),
use_damage_(false),
damage_handle_(BadValue),
damage_event_base_(-1),
damage_error_base_(-1),
current_buffer_(0),
pixel_format_(media::VideoFrame::RGB32),
last_buffer_(NULL) {
}
CapturerLinux::~CapturerLinux() {
DeinitXlib();
}
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;
}
if (ShouldUseXDamage()) {
InitXDamage();
}
// Register for changes to the dimensions of the root window.
XSelectInput(display_, root_window_, StructureNotifyMask);
return true;
}
void CapturerLinux::InitXDamage() {
// Setup XDamage to report changes in the damage window. Mark the whole
// window as invalid.
if (XDamageQueryExtension(display_, &damage_event_base_,
&damage_error_base_)) {
damage_handle_ = XDamageCreate(display_, root_window_,
XDamageReportDeltaRectangles);
if (damage_handle_ == BadValue) {
LOG(ERROR) << "Unable to create damage handle.";
} else {
// TODO(lambroslambrou): Disable DAMAGE in situations where it is known
// to fail, such as when Desktop Effects are enabled, with graphics
// drivers (nVidia, ATI) that fail to report DAMAGE notifications
// properly.
use_damage_ = true;
LOG(INFO) << "Using XDamage extension.";
}
} else {
LOG(INFO) << "Server does not support XDamage.";
}
}
void CapturerLinux::ScreenConfigurationChanged() {
last_buffer_ = NULL;
for (int i = 0; i < kNumBuffers; ++i) {
buffers_[i].set_needs_update();
}
helper_.ClearInvalidRegion();
x_server_pixel_buffer_.Init(display_);
}
media::VideoFrame::Format CapturerLinux::pixel_format() const {
return pixel_format_;
}
void CapturerLinux::ClearInvalidRegion() {
helper_.ClearInvalidRegion();
}
void CapturerLinux::InvalidateRegion(const SkRegion& invalid_region) {
helper_.InvalidateRegion(invalid_region);
}
void CapturerLinux::InvalidateScreen(const gfx::Size& size) {
helper_.InvalidateScreen(size);
}
void CapturerLinux::InvalidateFullScreen() {
helper_.InvalidateFullScreen();
last_buffer_ = NULL;
}
void CapturerLinux::CaptureInvalidRegion(
CaptureCompletedCallback* callback) {
scoped_ptr<CaptureCompletedCallback> callback_deleter(callback);
// TODO(lambroslambrou): In the non-DAMAGE case, there should be no need
// for any X event processing in this class.
ProcessPendingXEvents();
// Resize the current buffer if there was a recent change of
// screen-resolution.
VideoFrameBuffer &current = buffers_[current_buffer_];
current.Update(display_, root_window_);
// Also refresh the Differ helper used by CaptureFrame(), if needed.
if (!use_damage_ && !last_buffer_) {
differ_.reset(new Differ(current.size().width(), current.size().height(),
kBytesPerPixel, current.bytes_per_row()));
}
scoped_refptr<CaptureData> capture_data(CaptureFrame());
current_buffer_ = (current_buffer_ + 1) % kNumBuffers;
helper_.set_size_most_recent(capture_data->size());
callback->Run(capture_data);
}
void CapturerLinux::ProcessPendingXEvents() {
// 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;
SkRegion invalid_region;
for (int i = 0; i < events_to_process; i++) {
XNextEvent(display_, &e);
if (use_damage_ && (e.type == damage_event_base_ + XDamageNotify)) {
XDamageNotifyEvent *event = reinterpret_cast<XDamageNotifyEvent*>(&e);
// TODO(hclam): Perform more checks on the rect.
if (event->area.width <= 0 || event->area.height <= 0)
continue;
SkIRect damage_rect = SkIRect::MakeXYWH(event->area.x,
event->area.y,
event->area.width,
event->area.height);
invalid_region.op(damage_rect, SkRegion::kUnion_Op);
DVLOG(3) << "Damage received for rect at ("
<< damage_rect.fLeft << "," << damage_rect.fTop << ") size ("
<< damage_rect.width() << "," << damage_rect.height() << ")";
} else if (e.type == ConfigureNotify) {
ScreenConfigurationChanged();
invalid_region.setEmpty();
} else {
LOG(WARNING) << "Got unknown event type: " << e.type;
}
}
helper_.InvalidateRegion(invalid_region);
}
CaptureData* CapturerLinux::CaptureFrame() {
VideoFrameBuffer& buffer = buffers_[current_buffer_];
DataPlanes planes;
planes.data[0] = buffer.ptr();
planes.strides[0] = buffer.bytes_per_row();
CaptureData* capture_data = new CaptureData(planes, buffer.size(),
media::VideoFrame::RGB32);
// In the DAMAGE case, ensure the frame is up-to-date with the previous frame
// if any. If there isn't a previous frame, that means a screen-resolution
// change occurred, and |invalid_rects| will be updated to include the whole
// screen.
if (use_damage_ && last_buffer_)
SynchronizeFrame();
SkRegion invalid_region;
x_server_pixel_buffer_.Synchronize();
if (use_damage_ && last_buffer_) {
helper_.SwapInvalidRegion(&invalid_region);
for (SkRegion::Iterator it(invalid_region); !it.done(); it.next()) {
CaptureRect(it.rect(), capture_data);
}
// TODO(ajwong): We should only repair the rects that were copied!
XDamageSubtract(display_, damage_handle_, None, None);
} else {
// Doing full-screen polling, or this is the first capture after a
// screen-resolution change. In either case, need a full-screen capture.
SkIRect screen_rect = SkIRect::MakeWH(buffer.size().width(),
buffer.size().height());
CaptureRect(screen_rect, capture_data);
if (last_buffer_) {
// Full-screen polling, so calculate the invalid rects here, based on the
// changed pixels between current and previous buffers.
DCHECK(differ_ != NULL);
differ_->CalcDirtyRegion(last_buffer_, buffer.ptr(), &invalid_region);
} else {
// No previous buffer, so always invalidate the whole screen, whether
// or not DAMAGE is being used. DAMAGE doesn't necessarily send a
// full-screen notification after a screen-resolution change, so
// this is done here.
invalid_region.op(screen_rect, SkRegion::kUnion_Op);
}
}
capture_data->mutable_dirty_region() = invalid_region;
last_invalid_region_ = invalid_region;
last_buffer_ = buffer.ptr();
return capture_data;
}
void CapturerLinux::SynchronizeFrame() {
// Synchronize the current buffer with the previous 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
// |capturer_helper_|s region from |last_invalid_region_|.
// http://crbug.com/92354
DCHECK(last_buffer_);
VideoFrameBuffer& buffer = buffers_[current_buffer_];
for (SkRegion::Iterator it(last_invalid_region_); !it.done(); it.next()) {
const SkIRect& r = it.rect();
int offset = r.fTop * buffer.bytes_per_row() + r.fLeft * kBytesPerPixel;
for (int i = 0; i < r.height(); ++i) {
memcpy(buffer.ptr() + offset, last_buffer_ + offset,
r.width() * kBytesPerPixel);
offset += buffer.size().width() * kBytesPerPixel;
}
}
}
void CapturerLinux::DeinitXlib() {
if (gc_) {
XFreeGC(display_, gc_);
gc_ = NULL;
}
if (display_) {
XCloseDisplay(display_);
display_ = NULL;
}
}
void CapturerLinux::CaptureRect(const SkIRect& rect,
CaptureData* capture_data) {
uint8* image = x_server_pixel_buffer_.CaptureRect(rect);
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) {
DVLOG(3) << "Fast blitting";
FastBlit(image, rect, capture_data);
} else {
DVLOG(3) << "Slow blitting";
SlowBlit(image, rect, capture_data);
}
}
void CapturerLinux::FastBlit(uint8* image, const SkIRect& rect,
CaptureData* capture_data) {
uint8* src_pos = image;
int src_stride = x_server_pixel_buffer_.GetStride();
int dst_x = rect.fLeft, dst_y = rect.fTop;
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 SkIRect& 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.fLeft, dst_y = rect.fTop;
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() {
CapturerLinux* capturer = new CapturerLinux();
if (!capturer->Init()) {
delete capturer;
capturer = NULL;
}
return capturer;
}
} // namespace remoting