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 "webrtc/modules/desktop_capture/screen_capturer.h"
12
13 #include <string.h>
14 #include <set>
15
16 #include <X11/extensions/Xdamage.h>
17 #include <X11/extensions/Xfixes.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xutil.h>
20
21 #include "webrtc/base/checks.h"
22 #include "webrtc/base/scoped_ptr.h"
23 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
24 #include "webrtc/modules/desktop_capture/desktop_frame.h"
25 #include "webrtc/modules/desktop_capture/differ.h"
26 #include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
27 #include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
28 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
29 #include "webrtc/system_wrappers/include/logging.h"
30 #include "webrtc/system_wrappers/include/tick_util.h"
31
32 namespace webrtc {
33 namespace {
34
35 // A class to perform video frame capturing for Linux.
36 class ScreenCapturerLinux : public ScreenCapturer,
37 public SharedXDisplay::XEventHandler {
38 public:
39 ScreenCapturerLinux();
40 virtual ~ScreenCapturerLinux();
41
42 // TODO(ajwong): Do we really want this to be synchronous?
43 bool Init(const DesktopCaptureOptions& options);
44
45 // DesktopCapturer interface.
46 void Start(Callback* delegate) override;
47 void Capture(const DesktopRegion& region) override;
48
49 // ScreenCapturer interface.
50 bool GetScreenList(ScreenList* screens) override;
51 bool SelectScreen(ScreenId id) override;
52
53 private:
display()54 Display* display() { return options_.x_display()->display(); }
55
56 // SharedXDisplay::XEventHandler interface.
57 bool HandleXEvent(const XEvent& event) override;
58
59 void InitXDamage();
60
61 // Capture screen pixels to the current buffer in the queue. In the DAMAGE
62 // case, the ScreenCapturerHelper already holds the list of invalid rectangles
63 // from HandleXEvent(). In the non-DAMAGE case, this captures the
64 // whole screen, then calculates some invalid rectangles that include any
65 // differences between this and the previous capture.
66 DesktopFrame* CaptureScreen();
67
68 // Called when the screen configuration is changed.
69 void ScreenConfigurationChanged();
70
71 // Synchronize the current buffer with |last_buffer_|, by copying pixels from
72 // the area of |last_invalid_rects|.
73 // Note this only works on the assumption that kNumBuffers == 2, as
74 // |last_invalid_rects| holds the differences from the previous buffer and
75 // the one prior to that (which will then be the current buffer).
76 void SynchronizeFrame();
77
78 void DeinitXlib();
79
80 DesktopCaptureOptions options_;
81
82 Callback* callback_;
83
84 // X11 graphics context.
85 GC gc_;
86 Window root_window_;
87
88 // XFixes.
89 bool has_xfixes_;
90 int xfixes_event_base_;
91 int xfixes_error_base_;
92
93 // XDamage information.
94 bool use_damage_;
95 Damage damage_handle_;
96 int damage_event_base_;
97 int damage_error_base_;
98 XserverRegion damage_region_;
99
100 // Access to the X Server's pixel buffer.
101 XServerPixelBuffer x_server_pixel_buffer_;
102
103 // A thread-safe list of invalid rectangles, and the size of the most
104 // recently captured screen.
105 ScreenCapturerHelper helper_;
106
107 // Queue of the frames buffers.
108 ScreenCaptureFrameQueue queue_;
109
110 // Invalid region from the previous capture. This is used to synchronize the
111 // current with the last buffer used.
112 DesktopRegion last_invalid_region_;
113
114 // |Differ| for use when polling for changes.
115 rtc::scoped_ptr<Differ> differ_;
116
117 RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux);
118 };
119
ScreenCapturerLinux()120 ScreenCapturerLinux::ScreenCapturerLinux()
121 : callback_(NULL),
122 gc_(NULL),
123 root_window_(BadValue),
124 has_xfixes_(false),
125 xfixes_event_base_(-1),
126 xfixes_error_base_(-1),
127 use_damage_(false),
128 damage_handle_(0),
129 damage_event_base_(-1),
130 damage_error_base_(-1),
131 damage_region_(0) {
132 helper_.SetLogGridSize(4);
133 }
134
~ScreenCapturerLinux()135 ScreenCapturerLinux::~ScreenCapturerLinux() {
136 options_.x_display()->RemoveEventHandler(ConfigureNotify, this);
137 if (use_damage_) {
138 options_.x_display()->RemoveEventHandler(
139 damage_event_base_ + XDamageNotify, this);
140 }
141 DeinitXlib();
142 }
143
Init(const DesktopCaptureOptions & options)144 bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) {
145 options_ = options;
146
147 root_window_ = RootWindow(display(), DefaultScreen(display()));
148 if (root_window_ == BadValue) {
149 LOG(LS_ERROR) << "Unable to get the root window";
150 DeinitXlib();
151 return false;
152 }
153
154 gc_ = XCreateGC(display(), root_window_, 0, NULL);
155 if (gc_ == NULL) {
156 LOG(LS_ERROR) << "Unable to get graphics context";
157 DeinitXlib();
158 return false;
159 }
160
161 options_.x_display()->AddEventHandler(ConfigureNotify, this);
162
163 // Check for XFixes extension. This is required for cursor shape
164 // notifications, and for our use of XDamage.
165 if (XFixesQueryExtension(display(), &xfixes_event_base_,
166 &xfixes_error_base_)) {
167 has_xfixes_ = true;
168 } else {
169 LOG(LS_INFO) << "X server does not support XFixes.";
170 }
171
172 // Register for changes to the dimensions of the root window.
173 XSelectInput(display(), root_window_, StructureNotifyMask);
174
175 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
176 LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
177 return false;
178 }
179
180 if (options_.use_update_notifications()) {
181 InitXDamage();
182 }
183
184 return true;
185 }
186
InitXDamage()187 void ScreenCapturerLinux::InitXDamage() {
188 // Our use of XDamage requires XFixes.
189 if (!has_xfixes_) {
190 return;
191 }
192
193 // Check for XDamage extension.
194 if (!XDamageQueryExtension(display(), &damage_event_base_,
195 &damage_error_base_)) {
196 LOG(LS_INFO) << "X server does not support XDamage.";
197 return;
198 }
199
200 // TODO(lambroslambrou): Disable DAMAGE in situations where it is known
201 // to fail, such as when Desktop Effects are enabled, with graphics
202 // drivers (nVidia, ATI) that fail to report DAMAGE notifications
203 // properly.
204
205 // Request notifications every time the screen becomes damaged.
206 damage_handle_ = XDamageCreate(display(), root_window_,
207 XDamageReportNonEmpty);
208 if (!damage_handle_) {
209 LOG(LS_ERROR) << "Unable to initialize XDamage.";
210 return;
211 }
212
213 // Create an XFixes server-side region to collate damage into.
214 damage_region_ = XFixesCreateRegion(display(), 0, 0);
215 if (!damage_region_) {
216 XDamageDestroy(display(), damage_handle_);
217 LOG(LS_ERROR) << "Unable to create XFixes region.";
218 return;
219 }
220
221 options_.x_display()->AddEventHandler(
222 damage_event_base_ + XDamageNotify, this);
223
224 use_damage_ = true;
225 LOG(LS_INFO) << "Using XDamage extension.";
226 }
227
Start(Callback * callback)228 void ScreenCapturerLinux::Start(Callback* callback) {
229 RTC_DCHECK(!callback_);
230 RTC_DCHECK(callback);
231
232 callback_ = callback;
233 }
234
Capture(const DesktopRegion & region)235 void ScreenCapturerLinux::Capture(const DesktopRegion& region) {
236 TickTime capture_start_time = TickTime::Now();
237
238 queue_.MoveToNextFrame();
239
240 // Process XEvents for XDamage and cursor shape tracking.
241 options_.x_display()->ProcessPendingXEvents();
242
243 // ProcessPendingXEvents() may call ScreenConfigurationChanged() which
244 // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still
245 // in a good shape.
246 if (!x_server_pixel_buffer_.is_initialized()) {
247 // We failed to initialize pixel buffer.
248 callback_->OnCaptureCompleted(NULL);
249 return;
250 }
251
252 // If the current frame is from an older generation then allocate a new one.
253 // Note that we can't reallocate other buffers at this point, since the caller
254 // may still be reading from them.
255 if (!queue_.current_frame()) {
256 rtc::scoped_ptr<DesktopFrame> frame(
257 new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
258 queue_.ReplaceCurrentFrame(frame.release());
259 }
260
261 // Refresh the Differ helper used by CaptureFrame(), if needed.
262 DesktopFrame* frame = queue_.current_frame();
263 if (!use_damage_ && (
264 !differ_.get() ||
265 (differ_->width() != frame->size().width()) ||
266 (differ_->height() != frame->size().height()) ||
267 (differ_->bytes_per_row() != frame->stride()))) {
268 differ_.reset(new Differ(frame->size().width(), frame->size().height(),
269 DesktopFrame::kBytesPerPixel,
270 frame->stride()));
271 }
272
273 DesktopFrame* result = CaptureScreen();
274 last_invalid_region_ = result->updated_region();
275 result->set_capture_time_ms(
276 (TickTime::Now() - capture_start_time).Milliseconds());
277 callback_->OnCaptureCompleted(result);
278 }
279
GetScreenList(ScreenList * screens)280 bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) {
281 RTC_DCHECK(screens->size() == 0);
282 // TODO(jiayl): implement screen enumeration.
283 Screen default_screen;
284 default_screen.id = 0;
285 screens->push_back(default_screen);
286 return true;
287 }
288
SelectScreen(ScreenId id)289 bool ScreenCapturerLinux::SelectScreen(ScreenId id) {
290 // TODO(jiayl): implement screen selection.
291 return true;
292 }
293
HandleXEvent(const XEvent & event)294 bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) {
295 if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) {
296 const XDamageNotifyEvent* damage_event =
297 reinterpret_cast<const XDamageNotifyEvent*>(&event);
298 if (damage_event->damage != damage_handle_)
299 return false;
300 RTC_DCHECK(damage_event->level == XDamageReportNonEmpty);
301 return true;
302 } else if (event.type == ConfigureNotify) {
303 ScreenConfigurationChanged();
304 return true;
305 }
306 return false;
307 }
308
CaptureScreen()309 DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
310 DesktopFrame* frame = queue_.current_frame()->Share();
311 assert(x_server_pixel_buffer_.window_size().equals(frame->size()));
312
313 // Pass the screen size to the helper, so it can clip the invalid region if it
314 // expands that region to a grid.
315 helper_.set_size_most_recent(frame->size());
316
317 // In the DAMAGE case, ensure the frame is up-to-date with the previous frame
318 // if any. If there isn't a previous frame, that means a screen-resolution
319 // change occurred, and |invalid_rects| will be updated to include the whole
320 // screen.
321 if (use_damage_ && queue_.previous_frame())
322 SynchronizeFrame();
323
324 DesktopRegion* updated_region = frame->mutable_updated_region();
325
326 x_server_pixel_buffer_.Synchronize();
327 if (use_damage_ && queue_.previous_frame()) {
328 // Atomically fetch and clear the damage region.
329 XDamageSubtract(display(), damage_handle_, None, damage_region_);
330 int rects_num = 0;
331 XRectangle bounds;
332 XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_,
333 &rects_num, &bounds);
334 for (int i = 0; i < rects_num; ++i) {
335 updated_region->AddRect(DesktopRect::MakeXYWH(
336 rects[i].x, rects[i].y, rects[i].width, rects[i].height));
337 }
338 XFree(rects);
339 helper_.InvalidateRegion(*updated_region);
340
341 // Capture the damaged portions of the desktop.
342 helper_.TakeInvalidRegion(updated_region);
343
344 // Clip the damaged portions to the current screen size, just in case some
345 // spurious XDamage notifications were received for a previous (larger)
346 // screen size.
347 updated_region->IntersectWith(
348 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()));
349
350 for (DesktopRegion::Iterator it(*updated_region);
351 !it.IsAtEnd(); it.Advance()) {
352 x_server_pixel_buffer_.CaptureRect(it.rect(), frame);
353 }
354 } else {
355 // Doing full-screen polling, or this is the first capture after a
356 // screen-resolution change. In either case, need a full-screen capture.
357 DesktopRect screen_rect = DesktopRect::MakeSize(frame->size());
358 x_server_pixel_buffer_.CaptureRect(screen_rect, frame);
359
360 if (queue_.previous_frame()) {
361 // Full-screen polling, so calculate the invalid rects here, based on the
362 // changed pixels between current and previous buffers.
363 RTC_DCHECK(differ_.get() != NULL);
364 RTC_DCHECK(queue_.previous_frame()->data());
365 differ_->CalcDirtyRegion(queue_.previous_frame()->data(),
366 frame->data(), updated_region);
367 } else {
368 // No previous buffer, so always invalidate the whole screen, whether
369 // or not DAMAGE is being used. DAMAGE doesn't necessarily send a
370 // full-screen notification after a screen-resolution change, so
371 // this is done here.
372 updated_region->SetRect(screen_rect);
373 }
374 }
375
376 return frame;
377 }
378
ScreenConfigurationChanged()379 void ScreenCapturerLinux::ScreenConfigurationChanged() {
380 // Make sure the frame buffers will be reallocated.
381 queue_.Reset();
382
383 helper_.ClearInvalidRegion();
384 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
385 LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
386 "configuration change.";
387 }
388 }
389
SynchronizeFrame()390 void ScreenCapturerLinux::SynchronizeFrame() {
391 // Synchronize the current buffer with the previous one since we do not
392 // capture the entire desktop. Note that encoder may be reading from the
393 // previous buffer at this time so thread access complaints are false
394 // positives.
395
396 // TODO(hclam): We can reduce the amount of copying here by subtracting
397 // |capturer_helper_|s region from |last_invalid_region_|.
398 // http://crbug.com/92354
399 RTC_DCHECK(queue_.previous_frame());
400
401 DesktopFrame* current = queue_.current_frame();
402 DesktopFrame* last = queue_.previous_frame();
403 RTC_DCHECK(current != last);
404 for (DesktopRegion::Iterator it(last_invalid_region_);
405 !it.IsAtEnd(); it.Advance()) {
406 current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect());
407 }
408 }
409
DeinitXlib()410 void ScreenCapturerLinux::DeinitXlib() {
411 if (gc_) {
412 XFreeGC(display(), gc_);
413 gc_ = NULL;
414 }
415
416 x_server_pixel_buffer_.Release();
417
418 if (display()) {
419 if (damage_handle_) {
420 XDamageDestroy(display(), damage_handle_);
421 damage_handle_ = 0;
422 }
423
424 if (damage_region_) {
425 XFixesDestroyRegion(display(), damage_region_);
426 damage_region_ = 0;
427 }
428 }
429 }
430
431 } // namespace
432
433 // static
Create(const DesktopCaptureOptions & options)434 ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
435 if (!options.x_display())
436 return NULL;
437
438 rtc::scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux());
439 if (!capturer->Init(options))
440 capturer.reset();
441 return capturer.release();
442 }
443
444 } // namespace webrtc
445