blob: 9d8efdd448e5576872372946b187f4f1c9f9e5a4 [file] [log] [blame]
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/desktop_capture/linux/x_server_pixel_buffer.h"
#include <X11/Xutil.h>
#include <stdint.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/linux/window_list_utils.h"
#include "modules/desktop_capture/linux/x_error_trap.h"
#include "modules/desktop_capture/linux/x_window_property.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
// Returns the number of bits |mask| has to be shifted left so its last
// (most-significant) bit set becomes the most-significant bit of the word.
// When |mask| is 0 the function returns 31.
uint32_t MaskToShift(uint32_t mask) {
int shift = 0;
if ((mask & 0xffff0000u) == 0) {
mask <<= 16;
shift += 16;
}
if ((mask & 0xff000000u) == 0) {
mask <<= 8;
shift += 8;
}
if ((mask & 0xf0000000u) == 0) {
mask <<= 4;
shift += 4;
}
if ((mask & 0xc0000000u) == 0) {
mask <<= 2;
shift += 2;
}
if ((mask & 0x80000000u) == 0)
shift += 1;
return shift;
}
// Returns true if |image| is in RGB format.
bool IsXImageRGBFormat(XImage* image) {
return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 &&
image->green_mask == 0xff00 && image->blue_mask == 0xff;
}
// We expose two forms of blitting to handle variations in the pixel format.
// In FastBlit(), the operation is effectively a memcpy.
void FastBlit(XImage* x_image,
uint8_t* src_pos,
const DesktopRect& rect,
DesktopFrame* frame) {
int src_stride = x_image->bytes_per_line;
int dst_x = rect.left(), dst_y = rect.top();
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
int height = rect.height();
int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
for (int y = 0; y < height; ++y) {
memcpy(dst_pos, src_pos, row_bytes);
src_pos += src_stride;
dst_pos += frame->stride();
}
}
void SlowBlit(XImage* x_image,
uint8_t* src_pos,
const DesktopRect& rect,
DesktopFrame* frame) {
int src_stride = x_image->bytes_per_line;
int dst_x = rect.left(), dst_y = rect.top();
int width = rect.width(), height = rect.height();
uint32_t red_mask = x_image->red_mask;
uint32_t green_mask = x_image->red_mask;
uint32_t blue_mask = x_image->blue_mask;
uint32_t red_shift = MaskToShift(red_mask);
uint32_t green_shift = MaskToShift(green_mask);
uint32_t blue_shift = MaskToShift(blue_mask);
int bits_per_pixel = x_image->bits_per_pixel;
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
// TODO(hclam): Optimize, perhaps using MMX code or by converting to
// YUV directly.
// TODO(sergeyu): This code doesn't handle XImage byte order properly and
// won't work with 24bpp images. Fix it.
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;
uint32_t g = (pixel & green_mask) << green_shift;
uint32_t b = (pixel & blue_mask) << blue_shift;
// Write as 32-bit RGB.
dst_pos_32[x] =
((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff);
}
dst_pos += frame->stride();
src_pos += src_stride;
}
}
} // namespace
XServerPixelBuffer::XServerPixelBuffer() {}
XServerPixelBuffer::~XServerPixelBuffer() {
Release();
}
void XServerPixelBuffer::Release() {
if (x_image_) {
XDestroyImage(x_image_);
x_image_ = nullptr;
}
if (x_shm_image_) {
XDestroyImage(x_shm_image_);
x_shm_image_ = nullptr;
}
if (shm_pixmap_) {
XFreePixmap(display_, shm_pixmap_);
shm_pixmap_ = 0;
}
if (shm_gc_) {
XFreeGC(display_, shm_gc_);
shm_gc_ = nullptr;
}
ReleaseSharedMemorySegment();
window_ = 0;
}
void XServerPixelBuffer::ReleaseSharedMemorySegment() {
if (!shm_segment_info_)
return;
if (shm_segment_info_->shmaddr != nullptr)
shmdt(shm_segment_info_->shmaddr);
if (shm_segment_info_->shmid != -1)
shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
delete shm_segment_info_;
shm_segment_info_ = nullptr;
}
bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) {
Release();
display_ = cache->display();
XWindowAttributes attributes;
if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
return false;
}
if (cache->IccProfile() != None) {
// |window| is the root window when doing screen capture.
XWindowProperty<uint8_t> icc_profile_property(cache->display(), window,
cache->IccProfile());
if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) {
icc_profile_ = std::vector<uint8_t>(
icc_profile_property.data(),
icc_profile_property.data() + icc_profile_property.size());
} else {
RTC_LOG(LS_WARNING) << "Failed to get icc profile";
}
}
window_ = window;
InitShm(attributes);
return true;
}
void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
Visual* default_visual = attributes.visual;
int default_depth = attributes.depth;
int major, minor;
Bool have_pixmaps;
if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
// Shared memory not supported. CaptureRect will use the XImage API instead.
return;
}
bool using_shm = false;
shm_segment_info_ = new XShmSegmentInfo;
shm_segment_info_->shmid = -1;
shm_segment_info_->shmaddr = nullptr;
shm_segment_info_->readOnly = False;
x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth,
ZPixmap, 0, shm_segment_info_,
window_rect_.width(), window_rect_.height());
if (x_shm_image_) {
shm_segment_info_->shmid =
shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height,
IPC_CREAT | 0600);
if (shm_segment_info_->shmid != -1) {
void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0);
if (shmat_result != reinterpret_cast<void*>(-1)) {
shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result);
x_shm_image_->data = shm_segment_info_->shmaddr;
XErrorTrap error_trap(display_);
using_shm = XShmAttach(display_, shm_segment_info_);
XSync(display_, False);
if (error_trap.GetLastErrorAndDisable() != 0)
using_shm = false;
if (using_shm) {
RTC_LOG(LS_VERBOSE)
<< "Using X shared memory segment " << shm_segment_info_->shmid;
}
}
} else {
RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. "
"Performance may be degraded.";
}
}
if (!using_shm) {
RTC_LOG(LS_WARNING)
<< "Not using shared memory. Performance may be degraded.";
ReleaseSharedMemorySegment();
return;
}
if (have_pixmaps)
have_pixmaps = InitPixmaps(default_depth);
shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
shm_segment_info_->shmid = -1;
RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "."
<< minor << " with" << (have_pixmaps ? "" : "out")
<< " pixmaps.";
}
bool XServerPixelBuffer::InitPixmaps(int depth) {
if (XShmPixmapFormat(display_) != ZPixmap)
return false;
{
XErrorTrap error_trap(display_);
shm_pixmap_ = XShmCreatePixmap(
display_, window_, shm_segment_info_->shmaddr, shm_segment_info_,
window_rect_.width(), window_rect_.height(), depth);
XSync(display_, False);
if (error_trap.GetLastErrorAndDisable() != 0) {
// |shm_pixmap_| is not not valid because the request was not processed
// by the X Server, so zero it.
shm_pixmap_ = 0;
return false;
}
}
{
XErrorTrap error_trap(display_);
XGCValues shm_gc_values;
shm_gc_values.subwindow_mode = IncludeInferiors;
shm_gc_values.graphics_exposures = False;
shm_gc_ = XCreateGC(display_, window_,
GCSubwindowMode | GCGraphicsExposures, &shm_gc_values);
XSync(display_, False);
if (error_trap.GetLastErrorAndDisable() != 0) {
XFreePixmap(display_, shm_pixmap_);
shm_pixmap_ = 0;
shm_gc_ = 0; // See shm_pixmap_ comment above.
return false;
}
}
return true;
}
bool XServerPixelBuffer::IsWindowValid() const {
XWindowAttributes attributes;
{
XErrorTrap error_trap(display_);
if (!XGetWindowAttributes(display_, window_, &attributes) ||
error_trap.GetLastErrorAndDisable() != 0) {
return false;
}
}
return true;
}
void XServerPixelBuffer::Synchronize() {
if (shm_segment_info_ && !shm_pixmap_) {
// XShmGetImage can fail if the display is being reconfigured.
XErrorTrap error_trap(display_);
// XShmGetImage fails if the window is partially out of screen.
xshm_get_image_succeeded_ =
XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes);
}
}
bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
DesktopFrame* frame) {
RTC_DCHECK_LE(rect.right(), window_rect_.width());
RTC_DCHECK_LE(rect.bottom(), window_rect_.height());
XImage* image;
uint8_t* data;
if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) {
if (shm_pixmap_) {
XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(),
rect.top(), rect.width(), rect.height(), rect.left(),
rect.top());
XSync(display_, False);
}
image = x_shm_image_;
data = reinterpret_cast<uint8_t*>(image->data) +
rect.top() * image->bytes_per_line +
rect.left() * image->bits_per_pixel / 8;
} else {
if (x_image_)
XDestroyImage(x_image_);
x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
rect.width(), rect.height(), AllPlanes, ZPixmap);
if (!x_image_)
return false;
image = x_image_;
data = reinterpret_cast<uint8_t*>(image->data);
}
if (IsXImageRGBFormat(image)) {
FastBlit(image, data, rect, frame);
} else {
SlowBlit(image, data, rect, frame);
}
if (!icc_profile_.empty())
frame->set_icc_profile(icc_profile_);
return true;
}
} // namespace webrtc