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
),
57 VideoCaptureImpl::~VideoCaptureImpl() {
58 DCHECK(io_message_loop_
->BelongsToCurrentThread());
61 void VideoCaptureImpl::Init() {
62 // For creating callbacks in unittest, this class may be constructed from a
63 // different thread than the IO thread, e.g. wherever unittest runs on.
64 // Therefore, this function should define the thread ownership.
66 io_message_loop_
= base::MessageLoopProxy::current();
68 message_filter_
->AddDelegate(this);
71 void VideoCaptureImpl::DeInit() {
72 DCHECK(io_message_loop_
->BelongsToCurrentThread());
73 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
74 Send(new VideoCaptureHostMsg_Stop(device_id_
));
75 message_filter_
->RemoveDelegate(this);
78 void VideoCaptureImpl::SuspendCapture(bool suspend
) {
79 DCHECK(io_message_loop_
->BelongsToCurrentThread());
81 static_cast<IPC::Message
*>(new VideoCaptureHostMsg_Pause(device_id_
)) :
82 static_cast<IPC::Message
*>(
83 new VideoCaptureHostMsg_Resume(device_id_
, session_id_
, params_
)));
86 void VideoCaptureImpl::StartCapture(
88 const media::VideoCaptureParams
& params
,
89 const VideoCaptureStateUpdateCB
& state_update_cb
,
90 const VideoCaptureDeliverFrameCB
& deliver_frame_cb
) {
91 DCHECK(io_message_loop_
->BelongsToCurrentThread());
92 ClientInfo client_info
;
93 client_info
.params
= params
;
94 client_info
.state_update_cb
= state_update_cb
;
95 client_info
.deliver_frame_cb
= deliver_frame_cb
;
97 if (state_
== VIDEO_CAPTURE_STATE_ERROR
) {
98 state_update_cb
.Run(VIDEO_CAPTURE_STATE_ERROR
);
99 } else if (clients_pending_on_filter_
.count(client_id
) ||
100 clients_pending_on_restart_
.count(client_id
) ||
101 clients_
.count(client_id
)) {
102 LOG(FATAL
) << "This client has already started.";
103 } else if (!device_id_
) {
104 clients_pending_on_filter_
[client_id
] = client_info
;
106 // Note: |state_| might not be started at this point. But we tell
107 // client that we have started.
108 state_update_cb
.Run(VIDEO_CAPTURE_STATE_STARTED
);
109 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
110 clients_
[client_id
] = client_info
;
111 // TODO(sheu): Allowing resolution change will require that all
112 // outstanding clients of a capture session support resolution change.
113 DCHECK_EQ(params_
.resolution_change_policy
,
114 params
.resolution_change_policy
);
115 } else if (state_
== VIDEO_CAPTURE_STATE_STOPPING
) {
116 clients_pending_on_restart_
[client_id
] = client_info
;
117 DVLOG(1) << "StartCapture: Got new resolution "
118 << params
.requested_format
.frame_size
.ToString()
119 << " during stopping.";
121 clients_
[client_id
] = client_info
;
122 if (state_
== VIDEO_CAPTURE_STATE_STARTED
)
125 if (params_
.requested_format
.frame_rate
>
126 media::limits::kMaxFramesPerSecond
) {
127 params_
.requested_format
.frame_rate
=
128 media::limits::kMaxFramesPerSecond
;
130 DVLOG(1) << "StartCapture: starting with first resolution "
131 << params_
.requested_format
.frame_size
.ToString();
132 first_frame_timestamp_
= base::TimeTicks();
133 StartCaptureInternal();
138 void VideoCaptureImpl::StopCapture(int client_id
) {
139 DCHECK(io_message_loop_
->BelongsToCurrentThread());
141 // A client ID can be in only one client list.
142 // If this ID is in any client list, we can just remove it from
143 // that client list and don't have to run the other following RemoveClient().
144 if (!RemoveClient(client_id
, &clients_pending_on_filter_
)) {
145 if (!RemoveClient(client_id
, &clients_pending_on_restart_
)) {
146 RemoveClient(client_id
, &clients_
);
150 if (clients_
.empty()) {
151 DVLOG(1) << "StopCapture: No more client, stopping ...";
153 client_buffers_
.clear();
154 weak_factory_
.InvalidateWeakPtrs();
158 void VideoCaptureImpl::GetDeviceSupportedFormats(
159 const VideoCaptureDeviceFormatsCB
& callback
) {
160 DCHECK(io_message_loop_
->BelongsToCurrentThread());
161 device_formats_cb_queue_
.push_back(callback
);
162 if (device_formats_cb_queue_
.size() == 1)
163 Send(new VideoCaptureHostMsg_GetDeviceSupportedFormats(device_id_
,
167 void VideoCaptureImpl::GetDeviceFormatsInUse(
168 const VideoCaptureDeviceFormatsCB
& callback
) {
169 DCHECK(io_message_loop_
->BelongsToCurrentThread());
170 device_formats_in_use_cb_queue_
.push_back(callback
);
171 if (device_formats_in_use_cb_queue_
.size() == 1)
173 new VideoCaptureHostMsg_GetDeviceFormatsInUse(device_id_
, session_id_
));
176 void VideoCaptureImpl::OnBufferCreated(
177 base::SharedMemoryHandle handle
,
178 int length
, int buffer_id
) {
179 DCHECK(io_message_loop_
->BelongsToCurrentThread());
181 // In case client calls StopCapture before the arrival of created buffer,
182 // just close this buffer and return.
183 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
) {
184 base::SharedMemory::CloseHandle(handle
);
188 scoped_ptr
<base::SharedMemory
> shm(new base::SharedMemory(handle
, false));
189 if (!shm
->Map(length
)) {
190 DLOG(ERROR
) << "OnBufferCreated: Map failed.";
195 client_buffers_
.insert(std::make_pair(
197 new ClientBuffer(shm
.Pass(),
202 void VideoCaptureImpl::OnBufferDestroyed(int buffer_id
) {
203 DCHECK(io_message_loop_
->BelongsToCurrentThread());
205 const ClientBufferMap::iterator iter
= client_buffers_
.find(buffer_id
);
206 if (iter
== client_buffers_
.end())
209 DCHECK(!iter
->second
.get() || iter
->second
->HasOneRef())
210 << "Instructed to delete buffer we are still using.";
211 client_buffers_
.erase(iter
);
214 void VideoCaptureImpl::OnBufferReceived(int buffer_id
,
215 const gfx::Size
& coded_size
,
216 const gfx::Rect
& visible_rect
,
217 base::TimeTicks timestamp
,
218 const base::DictionaryValue
& metadata
) {
219 DCHECK(io_message_loop_
->BelongsToCurrentThread());
221 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
|| suspended_
) {
222 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
, 0));
226 if (first_frame_timestamp_
.is_null())
227 first_frame_timestamp_
= timestamp
;
229 // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
230 TRACE_EVENT_INSTANT2(
231 "cast_perf_test", "OnBufferReceived",
232 TRACE_EVENT_SCOPE_THREAD
,
233 "timestamp", timestamp
.ToInternalValue(),
234 "time_delta", (timestamp
- first_frame_timestamp_
).ToInternalValue());
236 const ClientBufferMap::const_iterator iter
= client_buffers_
.find(buffer_id
);
237 DCHECK(iter
!= client_buffers_
.end());
238 scoped_refptr
<ClientBuffer
> buffer
= iter
->second
;
239 scoped_refptr
<media::VideoFrame
> frame
=
240 media::VideoFrame::WrapExternalPackedMemory(
241 media::VideoFrame::I420
,
244 gfx::Size(visible_rect
.width(), visible_rect
.height()),
245 reinterpret_cast<uint8
*>(buffer
->buffer
->memory()),
247 buffer
->buffer
->handle(),
249 timestamp
- first_frame_timestamp_
,
250 media::BindToCurrentLoop(
251 base::Bind(&VideoCaptureImpl::OnClientBufferFinished
,
252 weak_factory_
.GetWeakPtr(),
256 frame
->metadata()->MergeInternalValuesFrom(metadata
);
258 for (const auto& client
: clients_
)
259 client
.second
.deliver_frame_cb
.Run(frame
, timestamp
);
262 void VideoCaptureImpl::OnMailboxBufferReceived(
264 const gpu::MailboxHolder
& mailbox_holder
,
265 const gfx::Size
& packed_frame_size
,
266 base::TimeTicks timestamp
,
267 const base::DictionaryValue
& metadata
) {
268 DCHECK(io_message_loop_
->BelongsToCurrentThread());
270 if (state_
!= VIDEO_CAPTURE_STATE_STARTED
|| suspended_
) {
271 Send(new VideoCaptureHostMsg_BufferReady(device_id_
, buffer_id
, 0));
275 if (first_frame_timestamp_
.is_null())
276 first_frame_timestamp_
= timestamp
;
278 scoped_refptr
<media::VideoFrame
> frame
= media::VideoFrame::WrapNativeTexture(
279 make_scoped_ptr(new gpu::MailboxHolder(mailbox_holder
)),
280 media::BindToCurrentLoop(base::Bind(
281 &VideoCaptureImpl::OnClientBufferFinished
, weak_factory_
.GetWeakPtr(),
282 buffer_id
, scoped_refptr
<ClientBuffer
>())),
283 packed_frame_size
, gfx::Rect(packed_frame_size
), packed_frame_size
,
284 timestamp
- first_frame_timestamp_
, false);
285 frame
->metadata()->MergeInternalValuesFrom(metadata
);
287 for (const auto& client
: clients_
)
288 client
.second
.deliver_frame_cb
.Run(frame
, timestamp
);
291 void VideoCaptureImpl::OnClientBufferFinished(
293 const scoped_refptr
<ClientBuffer
>& /* ignored_buffer */,
294 uint32 release_sync_point
) {
295 DCHECK(io_message_loop_
->BelongsToCurrentThread());
296 Send(new VideoCaptureHostMsg_BufferReady(
297 device_id_
, buffer_id
, release_sync_point
));
300 void VideoCaptureImpl::OnStateChanged(VideoCaptureState state
) {
301 DCHECK(io_message_loop_
->BelongsToCurrentThread());
304 case VIDEO_CAPTURE_STATE_STARTED
:
305 // Camera has started in the browser process. Since we have already
306 // told all clients that we have started there's nothing to do.
308 case VIDEO_CAPTURE_STATE_STOPPED
:
309 state_
= VIDEO_CAPTURE_STATE_STOPPED
;
310 DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_
;
311 client_buffers_
.clear();
312 weak_factory_
.InvalidateWeakPtrs();
313 if (!clients_
.empty() || !clients_pending_on_restart_
.empty())
316 case VIDEO_CAPTURE_STATE_PAUSED
:
317 for (const auto& client
: clients_
)
318 client
.second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_PAUSED
);
320 case VIDEO_CAPTURE_STATE_ERROR
:
321 DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_
;
322 for (const auto& client
: clients_
)
323 client
.second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_ERROR
);
325 state_
= VIDEO_CAPTURE_STATE_ERROR
;
327 case VIDEO_CAPTURE_STATE_ENDED
:
328 DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_
;
329 for (const auto& client
: clients_
) {
330 // We'll only notify the client that the stream has stopped.
331 client
.second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
334 state_
= VIDEO_CAPTURE_STATE_ENDED
;
341 void VideoCaptureImpl::OnDeviceSupportedFormatsEnumerated(
342 const media::VideoCaptureFormats
& supported_formats
) {
343 DCHECK(io_message_loop_
->BelongsToCurrentThread());
344 for (size_t i
= 0; i
< device_formats_cb_queue_
.size(); ++i
)
345 device_formats_cb_queue_
[i
].Run(supported_formats
);
346 device_formats_cb_queue_
.clear();
349 void VideoCaptureImpl::OnDeviceFormatsInUseReceived(
350 const media::VideoCaptureFormats
& formats_in_use
) {
351 DCHECK(io_message_loop_
->BelongsToCurrentThread());
352 for (size_t i
= 0; i
< device_formats_in_use_cb_queue_
.size(); ++i
)
353 device_formats_in_use_cb_queue_
[i
].Run(formats_in_use
);
354 device_formats_in_use_cb_queue_
.clear();
357 void VideoCaptureImpl::OnDelegateAdded(int32 device_id
) {
358 DCHECK(io_message_loop_
->BelongsToCurrentThread());
359 DVLOG(1) << "OnDelegateAdded: device_id " << device_id
;
361 device_id_
= device_id
;
362 ClientInfoMap::iterator it
= clients_pending_on_filter_
.begin();
363 while (it
!= clients_pending_on_filter_
.end()) {
364 const int client_id
= it
->first
;
365 const ClientInfo client_info
= it
->second
;
366 clients_pending_on_filter_
.erase(it
++);
367 StartCapture(client_id
, client_info
.params
, client_info
.state_update_cb
,
368 client_info
.deliver_frame_cb
);
372 void VideoCaptureImpl::StopDevice() {
373 DCHECK(io_message_loop_
->BelongsToCurrentThread());
375 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
376 state_
= VIDEO_CAPTURE_STATE_STOPPING
;
377 Send(new VideoCaptureHostMsg_Stop(device_id_
));
378 params_
.requested_format
.frame_size
.SetSize(0, 0);
382 void VideoCaptureImpl::RestartCapture() {
383 DCHECK(io_message_loop_
->BelongsToCurrentThread());
384 DCHECK_EQ(state_
, VIDEO_CAPTURE_STATE_STOPPED
);
388 clients_
.insert(clients_pending_on_restart_
.begin(),
389 clients_pending_on_restart_
.end());
390 clients_pending_on_restart_
.clear();
391 for (const auto& client
: clients_
) {
392 width
= std::max(width
,
393 client
.second
.params
.requested_format
.frame_size
.width());
395 height
, client
.second
.params
.requested_format
.frame_size
.height());
397 params_
.requested_format
.frame_size
.SetSize(width
, height
);
398 DVLOG(1) << "RestartCapture, "
399 << params_
.requested_format
.frame_size
.ToString();
400 StartCaptureInternal();
403 void VideoCaptureImpl::StartCaptureInternal() {
404 DCHECK(io_message_loop_
->BelongsToCurrentThread());
407 Send(new VideoCaptureHostMsg_Start(device_id_
, session_id_
, params_
));
408 state_
= VIDEO_CAPTURE_STATE_STARTED
;
411 void VideoCaptureImpl::Send(IPC::Message
* message
) {
412 DCHECK(io_message_loop_
->BelongsToCurrentThread());
413 message_filter_
->Send(message
);
416 bool VideoCaptureImpl::RemoveClient(int client_id
, ClientInfoMap
* clients
) {
417 DCHECK(io_message_loop_
->BelongsToCurrentThread());
420 const ClientInfoMap::iterator it
= clients
->find(client_id
);
421 if (it
!= clients
->end()) {
422 it
->second
.state_update_cb
.Run(VIDEO_CAPTURE_STATE_STOPPED
);
429 } // namespace content