Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(243)

Side by Side Diff: remoting/host/video_frame_capturer_linux.cc

Issue 10790075: Rename Capturer to VideoFrameCapturer. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebased. Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "remoting/host/video_frame_capturer.h"
6
7 #include <X11/Xlib.h>
8 #include <X11/Xutil.h>
9 #include <X11/extensions/Xdamage.h>
10 #include <X11/extensions/Xfixes.h>
11
12 #include <set>
13
14 #include "base/basictypes.h"
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "remoting/base/capture_data.h"
18 #include "remoting/host/differ.h"
19 #include "remoting/host/video_frame_capturer_helper.h"
20 #include "remoting/host/x_server_pixel_buffer.h"
21 #include "remoting/proto/control.pb.h"
22
23 namespace remoting {
24
25 namespace {
26
27 static const int kBytesPerPixel = 4;
28
29 // Default to false, since many systems have broken XDamage support - see
30 // http://crbug.com/73423.
31 static bool g_should_use_x_damage = false;
32
33 static bool ShouldUseXDamage() {
34 return g_should_use_x_damage;
35 }
36
37 // A class representing a full-frame pixel buffer
38 class VideoFrameBuffer {
39 public:
40 VideoFrameBuffer() : bytes_per_row_(0), needs_update_(true) {
41 size_.set(0, 0);
42 }
43
44 void Update(Display* display, Window root_window) {
45 if (needs_update_) {
46 needs_update_ = false;
47 XWindowAttributes root_attr;
48 XGetWindowAttributes(display, root_window, &root_attr);
49 if (root_attr.width != size_.width() ||
50 root_attr.height != size_.height()) {
51 size_.set(root_attr.width, root_attr.height);
52 bytes_per_row_ = size_.width() * kBytesPerPixel;
53 size_t buffer_size = size_.width() * size_.height() * kBytesPerPixel;
54 ptr_.reset(new uint8[buffer_size]);
55 }
56 }
57 }
58
59 SkISize size() const { return size_; }
60 int bytes_per_row() const { return bytes_per_row_; }
61 uint8* ptr() const { return ptr_.get(); }
62
63 void set_needs_update() { needs_update_ = true; }
64
65 private:
66 SkISize size_;
67 int bytes_per_row_;
68 scoped_array<uint8> ptr_;
69 bool needs_update_;
70
71 DISALLOW_COPY_AND_ASSIGN(VideoFrameBuffer);
72 };
73
74 // A class to perform video frame capturing for Linux.
75 class VideoFrameCapturerLinux : public VideoFrameCapturer {
76 public:
77 VideoFrameCapturerLinux();
78 virtual ~VideoFrameCapturerLinux();
79
80 bool Init(); // TODO(ajwong): Do we really want this to be synchronous?
81
82 // Capturer interface.
83 virtual void Start(const CursorShapeChangedCallback& callback) OVERRIDE;
84 virtual void Stop() OVERRIDE;
85 virtual void ScreenConfigurationChanged() OVERRIDE;
86 virtual media::VideoFrame::Format pixel_format() const OVERRIDE;
87 virtual void ClearInvalidRegion() OVERRIDE;
88 virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
89 virtual void InvalidateScreen(const SkISize& size) OVERRIDE;
90 virtual void InvalidateFullScreen() OVERRIDE;
91 virtual void CaptureInvalidRegion(
92 const CaptureCompletedCallback& callback) OVERRIDE;
93 virtual const SkISize& size_most_recent() const OVERRIDE;
94
95 private:
96 void InitXDamage();
97
98 // Read and handle all currently-pending XEvents.
99 // In the DAMAGE case, process the XDamage events and store the resulting
100 // damage rectangles in the VideoFrameCapturerHelper.
101 // In all cases, call ScreenConfigurationChanged() in response to any
102 // ConfigNotify events.
103 void ProcessPendingXEvents();
104
105 // Capture screen pixels, and return the data in a new CaptureData object,
106 // to be freed by the caller.
107 // In the DAMAGE case, the VideoFrameCapturerHelper already holds the list of
108 // invalid rectangles from ProcessPendingXEvents().
109 // In the non-DAMAGE case, this captures the whole screen, then calculates
110 // some invalid rectangles that include any differences between this and the
111 // previous capture.
112 CaptureData* CaptureFrame();
113
114 // Capture the cursor image and call the CursorShapeChangedCallback if it
115 // has been set (using SetCursorShapeChangedCallback).
116 void CaptureCursor();
117
118 // Synchronize the current buffer with |last_buffer_|, by copying pixels from
119 // the area of |last_invalid_rects|.
120 // Note this only works on the assumption that kNumBuffers == 2, as
121 // |last_invalid_rects| holds the differences from the previous buffer and
122 // the one prior to that (which will then be the current buffer).
123 void SynchronizeFrame();
124
125 void DeinitXlib();
126
127 // Capture a rectangle from |x_server_pixel_buffer_|, and copy the data into
128 // |capture_data|.
129 void CaptureRect(const SkIRect& rect, CaptureData* capture_data);
130
131 // We expose two forms of blitting to handle variations in the pixel format.
132 // In FastBlit, the operation is effectively a memcpy.
133 void FastBlit(uint8* image, const SkIRect& rect, CaptureData* capture_data);
134 void SlowBlit(uint8* image, const SkIRect& rect, CaptureData* capture_data);
135
136 // X11 graphics context.
137 Display* display_;
138 GC gc_;
139 Window root_window_;
140
141 // XFixes.
142 bool has_xfixes_;
143 int xfixes_event_base_;
144 int xfixes_error_base_;
145
146 // XDamage information.
147 bool use_damage_;
148 Damage damage_handle_;
149 int damage_event_base_;
150 int damage_error_base_;
151 XserverRegion damage_region_;
152
153 // Access to the X Server's pixel buffer.
154 XServerPixelBuffer x_server_pixel_buffer_;
155
156 // A thread-safe list of invalid rectangles, and the size of the most
157 // recently captured screen.
158 VideoFrameCapturerHelper helper_;
159
160 // Callback notified whenever the cursor shape is changed.
161 CursorShapeChangedCallback cursor_shape_changed_callback_;
162
163 // Capture state.
164 static const int kNumBuffers = 2;
165 VideoFrameBuffer buffers_[kNumBuffers];
166 int current_buffer_;
167
168 // Format of pixels returned in buffer.
169 media::VideoFrame::Format pixel_format_;
170
171 // Invalid region from the previous capture. This is used to synchronize the
172 // current with the last buffer used.
173 SkRegion last_invalid_region_;
174
175 // Last capture buffer used.
176 uint8* last_buffer_;
177
178 // |Differ| for use when polling for changes.
179 scoped_ptr<Differ> differ_;
180
181 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerLinux);
182 };
183
184 VideoFrameCapturerLinux::VideoFrameCapturerLinux()
185 : display_(NULL),
186 gc_(NULL),
187 root_window_(BadValue),
188 has_xfixes_(false),
189 xfixes_event_base_(-1),
190 xfixes_error_base_(-1),
191 use_damage_(false),
192 damage_handle_(0),
193 damage_event_base_(-1),
194 damage_error_base_(-1),
195 damage_region_(0),
196 current_buffer_(0),
197 pixel_format_(media::VideoFrame::RGB32),
198 last_buffer_(NULL) {
199 helper_.SetLogGridSize(4);
200 }
201
202 VideoFrameCapturerLinux::~VideoFrameCapturerLinux() {
203 DeinitXlib();
204 }
205
206 bool VideoFrameCapturerLinux::Init() {
207 // TODO(ajwong): We should specify the display string we are attaching to
208 // in the constructor.
209 display_ = XOpenDisplay(NULL);
210 if (!display_) {
211 LOG(ERROR) << "Unable to open display";
212 return false;
213 }
214
215 x_server_pixel_buffer_.Init(display_);
216
217 root_window_ = RootWindow(display_, DefaultScreen(display_));
218 if (root_window_ == BadValue) {
219 LOG(ERROR) << "Unable to get the root window";
220 DeinitXlib();
221 return false;
222 }
223
224 gc_ = XCreateGC(display_, root_window_, 0, NULL);
225 if (gc_ == NULL) {
226 LOG(ERROR) << "Unable to get graphics context";
227 DeinitXlib();
228 return false;
229 }
230
231 // Check for XFixes extension. This is required for cursor shape
232 // notifications, and for our use of XDamage.
233 if (XFixesQueryExtension(display_, &xfixes_event_base_,
234 &xfixes_error_base_)) {
235 has_xfixes_ = true;
236 } else {
237 LOG(INFO) << "X server does not support XFixes.";
238 }
239
240 if (ShouldUseXDamage()) {
241 InitXDamage();
242 }
243
244 // Register for changes to the dimensions of the root window.
245 XSelectInput(display_, root_window_, StructureNotifyMask);
246
247 if (has_xfixes_) {
248 // Register for changes to the cursor shape.
249 XFixesSelectCursorInput(display_, root_window_,
250 XFixesDisplayCursorNotifyMask);
251 }
252
253 return true;
254 }
255
256 void VideoFrameCapturerLinux::InitXDamage() {
257 // Our use of XDamage requires XFixes.
258 if (!has_xfixes_) {
259 return;
260 }
261
262 // Check for XDamage extension.
263 if (!XDamageQueryExtension(display_, &damage_event_base_,
264 &damage_error_base_)) {
265 LOG(INFO) << "X server does not support XDamage.";
266 return;
267 }
268
269 // TODO(lambroslambrou): Disable DAMAGE in situations where it is known
270 // to fail, such as when Desktop Effects are enabled, with graphics
271 // drivers (nVidia, ATI) that fail to report DAMAGE notifications
272 // properly.
273
274 // Request notifications every time the screen becomes damaged.
275 damage_handle_ = XDamageCreate(display_, root_window_,
276 XDamageReportNonEmpty);
277 if (!damage_handle_) {
278 LOG(ERROR) << "Unable to initialize XDamage.";
279 return;
280 }
281
282 // Create an XFixes server-side region to collate damage into.
283 damage_region_ = XFixesCreateRegion(display_, 0, 0);
284 if (!damage_region_) {
285 XDamageDestroy(display_, damage_handle_);
286 LOG(ERROR) << "Unable to create XFixes region.";
287 return;
288 }
289
290 use_damage_ = true;
291 LOG(INFO) << "Using XDamage extension.";
292 }
293
294 void VideoFrameCapturerLinux::Start(
295 const CursorShapeChangedCallback& callback) {
296 cursor_shape_changed_callback_ = callback;
297 }
298
299 void VideoFrameCapturerLinux::Stop() {
300 }
301
302 void VideoFrameCapturerLinux::ScreenConfigurationChanged() {
303 last_buffer_ = NULL;
304 for (int i = 0; i < kNumBuffers; ++i) {
305 buffers_[i].set_needs_update();
306 }
307 helper_.ClearInvalidRegion();
308 x_server_pixel_buffer_.Init(display_);
309 }
310
311 media::VideoFrame::Format VideoFrameCapturerLinux::pixel_format() const {
312 return pixel_format_;
313 }
314
315 void VideoFrameCapturerLinux::ClearInvalidRegion() {
316 helper_.ClearInvalidRegion();
317 }
318
319 void VideoFrameCapturerLinux::InvalidateRegion(const SkRegion& invalid_region) {
320 helper_.InvalidateRegion(invalid_region);
321 }
322
323 void VideoFrameCapturerLinux::InvalidateScreen(const SkISize& size) {
324 helper_.InvalidateScreen(size);
325 }
326
327 void VideoFrameCapturerLinux::InvalidateFullScreen() {
328 helper_.InvalidateFullScreen();
329 last_buffer_ = NULL;
330 }
331
332 void VideoFrameCapturerLinux::CaptureInvalidRegion(
333 const CaptureCompletedCallback& callback) {
334 // Process XEvents for XDamage and cursor shape tracking.
335 ProcessPendingXEvents();
336
337 // Resize the current buffer if there was a recent change of
338 // screen-resolution.
339 VideoFrameBuffer &current = buffers_[current_buffer_];
340 current.Update(display_, root_window_);
341 // Also refresh the Differ helper used by CaptureFrame(), if needed.
342 if (!use_damage_ && !last_buffer_) {
343 differ_.reset(new Differ(current.size().width(), current.size().height(),
344 kBytesPerPixel, current.bytes_per_row()));
345 }
346
347 scoped_refptr<CaptureData> capture_data(CaptureFrame());
348
349 current_buffer_ = (current_buffer_ + 1) % kNumBuffers;
350
351 callback.Run(capture_data);
352 }
353
354 void VideoFrameCapturerLinux::ProcessPendingXEvents() {
355 // Find the number of events that are outstanding "now." We don't just loop
356 // on XPending because we want to guarantee this terminates.
357 int events_to_process = XPending(display_);
358 XEvent e;
359
360 for (int i = 0; i < events_to_process; i++) {
361 XNextEvent(display_, &e);
362 if (use_damage_ && (e.type == damage_event_base_ + XDamageNotify)) {
363 XDamageNotifyEvent* event = reinterpret_cast<XDamageNotifyEvent*>(&e);
364 DCHECK(event->level == XDamageReportNonEmpty);
365 } else if (e.type == ConfigureNotify) {
366 ScreenConfigurationChanged();
367 } else if (has_xfixes_ &&
368 e.type == xfixes_event_base_ + XFixesCursorNotify) {
369 XFixesCursorNotifyEvent* cne;
370 cne = reinterpret_cast<XFixesCursorNotifyEvent*>(&e);
371 if (cne->subtype == XFixesDisplayCursorNotify) {
372 CaptureCursor();
373 }
374 } else {
375 LOG(WARNING) << "Got unknown event type: " << e.type;
376 }
377 }
378 }
379
380 void VideoFrameCapturerLinux::CaptureCursor() {
381 DCHECK(has_xfixes_);
382 if (cursor_shape_changed_callback_.is_null())
383 return;
384
385 XFixesCursorImage* img = XFixesGetCursorImage(display_);
386 if (!img) {
387 return;
388 }
389
390 int width = img->width;
391 int height = img->height;
392 int total_bytes = width * height * kBytesPerPixel;
393
394 scoped_ptr<protocol::CursorShapeInfo> cursor_proto(
395 new protocol::CursorShapeInfo());
396 cursor_proto->set_width(width);
397 cursor_proto->set_height(height);
398 cursor_proto->set_hotspot_x(img->xhot);
399 cursor_proto->set_hotspot_y(img->yhot);
400
401 cursor_proto->mutable_data()->resize(total_bytes);
402 uint8* proto_data = const_cast<uint8*>(reinterpret_cast<const uint8*>(
403 cursor_proto->mutable_data()->data()));
404
405 // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
406 unsigned long* src = img->pixels;
407 uint32* dst = reinterpret_cast<uint32*>(proto_data);
408 uint32* dst_end = dst + (width * height);
409 while (dst < dst_end) {
410 *dst++ = static_cast<uint32>(*src++);
411 }
412 XFree(img);
413
414 cursor_shape_changed_callback_.Run(cursor_proto.Pass());
415 }
416
417 CaptureData* VideoFrameCapturerLinux::CaptureFrame() {
418 VideoFrameBuffer& buffer = buffers_[current_buffer_];
419 DataPlanes planes;
420 planes.data[0] = buffer.ptr();
421 planes.strides[0] = buffer.bytes_per_row();
422
423 CaptureData* capture_data = new CaptureData(planes, buffer.size(),
424 media::VideoFrame::RGB32);
425
426 // Pass the screen size to the helper, so it can clip the invalid region if it
427 // expands that region to a grid.
428 helper_.set_size_most_recent(capture_data->size());
429
430 // In the DAMAGE case, ensure the frame is up-to-date with the previous frame
431 // if any. If there isn't a previous frame, that means a screen-resolution
432 // change occurred, and |invalid_rects| will be updated to include the whole
433 // screen.
434 if (use_damage_ && last_buffer_)
435 SynchronizeFrame();
436
437 SkRegion invalid_region;
438
439 x_server_pixel_buffer_.Synchronize();
440 if (use_damage_ && last_buffer_) {
441 // Atomically fetch and clear the damage region.
442 XDamageSubtract(display_, damage_handle_, None, damage_region_);
443 int nRects = 0;
444 XRectangle bounds;
445 XRectangle* rects = XFixesFetchRegionAndBounds(display_, damage_region_,
446 &nRects, &bounds);
447 for (int i=0; i<nRects; ++i) {
448 invalid_region.op(SkIRect::MakeXYWH(rects[i].x, rects[i].y,
449 rects[i].width, rects[i].height),
450 SkRegion::kUnion_Op);
451 }
452 XFree(rects);
453 helper_.InvalidateRegion(invalid_region);
454
455 // Capture the damaged portions of the desktop.
456 helper_.SwapInvalidRegion(&invalid_region);
457 for (SkRegion::Iterator it(invalid_region); !it.done(); it.next()) {
458 CaptureRect(it.rect(), capture_data);
459 }
460 } else {
461 // Doing full-screen polling, or this is the first capture after a
462 // screen-resolution change. In either case, need a full-screen capture.
463 SkIRect screen_rect = SkIRect::MakeWH(buffer.size().width(),
464 buffer.size().height());
465 CaptureRect(screen_rect, capture_data);
466
467 if (last_buffer_) {
468 // Full-screen polling, so calculate the invalid rects here, based on the
469 // changed pixels between current and previous buffers.
470 DCHECK(differ_ != NULL);
471 differ_->CalcDirtyRegion(last_buffer_, buffer.ptr(), &invalid_region);
472 } else {
473 // No previous buffer, so always invalidate the whole screen, whether
474 // or not DAMAGE is being used. DAMAGE doesn't necessarily send a
475 // full-screen notification after a screen-resolution change, so
476 // this is done here.
477 invalid_region.op(screen_rect, SkRegion::kUnion_Op);
478 }
479 }
480
481 capture_data->mutable_dirty_region() = invalid_region;
482 last_invalid_region_ = invalid_region;
483 last_buffer_ = buffer.ptr();
484 return capture_data;
485 }
486
487 void VideoFrameCapturerLinux::SynchronizeFrame() {
488 // Synchronize the current buffer with the previous one since we do not
489 // capture the entire desktop. Note that encoder may be reading from the
490 // previous buffer at this time so thread access complaints are false
491 // positives.
492
493 // TODO(hclam): We can reduce the amount of copying here by subtracting
494 // |capturer_helper_|s region from |last_invalid_region_|.
495 // http://crbug.com/92354
496 DCHECK(last_buffer_);
497 VideoFrameBuffer& buffer = buffers_[current_buffer_];
498 for (SkRegion::Iterator it(last_invalid_region_); !it.done(); it.next()) {
499 const SkIRect& r = it.rect();
500 int offset = r.fTop * buffer.bytes_per_row() + r.fLeft * kBytesPerPixel;
501 for (int i = 0; i < r.height(); ++i) {
502 memcpy(buffer.ptr() + offset, last_buffer_ + offset,
503 r.width() * kBytesPerPixel);
504 offset += buffer.size().width() * kBytesPerPixel;
505 }
506 }
507 }
508
509 void VideoFrameCapturerLinux::DeinitXlib() {
510 if (gc_) {
511 XFreeGC(display_, gc_);
512 gc_ = NULL;
513 }
514
515 x_server_pixel_buffer_.Release();
516
517 if (display_) {
518 if (damage_handle_)
519 XDamageDestroy(display_, damage_handle_);
520 if (damage_region_)
521 XFixesDestroyRegion(display_, damage_region_);
522 XCloseDisplay(display_);
523 display_ = NULL;
524 damage_handle_ = 0;
525 damage_region_ = 0;
526 }
527 }
528
529 void VideoFrameCapturerLinux::CaptureRect(const SkIRect& rect,
530 CaptureData* capture_data) {
531 uint8* image = x_server_pixel_buffer_.CaptureRect(rect);
532 int depth = x_server_pixel_buffer_.GetDepth();
533 int bpp = x_server_pixel_buffer_.GetBitsPerPixel();
534 bool is_rgb = x_server_pixel_buffer_.IsRgb();
535 if ((depth == 24 || depth == 32) && bpp == 32 && is_rgb) {
536 DVLOG(3) << "Fast blitting";
537 FastBlit(image, rect, capture_data);
538 } else {
539 DVLOG(3) << "Slow blitting";
540 SlowBlit(image, rect, capture_data);
541 }
542 }
543
544 void VideoFrameCapturerLinux::FastBlit(uint8* image, const SkIRect& rect,
545 CaptureData* capture_data) {
546 uint8* src_pos = image;
547 int src_stride = x_server_pixel_buffer_.GetStride();
548 int dst_x = rect.fLeft, dst_y = rect.fTop;
549
550 DataPlanes planes = capture_data->data_planes();
551 uint8* dst_buffer = planes.data[0];
552
553 const int dst_stride = planes.strides[0];
554
555 uint8* dst_pos = dst_buffer + dst_stride * dst_y;
556 dst_pos += dst_x * kBytesPerPixel;
557
558 int height = rect.height(), row_bytes = rect.width() * kBytesPerPixel;
559 for (int y = 0; y < height; ++y) {
560 memcpy(dst_pos, src_pos, row_bytes);
561 src_pos += src_stride;
562 dst_pos += dst_stride;
563 }
564 }
565
566 void VideoFrameCapturerLinux::SlowBlit(uint8* image, const SkIRect& rect,
567 CaptureData* capture_data) {
568 DataPlanes planes = capture_data->data_planes();
569 uint8* dst_buffer = planes.data[0];
570 const int dst_stride = planes.strides[0];
571 int src_stride = x_server_pixel_buffer_.GetStride();
572 int dst_x = rect.fLeft, dst_y = rect.fTop;
573 int width = rect.width(), height = rect.height();
574
575 unsigned int red_mask = x_server_pixel_buffer_.GetRedMask();
576 unsigned int blue_mask = x_server_pixel_buffer_.GetBlueMask();
577 unsigned int green_mask = x_server_pixel_buffer_.GetGreenMask();
578 unsigned int red_shift = x_server_pixel_buffer_.GetRedShift();
579 unsigned int blue_shift = x_server_pixel_buffer_.GetBlueShift();
580 unsigned int green_shift = x_server_pixel_buffer_.GetGreenShift();
581
582 unsigned int max_red = red_mask >> red_shift;
583 unsigned int max_blue = blue_mask >> blue_shift;
584 unsigned int max_green = green_mask >> green_shift;
585
586 unsigned int bits_per_pixel = x_server_pixel_buffer_.GetBitsPerPixel();
587
588 uint8* dst_pos = dst_buffer + dst_stride * dst_y;
589 uint8* src_pos = image;
590 dst_pos += dst_x * kBytesPerPixel;
591 // TODO(hclam): Optimize, perhaps using MMX code or by converting to
592 // YUV directly
593 for (int y = 0; y < height; y++) {
594 uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
595 uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
596 uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
597 for (int x = 0; x < width; x++) {
598 // Dereference through an appropriately-aligned pointer.
599 uint32_t pixel;
600 if (bits_per_pixel == 32)
601 pixel = src_pos_32[x];
602 else if (bits_per_pixel == 16)
603 pixel = src_pos_16[x];
604 else
605 pixel = src_pos[x];
606 uint32_t r = (((pixel & red_mask) >> red_shift) * 255) / max_red;
607 uint32_t b = (((pixel & blue_mask) >> blue_shift) * 255) / max_blue;
608 uint32_t g = (((pixel & green_mask) >> green_shift) * 255) / max_green;
609 // Write as 32-bit RGB.
610 dst_pos_32[x] = r << 16 | g << 8 | b;
611 }
612 dst_pos += dst_stride;
613 src_pos += src_stride;
614 }
615 }
616
617 const SkISize& VideoFrameCapturerLinux::size_most_recent() const {
618 return helper_.size_most_recent();
619 }
620
621 } // namespace
622
623 // static
624 VideoFrameCapturer* VideoFrameCapturer::Create() {
625 VideoFrameCapturerLinux* capturer = new VideoFrameCapturerLinux();
626 if (!capturer->Init()) {
627 delete capturer;
628 capturer = NULL;
629 }
630 return capturer;
631 }
632
633 // static
634 void VideoFrameCapturer::EnableXDamage(bool enable) {
635 g_should_use_x_damage = enable;
636 }
637
638 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/video_frame_capturer_helper_unittest.cc ('k') | remoting/host/video_frame_capturer_mac.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698