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 |