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

Unified Diff: media/video/capture/screen/screen_capture_device.cc

Issue 11680002: Implement screen capturer for MediaStream API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 11 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 side-by-side diff with in-line comments
Download patch
Index: media/video/capture/screen/screen_capture_device.cc
diff --git a/media/video/capture/screen/screen_capture_device.cc b/media/video/capture/screen/screen_capture_device.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0d168c57cc720ebb1ee62d3fe3de54d6e5d4fd8b
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_device.cc
@@ -0,0 +1,378 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capture_device.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace media {
+
+namespace {
+const int kBytesPerPixel = 4;
+} // namespace
+
+class ScreenCaptureDevice::Core
+ : public base::RefCountedThreadSafe<Core>,
+ public ScreenCapturer::Delegate {
+ public:
+ explicit Core(scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+ // Helper used in tests to supply a fake capturer.
+ void SetScreenCapturerForTest(scoped_ptr<ScreenCapturer> capturer) {
+ screen_capturer_ = capturer.Pass();
+ }
+
+ void Allocate(int width, int height,
+ int frame_rate,
+ EventHandler* event_handler);
+ void Start();
+ void Stop();
+ void DeAllocate();
+
+ // ScreenCapturer::Delegate interface. Called by |screen_capturer_| on the
+ // |task_runner_|.
+ virtual void OnCaptureCompleted(
+ scoped_refptr<ScreenCaptureData> capture_data) OVERRIDE;
+ virtual void OnCursorShapeChanged(
+ scoped_ptr<MouseCursorShape> cursor_shape) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ virtual ~Core();
+
+ // Helper methods that run on the |task_runner_|. Posted from the
+ // corresponding public methods.
+ void DoAllocate(int frame_rate);
+ void DoStart();
+ void DoStop();
+ void DoDeAllocate();
+
+ // Helper to schedule capture tasks.
+ void ScheduleCaptureTimer();
+
+ // Method that is scheduled on |task_runner_| to be called on regular interval
+ // to capture the screen.
+ void OnCaptureTimer();
+
+ // Captures a single frame.
+ void DoCapture();
+
+ // Task runner used for screen capturing operations.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // |event_handler_lock_| must be locked whenever |event_handler_| is used.
+ // It's necessary because DeAllocate() needs to reset it on the calling thread
+ // to ensure that the event handler is not called once DeAllocate() returns.
+ base::Lock event_handler_lock_;
+ EventHandler* event_handler_;
+
+ // Frame rate specified to Allocate().
+ int frame_rate_;
+
+ // The underlying ScreenCapturer instance used to capture frames.
+ scoped_ptr<ScreenCapturer> screen_capturer_;
+
+ // After Allocate() is called we need to call OnFrameInfo() method of the
+ // |event_handler_| to specify the size of the frames this capturer will
+ // produce. In order to get screen size from |screen_capturer_| we need to
+ // capture at least one frame. Once screen size is known it's stored in
+ // |frame_size_|.
+ bool waiting_for_frame_size_;
+ SkISize frame_size_;
+ SkBitmap resized_bitmap_;
+
+ // True between DoStart() and DoStop(). Can't just check |event_handler_|
+ // because |event_handler_| is used on the caller thread.
+ bool started_;
+
+ // True when we have delayed OnCaptureTimer() task posted on
+ // |task_runner_|.
+ bool capture_task_posted_;
+
+ // True when waiting for |screen_capturer_| to capture current frame.
+ bool capture_in_progress_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+ScreenCaptureDevice::Core::Core(
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : task_runner_(task_runner),
+ event_handler_(NULL),
+ waiting_for_frame_size_(false),
+ started_(false),
+ capture_task_posted_(false),
+ capture_in_progress_(false) {
+}
+
+ScreenCaptureDevice::Core::~Core() {
+}
+
+void ScreenCaptureDevice::Core::Allocate(int width, int height,
+ int frame_rate,
+ EventHandler* event_handler) {
+ DCHECK_GT(width, 0);
+ DCHECK_GT(height, 0);
+ DCHECK_GT(frame_rate, 0);
+
+ {
+ base::AutoLock auto_lock(event_handler_lock_);
+ event_handler_ = event_handler;
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::DoAllocate, this, frame_rate));
+}
+
+void ScreenCaptureDevice::Core::Start() {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::DoStart, this));
+}
+
+void ScreenCaptureDevice::Core::Stop() {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::DoStop, this));
+}
+
+void ScreenCaptureDevice::Core::DeAllocate() {
+ {
+ base::AutoLock auto_lock(event_handler_lock_);
+ event_handler_ = NULL;
+ }
+ task_runner_->PostTask(FROM_HERE, base::Bind(&Core::DoDeAllocate, this));
+}
+
+void ScreenCaptureDevice::Core::OnCaptureCompleted(
+ scoped_refptr<ScreenCaptureData> capture_data) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(capture_in_progress_);
+ DCHECK(!capture_data->size().isEmpty());
+
+ capture_in_progress_ = false;
+
+ if (waiting_for_frame_size_) {
+ frame_size_ = capture_data->size();
+ waiting_for_frame_size_ = false;
+
+ // Inform the EventHandler of the video frame dimensions and format.
+ VideoCaptureCapability caps;
+ caps.width = frame_size_.width();
+ caps.height = frame_size_.height();
+ caps.frame_rate = frame_rate_;
+ caps.color = VideoCaptureCapability::kARGB;
+ caps.expected_capture_delay =
+ base::Time::kMillisecondsPerSecond / frame_rate_;
+ caps.interlaced = false;
+
+ base::AutoLock auto_lock(event_handler_lock_);
+ if (event_handler_)
+ event_handler_->OnFrameInfo(caps);
+ }
+
+ if (!started_)
+ return;
+
+ size_t buffer_size = frame_size_.width() * frame_size_.height() *
+ ScreenCaptureData::kBytesPerPixel;
+
+ if (capture_data->size() == frame_size_) {
+ // If the captured frame matches the requested size, we don't need to
+ // resize it.
+ resized_bitmap_.reset();
+
+ base::AutoLock auto_lock(event_handler_lock_);
+ if (event_handler_) {
+ event_handler_->OnIncomingCapturedFrame(
+ capture_data->data(), buffer_size, base::Time::Now());
+ }
+ return;
+ }
+
+ // In case screen size has changed we need to resize the image. Resized image
+ // is stored to |resized_bitmap_|. Only regions of the screen that are
+ // changing are copied.
Wez 2013/02/01 00:44:34 The caller is expecting full frames, not differenc
Sergey Ulanov 2013/02/01 02:02:26 Right, but we keep resized_bitmap_ between the fra
+
+ SkRegion dirty_region = capture_data->dirty_region();
+
+ if (resized_bitmap_.width() != frame_size_.width() ||
+ resized_bitmap_.height() != frame_size_.height()) {
+ resized_bitmap_.setConfig(SkBitmap::kARGB_8888_Config,
+ frame_size_.width(), frame_size_.height());
+ resized_bitmap_.setIsOpaque(true);
+ resized_bitmap_.allocPixels();
+ dirty_region.setRect(SkIRect::MakeSize(frame_size_));
+ }
+
+ float scale_x = static_cast<float>(frame_size_.width()) /
+ capture_data->size().width();
+ float scale_y = static_cast<float>(frame_size_.height()) /
+ capture_data->size().height();
+ float scale;
+ float x, y;
+ // Center the image in case aspect ratio is different.
+ if (scale_x > scale_y) {
+ scale = scale_y;
+ x = (scale_x - scale_y) / scale * frame_size_.width() / 2.0;
+ y = 0.f;
+ } else {
+ scale = scale_x;
+ x = 0.f;
+ y = (scale_y - scale_x) / scale * frame_size_.height() / 2.0;
+ }
+
+ // Create skia device and canvas that draw to |resized_bitmap_|.
+ SkDevice device(resized_bitmap_);
+ SkCanvas canvas(&device);
+ canvas.scale(scale, scale);
+
+ int source_stride = capture_data->stride();
+ for (SkRegion::Iterator i(dirty_region); !i.done(); i.next()) {
+ SkBitmap source_bitmap;
+ source_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ i.rect().width(), i.rect().height(),
+ source_stride);
+ source_bitmap.setIsOpaque(true);
+ source_bitmap.setPixels(
+ capture_data->data() + i.rect().top() * source_stride +
+ i.rect().left() * ScreenCaptureData::kBytesPerPixel);
+ canvas.drawBitmap(source_bitmap, i.rect().left() + x / scale,
+ i.rect().top() + y / scale, NULL);
+ }
+
+ base::AutoLock auto_lock(event_handler_lock_);
+ if (event_handler_) {
+ event_handler_->OnIncomingCapturedFrame(
+ reinterpret_cast<uint8*>(resized_bitmap_.getPixels()), buffer_size,
+ base::Time::Now());
+ }
+}
+
+void ScreenCaptureDevice::Core::OnCursorShapeChanged(
+ scoped_ptr<MouseCursorShape> cursor_shape) {
+ // TODO(sergeyu): Store mouse cursor shape and then render it to each captured
+ // frame. crbug.com/173265 .
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+}
+
+void ScreenCaptureDevice::Core::DoAllocate(int frame_rate) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+
+ frame_rate_ = frame_rate;
+
+ // Create and start frame capturer.
+ if (!screen_capturer_)
+ screen_capturer_ = ScreenCapturer::Create();
+ if (screen_capturer_)
+ screen_capturer_->Start(this);
+
+ // Capture first frame, so that we can call OnFrameInfo() callback.
+ waiting_for_frame_size_ = true;
+ DoCapture();
+}
+
+void ScreenCaptureDevice::Core::DoStart() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ started_ = true;
+ if (!capture_task_posted_) {
+ ScheduleCaptureTimer();
+ DoCapture();
+ }
+}
+
+void ScreenCaptureDevice::Core::DoStop() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ started_ = false;
+ resized_bitmap_.reset();
+}
+
+void ScreenCaptureDevice::Core::DoDeAllocate() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DoStop();
+ if (screen_capturer_) {
+ screen_capturer_->Stop();
+ screen_capturer_.reset();
+ }
+ waiting_for_frame_size_ = false;
+}
+
+void ScreenCaptureDevice::Core::ScheduleCaptureTimer() {
+ DCHECK(!capture_task_posted_);
+ capture_task_posted_ = true;
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&Core::OnCaptureTimer, this),
+ base::TimeDelta::FromSeconds(1) / frame_rate_);
+}
+
+void ScreenCaptureDevice::Core::OnCaptureTimer() {
+ DCHECK(capture_task_posted_);
+ capture_task_posted_ = false;
+
+ if (!started_)
+ return;
+
+ // Schedule a task for the next frame.
+ ScheduleCaptureTimer();
+ DoCapture();
+}
+
+void ScreenCaptureDevice::Core::DoCapture() {
+ DCHECK(!capture_in_progress_);
+
+ capture_in_progress_ = true;
+ screen_capturer_->CaptureFrame();
+
+ // Assume that ScreenCapturer always calls OnCaptureCompleted()
+ // callback before it returns.
+ //
+ // TODO(sergeyu): Fix ScreenCapturer to return video frame
+ // synchronously instead of using Delegate interface.
+ DCHECK(!capture_in_progress_);
+}
+
+ScreenCaptureDevice::ScreenCaptureDevice(
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : core_(new Core(task_runner)) {
+ name_.device_name = "Screen";
+}
+
+ScreenCaptureDevice::~ScreenCaptureDevice() {
+ DeAllocate();
+}
+
+void ScreenCaptureDevice::SetScreenCapturerForTest(
+ scoped_ptr<ScreenCapturer> capturer) {
+ core_->SetScreenCapturerForTest(capturer.Pass());
+}
+
+void ScreenCaptureDevice::Allocate(int width, int height,
+ int frame_rate,
+ EventHandler* event_handler) {
+ core_->Allocate(width, height, frame_rate, event_handler);
+}
+
+void ScreenCaptureDevice::Start() {
+ core_->Start();
+}
+
+void ScreenCaptureDevice::Stop() {
+ core_->Stop();
+}
+
+void ScreenCaptureDevice::DeAllocate() {
+ core_->DeAllocate();
+}
+
+const VideoCaptureDevice::Name& ScreenCaptureDevice::device_name() {
+ return name_;
+}
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698