mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-17 23:57:59 +01:00

Currently, when users want to use the screen sharing and are using the Wayland display server (the default on Fedora distribution), then it doesn't work, because the WebRTC only includes the X11 implementation. This change adds the support by using the PipeWire multimedia server. The PipeWire implementation in WebRTC stays in screen-capturer-pipewire.c and is guarded by the rtc_use_pipewire build flag that is automatically enabled on Linux. More information are included in the relevant commit messages. Tested on the current Chromium master and Firefox. The sysroot changes are requested in: https://chromium-review.googlesource.com/c/chromium/src/+/1258174 Co-authored-by: Jan Grulich <grulja@gmail.com> Co-authored-by: Eike Rathke <erathke@redhat.com> Change-Id: I212074a4bc437b99a77bf383266026c5bfae7c4a BUG=chromium:682122 Change-Id: I212074a4bc437b99a77bf383266026c5bfae7c4a Reviewed-on: https://webrtc-review.googlesource.com/c/103504 Commit-Queue: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Brave Yao <braveyao@webrtc.org> Cr-Commit-Position: refs/heads/master@{#25461}
351 lines
10 KiB
C++
351 lines
10 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 <string.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 "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(Display* display, Window window) {
|
|
Release();
|
|
display_ = display;
|
|
|
|
XWindowAttributes attributes;
|
|
if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace webrtc
|