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