mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-19 16:47:50 +01:00

Bug: webrtc:12338 Change-Id: I300ba78fc4423db7030e555d7e51d2cb2246e9a4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/227162 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34678}
379 lines
11 KiB
C++
379 lines
11 KiB
C++
/*
|
|
* 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) {
|
|
RTC_DCHECK_LE(frame->top_left().x(), rect.left());
|
|
RTC_DCHECK_LE(frame->top_left().y(), rect.top());
|
|
|
|
int src_stride = x_image->bytes_per_line;
|
|
int dst_x = rect.left() - frame->top_left().x();
|
|
int dst_y = rect.top() - frame->top_left().y();
|
|
|
|
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) {
|
|
RTC_DCHECK_LE(frame->top_left().x(), rect.left());
|
|
RTC_DCHECK_LE(frame->top_left().y(), rect.top());
|
|
|
|
int src_stride = x_image->bytes_per_line;
|
|
int dst_x = rect.left() - frame->top_left().x();
|
|
int dst_y = rect.top() - frame->top_left().y();
|
|
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
|