2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
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.
11 #include "webrtc/modules/desktop_capture/screen_capturer.h"
16 #include <X11/extensions/Xdamage.h>
17 #include <X11/extensions/Xfixes.h>
19 #include <X11/Xutil.h>
21 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
22 #include "webrtc/modules/desktop_capture/desktop_frame.h"
23 #include "webrtc/modules/desktop_capture/differ.h"
24 #include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
25 #include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
26 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
27 #include "webrtc/system_wrappers/interface/logging.h"
28 #include "webrtc/system_wrappers/interface/scoped_ptr.h"
29 #include "webrtc/system_wrappers/interface/tick_util.h"
31 // TODO(sergeyu): Move this to a header where it can be shared.
33 #define DCHECK(condition) (void)(condition)
35 #define DCHECK(condition) if (!(condition)) {abort();}
42 // A class to perform video frame capturing for Linux.
43 class ScreenCapturerLinux : public ScreenCapturer,
44 public SharedXDisplay::XEventHandler {
46 ScreenCapturerLinux();
47 virtual ~ScreenCapturerLinux();
49 // TODO(ajwong): Do we really want this to be synchronous?
50 bool Init(const DesktopCaptureOptions& options);
52 // DesktopCapturer interface.
53 virtual void Start(Callback* delegate) OVERRIDE;
54 virtual void Capture(const DesktopRegion& region) OVERRIDE;
56 // ScreenCapturer interface.
57 virtual bool GetScreenList(ScreenList* screens) OVERRIDE;
58 virtual bool SelectScreen(ScreenId id) OVERRIDE;
61 Display* display() { return options_.x_display()->display(); }
63 // SharedXDisplay::XEventHandler interface.
64 virtual bool HandleXEvent(const XEvent& event) OVERRIDE;
68 // Capture screen pixels to the current buffer in the queue. In the DAMAGE
69 // case, the ScreenCapturerHelper already holds the list of invalid rectangles
70 // from HandleXEvent(). In the non-DAMAGE case, this captures the
71 // whole screen, then calculates some invalid rectangles that include any
72 // differences between this and the previous capture.
73 DesktopFrame* CaptureScreen();
75 // Called when the screen configuration is changed.
76 void ScreenConfigurationChanged();
78 // Synchronize the current buffer with |last_buffer_|, by copying pixels from
79 // the area of |last_invalid_rects|.
80 // Note this only works on the assumption that kNumBuffers == 2, as
81 // |last_invalid_rects| holds the differences from the previous buffer and
82 // the one prior to that (which will then be the current buffer).
83 void SynchronizeFrame();
87 DesktopCaptureOptions options_;
91 // X11 graphics context.
97 int xfixes_event_base_;
98 int xfixes_error_base_;
100 // XDamage information.
102 Damage damage_handle_;
103 int damage_event_base_;
104 int damage_error_base_;
105 XserverRegion damage_region_;
107 // Access to the X Server's pixel buffer.
108 XServerPixelBuffer x_server_pixel_buffer_;
110 // A thread-safe list of invalid rectangles, and the size of the most
111 // recently captured screen.
112 ScreenCapturerHelper helper_;
114 // Queue of the frames buffers.
115 ScreenCaptureFrameQueue queue_;
117 // Invalid region from the previous capture. This is used to synchronize the
118 // current with the last buffer used.
119 DesktopRegion last_invalid_region_;
121 // |Differ| for use when polling for changes.
122 scoped_ptr<Differ> differ_;
124 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux);
127 ScreenCapturerLinux::ScreenCapturerLinux()
130 root_window_(BadValue),
132 xfixes_event_base_(-1),
133 xfixes_error_base_(-1),
136 damage_event_base_(-1),
137 damage_error_base_(-1),
139 helper_.SetLogGridSize(4);
142 ScreenCapturerLinux::~ScreenCapturerLinux() {
143 options_.x_display()->RemoveEventHandler(ConfigureNotify, this);
145 options_.x_display()->RemoveEventHandler(
146 damage_event_base_ + XDamageNotify, this);
151 bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) {
154 root_window_ = RootWindow(display(), DefaultScreen(display()));
155 if (root_window_ == BadValue) {
156 LOG(LS_ERROR) << "Unable to get the root window";
161 gc_ = XCreateGC(display(), root_window_, 0, NULL);
163 LOG(LS_ERROR) << "Unable to get graphics context";
168 options_.x_display()->AddEventHandler(ConfigureNotify, this);
170 // Check for XFixes extension. This is required for cursor shape
171 // notifications, and for our use of XDamage.
172 if (XFixesQueryExtension(display(), &xfixes_event_base_,
173 &xfixes_error_base_)) {
176 LOG(LS_INFO) << "X server does not support XFixes.";
179 // Register for changes to the dimensions of the root window.
180 XSelectInput(display(), root_window_, StructureNotifyMask);
182 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
183 LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
187 if (options_.use_update_notifications()) {
194 void ScreenCapturerLinux::InitXDamage() {
195 // Our use of XDamage requires XFixes.
200 // Check for XDamage extension.
201 if (!XDamageQueryExtension(display(), &damage_event_base_,
202 &damage_error_base_)) {
203 LOG(LS_INFO) << "X server does not support XDamage.";
207 // TODO(lambroslambrou): Disable DAMAGE in situations where it is known
208 // to fail, such as when Desktop Effects are enabled, with graphics
209 // drivers (nVidia, ATI) that fail to report DAMAGE notifications
212 // Request notifications every time the screen becomes damaged.
213 damage_handle_ = XDamageCreate(display(), root_window_,
214 XDamageReportNonEmpty);
215 if (!damage_handle_) {
216 LOG(LS_ERROR) << "Unable to initialize XDamage.";
220 // Create an XFixes server-side region to collate damage into.
221 damage_region_ = XFixesCreateRegion(display(), 0, 0);
222 if (!damage_region_) {
223 XDamageDestroy(display(), damage_handle_);
224 LOG(LS_ERROR) << "Unable to create XFixes region.";
228 options_.x_display()->AddEventHandler(
229 damage_event_base_ + XDamageNotify, this);
232 LOG(LS_INFO) << "Using XDamage extension.";
235 void ScreenCapturerLinux::Start(Callback* callback) {
239 callback_ = callback;
242 void ScreenCapturerLinux::Capture(const DesktopRegion& region) {
243 TickTime capture_start_time = TickTime::Now();
245 queue_.MoveToNextFrame();
247 // Process XEvents for XDamage and cursor shape tracking.
248 options_.x_display()->ProcessPendingXEvents();
250 // ProcessPendingXEvents() may call ScreenConfigurationChanged() which
251 // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still
253 if (!x_server_pixel_buffer_.is_initialized()) {
254 // We failed to initialize pixel buffer.
255 callback_->OnCaptureCompleted(NULL);
259 // If the current frame is from an older generation then allocate a new one.
260 // Note that we can't reallocate other buffers at this point, since the caller
261 // may still be reading from them.
262 if (!queue_.current_frame()) {
263 scoped_ptr<DesktopFrame> frame(
264 new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
265 queue_.ReplaceCurrentFrame(frame.release());
268 // Refresh the Differ helper used by CaptureFrame(), if needed.
269 DesktopFrame* frame = queue_.current_frame();
270 if (!use_damage_ && (
272 (differ_->width() != frame->size().width()) ||
273 (differ_->height() != frame->size().height()) ||
274 (differ_->bytes_per_row() != frame->stride()))) {
275 differ_.reset(new Differ(frame->size().width(), frame->size().height(),
276 DesktopFrame::kBytesPerPixel,
280 DesktopFrame* result = CaptureScreen();
281 last_invalid_region_ = result->updated_region();
282 result->set_capture_time_ms(
283 (TickTime::Now() - capture_start_time).Milliseconds());
284 callback_->OnCaptureCompleted(result);
287 bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) {
288 DCHECK(screens->size() == 0);
289 // TODO(jiayl): implement screen enumeration.
290 Screen default_screen;
291 default_screen.id = 0;
292 screens->push_back(default_screen);
296 bool ScreenCapturerLinux::SelectScreen(ScreenId id) {
297 // TODO(jiayl): implement screen selection.
301 bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) {
302 if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) {
303 const XDamageNotifyEvent* damage_event =
304 reinterpret_cast<const XDamageNotifyEvent*>(&event);
305 if (damage_event->damage != damage_handle_)
307 DCHECK(damage_event->level == XDamageReportNonEmpty);
309 } else if (event.type == ConfigureNotify) {
310 ScreenConfigurationChanged();
316 DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
317 DesktopFrame* frame = queue_.current_frame()->Share();
318 assert(x_server_pixel_buffer_.window_size().equals(frame->size()));
320 // Pass the screen size to the helper, so it can clip the invalid region if it
321 // expands that region to a grid.
322 helper_.set_size_most_recent(frame->size());
324 // In the DAMAGE case, ensure the frame is up-to-date with the previous frame
325 // if any. If there isn't a previous frame, that means a screen-resolution
326 // change occurred, and |invalid_rects| will be updated to include the whole
328 if (use_damage_ && queue_.previous_frame())
331 DesktopRegion* updated_region = frame->mutable_updated_region();
333 x_server_pixel_buffer_.Synchronize();
334 if (use_damage_ && queue_.previous_frame()) {
335 // Atomically fetch and clear the damage region.
336 XDamageSubtract(display(), damage_handle_, None, damage_region_);
339 XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_,
340 &rects_num, &bounds);
341 for (int i = 0; i < rects_num; ++i) {
342 updated_region->AddRect(DesktopRect::MakeXYWH(
343 rects[i].x, rects[i].y, rects[i].width, rects[i].height));
346 helper_.InvalidateRegion(*updated_region);
348 // Capture the damaged portions of the desktop.
349 helper_.TakeInvalidRegion(updated_region);
351 // Clip the damaged portions to the current screen size, just in case some
352 // spurious XDamage notifications were received for a previous (larger)
354 updated_region->IntersectWith(
355 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()));
357 for (DesktopRegion::Iterator it(*updated_region);
358 !it.IsAtEnd(); it.Advance()) {
359 x_server_pixel_buffer_.CaptureRect(it.rect(), frame);
362 // Doing full-screen polling, or this is the first capture after a
363 // screen-resolution change. In either case, need a full-screen capture.
364 DesktopRect screen_rect = DesktopRect::MakeSize(frame->size());
365 x_server_pixel_buffer_.CaptureRect(screen_rect, frame);
367 if (queue_.previous_frame()) {
368 // Full-screen polling, so calculate the invalid rects here, based on the
369 // changed pixels between current and previous buffers.
370 DCHECK(differ_.get() != NULL);
371 DCHECK(queue_.previous_frame()->data());
372 differ_->CalcDirtyRegion(queue_.previous_frame()->data(),
373 frame->data(), updated_region);
375 // No previous buffer, so always invalidate the whole screen, whether
376 // or not DAMAGE is being used. DAMAGE doesn't necessarily send a
377 // full-screen notification after a screen-resolution change, so
378 // this is done here.
379 updated_region->SetRect(screen_rect);
386 void ScreenCapturerLinux::ScreenConfigurationChanged() {
387 // Make sure the frame buffers will be reallocated.
390 helper_.ClearInvalidRegion();
391 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
392 LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
393 "configuration change.";
397 void ScreenCapturerLinux::SynchronizeFrame() {
398 // Synchronize the current buffer with the previous one since we do not
399 // capture the entire desktop. Note that encoder may be reading from the
400 // previous buffer at this time so thread access complaints are false
403 // TODO(hclam): We can reduce the amount of copying here by subtracting
404 // |capturer_helper_|s region from |last_invalid_region_|.
405 // http://crbug.com/92354
406 DCHECK(queue_.previous_frame());
408 DesktopFrame* current = queue_.current_frame();
409 DesktopFrame* last = queue_.previous_frame();
410 DCHECK(current != last);
411 for (DesktopRegion::Iterator it(last_invalid_region_);
412 !it.IsAtEnd(); it.Advance()) {
413 current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect());
417 void ScreenCapturerLinux::DeinitXlib() {
419 XFreeGC(display(), gc_);
423 x_server_pixel_buffer_.Release();
426 if (damage_handle_) {
427 XDamageDestroy(display(), damage_handle_);
431 if (damage_region_) {
432 XFixesDestroyRegion(display(), damage_region_);
441 ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
442 if (!options.x_display())
445 scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux());
446 if (!capturer->Init(options))
448 return capturer.release();
451 } // namespace webrtc