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

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

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

Powered by Google App Engine
This is Rietveld 408576698