Index: content/browser/renderer_host/media/video_capture_device_impl.cc |
diff --git a/content/browser/renderer_host/media/video_capture_device_impl.cc b/content/browser/renderer_host/media/video_capture_device_impl.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..54af30f71b461e6ad74bfa760c18c8dcc832cb45 |
--- /dev/null |
+++ b/content/browser/renderer_host/media/video_capture_device_impl.cc |
@@ -0,0 +1,280 @@ |
+// Copyright 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/video_capture_device_impl.h" |
+ |
+#include "base/basictypes.h" |
+#include "base/bind.h" |
+#include "base/callback_forward.h" |
+#include "base/callback_helpers.h" |
+#include "base/debug/trace_event.h" |
+#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/message_loop/message_loop_proxy.h" |
+#include "base/metrics/histogram.h" |
+#include "base/sequenced_task_runner.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/synchronization/lock.h" |
+#include "base/threading/thread.h" |
+#include "base/threading/thread_checker.h" |
+#include "base/time/time.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "media/base/bind_to_loop.h" |
+#include "media/base/video_frame.h" |
+#include "media/video/capture/video_capture_types.h" |
+#include "ui/gfx/rect.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+void DeleteCaptureMachineOnUIThread( |
+ scoped_ptr<VideoCaptureMachine> capture_machine) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (capture_machine) { |
+ capture_machine->Stop(); |
+ capture_machine.reset(); |
+ } |
+} |
+ |
+} // namespace |
+ |
+ThreadSafeCaptureOracle::ThreadSafeCaptureOracle( |
+ scoped_ptr<media::VideoCaptureDevice::Client> client, |
+ scoped_ptr<VideoCaptureOracle> oracle, |
+ const gfx::Size& capture_size, |
+ int frame_rate) |
+ : client_(client.Pass()), |
+ oracle_(oracle.Pass()), |
+ capture_size_(capture_size), |
+ frame_rate_(frame_rate) {} |
+ |
+ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {} |
+ |
+bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture( |
+ VideoCaptureOracle::Event event, |
+ base::Time event_time, |
+ scoped_refptr<media::VideoFrame>* storage, |
+ CaptureFrameCallback* callback) { |
+ base::AutoLock guard(lock_); |
+ |
+ if (!client_) |
+ return false; // Capture is stopped. |
+ |
+ scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer = |
+ client_->ReserveOutputBuffer(media::VideoFrame::I420, capture_size_); |
+ const bool should_capture = |
+ oracle_->ObserveEventAndDecideCapture(event, event_time); |
+ const bool content_is_dirty = |
+ (event == VideoCaptureOracle::kCompositorUpdate || |
+ event == VideoCaptureOracle::kSoftwarePaint); |
+ const char* event_name = |
+ (event == VideoCaptureOracle::kTimerPoll ? "poll" : |
+ (event == VideoCaptureOracle::kCompositorUpdate ? "gpu" : |
+ "paint")); |
+ |
+ // Consider the various reasons not to initiate a capture. |
+ if (should_capture && !output_buffer) { |
+ TRACE_EVENT_INSTANT1("mirroring", |
+ "EncodeLimited", |
+ TRACE_EVENT_SCOPE_THREAD, |
+ "trigger", |
+ event_name); |
+ return false; |
+ } else if (!should_capture && output_buffer) { |
+ if (content_is_dirty) { |
+ // 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", event_name); |
+ } |
+ return false; |
+ } else if (!should_capture && !output_buffer) { |
+ // 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", event_name); |
+ return false; |
+ } |
+ int frame_number = oracle_->RecordCapture(); |
+ TRACE_EVENT_ASYNC_BEGIN2("mirroring", "Capture", output_buffer.get(), |
+ "frame_number", frame_number, |
+ "trigger", event_name); |
+ *callback = base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame, |
+ this, |
+ output_buffer, |
+ frame_number); |
+ *storage = media::VideoFrame::WrapExternalPackedMemory( |
+ media::VideoFrame::I420, |
+ capture_size_, |
+ gfx::Rect(capture_size_), |
+ capture_size_, |
+ static_cast<uint8*>(output_buffer->data()), |
+ output_buffer->size(), |
+ base::SharedMemory::NULLHandle(), |
+ base::TimeDelta(), |
+ base::Closure()); |
+ return true; |
+} |
+ |
+void ThreadSafeCaptureOracle::Stop() { |
+ base::AutoLock guard(lock_); |
+ client_.reset(); |
+} |
+ |
+void ThreadSafeCaptureOracle::ReportError() { |
+ base::AutoLock guard(lock_); |
+ if (client_) |
+ client_->OnError(); |
+} |
+ |
+void ThreadSafeCaptureOracle::DidCaptureFrame( |
+ scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer, |
+ int frame_number, |
+ base::Time timestamp, |
+ bool success) { |
+ base::AutoLock guard(lock_); |
+ TRACE_EVENT_ASYNC_END2("mirroring", "Capture", buffer.get(), |
+ "success", success, |
+ "timestamp", timestamp.ToInternalValue()); |
+ |
+ if (!client_) |
+ return; // Capture is stopped. |
+ |
+ if (success) { |
+ if (oracle_->CompleteCapture(frame_number, timestamp)) { |
+ client_->OnIncomingCapturedBuffer(buffer, |
+ media::VideoFrame::I420, |
+ capture_size_, |
+ timestamp, |
+ frame_rate_); |
+ } |
+ } |
+} |
+ |
+void VideoCaptureDeviceImpl::AllocateAndStart( |
+ const media::VideoCaptureParams& params, |
+ scoped_ptr<media::VideoCaptureDevice::Client> client) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ if (state_ != kIdle) { |
+ DVLOG(1) << "Allocate() invoked when not in state Idle."; |
+ return; |
+ } |
+ |
+ if (params.requested_format.frame_rate <= 0) { |
+ DVLOG(1) << "invalid frame_rate: " << params.requested_format.frame_rate; |
+ client->OnError(); |
+ return; |
+ } |
+ |
+ // Frame dimensions must each be a positive, even integer, since the client |
+ // wants (or will convert to) YUV420. |
+ gfx::Size frame_size(MakeEven(params.requested_format.frame_size.width()), |
+ MakeEven(params.requested_format.frame_size.height())); |
+ if (frame_size.width() < kMinFrameWidth || |
+ frame_size.height() < kMinFrameHeight) { |
+ DVLOG(1) << "invalid frame size: " << frame_size.ToString(); |
+ client->OnError(); |
+ return; |
+ } |
+ |
+ base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds( |
+ 1000000.0 / params.requested_format.frame_rate + 0.5); |
+ |
+ scoped_ptr<VideoCaptureOracle> oracle( |
+ new VideoCaptureOracle(capture_period, |
+ kAcceleratedSubscriberIsSupported)); |
+ oracle_proxy_ = |
+ new ThreadSafeCaptureOracle(client.Pass(), |
+ oracle.Pass(), |
+ frame_size, |
+ params.requested_format.frame_rate); |
+ |
+ // Starts the capture machine asynchronously. |
+ BrowserThread::PostTaskAndReplyWithResult( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&VideoCaptureMachine::Start, |
+ base::Unretained(capture_machine_.get()), |
+ oracle_proxy_), |
+ base::Bind(&VideoCaptureDeviceImpl::CaptureStarted, |
+ AsWeakPtr())); |
+ |
+ TransitionStateTo(kCapturing); |
+} |
+ |
+void VideoCaptureDeviceImpl::StopAndDeAllocate() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ if (state_ != kCapturing) |
+ return; |
+ |
+ oracle_proxy_->Stop(); |
+ oracle_proxy_ = NULL; |
+ |
+ TransitionStateTo(kIdle); |
+ |
+ // Stops the capture machine asynchronously. |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, base::Bind( |
+ &VideoCaptureMachine::Stop, |
+ base::Unretained(capture_machine_.get()))); |
+} |
+ |
+void VideoCaptureDeviceImpl::CaptureStarted(bool success) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (!success) { |
+ DVLOG(1) << "Failed to start capture machine."; |
+ Error(); |
+ } |
+} |
+ |
+VideoCaptureDeviceImpl::VideoCaptureDeviceImpl( |
+ scoped_ptr<VideoCaptureMachine> capture_machine) |
+ : state_(kIdle), |
+ capture_machine_(capture_machine.Pass()) {} |
+ |
+VideoCaptureDeviceImpl::~VideoCaptureDeviceImpl() { |
+ // If capture_machine is not NULL, then we need to return to the UI thread to |
+ // safely stop the capture machine. |
+ if (capture_machine_) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, base::Bind( |
+ &DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine_))); |
+ } |
+ DVLOG(1) << "VideoCaptureDeviceImpl@" << this << " destroying."; |
+} |
+ |
+void VideoCaptureDeviceImpl::TransitionStateTo(State next_state) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+#ifndef NDEBUG |
+ static const char* kStateNames[] = { |
+ "Idle", "Allocated", "Capturing", "Error" |
+ }; |
+ DVLOG(1) << "State change: " << kStateNames[state_] |
+ << " --> " << kStateNames[next_state]; |
+#endif |
+ |
+ state_ = next_state; |
+} |
+ |
+void VideoCaptureDeviceImpl::Error() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ if (state_ == kIdle) |
+ return; |
+ |
+ if (oracle_proxy_) |
+ oracle_proxy_->ReportError(); |
+ |
+ StopAndDeAllocate(); |
+ TransitionStateTo(kError); |
+} |
+ |
+} // namespace content |