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

Side by Side Diff: remoting/capturer/video_frame_capturer_win.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 <windows.h>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/file_path.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/scoped_native_library.h"
15 #include "base/stl_util.h"
16 #include "base/time.h"
17 #include "base/utf_string_conversions.h"
18 #include "base/win/scoped_gdi_object.h"
19 #include "base/win/scoped_hdc.h"
20 #include "remoting/capturer/capture_data.h"
21 #include "remoting/capturer/differ.h"
22 #include "remoting/capturer/mouse_cursor_shape.h"
23 #include "remoting/capturer/shared_buffer_factory.h"
24 #include "remoting/capturer/video_frame.h"
25 #include "remoting/capturer/video_frame_capturer_helper.h"
26 #include "remoting/capturer/video_frame_queue.h"
27 #include "remoting/capturer/win/desktop.h"
28 #include "remoting/capturer/win/scoped_thread_desktop.h"
29 #include "third_party/skia/include/core/SkColorPriv.h"
30
31 namespace remoting {
32
33 namespace {
34
35 // Constants from dwmapi.h.
36 const UINT DWM_EC_DISABLECOMPOSITION = 0;
37 const UINT DWM_EC_ENABLECOMPOSITION = 1;
38
39 typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
40
41 const char kDwmapiLibraryName[] = "dwmapi";
42
43 // Pixel colors used when generating cursor outlines.
44 const uint32 kPixelBgraBlack = 0xff000000;
45 const uint32 kPixelBgraWhite = 0xffffffff;
46 const uint32 kPixelBgraTransparent = 0x00000000;
47
48 // A class representing a full-frame pixel buffer.
49 class VideoFrameWin : public VideoFrame {
50 public:
51 VideoFrameWin(HDC desktop_dc, const SkISize& size,
52 SharedBufferFactory* shared_buffer_factory);
53 virtual ~VideoFrameWin();
54
55 // Returns handle of the device independent bitmap representing this frame
56 // buffer to GDI.
57 HBITMAP GetBitmap();
58
59 private:
60 // Allocates a device independent bitmap representing this frame buffer to
61 // GDI.
62 void AllocateBitmap(HDC desktop_dc, const SkISize& size);
63
64 // Handle of the device independent bitmap representing this frame buffer to
65 // GDI.
66 base::win::ScopedBitmap bitmap_;
67
68 // Used to allocate shared memory buffers if set.
69 SharedBufferFactory* shared_buffer_factory_;
70
71 DISALLOW_COPY_AND_ASSIGN(VideoFrameWin);
72 };
73
74 // VideoFrameCapturerWin captures 32bit RGB using GDI.
75 //
76 // VideoFrameCapturerWin is double-buffered as required by VideoFrameCapturer.
77 // See remoting/host/video_frame_capturer.h.
78 class VideoFrameCapturerWin : public VideoFrameCapturer {
79 public:
80 VideoFrameCapturerWin();
81 explicit VideoFrameCapturerWin(SharedBufferFactory* shared_buffer_factory);
82 virtual ~VideoFrameCapturerWin();
83
84 // Overridden from VideoFrameCapturer:
85 virtual void Start(Delegate* delegate) OVERRIDE;
86 virtual void Stop() OVERRIDE;
87 virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
88 virtual void CaptureFrame() OVERRIDE;
89
90 private:
91 // Make sure that the device contexts match the screen configuration.
92 void PrepareCaptureResources();
93
94 // Creates a CaptureData instance wrapping the current framebuffer and
95 // notifies |delegate_|.
96 void CaptureRegion(const SkRegion& region,
97 const base::Time& capture_start_time);
98
99 // Captures the current screen contents into the current buffer.
100 void CaptureImage();
101
102 // Expand the cursor shape to add a white outline for visibility against
103 // dark backgrounds.
104 void AddCursorOutline(int width, int height, uint32* dst);
105
106 // Capture the current cursor shape.
107 void CaptureCursor();
108
109 // Used to allocate shared memory buffers if set.
110 SharedBufferFactory* shared_buffer_factory_;
111
112 Delegate* delegate_;
113
114 // A thread-safe list of invalid rectangles, and the size of the most
115 // recently captured screen.
116 VideoFrameCapturerHelper helper_;
117
118 // Snapshot of the last cursor bitmap we sent to the client. This is used
119 // to diff against the current cursor so we only send a cursor-change
120 // message when the shape has changed.
121 MouseCursorShape last_cursor_;
122
123 ScopedThreadDesktop desktop_;
124
125 // GDI resources used for screen capture.
126 scoped_ptr<base::win::ScopedGetDC> desktop_dc_;
127 base::win::ScopedCreateDC memory_dc_;
128
129 // Queue of the frames buffers.
130 VideoFrameQueue queue_;
131
132 // Rectangle describing the bounds of the desktop device context.
133 SkIRect desktop_dc_rect_;
134
135 // Class to calculate the difference between two screen bitmaps.
136 scoped_ptr<Differ> differ_;
137
138 base::ScopedNativeLibrary dwmapi_library_;
139 DwmEnableCompositionFunc composition_func_;
140
141 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerWin);
142 };
143
144 // 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors.
145 static const int kPixelsPerMeter = 3780;
146
147 VideoFrameWin::VideoFrameWin(
148 HDC desktop_dc,
149 const SkISize& size,
150 SharedBufferFactory* shared_buffer_factory)
151 : shared_buffer_factory_(shared_buffer_factory) {
152 // Allocate a shared memory buffer.
153 uint32 buffer_size =
154 size.width() * size.height() * CaptureData::kBytesPerPixel;
155 if (shared_buffer_factory_) {
156 scoped_refptr<SharedBuffer> shared_buffer =
157 shared_buffer_factory_->CreateSharedBuffer(buffer_size);
158 CHECK(shared_buffer->ptr() != NULL);
159 set_shared_buffer(shared_buffer);
160 }
161
162 AllocateBitmap(desktop_dc, size);
163 }
164
165 VideoFrameWin::~VideoFrameWin() {
166 if (shared_buffer())
167 shared_buffer_factory_->ReleaseSharedBuffer(shared_buffer());
168 }
169
170 HBITMAP VideoFrameWin::GetBitmap() {
171 return bitmap_;
172 }
173
174 void VideoFrameWin::AllocateBitmap(HDC desktop_dc, const SkISize& size) {
175 int bytes_per_row = size.width() * CaptureData::kBytesPerPixel;
176
177 // Describe a device independent bitmap (DIB) that is the size of the desktop.
178 BITMAPINFO bmi;
179 memset(&bmi, 0, sizeof(bmi));
180 bmi.bmiHeader.biHeight = -size.height();
181 bmi.bmiHeader.biWidth = size.width();
182 bmi.bmiHeader.biPlanes = 1;
183 bmi.bmiHeader.biBitCount = CaptureData::kBytesPerPixel * 8;
184 bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
185 bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
186 bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter;
187 bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter;
188
189 // Create the DIB, and store a pointer to its pixel buffer.
190 HANDLE section_handle = NULL;
191 if (shared_buffer())
192 section_handle = shared_buffer()->handle();
193 void* data = NULL;
194 bitmap_ = CreateDIBSection(desktop_dc, &bmi, DIB_RGB_COLORS, &data,
195 section_handle, 0);
196
197 // TODO(wez): Cope gracefully with failure (crbug.com/157170).
198 CHECK(bitmap_ != NULL);
199 CHECK(data != NULL);
200
201 set_pixels(reinterpret_cast<uint8*>(data));
202 set_dimensions(SkISize::Make(bmi.bmiHeader.biWidth,
203 std::abs(bmi.bmiHeader.biHeight)));
204 set_bytes_per_row(
205 bmi.bmiHeader.biSizeImage / std::abs(bmi.bmiHeader.biHeight));
206 }
207
208 VideoFrameCapturerWin::VideoFrameCapturerWin()
209 : shared_buffer_factory_(NULL),
210 delegate_(NULL),
211 desktop_dc_rect_(SkIRect::MakeEmpty()),
212 composition_func_(NULL) {
213 }
214
215 VideoFrameCapturerWin::VideoFrameCapturerWin(
216 SharedBufferFactory* shared_buffer_factory)
217 : shared_buffer_factory_(shared_buffer_factory),
218 delegate_(NULL),
219 desktop_dc_rect_(SkIRect::MakeEmpty()),
220 composition_func_(NULL) {
221 }
222
223 VideoFrameCapturerWin::~VideoFrameCapturerWin() {
224 }
225
226 void VideoFrameCapturerWin::InvalidateRegion(const SkRegion& invalid_region) {
227 helper_.InvalidateRegion(invalid_region);
228 }
229
230 void VideoFrameCapturerWin::CaptureFrame() {
231 base::Time capture_start_time = base::Time::Now();
232
233 // Force the system to power-up display hardware, if it has been suspended.
234 SetThreadExecutionState(ES_DISPLAY_REQUIRED);
235
236 // Make sure the GDI capture resources are up-to-date.
237 PrepareCaptureResources();
238
239 // Copy screen bits to the current buffer.
240 CaptureImage();
241
242 const VideoFrame* current_buffer = queue_.current_frame();
243 const VideoFrame* last_buffer = queue_.previous_frame();
244 if (last_buffer) {
245 // Make sure the differencer is set up correctly for these previous and
246 // current screens.
247 if (!differ_.get() ||
248 (differ_->width() != current_buffer->dimensions().width()) ||
249 (differ_->height() != current_buffer->dimensions().height()) ||
250 (differ_->bytes_per_row() != current_buffer->bytes_per_row())) {
251 differ_.reset(new Differ(current_buffer->dimensions().width(),
252 current_buffer->dimensions().height(),
253 CaptureData::kBytesPerPixel,
254 current_buffer->bytes_per_row()));
255 }
256
257 // Calculate difference between the two last captured frames.
258 SkRegion region;
259 differ_->CalcDirtyRegion(last_buffer->pixels(), current_buffer->pixels(),
260 &region);
261 InvalidateRegion(region);
262 } else {
263 // No previous frame is available. Invalidate the whole screen.
264 helper_.InvalidateScreen(current_buffer->dimensions());
265 }
266
267 // Wrap the captured frame into CaptureData structure and invoke
268 // the completion callback.
269 SkRegion invalid_region;
270 helper_.SwapInvalidRegion(&invalid_region);
271 CaptureRegion(invalid_region, capture_start_time);
272
273 // Check for cursor shape update.
274 CaptureCursor();
275 }
276
277 void VideoFrameCapturerWin::Start(Delegate* delegate) {
278 DCHECK(delegate_ == NULL);
279
280 delegate_ = delegate;
281
282 // Load dwmapi.dll dynamically since it is not available on XP.
283 if (!dwmapi_library_.is_valid()) {
284 FilePath path(base::GetNativeLibraryName(UTF8ToUTF16(kDwmapiLibraryName)));
285 dwmapi_library_.Reset(base::LoadNativeLibrary(path, NULL));
286 }
287
288 if (dwmapi_library_.is_valid() && composition_func_ == NULL) {
289 composition_func_ = static_cast<DwmEnableCompositionFunc>(
290 dwmapi_library_.GetFunctionPointer("DwmEnableComposition"));
291 }
292
293 // Vote to disable Aero composited desktop effects while capturing. Windows
294 // will restore Aero automatically if the process exits. This has no effect
295 // under Windows 8 or higher. See crbug.com/124018.
296 if (composition_func_ != NULL) {
297 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
298 }
299 }
300
301 void VideoFrameCapturerWin::Stop() {
302 // Restore Aero.
303 if (composition_func_ != NULL) {
304 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
305 }
306
307 delegate_ = NULL;
308 }
309
310 void VideoFrameCapturerWin::PrepareCaptureResources() {
311 // Switch to the desktop receiving user input if different from the current
312 // one.
313 scoped_ptr<Desktop> input_desktop = Desktop::GetInputDesktop();
314 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
315 // Release GDI resources otherwise SetThreadDesktop will fail.
316 desktop_dc_.reset();
317 memory_dc_.Set(NULL);
318
319 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
320 // So we can continue capture screen bits, just from the wrong desktop.
321 desktop_.SetThreadDesktop(input_desktop.Pass());
322 }
323
324 // If the display bounds have changed then recreate GDI resources.
325 // TODO(wez): Also check for pixel format changes.
326 SkIRect screen_rect(SkIRect::MakeXYWH(
327 GetSystemMetrics(SM_XVIRTUALSCREEN),
328 GetSystemMetrics(SM_YVIRTUALSCREEN),
329 GetSystemMetrics(SM_CXVIRTUALSCREEN),
330 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
331 if (screen_rect != desktop_dc_rect_) {
332 desktop_dc_.reset();
333 memory_dc_.Set(NULL);
334 desktop_dc_rect_.setEmpty();
335 }
336
337 if (desktop_dc_.get() == NULL) {
338 DCHECK(memory_dc_.Get() == NULL);
339
340 // Create GDI device contexts to capture from the desktop into memory.
341 desktop_dc_.reset(new base::win::ScopedGetDC(NULL));
342 memory_dc_.Set(CreateCompatibleDC(*desktop_dc_));
343 desktop_dc_rect_ = screen_rect;
344
345 // Make sure the frame buffers will be reallocated.
346 queue_.SetAllFramesNeedUpdate();
347
348 helper_.ClearInvalidRegion();
349 }
350 }
351
352 void VideoFrameCapturerWin::CaptureRegion(
353 const SkRegion& region,
354 const base::Time& capture_start_time) {
355 const VideoFrame* current_buffer = queue_.current_frame();
356
357 scoped_refptr<CaptureData> data(
358 new CaptureData(current_buffer->pixels(), current_buffer->bytes_per_row(),
359 current_buffer->dimensions()));
360 data->mutable_dirty_region() = region;
361 data->set_shared_buffer(current_buffer->shared_buffer());
362
363 helper_.set_size_most_recent(data->size());
364
365 queue_.DoneWithCurrentFrame();
366
367 data->set_capture_time_ms(
368 (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
369 delegate_->OnCaptureCompleted(data);
370 }
371
372 void VideoFrameCapturerWin::CaptureImage() {
373 // If the current buffer is from an older generation then allocate a new one.
374 // Note that we can't reallocate other buffers at this point, since the caller
375 // may still be reading from them.
376 if (queue_.current_frame_needs_update()) {
377 DCHECK(desktop_dc_.get() != NULL);
378 DCHECK(memory_dc_.Get() != NULL);
379
380 SkISize size = SkISize::Make(desktop_dc_rect_.width(),
381 desktop_dc_rect_.height());
382 scoped_ptr<VideoFrameWin> buffer(
383 new VideoFrameWin(*desktop_dc_, size, shared_buffer_factory_));
384 queue_.ReplaceCurrentFrame(buffer.PassAs<VideoFrame>());
385 }
386
387 // Select the target bitmap into the memory dc and copy the rect from desktop
388 // to memory.
389 VideoFrameWin* current = static_cast<VideoFrameWin*>(queue_.current_frame());
390 HGDIOBJ previous_object = SelectObject(memory_dc_, current->GetBitmap());
391 if (previous_object != NULL) {
392 BitBlt(memory_dc_,
393 0, 0, desktop_dc_rect_.width(), desktop_dc_rect_.height(),
394 *desktop_dc_,
395 desktop_dc_rect_.x(), desktop_dc_rect_.y(),
396 SRCCOPY | CAPTUREBLT);
397
398 // Select back the previously selected object to that the device contect
399 // could be destroyed independently of the bitmap if needed.
400 SelectObject(memory_dc_, previous_object);
401 }
402 }
403
404 void VideoFrameCapturerWin::AddCursorOutline(int width,
405 int height,
406 uint32* dst) {
407 for (int y = 0; y < height; y++) {
408 for (int x = 0; x < width; x++) {
409 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
410 // neighbor pixels to see if this should be changed to an outline pixel.
411 if (*dst == kPixelBgraTransparent) {
412 // Change to white pixel if any neighbors (top, bottom, left, right)
413 // are black.
414 if ((y > 0 && dst[-width] == kPixelBgraBlack) ||
415 (y < height - 1 && dst[width] == kPixelBgraBlack) ||
416 (x > 0 && dst[-1] == kPixelBgraBlack) ||
417 (x < width - 1 && dst[1] == kPixelBgraBlack)) {
418 *dst = kPixelBgraWhite;
419 }
420 }
421 dst++;
422 }
423 }
424 }
425
426 void VideoFrameCapturerWin::CaptureCursor() {
427 CURSORINFO cursor_info;
428 cursor_info.cbSize = sizeof(CURSORINFO);
429 if (!GetCursorInfo(&cursor_info)) {
430 VLOG(3) << "Unable to get cursor info. Error = " << GetLastError();
431 return;
432 }
433
434 // Note that this does not need to be freed.
435 HCURSOR hcursor = cursor_info.hCursor;
436 ICONINFO iinfo;
437 if (!GetIconInfo(hcursor, &iinfo)) {
438 VLOG(3) << "Unable to get cursor icon info. Error = " << GetLastError();
439 return;
440 }
441 int hotspot_x = iinfo.xHotspot;
442 int hotspot_y = iinfo.yHotspot;
443
444 // Get the cursor bitmap.
445 base::win::ScopedBitmap hbitmap;
446 BITMAP bitmap;
447 bool color_bitmap;
448 if (iinfo.hbmColor) {
449 // Color cursor bitmap.
450 color_bitmap = true;
451 hbitmap.Set((HBITMAP)CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0,
452 LR_CREATEDIBSECTION));
453 if (!hbitmap.Get()) {
454 VLOG(3) << "Unable to copy color cursor image. Error = "
455 << GetLastError();
456 return;
457 }
458
459 // Free the color and mask bitmaps since we only need our copy.
460 DeleteObject(iinfo.hbmColor);
461 DeleteObject(iinfo.hbmMask);
462 } else {
463 // Black and white (xor) cursor.
464 color_bitmap = false;
465 hbitmap.Set(iinfo.hbmMask);
466 }
467
468 if (!GetObject(hbitmap.Get(), sizeof(BITMAP), &bitmap)) {
469 VLOG(3) << "Unable to get cursor bitmap. Error = " << GetLastError();
470 return;
471 }
472
473 int width = bitmap.bmWidth;
474 int height = bitmap.bmHeight;
475 // For non-color cursors, the mask contains both an AND and an XOR mask and
476 // the height includes both. Thus, the width is correct, but we need to
477 // divide by 2 to get the correct mask height.
478 if (!color_bitmap) {
479 height /= 2;
480 }
481 int data_size = height * width * CaptureData::kBytesPerPixel;
482
483 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape());
484 cursor->data.resize(data_size);
485 uint8* cursor_dst_data =
486 reinterpret_cast<uint8*>(string_as_array(&cursor->data));
487
488 // Copy/convert cursor bitmap into format needed by chromotocol.
489 int row_bytes = bitmap.bmWidthBytes;
490 if (color_bitmap) {
491 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) {
492 VLOG(3) << "Unsupported color cursor format. Error = " << GetLastError();
493 return;
494 }
495
496 // Copy across colour cursor imagery.
497 // MouseCursorShape stores imagery top-down, and premultiplied
498 // by the alpha channel, whereas windows stores them bottom-up
499 // and not premultiplied.
500 uint8* cursor_src_data = reinterpret_cast<uint8*>(bitmap.bmBits);
501 uint8* src = cursor_src_data + ((height - 1) * row_bytes);
502 uint8* dst = cursor_dst_data;
503 for (int row = 0; row < height; ++row) {
504 for (int column = 0; column < width; ++column) {
505 dst[0] = SkAlphaMul(src[0], src[3]);
506 dst[1] = SkAlphaMul(src[1], src[3]);
507 dst[2] = SkAlphaMul(src[2], src[3]);
508 dst[3] = src[3];
509 dst += CaptureData::kBytesPerPixel;
510 src += CaptureData::kBytesPerPixel;
511 }
512 src -= row_bytes + (width * CaptureData::kBytesPerPixel);
513 }
514 } else {
515 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) {
516 VLOG(3) << "Unsupported cursor mask format. Error = " << GetLastError();
517 return;
518 }
519
520 // x2 because there are 2 masks in the bitmap: AND and XOR.
521 int mask_bytes = height * row_bytes * 2;
522 scoped_array<uint8> mask(new uint8[mask_bytes]);
523 if (!GetBitmapBits(hbitmap.Get(), mask_bytes, mask.get())) {
524 VLOG(3) << "Unable to get cursor mask bits. Error = " << GetLastError();
525 return;
526 }
527 uint8* and_mask = mask.get();
528 uint8* xor_mask = mask.get() + height * row_bytes;
529 uint8* dst = cursor_dst_data;
530 bool add_outline = false;
531 for (int y = 0; y < height; y++) {
532 for (int x = 0; x < width; x++) {
533 int byte = y * row_bytes + x / 8;
534 int bit = 7 - x % 8;
535 int and = and_mask[byte] & (1 << bit);
536 int xor = xor_mask[byte] & (1 << bit);
537
538 // The two cursor masks combine as follows:
539 // AND XOR Windows Result Our result RGB Alpha
540 // 0 0 Black Black 00 ff
541 // 0 1 White White ff ff
542 // 1 0 Screen Transparent 00 00
543 // 1 1 Reverse-screen Black 00 ff
544 // Since we don't support XOR cursors, we replace the "Reverse Screen"
545 // with black. In this case, we also add an outline around the cursor
546 // so that it is visible against a dark background.
547 int rgb = (!and && xor) ? 0xff : 0x00;
548 int alpha = (and && !xor) ? 0x00 : 0xff;
549 *dst++ = rgb;
550 *dst++ = rgb;
551 *dst++ = rgb;
552 *dst++ = alpha;
553 if (and && xor) {
554 add_outline = true;
555 }
556 }
557 }
558 if (add_outline) {
559 AddCursorOutline(width, height,
560 reinterpret_cast<uint32*>(cursor_dst_data));
561 }
562 }
563
564 cursor->size.set(width, height);
565 cursor->hotspot.set(hotspot_x, hotspot_y);
566
567 // Compare the current cursor with the last one we sent to the client. If
568 // they're the same, then don't bother sending the cursor again.
569 if (last_cursor_.size == cursor->size &&
570 last_cursor_.hotspot == cursor->hotspot &&
571 last_cursor_.data == cursor->data) {
572 return;
573 }
574
575 VLOG(3) << "Sending updated cursor: " << width << "x" << height;
576
577 // Record the last cursor image that we sent to the client.
578 last_cursor_ = *cursor;
579
580 delegate_->OnCursorShapeChanged(cursor.Pass());
581 }
582
583 } // namespace
584
585 // static
586 scoped_ptr<VideoFrameCapturer> VideoFrameCapturer::Create() {
587 return scoped_ptr<VideoFrameCapturer>(new VideoFrameCapturerWin());
588 }
589
590 // static
591 scoped_ptr<VideoFrameCapturer> VideoFrameCapturer::CreateWithFactory(
592 SharedBufferFactory* shared_buffer_factory) {
593 scoped_ptr<VideoFrameCapturerWin> capturer(
594 new VideoFrameCapturerWin(shared_buffer_factory));
595 return capturer.PassAs<VideoFrameCapturer>();
596 }
597
598 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/capturer/video_frame_capturer_unittest.cc ('k') | remoting/capturer/video_frame_queue.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698