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.
4
5 #include "content/renderer/media/video_capture_impl.h"
6
7 #include "base/bind.h"
8 #include "base/stl_util.h"
9 #include "content/child/child_process.h"
10 #include "content/common/media/video_capture_messages.h"
11 #include "media/base/bind_to_loop.h"
12 #include "media/base/limits.h"
13 #include "media/base/video_frame.h"
14
15 namespace content {
16
17 class VideoCaptureImpl::ClientBuffer
18 : public base::RefCountedThreadSafe<ClientBuffer> {
19 public:
ClientBuffer(scoped_ptr<base::SharedMemory> buffer,size_t buffer_size)20 ClientBuffer(scoped_ptr<base::SharedMemory> buffer,
21 size_t buffer_size)
22 : buffer(buffer.Pass()),
23 buffer_size(buffer_size) {}
24 const scoped_ptr<base::SharedMemory> buffer;
25 const size_t buffer_size;
26
27 private:
28 friend class base::RefCountedThreadSafe<ClientBuffer>;
29
~ClientBuffer()30 virtual ~ClientBuffer() {}
31
32 DISALLOW_COPY_AND_ASSIGN(ClientBuffer);
33 };
34
CaptureStarted()35 bool VideoCaptureImpl::CaptureStarted() {
36 return state_ == VIDEO_CAPTURE_STATE_STARTED;
37 }
38
CaptureFrameRate()39 int VideoCaptureImpl::CaptureFrameRate() {
40 return last_frame_format_.frame_rate;
41 }
42
VideoCaptureImpl(const media::VideoCaptureSessionId session_id,base::MessageLoopProxy * capture_message_loop_proxy,VideoCaptureMessageFilter * filter)43 VideoCaptureImpl::VideoCaptureImpl(
44 const media::VideoCaptureSessionId session_id,
45 base::MessageLoopProxy* capture_message_loop_proxy,
46 VideoCaptureMessageFilter* filter)
47 : VideoCapture(),
48 message_filter_(filter),
49 capture_message_loop_proxy_(capture_message_loop_proxy),
50 io_message_loop_proxy_(ChildProcess::current()->io_message_loop_proxy()),
51 device_id_(0),
52 session_id_(session_id),
53 suspended_(false),
54 state_(VIDEO_CAPTURE_STATE_STOPPED),
55 weak_this_factory_(this) {
56 DCHECK(filter);
57 }
58
~VideoCaptureImpl()59 VideoCaptureImpl::~VideoCaptureImpl() {}
60
Init()61 void VideoCaptureImpl::Init() {
62 if (!io_message_loop_proxy_->BelongsToCurrentThread()) {
63 io_message_loop_proxy_->PostTask(FROM_HERE,
64 base::Bind(&VideoCaptureImpl::AddDelegateOnIOThread,
65 base::Unretained(this)));
66 } else {
67 AddDelegateOnIOThread();
68 }
69 }
70
DeInit(base::Closure task)71 void VideoCaptureImpl::DeInit(base::Closure task) {
72 capture_message_loop_proxy_->PostTask(FROM_HERE,
73 base::Bind(&VideoCaptureImpl::DoDeInitOnCaptureThread,
74 base::Unretained(this), task));
75 }
76
StartCapture(media::VideoCapture::EventHandler * handler,const media::VideoCaptureParams & params)77 void VideoCaptureImpl::StartCapture(
78 media::VideoCapture::EventHandler* handler,
79 const media::VideoCaptureParams& params) {
80 capture_message_loop_proxy_->PostTask(FROM_HERE,
81 base::Bind(&VideoCaptureImpl::DoStartCaptureOnCaptureThread,
82 base::Unretained(this), handler, params));
83 }
84
StopCapture(media::VideoCapture::EventHandler * handler)85 void VideoCaptureImpl::StopCapture(media::VideoCapture::EventHandler* handler) {
86 capture_message_loop_proxy_->PostTask(FROM_HERE,
87 base::Bind(&VideoCaptureImpl::DoStopCaptureOnCaptureThread,
88 base::Unretained(this), handler));
89 }
90
OnBufferCreated(base::SharedMemoryHandle handle,int length,int buffer_id)91 void VideoCaptureImpl::OnBufferCreated(
92 base::SharedMemoryHandle handle,
93 int length, int buffer_id) {
94 capture_message_loop_proxy_->PostTask(FROM_HERE,
95 base::Bind(&VideoCaptureImpl::DoBufferCreatedOnCaptureThread,
96 base::Unretained(this), handle, length, buffer_id));
97 }
98
OnBufferDestroyed(int buffer_id)99 void VideoCaptureImpl::OnBufferDestroyed(int buffer_id) {
100 capture_message_loop_proxy_->PostTask(FROM_HERE,
101 base::Bind(&VideoCaptureImpl::DoBufferDestroyedOnCaptureThread,
102 base::Unretained(this), buffer_id));
103 }
104
OnBufferReceived(int buffer_id,base::Time timestamp,const media::VideoCaptureFormat & format)105 void VideoCaptureImpl::OnBufferReceived(
106 int buffer_id,
107 base::Time timestamp,
108 const media::VideoCaptureFormat& format) {
109 capture_message_loop_proxy_->PostTask(FROM_HERE,
110 base::Bind(&VideoCaptureImpl::DoBufferReceivedOnCaptureThread,
111 base::Unretained(this), buffer_id, timestamp, format));
112 }
113
OnStateChanged(VideoCaptureState state)114 void VideoCaptureImpl::OnStateChanged(VideoCaptureState state) {
115 capture_message_loop_proxy_->PostTask(FROM_HERE,
116 base::Bind(&VideoCaptureImpl::DoStateChangedOnCaptureThread,
117 base::Unretained(this), state));
118 }
119
OnDelegateAdded(int32 device_id)120 void VideoCaptureImpl::OnDelegateAdded(int32 device_id) {
121 capture_message_loop_proxy_->PostTask(FROM_HERE,
122 base::Bind(&VideoCaptureImpl::DoDelegateAddedOnCaptureThread,
123 base::Unretained(this), device_id));
124 }
125
SuspendCapture(bool suspend)126 void VideoCaptureImpl::SuspendCapture(bool suspend) {
127 capture_message_loop_proxy_->PostTask(FROM_HERE,
128 base::Bind(&VideoCaptureImpl::DoSuspendCaptureOnCaptureThread,
129 base::Unretained(this), suspend));
130 }
131
DoDeInitOnCaptureThread(base::Closure task)132 void VideoCaptureImpl::DoDeInitOnCaptureThread(base::Closure task) {
133 if (state_ == VIDEO_CAPTURE_STATE_STARTED)
134 Send(new VideoCaptureHostMsg_Stop(device_id_));
135
136 io_message_loop_proxy_->PostTask(FROM_HERE,
137 base::Bind(&VideoCaptureImpl::RemoveDelegateOnIOThread,
138 base::Unretained(this), task));
139 }
140
DoStartCaptureOnCaptureThread(media::VideoCapture::EventHandler * handler,const media::VideoCaptureParams & params)141 void VideoCaptureImpl::DoStartCaptureOnCaptureThread(
142 media::VideoCapture::EventHandler* handler,
143 const media::VideoCaptureParams& params) {
144 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
145
146 if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
147 handler->OnError(this, 1);
148 handler->OnRemoved(this);
149 } else if ((clients_pending_on_filter_.find(handler) !=
150 clients_pending_on_filter_.end()) ||
151 (clients_pending_on_restart_.find(handler) !=
152 clients_pending_on_restart_.end()) ||
153 clients_.find(handler) != clients_.end() ) {
154 // This client has started.
155 } else if (!device_id_) {
156 clients_pending_on_filter_[handler] = params;
157 } else {
158 handler->OnStarted(this);
159 if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
160 clients_[handler] = params;
161 } else if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
162 clients_pending_on_restart_[handler] = params;
163 DVLOG(1) << "StartCapture: Got new resolution "
164 << params.requested_format.frame_size.ToString()
165 << " during stopping.";
166 } else {
167 // TODO(sheu): Allowing resolution change will require that all
168 // outstanding clients of a capture session support resolution change.
169 DCHECK(!params.allow_resolution_change);
170 clients_[handler] = params;
171 DCHECK_EQ(1ul, clients_.size());
172 params_ = params;
173 if (params_.requested_format.frame_rate >
174 media::limits::kMaxFramesPerSecond) {
175 params_.requested_format.frame_rate =
176 media::limits::kMaxFramesPerSecond;
177 }
178 DVLOG(1) << "StartCapture: starting with first resolution "
179 << params_.requested_format.frame_size.ToString();
180
181 StartCaptureInternal();
182 }
183 }
184 }
185
DoStopCaptureOnCaptureThread(media::VideoCapture::EventHandler * handler)186 void VideoCaptureImpl::DoStopCaptureOnCaptureThread(
187 media::VideoCapture::EventHandler* handler) {
188 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
189
190 // A handler can be in only one client list.
191 // If this handler is in any client list, we can just remove it from
192 // that client list and don't have to run the other following RemoveClient().
193 RemoveClient(handler, &clients_pending_on_filter_) ||
194 RemoveClient(handler, &clients_pending_on_restart_) ||
195 RemoveClient(handler, &clients_);
196
197 if (clients_.empty()) {
198 DVLOG(1) << "StopCapture: No more client, stopping ...";
199 StopDevice();
200 client_buffers_.clear();
201 weak_this_factory_.InvalidateWeakPtrs();
202 }
203 }
204
DoBufferCreatedOnCaptureThread(base::SharedMemoryHandle handle,int length,int buffer_id)205 void VideoCaptureImpl::DoBufferCreatedOnCaptureThread(
206 base::SharedMemoryHandle handle,
207 int length, int buffer_id) {
208 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
209
210 // In case client calls StopCapture before the arrival of created buffer,
211 // just close this buffer and return.
212 if (state_ != VIDEO_CAPTURE_STATE_STARTED) {
213 base::SharedMemory::CloseHandle(handle);
214 return;
215 }
216
217 scoped_ptr<base::SharedMemory> shm(new base::SharedMemory(handle, false));
218 if (!shm->Map(length)) {
219 DLOG(ERROR) << "DoBufferCreatedOnCaptureThread: Map() failed.";
220 return;
221 }
222
223 bool inserted =
224 client_buffers_.insert(std::make_pair(
225 buffer_id,
226 new ClientBuffer(shm.Pass(),
227 length))).second;
228 DCHECK(inserted);
229 }
230
DoBufferDestroyedOnCaptureThread(int buffer_id)231 void VideoCaptureImpl::DoBufferDestroyedOnCaptureThread(int buffer_id) {
232 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
233
234 ClientBufferMap::iterator iter = client_buffers_.find(buffer_id);
235 if (iter == client_buffers_.end())
236 return;
237
238 DCHECK(!iter->second || iter->second->HasOneRef())
239 << "Instructed to delete buffer we are still using.";
240 client_buffers_.erase(iter);
241 }
242
DoBufferReceivedOnCaptureThread(int buffer_id,base::Time timestamp,const media::VideoCaptureFormat & format)243 void VideoCaptureImpl::DoBufferReceivedOnCaptureThread(
244 int buffer_id,
245 base::Time timestamp,
246 const media::VideoCaptureFormat& format) {
247 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
248
249 if (state_ != VIDEO_CAPTURE_STATE_STARTED || suspended_) {
250 Send(new VideoCaptureHostMsg_BufferReady(device_id_, buffer_id));
251 return;
252 }
253
254 last_frame_format_ = format;
255
256 ClientBufferMap::iterator iter = client_buffers_.find(buffer_id);
257 DCHECK(iter != client_buffers_.end());
258 scoped_refptr<ClientBuffer> buffer = iter->second;
259 scoped_refptr<media::VideoFrame> frame =
260 media::VideoFrame::WrapExternalPackedMemory(
261 media::VideoFrame::I420,
262 last_frame_format_.frame_size,
263 gfx::Rect(last_frame_format_.frame_size),
264 last_frame_format_.frame_size,
265 reinterpret_cast<uint8*>(buffer->buffer->memory()),
266 buffer->buffer_size,
267 buffer->buffer->handle(),
268 // TODO(sheu): convert VideoCaptureMessageFilter::Delegate to use
269 // base::TimeTicks instead of base::Time. http://crbug.com/249215
270 timestamp - base::Time::UnixEpoch(),
271 media::BindToLoop(
272 capture_message_loop_proxy_,
273 base::Bind(
274 &VideoCaptureImpl::DoClientBufferFinishedOnCaptureThread,
275 weak_this_factory_.GetWeakPtr(),
276 buffer_id,
277 buffer)));
278
279 for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it)
280 it->first->OnFrameReady(this, frame);
281 }
282
DoClientBufferFinishedOnCaptureThread(int buffer_id,const scoped_refptr<ClientBuffer> & buffer)283 void VideoCaptureImpl::DoClientBufferFinishedOnCaptureThread(
284 int buffer_id,
285 const scoped_refptr<ClientBuffer>& buffer) {
286 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
287 Send(new VideoCaptureHostMsg_BufferReady(device_id_, buffer_id));
288 }
289
DoStateChangedOnCaptureThread(VideoCaptureState state)290 void VideoCaptureImpl::DoStateChangedOnCaptureThread(VideoCaptureState state) {
291 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
292
293 switch (state) {
294 case VIDEO_CAPTURE_STATE_STARTED:
295 break;
296 case VIDEO_CAPTURE_STATE_STOPPED:
297 state_ = VIDEO_CAPTURE_STATE_STOPPED;
298 DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_;
299 client_buffers_.clear();
300 weak_this_factory_.InvalidateWeakPtrs();
301 if (!clients_.empty() || !clients_pending_on_restart_.empty())
302 RestartCapture();
303 break;
304 case VIDEO_CAPTURE_STATE_PAUSED:
305 for (ClientInfo::iterator it = clients_.begin();
306 it != clients_.end(); ++it) {
307 it->first->OnPaused(this);
308 }
309 break;
310 case VIDEO_CAPTURE_STATE_ERROR:
311 DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_;
312 for (ClientInfo::iterator it = clients_.begin();
313 it != clients_.end(); ++it) {
314 // TODO(wjia): browser process would send error code.
315 it->first->OnError(this, 1);
316 it->first->OnRemoved(this);
317 }
318 clients_.clear();
319 state_ = VIDEO_CAPTURE_STATE_ERROR;
320 break;
321 case VIDEO_CAPTURE_STATE_ENDED:
322 DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_;
323 for (ClientInfo::iterator it = clients_.begin();
324 it != clients_.end(); ++it) {
325 it->first->OnRemoved(this);
326 }
327 clients_.clear();
328 state_ = VIDEO_CAPTURE_STATE_ENDED;
329 break;
330 default:
331 break;
332 }
333 }
334
DoDelegateAddedOnCaptureThread(int32 device_id)335 void VideoCaptureImpl::DoDelegateAddedOnCaptureThread(int32 device_id) {
336 DVLOG(1) << "DoDelegateAdded: device_id " << device_id;
337 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
338
339 device_id_ = device_id;
340 for (ClientInfo::iterator it = clients_pending_on_filter_.begin();
341 it != clients_pending_on_filter_.end(); ) {
342 media::VideoCapture::EventHandler* handler = it->first;
343 const media::VideoCaptureParams params = it->second;
344 clients_pending_on_filter_.erase(it++);
345 StartCapture(handler, params);
346 }
347 }
348
DoSuspendCaptureOnCaptureThread(bool suspend)349 void VideoCaptureImpl::DoSuspendCaptureOnCaptureThread(bool suspend) {
350 DVLOG(1) << "DoSuspendCapture: suspend " << (suspend ? "yes" : "no");
351 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
352
353 suspended_ = suspend;
354 }
355
StopDevice()356 void VideoCaptureImpl::StopDevice() {
357 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
358
359 if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
360 state_ = VIDEO_CAPTURE_STATE_STOPPING;
361 Send(new VideoCaptureHostMsg_Stop(device_id_));
362 params_.requested_format.frame_size.SetSize(0, 0);
363 }
364 }
365
RestartCapture()366 void VideoCaptureImpl::RestartCapture() {
367 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
368 DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPED);
369
370 int width = 0;
371 int height = 0;
372 for (ClientInfo::iterator it = clients_.begin();
373 it != clients_.end(); ++it) {
374 width = std::max(width, it->second.requested_format.frame_size.width());
375 height = std::max(height, it->second.requested_format.frame_size.height());
376 }
377 for (ClientInfo::iterator it = clients_pending_on_restart_.begin();
378 it != clients_pending_on_restart_.end(); ) {
379 width = std::max(width, it->second.requested_format.frame_size.width());
380 height = std::max(height, it->second.requested_format.frame_size.height());
381 clients_[it->first] = it->second;
382 clients_pending_on_restart_.erase(it++);
383 }
384 params_.requested_format.frame_size.SetSize(width, height);
385 DVLOG(1) << "RestartCapture, "
386 << params_.requested_format.frame_size.ToString();
387 StartCaptureInternal();
388 }
389
StartCaptureInternal()390 void VideoCaptureImpl::StartCaptureInternal() {
391 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
392 DCHECK(device_id_);
393
394 Send(new VideoCaptureHostMsg_Start(device_id_, session_id_, params_));
395 state_ = VIDEO_CAPTURE_STATE_STARTED;
396 }
397
AddDelegateOnIOThread()398 void VideoCaptureImpl::AddDelegateOnIOThread() {
399 DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
400 message_filter_->AddDelegate(this);
401 }
402
RemoveDelegateOnIOThread(base::Closure task)403 void VideoCaptureImpl::RemoveDelegateOnIOThread(base::Closure task) {
404 DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
405 message_filter_->RemoveDelegate(this);
406 capture_message_loop_proxy_->PostTask(FROM_HERE, task);
407 }
408
Send(IPC::Message * message)409 void VideoCaptureImpl::Send(IPC::Message* message) {
410 io_message_loop_proxy_->PostTask(FROM_HERE,
411 base::Bind(base::IgnoreResult(&VideoCaptureMessageFilter::Send),
412 message_filter_.get(), message));
413 }
414
RemoveClient(media::VideoCapture::EventHandler * handler,ClientInfo * clients)415 bool VideoCaptureImpl::RemoveClient(
416 media::VideoCapture::EventHandler* handler,
417 ClientInfo* clients) {
418 DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
419 bool found = false;
420
421 ClientInfo::iterator it = clients->find(handler);
422 if (it != clients->end()) {
423 handler->OnStopped(this);
424 handler->OnRemoved(this);
425 clients->erase(it);
426 found = true;
427 }
428 return found;
429 }
430
431 } // namespace content
432