1 // Copyright 2013 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.
5 #include "content/browser/renderer_host/media/video_capture_device_impl.h"
7 #include "base/basictypes.h"
9 #include "base/callback_forward.h"
10 #include "base/callback_helpers.h"
11 #include "base/debug/trace_event.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/message_loop/message_loop_proxy.h"
16 #include "base/metrics/histogram.h"
17 #include "base/sequenced_task_runner.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/synchronization/lock.h"
20 #include "base/threading/thread.h"
21 #include "base/threading/thread_checker.h"
22 #include "base/time/time.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "media/base/bind_to_loop.h"
25 #include "media/base/video_frame.h"
26 #include "media/base/video_util.h"
27 #include "media/video/capture/video_capture_types.h"
28 #include "ui/gfx/rect.h"
34 void DeleteCaptureMachineOnUIThread(
35 scoped_ptr
<VideoCaptureMachine
> capture_machine
) {
36 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
37 if (capture_machine
) {
38 capture_machine
->Stop();
39 capture_machine
.reset();
45 ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
46 scoped_ptr
<media::VideoCaptureDevice::Client
> client
,
47 scoped_ptr
<VideoCaptureOracle
> oracle
,
48 const media::VideoCaptureParams
& params
)
49 : client_(client
.Pass()),
50 oracle_(oracle
.Pass()),
52 capture_size_updated_(false) {
53 // Frame dimensions must each be an even integer since the client wants (or
54 // will convert to) YUV420.
55 capture_size_
= gfx::Size(
56 MakeEven(params
.requested_format
.frame_size
.width()),
57 MakeEven(params
.requested_format
.frame_size
.height()));
58 frame_rate_
= params
.requested_format
.frame_rate
;
61 ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {}
63 bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
64 VideoCaptureOracle::Event event
,
65 base::Time event_time
,
66 scoped_refptr
<media::VideoFrame
>* storage
,
67 CaptureFrameCallback
* callback
) {
68 base::AutoLock
guard(lock_
);
71 return false; // Capture is stopped.
73 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
> output_buffer
=
74 client_
->ReserveOutputBuffer(media::VideoFrame::I420
, capture_size_
);
75 const bool should_capture
=
76 oracle_
->ObserveEventAndDecideCapture(event
, event_time
);
77 const bool content_is_dirty
=
78 (event
== VideoCaptureOracle::kCompositorUpdate
||
79 event
== VideoCaptureOracle::kSoftwarePaint
);
80 const char* event_name
=
81 (event
== VideoCaptureOracle::kTimerPoll
? "poll" :
82 (event
== VideoCaptureOracle::kCompositorUpdate
? "gpu" :
85 // Consider the various reasons not to initiate a capture.
86 if (should_capture
&& !output_buffer
) {
87 TRACE_EVENT_INSTANT1("mirroring",
89 TRACE_EVENT_SCOPE_THREAD
,
93 } else if (!should_capture
&& output_buffer
) {
94 if (content_is_dirty
) {
95 // This is a normal and acceptable way to drop a frame. We've hit our
96 // capture rate limit: for example, the content is animating at 60fps but
97 // we're capturing at 30fps.
98 TRACE_EVENT_INSTANT1("mirroring", "FpsRateLimited",
99 TRACE_EVENT_SCOPE_THREAD
,
100 "trigger", event_name
);
103 } else if (!should_capture
&& !output_buffer
) {
104 // We decided not to capture, but we wouldn't have been able to if we wanted
105 // to because no output buffer was available.
106 TRACE_EVENT_INSTANT1("mirroring", "NearlyEncodeLimited",
107 TRACE_EVENT_SCOPE_THREAD
,
108 "trigger", event_name
);
111 int frame_number
= oracle_
->RecordCapture();
112 TRACE_EVENT_ASYNC_BEGIN2("mirroring", "Capture", output_buffer
.get(),
113 "frame_number", frame_number
,
114 "trigger", event_name
);
115 *callback
= base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame
,
119 *storage
= media::VideoFrame::WrapExternalPackedMemory(
120 media::VideoFrame::I420
,
122 gfx::Rect(capture_size_
),
124 static_cast<uint8
*>(output_buffer
->data()),
125 output_buffer
->size(),
126 base::SharedMemory::NULLHandle(),
132 void ThreadSafeCaptureOracle::UpdateCaptureSize(const gfx::Size
& source_size
) {
133 base::AutoLock
guard(lock_
);
135 // If this is the first call to UpdateCaptureSize(), or the receiver supports
136 // variable resolution, then determine the capture size by treating the
137 // requested width and height as maxima.
138 if (!capture_size_updated_
|| params_
.allow_resolution_change
) {
139 // The capture resolution should not exceed the source frame size.
140 // In other words it should downscale the image but not upscale it.
141 if (source_size
.width() > params_
.requested_format
.frame_size
.width() ||
142 source_size
.height() > params_
.requested_format
.frame_size
.height()) {
143 gfx::Rect capture_rect
= media::ComputeLetterboxRegion(
144 gfx::Rect(params_
.requested_format
.frame_size
), source_size
);
145 capture_size_
= gfx::Size(MakeEven(capture_rect
.width()),
146 MakeEven(capture_rect
.height()));
148 capture_size_
= gfx::Size(MakeEven(source_size
.width()),
149 MakeEven(source_size
.height()));
151 capture_size_updated_
= true;
155 void ThreadSafeCaptureOracle::Stop() {
156 base::AutoLock
guard(lock_
);
160 void ThreadSafeCaptureOracle::ReportError() {
161 base::AutoLock
guard(lock_
);
166 void ThreadSafeCaptureOracle::DidCaptureFrame(
167 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
> buffer
,
169 base::Time timestamp
,
171 base::AutoLock
guard(lock_
);
172 TRACE_EVENT_ASYNC_END2("mirroring", "Capture", buffer
.get(),
174 "timestamp", timestamp
.ToInternalValue());
177 return; // Capture is stopped.
180 if (oracle_
->CompleteCapture(frame_number
, timestamp
)) {
181 client_
->OnIncomingCapturedBuffer(buffer
,
182 media::VideoFrame::I420
,
190 void VideoCaptureDeviceImpl::AllocateAndStart(
191 const media::VideoCaptureParams
& params
,
192 scoped_ptr
<media::VideoCaptureDevice::Client
> client
) {
193 DCHECK(thread_checker_
.CalledOnValidThread());
195 if (state_
!= kIdle
) {
196 DVLOG(1) << "Allocate() invoked when not in state Idle.";
200 if (params
.requested_format
.frame_rate
<= 0) {
201 DVLOG(1) << "invalid frame_rate: " << params
.requested_format
.frame_rate
;
206 if (params
.requested_format
.frame_size
.width() < kMinFrameWidth
||
207 params
.requested_format
.frame_size
.height() < kMinFrameHeight
) {
208 DVLOG(1) << "invalid frame size: "
209 << params
.requested_format
.frame_size
.ToString();
214 base::TimeDelta capture_period
= base::TimeDelta::FromMicroseconds(
215 1000000.0 / params
.requested_format
.frame_rate
+ 0.5);
217 scoped_ptr
<VideoCaptureOracle
> oracle(
218 new VideoCaptureOracle(capture_period
,
219 kAcceleratedSubscriberIsSupported
));
221 new ThreadSafeCaptureOracle(client
.Pass(), oracle
.Pass(), params
);
223 // Starts the capture machine asynchronously.
224 BrowserThread::PostTaskAndReplyWithResult(
225 BrowserThread::UI
, FROM_HERE
,
226 base::Bind(&VideoCaptureMachine::Start
,
227 base::Unretained(capture_machine_
.get()),
229 base::Bind(&VideoCaptureDeviceImpl::CaptureStarted
,
232 TransitionStateTo(kCapturing
);
235 void VideoCaptureDeviceImpl::StopAndDeAllocate() {
236 DCHECK(thread_checker_
.CalledOnValidThread());
238 if (state_
!= kCapturing
)
241 oracle_proxy_
->Stop();
242 oracle_proxy_
= NULL
;
244 TransitionStateTo(kIdle
);
246 // Stops the capture machine asynchronously.
247 BrowserThread::PostTask(
248 BrowserThread::UI
, FROM_HERE
, base::Bind(
249 &VideoCaptureMachine::Stop
,
250 base::Unretained(capture_machine_
.get())));
253 void VideoCaptureDeviceImpl::CaptureStarted(bool success
) {
254 DCHECK(thread_checker_
.CalledOnValidThread());
256 DVLOG(1) << "Failed to start capture machine.";
261 VideoCaptureDeviceImpl::VideoCaptureDeviceImpl(
262 scoped_ptr
<VideoCaptureMachine
> capture_machine
)
264 capture_machine_(capture_machine
.Pass()) {}
266 VideoCaptureDeviceImpl::~VideoCaptureDeviceImpl() {
267 // If capture_machine is not NULL, then we need to return to the UI thread to
268 // safely stop the capture machine.
269 if (capture_machine_
) {
270 BrowserThread::PostTask(
271 BrowserThread::UI
, FROM_HERE
, base::Bind(
272 &DeleteCaptureMachineOnUIThread
, base::Passed(&capture_machine_
)));
274 DVLOG(1) << "VideoCaptureDeviceImpl@" << this << " destroying.";
277 void VideoCaptureDeviceImpl::TransitionStateTo(State next_state
) {
278 DCHECK(thread_checker_
.CalledOnValidThread());
281 static const char* kStateNames
[] = {
282 "Idle", "Allocated", "Capturing", "Error"
284 DVLOG(1) << "State change: " << kStateNames
[state_
]
285 << " --> " << kStateNames
[next_state
];
291 void VideoCaptureDeviceImpl::Error() {
292 DCHECK(thread_checker_
.CalledOnValidThread());
298 oracle_proxy_
->ReportError();
301 TransitionStateTo(kError
);
304 } // namespace content