1 // Copyright (c) 2012 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 // Notes about usage of this object by VideoCaptureImplManager.
7 // VideoCaptureImplManager access this object by using a Unretained()
8 // binding and tasks on the IO thread. It is then important that
9 // VideoCaptureImpl never post task to itself. All operations must be
12 #include "content/renderer/media/video_capture_impl.h"
14 #include "base/bind.h"
15 #include "base/stl_util.h"
16 #include "content/child/child_process.h"
17 #include "content/common/media/video_capture_messages.h"
18 #include "media/base/bind_to_current_loop.h"
19 #include "media/base/limits.h"
20 #include "media/base/video_frame.h"
24 class VideoCaptureImpl::ClientBuffer
25 : public base::RefCountedThreadSafe
<ClientBuffer
> {
27 ClientBuffer(scoped_ptr
<base::SharedMemory
> buffer
,
29 : buffer(buffer
.Pass()),
30 buffer_size(buffer_size
) {}
31 const scoped_ptr
<base::SharedMemory
> buffer
;
32 const size_t buffer_size
;
35 friend class base::RefCountedThreadSafe
<ClientBuffer
>;
37 virtual ~ClientBuffer() {}
39 DISALLOW_COPY_AND_ASSIGN(ClientBuffer
);
42 VideoCaptureImpl::ClientInfo::ClientInfo() {}
43 VideoCaptureImpl::ClientInfo::~ClientInfo() {}
45 VideoCaptureImpl::VideoCaptureImpl(
46 const media::VideoCaptureSessionId session_id
,
47 VideoCaptureMessageFilter
* filter
)
48 : message_filter_(filter
),
50 session_id_(session_id
),
52 state_(VIDEO_CAPTURE_STATE_STOPPED
),
55 thread_checker_
.DetachFromThread();
58 VideoCaptureImpl::~VideoCaptureImpl() {
59 DCHECK(thread_checker_
.CalledOnValidThread());
62 void VideoCaptureImpl::Init() {
63 DCHECK(thread_checker_
.CalledOnValidThread());
64 message_filter_
->AddDelegate(this);
67 void VideoCaptureImpl::DeInit() {
68 DCHECK(thread_checker_
.CalledOnValidThread());
69 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
70 Send(new VideoCaptureHostMsg_Stop(device_id_
));
71 message_filter_
->RemoveDelegate(this);
74 void VideoCaptureImpl::SuspendCapture(bool suspend
) {
75 DCHECK(thread_checker_
.CalledOnValidThread());
77 static_cast<IPC::Message
*>(new VideoCaptureHostMsg_Pause(device_id_
)) :
78 static_cast<IPC::Message
*>(
79 new VideoCaptureHostMsg_Resume(device_id_
, session_id_
, params_
)));
82 void VideoCaptureImpl::StartCapture(
84 const media::VideoCaptureParams
& params
,
85 const VideoCaptureStateUpdateCB
& state_update_cb
,
86 const VideoCaptureDeliverFrameCB
& deliver_frame_cb
) {
87 DCHECK(thread_checker_
.CalledOnValidThread());
88 ClientInfo client_info
;
89 client_info
.params
= params
;
90 client_info
.state_update_cb
= state_update_cb
;
91 client_info
.deliver_frame_cb
= deliver_frame_cb
;
93 if (state_
== VIDEO_CAPTURE_STATE_ERROR
) {
94 state_update_cb
.Run(VIDEO_CAPTURE_STATE_ERROR
);
95 } else if (clients_pending_on_filter_
.count(client_id
) ||
96 clients_pending_on_restart_
.count(client_id
) ||
97 clients_
.count(client_id
)) {
98 LOG(FATAL
) << "This client has already started.";
99 } else if (!device_id_
) {
100 clients_pending_on_filter_
[client_id
] = client_info
;
102 // Note: |state_| might not be started at this point. But we tell
103 // client that we have started.
104 state_update_cb
.Run(VIDEO_CAPTURE_STATE_STARTED
);
105 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
106 clients_
[client_id
] = client_info
;
107 // TODO(sheu): Allowing resolution change will require that all
108 // outstanding clients of a capture session support resolution change.
109 DCHECK_EQ(params_
.resolution_change_policy
,
110 params
.resolution_change_policy
);
111 } else if (state_
== VIDEO_CAPTURE_STATE_STOPPING
) {
112 clients_pending_on_restart_
[client_id
] = client_info
;
113 DVLOG(1) << "StartCapture: Got new resolution "
114 << params
.requested_format
.frame_size
.ToString()
115 << " during stopping.";
117 clients_
[client_id
] = client_info
;
118 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
121 if (params_
.requested_format
.frame_rate
>
122 media::limits::kMaxFramesPerSecond
) {
123 params_
.requested_format
.frame_rate
=
124 media::limits::kMaxFramesPerSecond
;
126 DVLOG(1) << "StartCapture: starting with first resolution "
127 << params_
.requested_format
.frame_size
.ToString();
128 first_frame_timestamp_
= base::TimeTicks();
129 StartCaptureInternal();
134 void VideoCaptureImpl::StopCapture(int client_id
) {
135 DCHECK(thread_checker_
.CalledOnValidThread());
137 // A client ID can be in only one client list.
138 // If this ID is in any client list, we can just remove it from
139 // that client list and don't have to run the other following RemoveClient().
140 if (!RemoveClient(client_id
, &clients_pending_on_filter_
)) {
141 if (!RemoveClient(client_id
, &clients_pending_on_restart_
)) {
142 RemoveClient(client_id
, &clients_
);
146 if (clients_
.empty()) {
147 DVLOG(1) << "StopCapture: No more client, stopping ...";
149 client_buffers_
.clear();
150 weak_factory_
.InvalidateWeakPtrs();
154 void VideoCaptureImpl::GetDeviceSupportedFormats(
155 const VideoCaptureDeviceFormatsCB
& callback
) {
156 DCHECK(thread_checker_
.CalledOnValidThread());
157 device_formats_cb_queue_
.push_back(callback
);
158 if (device_formats_cb_queue_
.size() == 1)
159 Send(new VideoCaptureHostMsg_GetDeviceSupportedFormats(device_id_
,
163 void VideoCaptureImpl::GetDeviceFormatsInUse(
164 const VideoCaptureDeviceFormatsCB
& callback
) {
165 DCHECK(thread_checker_
.CalledOnValidThread());
166 device_formats_in_use_cb_queue_
.push_back(callback
);
167 if (device_formats_in_use_cb_queue_
.size() == 1)
169 new VideoCaptureHostMsg_GetDeviceFormatsInUse(device_id_
, session_id_
));
172 void VideoCaptureImpl::OnBufferCreated(
173 base::SharedMemoryHandle handle
,
174 int length
, int buffer_id
) {
175 DCHECK(thread_checker_
.CalledOnValidThread());
177 // In case client calls StopCapture before the arrival of created buffer,
178 // just close this buffer and return.
179 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
) {
180 base::SharedMemory::CloseHandle(handle
);
184 scoped_ptr
<base::SharedMemory
> shm(new base::SharedMemory(handle
, false));
185 if (!shm
->Map(length
)) {
186 DLOG(ERROR
) << "OnBufferCreated: Map failed.";
191 client_buffers_
.insert(std::make_pair(
193 new ClientBuffer(shm
.Pass(),
198 void VideoCaptureImpl::OnBufferDestroyed(int buffer_id
) {
199 DCHECK(thread_checker_
.CalledOnValidThread());
201 ClientBufferMap::iterator iter
= client_buffers_
.find(buffer_id
);
202 if (iter
== client_buffers_
.end())
205 DCHECK(!iter
->second
.get() || iter
->second
->HasOneRef())
206 << "Instructed to delete buffer we are still using.";
207 client_buffers_
.erase(iter
);
210 void VideoCaptureImpl::OnBufferReceived(int buffer_id
,
211 const media::VideoCaptureFormat
& format
,
212 const gfx::Rect
& visible_rect
,
213 base::TimeTicks timestamp
) {
214 DCHECK(thread_checker_
.CalledOnValidThread());
216 // The capture pipeline supports only I420 for now.
217 DCHECK_EQ(format
.pixel_format
, media::PIXEL_FORMAT_I420
);
219 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
|| suspended_
) {
220 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
, 0));
224 last_frame_format_
= format
;
225 if (first_frame_timestamp_
.is_null())
226 first_frame_timestamp_
= timestamp
;
228 // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
229 TRACE_EVENT_INSTANT2(
230 "cast_perf_test", "OnBufferReceived",
231 TRACE_EVENT_SCOPE_THREAD
,
232 "timestamp", timestamp
.ToInternalValue(),
233 "time_delta", (timestamp
- first_frame_timestamp_
).ToInternalValue());
235 ClientBufferMap::iterator iter
= client_buffers_
.find(buffer_id
);
236 DCHECK(iter
!= client_buffers_
.end());
237 scoped_refptr
<ClientBuffer
> buffer
= iter
->second
;
238 scoped_refptr
<media::VideoFrame
> frame
=
239 media::VideoFrame::WrapExternalPackedMemory(
240 media::VideoFrame::I420
,
241 last_frame_format_
.frame_size
,
243 gfx::Size(visible_rect
.width(), visible_rect
.height()),
244 reinterpret_cast<uint8
*>(buffer
->buffer
->memory()),
246 buffer
->buffer
->handle(),
247 timestamp
- first_frame_timestamp_
,
248 media::BindToCurrentLoop(
249 base::Bind(&VideoCaptureImpl::OnClientBufferFinished
,
250 weak_factory_
.GetWeakPtr(),
255 for (ClientInfoMap::iterator it
= clients_
.begin(); it
!= clients_
.end();
257 it
->second
.deliver_frame_cb
.Run(frame
, format
, timestamp
);
261 static void NullReadPixelsCB(const SkBitmap
& bitmap
) { NOTIMPLEMENTED(); }
263 void VideoCaptureImpl::OnMailboxBufferReceived(
265 const gpu::MailboxHolder
& mailbox_holder
,
266 const media::VideoCaptureFormat
& format
,
267 base::TimeTicks timestamp
) {
268 DCHECK(thread_checker_
.CalledOnValidThread());
270 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
|| suspended_
) {
271 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
, 0));
275 last_frame_format_
= format
;
276 if (first_frame_timestamp_
.is_null())
277 first_frame_timestamp_
= timestamp
;
279 scoped_refptr
<media::VideoFrame
> frame
= media::VideoFrame::WrapNativeTexture(
280 make_scoped_ptr(new gpu::MailboxHolder(mailbox_holder
)),
281 media::BindToCurrentLoop(
282 base::Bind(&VideoCaptureImpl::OnClientBufferFinished
,
283 weak_factory_
.GetWeakPtr(),
285 scoped_refptr
<ClientBuffer
>())),
286 last_frame_format_
.frame_size
,
287 gfx::Rect(last_frame_format_
.frame_size
),
288 last_frame_format_
.frame_size
,
289 timestamp
- first_frame_timestamp_
,
290 base::Bind(&NullReadPixelsCB
));
292 for (ClientInfoMap::iterator it
= clients_
.begin(); it
!= clients_
.end();
294 it
->second
.deliver_frame_cb
.Run(frame
, format
, timestamp
);
298 void VideoCaptureImpl::OnClientBufferFinished(
300 const scoped_refptr
<ClientBuffer
>& /* ignored_buffer */,
301 uint32 release_sync_point
) {
302 DCHECK(thread_checker_
.CalledOnValidThread());
303 Send(new VideoCaptureHostMsg_BufferReady(
304 device_id_
, buffer_id
, release_sync_point
));
307 void VideoCaptureImpl::OnStateChanged(VideoCaptureState state
) {
308 DCHECK(thread_checker_
.CalledOnValidThread());
311 case VIDEO_CAPTURE_STATE_STARTED
:
312 // Camera has started in the browser process. Since we have already
313 // told all clients that we have started there's nothing to do.
315 case VIDEO_CAPTURE_STATE_STOPPED
:
316 state_
= VIDEO_CAPTURE_STATE_STOPPED
;
317 DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_
;
318 client_buffers_
.clear();
319 weak_factory_
.InvalidateWeakPtrs();
320 if (!clients_
.empty() || !clients_pending_on_restart_
.empty())
323 case VIDEO_CAPTURE_STATE_PAUSED
:
324 for (ClientInfoMap::iterator it
= clients_
.begin();
325 it
!= clients_
.end(); ++it
) {
326 it
->second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_PAUSED
);
329 case VIDEO_CAPTURE_STATE_ERROR
:
330 DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_
;
331 for (ClientInfoMap::iterator it
= clients_
.begin();
332 it
!= clients_
.end(); ++it
) {
333 it
->second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_ERROR
);
336 state_
= VIDEO_CAPTURE_STATE_ERROR
;
338 case VIDEO_CAPTURE_STATE_ENDED
:
339 DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_
;
340 for (ClientInfoMap::iterator it
= clients_
.begin();
341 it
!= clients_
.end(); ++it
) {
342 // We'll only notify the client that the stream has stopped.
343 it
->second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
346 state_
= VIDEO_CAPTURE_STATE_ENDED
;
353 void VideoCaptureImpl::OnDeviceSupportedFormatsEnumerated(
354 const media::VideoCaptureFormats
& supported_formats
) {
355 DCHECK(thread_checker_
.CalledOnValidThread());
356 for (size_t i
= 0; i
< device_formats_cb_queue_
.size(); ++i
)
357 device_formats_cb_queue_
[i
].Run(supported_formats
);
358 device_formats_cb_queue_
.clear();
361 void VideoCaptureImpl::OnDeviceFormatsInUseReceived(
362 const media::VideoCaptureFormats
& formats_in_use
) {
363 DCHECK(thread_checker_
.CalledOnValidThread());
364 for (size_t i
= 0; i
< device_formats_in_use_cb_queue_
.size(); ++i
)
365 device_formats_in_use_cb_queue_
[i
].Run(formats_in_use
);
366 device_formats_in_use_cb_queue_
.clear();
369 void VideoCaptureImpl::OnDelegateAdded(int32 device_id
) {
370 DCHECK(thread_checker_
.CalledOnValidThread());
371 DVLOG(1) << "OnDelegateAdded: device_id " << device_id
;
373 device_id_
= device_id
;
374 for (ClientInfoMap::iterator it
= clients_pending_on_filter_
.begin();
375 it
!= clients_pending_on_filter_
.end(); ) {
376 int client_id
= it
->first
;
377 VideoCaptureStateUpdateCB state_update_cb
=
378 it
->second
.state_update_cb
;
379 VideoCaptureDeliverFrameCB deliver_frame_cb
=
380 it
->second
.deliver_frame_cb
;
381 const media::VideoCaptureParams params
= it
->second
.params
;
382 clients_pending_on_filter_
.erase(it
++);
383 StartCapture(client_id
, params
, state_update_cb
,
388 void VideoCaptureImpl::StopDevice() {
389 DCHECK(thread_checker_
.CalledOnValidThread());
391 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
392 state_
= VIDEO_CAPTURE_STATE_STOPPING
;
393 Send(new VideoCaptureHostMsg_Stop(device_id_
));
394 params_
.requested_format
.frame_size
.SetSize(0, 0);
398 void VideoCaptureImpl::RestartCapture() {
399 DCHECK(thread_checker_
.CalledOnValidThread());
400 DCHECK_EQ(state_
, VIDEO_CAPTURE_STATE_STOPPED
);
404 clients_
.insert(clients_pending_on_restart_
.begin(),
405 clients_pending_on_restart_
.end());
406 clients_pending_on_restart_
.clear();
407 for (ClientInfoMap::iterator it
= clients_
.begin();
408 it
!= clients_
.end(); ++it
) {
409 width
= std::max(width
,
410 it
->second
.params
.requested_format
.frame_size
.width());
411 height
= std::max(height
,
412 it
->second
.params
.requested_format
.frame_size
.height());
414 params_
.requested_format
.frame_size
.SetSize(width
, height
);
415 DVLOG(1) << "RestartCapture, "
416 << params_
.requested_format
.frame_size
.ToString();
417 StartCaptureInternal();
420 void VideoCaptureImpl::StartCaptureInternal() {
421 DCHECK(thread_checker_
.CalledOnValidThread());
424 Send(new VideoCaptureHostMsg_Start(device_id_
, session_id_
, params_
));
425 state_
= VIDEO_CAPTURE_STATE_STARTED
;
428 void VideoCaptureImpl::Send(IPC::Message
* message
) {
429 DCHECK(thread_checker_
.CalledOnValidThread());
430 message_filter_
->Send(message
);
433 bool VideoCaptureImpl::RemoveClient(int client_id
, ClientInfoMap
* clients
) {
434 DCHECK(thread_checker_
.CalledOnValidThread());
437 ClientInfoMap::iterator it
= clients
->find(client_id
);
438 if (it
!= clients
->end()) {
439 it
->second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
446 } // namespace content