1 /*
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/desktop_capture/linux/x_server_pixel_buffer.h"
12
13 #include <X11/Xutil.h>
14 #include <stdint.h>
15 #include <string.h>
16 #include <sys/ipc.h>
17 #include <sys/shm.h>
18
19 #include "modules/desktop_capture/desktop_frame.h"
20 #include "modules/desktop_capture/linux/window_list_utils.h"
21 #include "modules/desktop_capture/linux/x_error_trap.h"
22 #include "modules/desktop_capture/linux/x_window_property.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/logging.h"
25
26 namespace webrtc {
27
28 namespace {
29
30 // Returns the number of bits |mask| has to be shifted left so its last
31 // (most-significant) bit set becomes the most-significant bit of the word.
32 // When |mask| is 0 the function returns 31.
MaskToShift(uint32_t mask)33 uint32_t MaskToShift(uint32_t mask) {
34 int shift = 0;
35 if ((mask & 0xffff0000u) == 0) {
36 mask <<= 16;
37 shift += 16;
38 }
39 if ((mask & 0xff000000u) == 0) {
40 mask <<= 8;
41 shift += 8;
42 }
43 if ((mask & 0xf0000000u) == 0) {
44 mask <<= 4;
45 shift += 4;
46 }
47 if ((mask & 0xc0000000u) == 0) {
48 mask <<= 2;
49 shift += 2;
50 }
51 if ((mask & 0x80000000u) == 0)
52 shift += 1;
53
54 return shift;
55 }
56
57 // Returns true if |image| is in RGB format.
IsXImageRGBFormat(XImage * image)58 bool IsXImageRGBFormat(XImage* image) {
59 return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 &&
60 image->green_mask == 0xff00 && image->blue_mask == 0xff;
61 }
62
63 // We expose two forms of blitting to handle variations in the pixel format.
64 // In FastBlit(), the operation is effectively a memcpy.
FastBlit(XImage * x_image,uint8_t * src_pos,const DesktopRect & rect,DesktopFrame * frame)65 void FastBlit(XImage* x_image,
66 uint8_t* src_pos,
67 const DesktopRect& rect,
68 DesktopFrame* frame) {
69 RTC_DCHECK_LE(frame->top_left().x(), rect.left());
70 RTC_DCHECK_LE(frame->top_left().y(), rect.top());
71
72 int src_stride = x_image->bytes_per_line;
73 int dst_x = rect.left() - frame->top_left().x();
74 int dst_y = rect.top() - frame->top_left().y();
75
76 uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
77 dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
78
79 int height = rect.height();
80 int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
81 for (int y = 0; y < height; ++y) {
82 memcpy(dst_pos, src_pos, row_bytes);
83 src_pos += src_stride;
84 dst_pos += frame->stride();
85 }
86 }
87
SlowBlit(XImage * x_image,uint8_t * src_pos,const DesktopRect & rect,DesktopFrame * frame)88 void SlowBlit(XImage* x_image,
89 uint8_t* src_pos,
90 const DesktopRect& rect,
91 DesktopFrame* frame) {
92 RTC_DCHECK_LE(frame->top_left().x(), rect.left());
93 RTC_DCHECK_LE(frame->top_left().y(), rect.top());
94
95 int src_stride = x_image->bytes_per_line;
96 int dst_x = rect.left() - frame->top_left().x();
97 int dst_y = rect.top() - frame->top_left().y();
98 int width = rect.width(), height = rect.height();
99
100 uint32_t red_mask = x_image->red_mask;
101 uint32_t green_mask = x_image->red_mask;
102 uint32_t blue_mask = x_image->blue_mask;
103
104 uint32_t red_shift = MaskToShift(red_mask);
105 uint32_t green_shift = MaskToShift(green_mask);
106 uint32_t blue_shift = MaskToShift(blue_mask);
107
108 int bits_per_pixel = x_image->bits_per_pixel;
109
110 uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
111 dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
112 // TODO(hclam): Optimize, perhaps using MMX code or by converting to
113 // YUV directly.
114 // TODO(sergeyu): This code doesn't handle XImage byte order properly and
115 // won't work with 24bpp images. Fix it.
116 for (int y = 0; y < height; y++) {
117 uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
118 uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
119 uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
120 for (int x = 0; x < width; x++) {
121 // Dereference through an appropriately-aligned pointer.
122 uint32_t pixel;
123 if (bits_per_pixel == 32) {
124 pixel = src_pos_32[x];
125 } else if (bits_per_pixel == 16) {
126 pixel = src_pos_16[x];
127 } else {
128 pixel = src_pos[x];
129 }
130 uint32_t r = (pixel & red_mask) << red_shift;
131 uint32_t g = (pixel & green_mask) << green_shift;
132 uint32_t b = (pixel & blue_mask) << blue_shift;
133 // Write as 32-bit RGB.
134 dst_pos_32[x] =
135 ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff);
136 }
137 dst_pos += frame->stride();
138 src_pos += src_stride;
139 }
140 }
141
142 } // namespace
143
XServerPixelBuffer()144 XServerPixelBuffer::XServerPixelBuffer() {}
145
~XServerPixelBuffer()146 XServerPixelBuffer::~XServerPixelBuffer() {
147 Release();
148 }
149
Release()150 void XServerPixelBuffer::Release() {
151 if (x_image_) {
152 XDestroyImage(x_image_);
153 x_image_ = nullptr;
154 }
155 if (x_shm_image_) {
156 XDestroyImage(x_shm_image_);
157 x_shm_image_ = nullptr;
158 }
159 if (shm_pixmap_) {
160 XFreePixmap(display_, shm_pixmap_);
161 shm_pixmap_ = 0;
162 }
163 if (shm_gc_) {
164 XFreeGC(display_, shm_gc_);
165 shm_gc_ = nullptr;
166 }
167
168 ReleaseSharedMemorySegment();
169
170 window_ = 0;
171 }
172
ReleaseSharedMemorySegment()173 void XServerPixelBuffer::ReleaseSharedMemorySegment() {
174 if (!shm_segment_info_)
175 return;
176 if (shm_segment_info_->shmaddr != nullptr)
177 shmdt(shm_segment_info_->shmaddr);
178 if (shm_segment_info_->shmid != -1)
179 shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
180 delete shm_segment_info_;
181 shm_segment_info_ = nullptr;
182 }
183
Init(XAtomCache * cache,Window window)184 bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) {
185 Release();
186 display_ = cache->display();
187
188 XWindowAttributes attributes;
189 if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
190 return false;
191 }
192
193 if (cache->IccProfile() != None) {
194 // |window| is the root window when doing screen capture.
195 XWindowProperty<uint8_t> icc_profile_property(cache->display(), window,
196 cache->IccProfile());
197 if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) {
198 icc_profile_ = std::vector<uint8_t>(
199 icc_profile_property.data(),
200 icc_profile_property.data() + icc_profile_property.size());
201 } else {
202 RTC_LOG(LS_WARNING) << "Failed to get icc profile";
203 }
204 }
205
206 window_ = window;
207 InitShm(attributes);
208
209 return true;
210 }
211
InitShm(const XWindowAttributes & attributes)212 void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
213 Visual* default_visual = attributes.visual;
214 int default_depth = attributes.depth;
215
216 int major, minor;
217 Bool have_pixmaps;
218 if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
219 // Shared memory not supported. CaptureRect will use the XImage API instead.
220 return;
221 }
222
223 bool using_shm = false;
224 shm_segment_info_ = new XShmSegmentInfo;
225 shm_segment_info_->shmid = -1;
226 shm_segment_info_->shmaddr = nullptr;
227 shm_segment_info_->readOnly = False;
228 x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth,
229 ZPixmap, 0, shm_segment_info_,
230 window_rect_.width(), window_rect_.height());
231 if (x_shm_image_) {
232 shm_segment_info_->shmid =
233 shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height,
234 IPC_CREAT | 0600);
235 if (shm_segment_info_->shmid != -1) {
236 void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0);
237 if (shmat_result != reinterpret_cast<void*>(-1)) {
238 shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result);
239 x_shm_image_->data = shm_segment_info_->shmaddr;
240
241 XErrorTrap error_trap(display_);
242 using_shm = XShmAttach(display_, shm_segment_info_);
243 XSync(display_, False);
244 if (error_trap.GetLastErrorAndDisable() != 0)
245 using_shm = false;
246 if (using_shm) {
247 RTC_LOG(LS_VERBOSE)
248 << "Using X shared memory segment " << shm_segment_info_->shmid;
249 }
250 }
251 } else {
252 RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. "
253 "Performance may be degraded.";
254 }
255 }
256
257 if (!using_shm) {
258 RTC_LOG(LS_WARNING)
259 << "Not using shared memory. Performance may be degraded.";
260 ReleaseSharedMemorySegment();
261 return;
262 }
263
264 if (have_pixmaps)
265 have_pixmaps = InitPixmaps(default_depth);
266
267 shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
268 shm_segment_info_->shmid = -1;
269
270 RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "."
271 << minor << " with" << (have_pixmaps ? "" : "out")
272 << " pixmaps.";
273 }
274
InitPixmaps(int depth)275 bool XServerPixelBuffer::InitPixmaps(int depth) {
276 if (XShmPixmapFormat(display_) != ZPixmap)
277 return false;
278
279 {
280 XErrorTrap error_trap(display_);
281 shm_pixmap_ = XShmCreatePixmap(
282 display_, window_, shm_segment_info_->shmaddr, shm_segment_info_,
283 window_rect_.width(), window_rect_.height(), depth);
284 XSync(display_, False);
285 if (error_trap.GetLastErrorAndDisable() != 0) {
286 // |shm_pixmap_| is not not valid because the request was not processed
287 // by the X Server, so zero it.
288 shm_pixmap_ = 0;
289 return false;
290 }
291 }
292
293 {
294 XErrorTrap error_trap(display_);
295 XGCValues shm_gc_values;
296 shm_gc_values.subwindow_mode = IncludeInferiors;
297 shm_gc_values.graphics_exposures = False;
298 shm_gc_ = XCreateGC(display_, window_,
299 GCSubwindowMode | GCGraphicsExposures, &shm_gc_values);
300 XSync(display_, False);
301 if (error_trap.GetLastErrorAndDisable() != 0) {
302 XFreePixmap(display_, shm_pixmap_);
303 shm_pixmap_ = 0;
304 shm_gc_ = 0; // See shm_pixmap_ comment above.
305 return false;
306 }
307 }
308
309 return true;
310 }
311
IsWindowValid() const312 bool XServerPixelBuffer::IsWindowValid() const {
313 XWindowAttributes attributes;
314 {
315 XErrorTrap error_trap(display_);
316 if (!XGetWindowAttributes(display_, window_, &attributes) ||
317 error_trap.GetLastErrorAndDisable() != 0) {
318 return false;
319 }
320 }
321 return true;
322 }
323
Synchronize()324 void XServerPixelBuffer::Synchronize() {
325 if (shm_segment_info_ && !shm_pixmap_) {
326 // XShmGetImage can fail if the display is being reconfigured.
327 XErrorTrap error_trap(display_);
328 // XShmGetImage fails if the window is partially out of screen.
329 xshm_get_image_succeeded_ =
330 XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes);
331 }
332 }
333
CaptureRect(const DesktopRect & rect,DesktopFrame * frame)334 bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
335 DesktopFrame* frame) {
336 RTC_DCHECK_LE(rect.right(), window_rect_.width());
337 RTC_DCHECK_LE(rect.bottom(), window_rect_.height());
338
339 XImage* image;
340 uint8_t* data;
341
342 if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) {
343 if (shm_pixmap_) {
344 XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(),
345 rect.top(), rect.width(), rect.height(), rect.left(),
346 rect.top());
347 XSync(display_, False);
348 }
349
350 image = x_shm_image_;
351 data = reinterpret_cast<uint8_t*>(image->data) +
352 rect.top() * image->bytes_per_line +
353 rect.left() * image->bits_per_pixel / 8;
354
355 } else {
356 if (x_image_)
357 XDestroyImage(x_image_);
358 x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
359 rect.width(), rect.height(), AllPlanes, ZPixmap);
360 if (!x_image_)
361 return false;
362
363 image = x_image_;
364 data = reinterpret_cast<uint8_t*>(image->data);
365 }
366
367 if (IsXImageRGBFormat(image)) {
368 FastBlit(image, data, rect, frame);
369 } else {
370 SlowBlit(image, data, rect, frame);
371 }
372
373 if (!icc_profile_.empty())
374 frame->set_icc_profile(icc_profile_);
375
376 return true;
377 }
378
379 } // namespace webrtc
380