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