diff options
Diffstat (limited to 'webrtc/modules/desktop_capture/screen_capturer_x11.cc')
-rw-r--r-- | webrtc/modules/desktop_capture/screen_capturer_x11.cc | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/webrtc/modules/desktop_capture/screen_capturer_x11.cc b/webrtc/modules/desktop_capture/screen_capturer_x11.cc new file mode 100644 index 0000000000..3a3c418654 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_x11.cc @@ -0,0 +1,444 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include <string.h> +#include <set> + +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "webrtc/base/checks.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { +namespace { + +// A class to perform video frame capturing for Linux. +class ScreenCapturerLinux : public ScreenCapturer, + public SharedXDisplay::XEventHandler { + public: + ScreenCapturerLinux(); + virtual ~ScreenCapturerLinux(); + + // TODO(ajwong): Do we really want this to be synchronous? + bool Init(const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* delegate) override; + void Capture(const DesktopRegion& region) override; + + // ScreenCapturer interface. + bool GetScreenList(ScreenList* screens) override; + bool SelectScreen(ScreenId id) override; + + private: + Display* display() { return options_.x_display()->display(); } + + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + void InitXDamage(); + + // Capture screen pixels to the current buffer in the queue. In the DAMAGE + // case, the ScreenCapturerHelper already holds the list of invalid rectangles + // from HandleXEvent(). In the non-DAMAGE case, this captures the + // whole screen, then calculates some invalid rectangles that include any + // differences between this and the previous capture. + DesktopFrame* CaptureScreen(); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + // Synchronize the current buffer with |last_buffer_|, by copying pixels from + // the area of |last_invalid_rects|. + // Note this only works on the assumption that kNumBuffers == 2, as + // |last_invalid_rects| holds the differences from the previous buffer and + // the one prior to that (which will then be the current buffer). + void SynchronizeFrame(); + + void DeinitXlib(); + + DesktopCaptureOptions options_; + + Callback* callback_; + + // X11 graphics context. + GC gc_; + Window root_window_; + + // XFixes. + bool has_xfixes_; + int xfixes_event_base_; + int xfixes_error_base_; + + // XDamage information. + bool use_damage_; + Damage damage_handle_; + int damage_event_base_; + int damage_error_base_; + XserverRegion damage_region_; + + // Access to the X Server's pixel buffer. + XServerPixelBuffer x_server_pixel_buffer_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Invalid region from the previous capture. This is used to synchronize the + // current with the last buffer used. + DesktopRegion last_invalid_region_; + + // |Differ| for use when polling for changes. + rtc::scoped_ptr<Differ> differ_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux); +}; + +ScreenCapturerLinux::ScreenCapturerLinux() + : callback_(NULL), + gc_(NULL), + root_window_(BadValue), + has_xfixes_(false), + xfixes_event_base_(-1), + xfixes_error_base_(-1), + use_damage_(false), + damage_handle_(0), + damage_event_base_(-1), + damage_error_base_(-1), + damage_region_(0) { + helper_.SetLogGridSize(4); +} + +ScreenCapturerLinux::~ScreenCapturerLinux() { + options_.x_display()->RemoveEventHandler(ConfigureNotify, this); + if (use_damage_) { + options_.x_display()->RemoveEventHandler( + damage_event_base_ + XDamageNotify, this); + } + DeinitXlib(); +} + +bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) { + options_ = options; + + root_window_ = RootWindow(display(), DefaultScreen(display())); + if (root_window_ == BadValue) { + LOG(LS_ERROR) << "Unable to get the root window"; + DeinitXlib(); + return false; + } + + gc_ = XCreateGC(display(), root_window_, 0, NULL); + if (gc_ == NULL) { + LOG(LS_ERROR) << "Unable to get graphics context"; + DeinitXlib(); + return false; + } + + options_.x_display()->AddEventHandler(ConfigureNotify, this); + + // Check for XFixes extension. This is required for cursor shape + // notifications, and for our use of XDamage. + if (XFixesQueryExtension(display(), &xfixes_event_base_, + &xfixes_error_base_)) { + has_xfixes_ = true; + } else { + LOG(LS_INFO) << "X server does not support XFixes."; + } + + // Register for changes to the dimensions of the root window. + XSelectInput(display(), root_window_, StructureNotifyMask); + + if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { + LOG(LS_ERROR) << "Failed to initialize pixel buffer."; + return false; + } + + if (options_.use_update_notifications()) { + InitXDamage(); + } + + return true; +} + +void ScreenCapturerLinux::InitXDamage() { + // Our use of XDamage requires XFixes. + if (!has_xfixes_) { + return; + } + + // Check for XDamage extension. + if (!XDamageQueryExtension(display(), &damage_event_base_, + &damage_error_base_)) { + LOG(LS_INFO) << "X server does not support XDamage."; + return; + } + + // TODO(lambroslambrou): Disable DAMAGE in situations where it is known + // to fail, such as when Desktop Effects are enabled, with graphics + // drivers (nVidia, ATI) that fail to report DAMAGE notifications + // properly. + + // Request notifications every time the screen becomes damaged. + damage_handle_ = XDamageCreate(display(), root_window_, + XDamageReportNonEmpty); + if (!damage_handle_) { + LOG(LS_ERROR) << "Unable to initialize XDamage."; + return; + } + + // Create an XFixes server-side region to collate damage into. + damage_region_ = XFixesCreateRegion(display(), 0, 0); + if (!damage_region_) { + XDamageDestroy(display(), damage_handle_); + LOG(LS_ERROR) << "Unable to create XFixes region."; + return; + } + + options_.x_display()->AddEventHandler( + damage_event_base_ + XDamageNotify, this); + + use_damage_ = true; + LOG(LS_INFO) << "Using XDamage extension."; +} + +void ScreenCapturerLinux::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void ScreenCapturerLinux::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Process XEvents for XDamage and cursor shape tracking. + options_.x_display()->ProcessPendingXEvents(); + + // ProcessPendingXEvents() may call ScreenConfigurationChanged() which + // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still + // in a good shape. + if (!x_server_pixel_buffer_.is_initialized()) { + // We failed to initialize pixel buffer. + callback_->OnCaptureCompleted(NULL); + return; + } + + // If the current frame is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) { + rtc::scoped_ptr<DesktopFrame> frame( + new BasicDesktopFrame(x_server_pixel_buffer_.window_size())); + queue_.ReplaceCurrentFrame(frame.release()); + } + + // Refresh the Differ helper used by CaptureFrame(), if needed. + DesktopFrame* frame = queue_.current_frame(); + if (!use_damage_ && ( + !differ_.get() || + (differ_->width() != frame->size().width()) || + (differ_->height() != frame->size().height()) || + (differ_->bytes_per_row() != frame->stride()))) { + differ_.reset(new Differ(frame->size().width(), frame->size().height(), + DesktopFrame::kBytesPerPixel, + frame->stride())); + } + + DesktopFrame* result = CaptureScreen(); + last_invalid_region_ = result->updated_region(); + result->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(result); +} + +bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) { + RTC_DCHECK(screens->size() == 0); + // TODO(jiayl): implement screen enumeration. + Screen default_screen; + default_screen.id = 0; + screens->push_back(default_screen); + return true; +} + +bool ScreenCapturerLinux::SelectScreen(ScreenId id) { + // TODO(jiayl): implement screen selection. + return true; +} + +bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) { + if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) { + const XDamageNotifyEvent* damage_event = + reinterpret_cast<const XDamageNotifyEvent*>(&event); + if (damage_event->damage != damage_handle_) + return false; + RTC_DCHECK(damage_event->level == XDamageReportNonEmpty); + return true; + } else if (event.type == ConfigureNotify) { + ScreenConfigurationChanged(); + return true; + } + return false; +} + +DesktopFrame* ScreenCapturerLinux::CaptureScreen() { + DesktopFrame* frame = queue_.current_frame()->Share(); + assert(x_server_pixel_buffer_.window_size().equals(frame->size())); + + // Pass the screen size to the helper, so it can clip the invalid region if it + // expands that region to a grid. + helper_.set_size_most_recent(frame->size()); + + // In the DAMAGE case, ensure the frame is up-to-date with the previous frame + // if any. If there isn't a previous frame, that means a screen-resolution + // change occurred, and |invalid_rects| will be updated to include the whole + // screen. + if (use_damage_ && queue_.previous_frame()) + SynchronizeFrame(); + + DesktopRegion* updated_region = frame->mutable_updated_region(); + + x_server_pixel_buffer_.Synchronize(); + if (use_damage_ && queue_.previous_frame()) { + // Atomically fetch and clear the damage region. + XDamageSubtract(display(), damage_handle_, None, damage_region_); + int rects_num = 0; + XRectangle bounds; + XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_, + &rects_num, &bounds); + for (int i = 0; i < rects_num; ++i) { + updated_region->AddRect(DesktopRect::MakeXYWH( + rects[i].x, rects[i].y, rects[i].width, rects[i].height)); + } + XFree(rects); + helper_.InvalidateRegion(*updated_region); + + // Capture the damaged portions of the desktop. + helper_.TakeInvalidRegion(updated_region); + + // Clip the damaged portions to the current screen size, just in case some + // spurious XDamage notifications were received for a previous (larger) + // screen size. + updated_region->IntersectWith( + DesktopRect::MakeSize(x_server_pixel_buffer_.window_size())); + + for (DesktopRegion::Iterator it(*updated_region); + !it.IsAtEnd(); it.Advance()) { + x_server_pixel_buffer_.CaptureRect(it.rect(), frame); + } + } else { + // Doing full-screen polling, or this is the first capture after a + // screen-resolution change. In either case, need a full-screen capture. + DesktopRect screen_rect = DesktopRect::MakeSize(frame->size()); + x_server_pixel_buffer_.CaptureRect(screen_rect, frame); + + if (queue_.previous_frame()) { + // Full-screen polling, so calculate the invalid rects here, based on the + // changed pixels between current and previous buffers. + RTC_DCHECK(differ_.get() != NULL); + RTC_DCHECK(queue_.previous_frame()->data()); + differ_->CalcDirtyRegion(queue_.previous_frame()->data(), + frame->data(), updated_region); + } else { + // No previous buffer, so always invalidate the whole screen, whether + // or not DAMAGE is being used. DAMAGE doesn't necessarily send a + // full-screen notification after a screen-resolution change, so + // this is done here. + updated_region->SetRect(screen_rect); + } + } + + return frame; +} + +void ScreenCapturerLinux::ScreenConfigurationChanged() { + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { + LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " + "configuration change."; + } +} + +void ScreenCapturerLinux::SynchronizeFrame() { + // Synchronize the current buffer with the previous one since we do not + // capture the entire desktop. Note that encoder may be reading from the + // previous buffer at this time so thread access complaints are false + // positives. + + // TODO(hclam): We can reduce the amount of copying here by subtracting + // |capturer_helper_|s region from |last_invalid_region_|. + // http://crbug.com/92354 + RTC_DCHECK(queue_.previous_frame()); + + DesktopFrame* current = queue_.current_frame(); + DesktopFrame* last = queue_.previous_frame(); + RTC_DCHECK(current != last); + for (DesktopRegion::Iterator it(last_invalid_region_); + !it.IsAtEnd(); it.Advance()) { + current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect()); + } +} + +void ScreenCapturerLinux::DeinitXlib() { + if (gc_) { + XFreeGC(display(), gc_); + gc_ = NULL; + } + + x_server_pixel_buffer_.Release(); + + if (display()) { + if (damage_handle_) { + XDamageDestroy(display(), damage_handle_); + damage_handle_ = 0; + } + + if (damage_region_) { + XFixesDestroyRegion(display(), damage_region_); + damage_region_ = 0; + } + } +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { + if (!options.x_display()) + return NULL; + + rtc::scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux()); + if (!capturer->Init(options)) + capturer.reset(); + return capturer.release(); +} + +} // namespace webrtc |