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

Unified Diff: content/browser/renderer_host/media/desktop_capture_device_aura.cc

Issue 23726015: All together now (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@screencast_cap
Patch Set: Created 7 years, 4 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
« no previous file with comments | « content/browser/renderer_host/media/desktop_capture_device_aura.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/browser/renderer_host/media/desktop_capture_device_aura.cc
diff --git a/content/browser/renderer_host/media/desktop_capture_device_aura.cc b/content/browser/renderer_host/media/desktop_capture_device_aura.cc
new file mode 100644
index 0000000000000000000000000000000000000000..26b6a57710b42a1d41e410a78a5f4512d1603517
--- /dev/null
+++ b/content/browser/renderer_host/media/desktop_capture_device_aura.cc
@@ -0,0 +1,468 @@
+// Copyright (c) 2013 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 "content/browser/renderer_host/media/desktop_capture_device_aura.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/aura/image_transport_factory.h"
+#include "content/browser/renderer_host/media/video_capture_oracle.h"
+#include "content/common/gpu/surface_capturer.h"
+#include "media/base/bitstream_buffer.h"
+#include "media/base/video_decoder_config.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_retriever_delegate.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+namespace {
+
+const int kMinFrameWidth = 2;
+const int kMinFrameHeight = 2;
+
+const int kDefaultBitrate = 64;
+
+// Returns the nearest even integer closer to zero.
+template<typename IntType>
+IntType MakeEven(IntType x) {
+ return x & static_cast<IntType>(-2);
+}
+
+} // anonymous namespace
+
+// The implementation class that actually handles screen capture. The capture
+// callback for the output surface is bound with a scoped_refptr to this object
+// to allow for thread-safe destruction.
+//
+// This class uses manual locking for thread-safety since it needs to be able
+// to be used from thwo different threads:
+// * The media manager thread (Allocate(), Start(), Stop(), DeAllocate())
+// * The output surface thread (DoFrameCapture())
+
+class DesktopCaptureDeviceAura::Impl
+ : public SurfaceCapturer::Client,
+ public ui::CompositorObserver,
+ public base::RefCountedThreadSafe<Impl> {
+ public:
+ Impl(const base::WeakPtr<DesktopCaptureDeviceAura>& weak_client,
+ const scoped_refptr<base::MessageLoopProxy>& client_loop,
+ scoped_ptr<VideoCaptureOracle> oracle,
+ media::VideoCaptureDevice::EventHandler* consumer,
+ const gfx::Size& requested_size,
+ int requested_frame_rate);
+ virtual ~Impl();
+
+ void CreateCapturerOnUIThread();
+
+ void Start();
+ void Stop();
+ void Destroy();
+
+ // Implements SurfaceCapturer::Client.
+ virtual void NotifyCaptureParameters(const gfx::Size& buffer_size,
+ const gfx::Rect& visible_rect) OVERRIDE;
+ virtual void NotifyCopyCaptureDone(
+ const scoped_refptr<media::VideoFrame>& frame) OVERRIDE;
+ virtual void NotifyError(SurfaceCapturer::Error error) OVERRIDE;
+
+ // Implements ui::CompositorObserver.
+ virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {}
+ virtual void OnCompositingStarted(ui::Compositor* compositor,
+ base::TimeTicks start_time) OVERRIDE {}
+ virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE;
+ virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {}
+ virtual void OnCompositingLockStateChanged(
+ ui::Compositor* compositor) OVERRIDE {}
+ virtual void OnUpdateVSyncParameters(ui::Compositor* compositor,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE {}
+ private:
+ static const size_t kMaxPendingFrames = 2;
+
+ void ReportError();
+ void DoFrameCapture(bool is_compositor_update);
+
+ // The timer should be started and stopped on UI thread because this is the
+ // thread in which the surface capturer is initialized.
+ void StartCaptureOnUIThread();
+ void StopCaptureOnUIThread();
+
+ static void DestroyCapturerOnUIThread(scoped_ptr<SurfaceCapturer> capturer);
+
+ const base::ThreadChecker thread_checker_;
+
+ const base::WeakPtr<DesktopCaptureDeviceAura> weak_client_;
+ const scoped_refptr<base::MessageLoopProxy> client_loop_;
+ const gfx::Size requested_size_;
+ const int requested_frame_rate_;
+ base::RepeatingTimer<DesktopCaptureDeviceAura::Impl> timer_;
+
+ // This lock protects everything under it.
+ base::Lock lock_;
+ scoped_ptr<VideoCaptureOracle> oracle_;
+ scoped_ptr<SurfaceCapturer> capturer_;
+ media::VideoCaptureDevice::EventHandler* consumer_;
+ ui::Compositor* compositor_;
+ bool is_started_;
+
+ // Map from media::VideoFrame* to frame number.
+ typedef std::map<media::VideoFrame*, int> FrameNumberMap;
+ FrameNumberMap pending_frames_;
+};
+
+DesktopCaptureDeviceAura::Impl::Impl(
+ const base::WeakPtr<DesktopCaptureDeviceAura>& weak_client,
+ const scoped_refptr<base::MessageLoopProxy>& client_loop,
+ scoped_ptr<VideoCaptureOracle> oracle,
+ media::VideoCaptureDevice::EventHandler* consumer,
+ const gfx::Size& requested_size,
+ int requested_frame_rate)
+ : thread_checker_(),
+ weak_client_(weak_client),
+ client_loop_(client_loop),
+ requested_size_(requested_size),
+ requested_frame_rate_(requested_frame_rate),
+ oracle_(oracle.Pass()),
+ consumer_(consumer),
+ compositor_(NULL),
+ is_started_(false) {
+}
+
+DesktopCaptureDeviceAura::Impl::~Impl() {
+ DCHECK(pending_frames_.empty());
+}
+
+void DesktopCaptureDeviceAura::Impl::CreateCapturerOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ base::AutoLock guard(lock_);
+ DCHECK(!capturer_);
+
+ if (!oracle_ || !consumer_) {
+ // We were destroyed sometime in the interim.
+ return;
+ }
+
+ ui::CompositorRetrieverDelegate* delegate =
+ ui::CompositorRetrieverDelegate::GetInstance();
+ if (!delegate) {
+ NOTREACHED();
+ return;
+ }
+
+ // TODO(scottmg): Native is wrong http://crbug.com/133312.
+ // TODO(hshi): support multiple displays. The display identifier should be
+ // passed to DesktopCaptureDeviceAura::Create in the DesktopMediaID.
+ const gfx::Display& display =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+
+ ui::Compositor* compositor = delegate->GetCompositorForDisplay(display);
+ if (!compositor) {
+ ReportError();
+ return;
+ }
+
+ SurfaceCapturingContextFactory* factory =
+ ImageTransportFactory::GetInstance()->AsSurfaceCapturingContextFactory();
+ if (!factory) {
+ ReportError();
+ return;
+ }
+
+ scoped_ptr<SurfaceCapturer> capturer =
+ factory->CreateOutputSurfaceCapturer(compositor, this);
+ if (!capturer) {
+ ReportError();
+ return;
+ }
+
+ capturer->Initialize(media::VideoFrame::I420);
+ capturer_ = capturer.Pass();
+ compositor_ = compositor;
+}
+
+void DesktopCaptureDeviceAura::Impl::StartCaptureOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ const base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds(
+ 1000000.0 / requested_frame_rate_ + 0.5);
+ timer_.Start(
+ FROM_HERE, capture_period,
+ base::Bind(&DesktopCaptureDeviceAura::Impl::DoFrameCapture, this, false));
+
+ DCHECK(compositor_);
+ compositor_->AddObserver(this);
+}
+
+void DesktopCaptureDeviceAura::Impl::StopCaptureOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ timer_.Stop();
+
+ DCHECK(compositor_);
+ compositor_->RemoveObserver(this);
+}
+
+void DesktopCaptureDeviceAura::Impl::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::AutoLock guard(lock_);
+ is_started_ = true;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::Impl::StartCaptureOnUIThread, this));
+}
+
+void DesktopCaptureDeviceAura::Impl::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::AutoLock guard(lock_);
+ is_started_ = false;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::Impl::StopCaptureOnUIThread, this));
+}
+
+void DesktopCaptureDeviceAura::Impl::Destroy() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::AutoLock guard(lock_);
+ if (oracle_)
+ oracle_.reset();
+ if (capturer_) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::Impl::DestroyCapturerOnUIThread,
+ base::Passed(&capturer_)));
+ }
+ consumer_ = NULL;
+ is_started_ = false;
+}
+
+void DesktopCaptureDeviceAura::Impl::DoFrameCapture(bool is_compositor_update) {
+ base::AutoLock guard(lock_);
+ if (!oracle_ || !capturer_ || !consumer_)
+ return;
+
+ if (!is_started_)
+ return;
+
+ if (pending_frames_.size() >= kMaxPendingFrames)
+ return;
+
+ bool has_frame = true;
+ scoped_refptr<media::VideoFrame> frame = consumer_->ReserveOutputBuffer();
+ if (!frame) {
+ has_frame = false;
+ }
+
+ const base::Time now = base::Time::Now();
+ const VideoCaptureOracle::Event event =
+ is_compositor_update ? VideoCaptureOracle::kCompositorUpdate
+ : VideoCaptureOracle::kTimerPoll;
+ const bool should_capture = oracle_->ObserveEventAndDecideCapture(event, now);
+
+ // Consider the various reasons not to initiate a capture.
+ if (should_capture && !has_frame) {
+ TRACE_EVENT_INSTANT1("mirroring", "EncodeLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger", "gpu");
+ return;
+ } else if (!should_capture && has_frame) {
+ // This is a normal and acceptable way to drop a frame. We've hit our
+ // capture rate limit: for example, the content is animating at 60fps but
+ // we're capturing at 30fps.
+ TRACE_EVENT_INSTANT1("mirroring", "FpsRateLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger", "gpu");
+ return;
+ } else if (!should_capture && !has_frame) {
+ // We decided not to capture, but we wouldn't have been able to if we wanted
+ // to because no output buffer was available.
+ TRACE_EVENT_INSTANT1("mirroring", "NearlyEncodeLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger", "gpu");
+ return;
+ }
+
+ // Enqueue a video frame for capture output.
+ DCHECK(pending_frames_.find(frame.get()) == pending_frames_.end());
+ pending_frames_[frame.get()] = oracle_->RecordCapture();
+ capturer_->CopyCaptureToVideoFrame(frame);
+}
+
+void DesktopCaptureDeviceAura::Impl::ReportError() {
+ LOG(ERROR) << "DesktopCaptureDeviceAura::ReportError()";
+ client_loop_->PostTask(FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::ReportError,
+ weak_client_));
+}
+
+void DesktopCaptureDeviceAura::Impl::NotifyCaptureParameters(
+ const gfx::Size& buffer_size, const gfx::Rect& visible_rect) {
+ base::AutoLock guard(lock_);
+ if (!consumer_)
+ return;
+
+ // Initialize capture settings which will be consistent for the duration of
+ // the capture.
+ media::VideoCaptureCapability settings;
+
+ // We'll ignore requested_size_ and just use the size as reported by the
+ // SurfaceCapturer.
+ settings.width = buffer_size.width();
+ settings.height = buffer_size.height();
+ settings.frame_rate = requested_frame_rate_;
+ // Note: the value of |settings.color| doesn't matter if we use only the
+ // VideoFrame based methods on |consumer|.
+ settings.color = media::VideoCaptureCapability::kI420;
+ settings.expected_capture_delay = 0;
+ settings.interlaced = false;
+
+ consumer_->OnFrameInfo(settings);
+}
+
+void DesktopCaptureDeviceAura::Impl::NotifyCopyCaptureDone(
+ const scoped_refptr<media::VideoFrame>& frame) {
+ if (!oracle_ || !capturer_ || !consumer_)
+ return;
+
+ FrameNumberMap::const_iterator it = pending_frames_.find(frame.get());
+ if (it == pending_frames_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Notify oracle that capture is complete.
+ int frame_number = it->second;
+ const base::Time now = base::Time::Now();
+ if (oracle_->CompleteCapture(frame_number, now)) {
+ // Deliver the captured frame to the consumer.
+ consumer_->OnIncomingCapturedVideoFrame(frame, now);
+ }
+ pending_frames_.erase(frame.get());
+}
+
+void DesktopCaptureDeviceAura::Impl::NotifyError(
+ SurfaceCapturer::Error error) {
+ ReportError();
+}
+
+void DesktopCaptureDeviceAura::Impl::OnCompositingEnded(
+ ui::Compositor* compositor) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::Impl::DoFrameCapture, this, true));
+}
+
+// static
+void DesktopCaptureDeviceAura::Impl::DestroyCapturerOnUIThread(
+ scoped_ptr<SurfaceCapturer> capturer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ capturer.release()->Destroy();
+}
+
+DesktopCaptureDeviceAura::DesktopCaptureDeviceAura(const Name& device_name)
+ : thread_checker_(),
+ device_name_(device_name),
+ state_(kIdle),
+ weak_ptr_factory_(this) {
+}
+
+DesktopCaptureDeviceAura::~DesktopCaptureDeviceAura() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!impl_);
+}
+
+// static
+media::VideoCaptureDevice* DesktopCaptureDeviceAura::Create(
+ const DesktopMediaID& source) {
+ media::VideoCaptureDevice::Name name("AuraScreen", source.ToString());
+ return new DesktopCaptureDeviceAura(name);
+}
+
+void DesktopCaptureDeviceAura::Allocate(
+ const media::VideoCaptureCapability& capture_format,
+ VideoCaptureDevice::EventHandler* consumer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!impl_);
+
+ if (state_ != kIdle) {
+ DVLOG(1) << "Allocate() invoked when not in state idle.";
+ return;
+ }
+
+ // Frame dimensions must each be a positive, even integer, since the consumer
+ // wants (or will convert to) YUV420.
+ int width = MakeEven(capture_format.width);
+ int height = MakeEven(capture_format.height);
+ if (width < kMinFrameWidth || height < kMinFrameHeight) {
+ DVLOG(1) << "invalid width (" << width << ") and/or height ("
+ << height << ")";
+ consumer->OnError();
+ return;
+ }
+
+ base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds(
+ 1000000.0 / capture_format.frame_rate + 0.5);
+
+ scoped_ptr<VideoCaptureOracle> oracle(new VideoCaptureOracle(
+ capture_period, true));
+
+ impl_ = new Impl(weak_ptr_factory_.GetWeakPtr(),
+ base::MessageLoopProxy::current(),
+ oracle.Pass(),
+ consumer,
+ gfx::Size(width, height),
+ capture_format.frame_rate);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DesktopCaptureDeviceAura::Impl::CreateCapturerOnUIThread,
+ impl_));
+
+ state_ = kAllocated;
+}
+
+void DesktopCaptureDeviceAura::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != kAllocated)
+ return;
+
+ impl_->Start();
+ state_ = kCapturing;
+}
+
+void DesktopCaptureDeviceAura::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != kCapturing)
+ return;
+
+ impl_->Stop();
+ state_ = kAllocated;
+}
+
+void DesktopCaptureDeviceAura::DeAllocate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ Stop();
+
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ if (impl_) {
+ impl_->Destroy();
+ impl_ = NULL;
+ }
+ state_ = kIdle;
+}
+
+const media::VideoCaptureDevice::Name&
+DesktopCaptureDeviceAura::device_name() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return device_name_;
+}
+
+void DesktopCaptureDeviceAura::ReportError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ Stop();
+ state_ = kError;
+}
+
+} // namespace content
« no previous file with comments | « content/browser/renderer_host/media/desktop_capture_device_aura.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698