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 // Notes about usage of this object by VideoCaptureImplManager.
6 //
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
10 // synchronous.
11
12 #include "content/renderer/media/video_capture_impl.h"
13
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"
21
22 namespace content {
23
24 class VideoCaptureImpl::ClientBuffer
25 : public base::RefCountedThreadSafe<ClientBuffer> {
26 public:
ClientBuffer(scoped_ptr<base::SharedMemory> buffer,size_t buffer_size)27 ClientBuffer(scoped_ptr<base::SharedMemory> buffer,
28 size_t buffer_size)
29 : buffer(buffer.Pass()),
30 buffer_size(buffer_size) {}
31 const scoped_ptr<base::SharedMemory> buffer;
32 const size_t buffer_size;
33
34 private:
35 friend class base::RefCountedThreadSafe<ClientBuffer>;
36
~ClientBuffer()37 virtual ~ClientBuffer() {}
38
39 DISALLOW_COPY_AND_ASSIGN(ClientBuffer);
40 };
41
ClientInfo()42 VideoCaptureImpl::ClientInfo::ClientInfo() {}
~ClientInfo()43 VideoCaptureImpl::ClientInfo::~ClientInfo() {}
44
VideoCaptureImpl(const media::VideoCaptureSessionId session_id,VideoCaptureMessageFilter * filter)45 VideoCaptureImpl::VideoCaptureImpl(
46 const media::VideoCaptureSessionId session_id,
47 VideoCaptureMessageFilter* filter)
48 : message_filter_(filter),
49 device_id_(0),
50 session_id_(session_id),
51 suspended_(false),
52 state_(VIDEO_CAPTURE_STATE_STOPPED),
53 weak_factory_(this) {
54 DCHECK(filter);
55 thread_checker_.DetachFromThread();
56 }
57
~VideoCaptureImpl()58 VideoCaptureImpl::~VideoCaptureImpl() {
59 DCHECK(thread_checker_.CalledOnValidThread());
60 }
61
Init()62 void VideoCaptureImpl::Init() {
63 DCHECK(thread_checker_.CalledOnValidThread());
64 message_filter_->AddDelegate(this);
65 }
66
DeInit()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);
72 }
73
SuspendCapture(bool suspend)74 void VideoCaptureImpl::SuspendCapture(bool suspend) {
75 DCHECK(thread_checker_.CalledOnValidThread());
76 suspended_ = suspend;
77 }
78
StartCapture(int client_id,const media::VideoCaptureParams & params,const VideoCaptureStateUpdateCB & state_update_cb,const VideoCaptureDeliverFrameCB & deliver_frame_cb)79 void VideoCaptureImpl::StartCapture(
80 int client_id,
81 const media::VideoCaptureParams& params,
82 const VideoCaptureStateUpdateCB& state_update_cb,
83 const VideoCaptureDeliverFrameCB& deliver_frame_cb) {
84 DCHECK(thread_checker_.CalledOnValidThread());
85 ClientInfo client_info;
86 client_info.params = params;
87 client_info.state_update_cb = state_update_cb;
88 client_info.deliver_frame_cb = deliver_frame_cb;
89
90 if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
91 state_update_cb.Run(VIDEO_CAPTURE_STATE_ERROR);
92 } else if (clients_pending_on_filter_.count(client_id) ||
93 clients_pending_on_restart_.count(client_id) ||
94 clients_.count(client_id)) {
95 LOG(FATAL) << "This client has already started.";
96 } else if (!device_id_) {
97 clients_pending_on_filter_[client_id] = client_info;
98 } else {
99 // Note: |state_| might not be started at this point. But we tell
100 // client that we have started.
101 state_update_cb.Run(VIDEO_CAPTURE_STATE_STARTED);
102 if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
103 clients_[client_id] = client_info;
104 // TODO(sheu): Allowing resolution change will require that all
105 // outstanding clients of a capture session support resolution change.
106 DCHECK_EQ(params_.allow_resolution_change,
107 params.allow_resolution_change);
108 } else if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
109 clients_pending_on_restart_[client_id] = client_info;
110 DVLOG(1) << "StartCapture: Got new resolution "
111 << params.requested_format.frame_size.ToString()
112 << " during stopping.";
113 } else {
114 clients_[client_id] = client_info;
115 if (state_ == VIDEO_CAPTURE_STATE_STARTED)
116 return;
117 params_ = params;
118 if (params_.requested_format.frame_rate >
119 media::limits::kMaxFramesPerSecond) {
120 params_.requested_format.frame_rate =
121 media::limits::kMaxFramesPerSecond;
122 }
123 DVLOG(1) << "StartCapture: starting with first resolution "
124 << params_.requested_format.frame_size.ToString();
125 first_frame_timestamp_ = base::TimeTicks();
126 StartCaptureInternal();
127 }
128 }
129 }
130
StopCapture(int client_id)131 void VideoCaptureImpl::StopCapture(int client_id) {
132 DCHECK(thread_checker_.CalledOnValidThread());
133
134 // A client ID can be in only one client list.
135 // If this ID is in any client list, we can just remove it from
136 // that client list and don't have to run the other following RemoveClient().
137 if (!RemoveClient(client_id, &clients_pending_on_filter_)) {
138 if (!RemoveClient(client_id, &clients_pending_on_restart_)) {
139 RemoveClient(client_id, &clients_);
140 }
141 }
142
143 if (clients_.empty()) {
144 DVLOG(1) << "StopCapture: No more client, stopping ...";
145 StopDevice();
146 client_buffers_.clear();
147 weak_factory_.InvalidateWeakPtrs();
148 }
149 }
150
GetDeviceSupportedFormats(const VideoCaptureDeviceFormatsCB & callback)151 void VideoCaptureImpl::GetDeviceSupportedFormats(
152 const VideoCaptureDeviceFormatsCB& callback) {
153 DCHECK(thread_checker_.CalledOnValidThread());
154 device_formats_cb_queue_.push_back(callback);
155 if (device_formats_cb_queue_.size() == 1)
156 Send(new VideoCaptureHostMsg_GetDeviceSupportedFormats(device_id_,
157 session_id_));
158 }
159
GetDeviceFormatsInUse(const VideoCaptureDeviceFormatsCB & callback)160 void VideoCaptureImpl::GetDeviceFormatsInUse(
161 const VideoCaptureDeviceFormatsCB& callback) {
162 DCHECK(thread_checker_.CalledOnValidThread());
163 device_formats_in_use_cb_queue_.push_back(callback);
164 if (device_formats_in_use_cb_queue_.size() == 1)
165 Send(
166 new VideoCaptureHostMsg_GetDeviceFormatsInUse(device_id_, session_id_));
167 }
168
OnBufferCreated(base::SharedMemoryHandle handle,int length,int buffer_id)169 void VideoCaptureImpl::OnBufferCreated(
170 base::SharedMemoryHandle handle,
171 int length, int buffer_id) {
172 DCHECK(thread_checker_.CalledOnValidThread());
173
174 // In case client calls StopCapture before the arrival of created buffer,
175 // just close this buffer and return.
176 if (state_ != VIDEO_CAPTURE_STATE_STARTED) {
177 base::SharedMemory::CloseHandle(handle);
178 return;
179 }
180
181 scoped_ptr<base::SharedMemory> shm(new base::SharedMemory(handle, false));
182 if (!shm->Map(length)) {
183 DLOG(ERROR) << "OnBufferCreated: Map failed.";
184 return;
185 }
186
187 bool inserted =
188 client_buffers_.insert(std::make_pair(
189 buffer_id,
190 new ClientBuffer(shm.Pass(),
191 length))).second;
192 DCHECK(inserted);
193 }
194
OnBufferDestroyed(int buffer_id)195 void VideoCaptureImpl::OnBufferDestroyed(int buffer_id) {
196 DCHECK(thread_checker_.CalledOnValidThread());
197
198 ClientBufferMap::iterator iter = client_buffers_.find(buffer_id);
199 if (iter == client_buffers_.end())
200 return;
201
202 DCHECK(!iter->second || iter->second->HasOneRef())
203 << "Instructed to delete buffer we are still using.";
204 client_buffers_.erase(iter);
205 }
206
OnBufferReceived(int buffer_id,const media::VideoCaptureFormat & format,base::TimeTicks timestamp)207 void VideoCaptureImpl::OnBufferReceived(int buffer_id,
208 const media::VideoCaptureFormat& format,
209 base::TimeTicks timestamp) {
210 DCHECK(thread_checker_.CalledOnValidThread());
211
212 // The capture pipeline supports only I420 for now.
213 DCHECK_EQ(format.pixel_format, media::PIXEL_FORMAT_I420);
214
215 if (state_ != VIDEO_CAPTURE_STATE_STARTED || suspended_) {
216 Send(new VideoCaptureHostMsg_BufferReady(
217 device_id_, buffer_id, std::vector<uint32>()));
218 return;
219 }
220
221 last_frame_format_ = format;
222 if (first_frame_timestamp_.is_null())
223 first_frame_timestamp_ = timestamp;
224
225 // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc
226 TRACE_EVENT_INSTANT2(
227 "cast_perf_test", "OnBufferReceived",
228 TRACE_EVENT_SCOPE_THREAD,
229 "timestamp", timestamp.ToInternalValue(),
230 "time_delta", (timestamp - first_frame_timestamp_).ToInternalValue());
231
232 ClientBufferMap::iterator iter = client_buffers_.find(buffer_id);
233 DCHECK(iter != client_buffers_.end());
234 scoped_refptr<ClientBuffer> buffer = iter->second;
235 scoped_refptr<media::VideoFrame> frame =
236 media::VideoFrame::WrapExternalPackedMemory(
237 media::VideoFrame::I420,
238 last_frame_format_.frame_size,
239 gfx::Rect(last_frame_format_.frame_size),
240 last_frame_format_.frame_size,
241 reinterpret_cast<uint8*>(buffer->buffer->memory()),
242 buffer->buffer_size,
243 buffer->buffer->handle(),
244 timestamp - first_frame_timestamp_,
245 media::BindToCurrentLoop(
246 base::Bind(&VideoCaptureImpl::OnClientBufferFinished,
247 weak_factory_.GetWeakPtr(),
248 buffer_id,
249 buffer,
250 std::vector<uint32>())));
251
252 for (ClientInfoMap::iterator it = clients_.begin(); it != clients_.end();
253 ++it) {
254 it->second.deliver_frame_cb.Run(frame, format, timestamp);
255 }
256 }
257
NullReadPixelsCB(const SkBitmap & bitmap)258 static void NullReadPixelsCB(const SkBitmap& bitmap) { NOTIMPLEMENTED(); }
259
OnMailboxBufferReceived(int buffer_id,const gpu::MailboxHolder & mailbox_holder,const media::VideoCaptureFormat & format,base::TimeTicks timestamp)260 void VideoCaptureImpl::OnMailboxBufferReceived(
261 int buffer_id,
262 const gpu::MailboxHolder& mailbox_holder,
263 const media::VideoCaptureFormat& format,
264 base::TimeTicks timestamp) {
265 DCHECK(thread_checker_.CalledOnValidThread());
266
267 if (state_ != VIDEO_CAPTURE_STATE_STARTED || suspended_) {
268 Send(new VideoCaptureHostMsg_BufferReady(
269 device_id_, buffer_id, std::vector<uint32>()));
270 return;
271 }
272
273 last_frame_format_ = format;
274 if (first_frame_timestamp_.is_null())
275 first_frame_timestamp_ = timestamp;
276
277 scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapNativeTexture(
278 make_scoped_ptr(new gpu::MailboxHolder(mailbox_holder)),
279 media::BindToCurrentLoop(
280 base::Bind(&VideoCaptureImpl::OnClientBufferFinished,
281 weak_factory_.GetWeakPtr(),
282 buffer_id,
283 scoped_refptr<ClientBuffer>())),
284 last_frame_format_.frame_size,
285 gfx::Rect(last_frame_format_.frame_size),
286 last_frame_format_.frame_size,
287 timestamp - first_frame_timestamp_,
288 base::Bind(&NullReadPixelsCB));
289
290 for (ClientInfoMap::iterator it = clients_.begin(); it != clients_.end();
291 ++it) {
292 it->second.deliver_frame_cb.Run(frame, format, timestamp);
293 }
294 }
295
OnClientBufferFinished(int buffer_id,const scoped_refptr<ClientBuffer> &,const std::vector<uint32> & release_sync_points)296 void VideoCaptureImpl::OnClientBufferFinished(
297 int buffer_id,
298 const scoped_refptr<ClientBuffer>& /* ignored_buffer */,
299 const std::vector<uint32>& release_sync_points) {
300 DCHECK(thread_checker_.CalledOnValidThread());
301 Send(new VideoCaptureHostMsg_BufferReady(
302 device_id_, buffer_id, release_sync_points));
303 }
304
OnStateChanged(VideoCaptureState state)305 void VideoCaptureImpl::OnStateChanged(VideoCaptureState state) {
306 DCHECK(thread_checker_.CalledOnValidThread());
307
308 switch (state) {
309 case VIDEO_CAPTURE_STATE_STARTED:
310 // Camera has started in the browser process. Since we have already
311 // told all clients that we have started there's nothing to do.
312 break;
313 case VIDEO_CAPTURE_STATE_STOPPED:
314 state_ = VIDEO_CAPTURE_STATE_STOPPED;
315 DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_;
316 client_buffers_.clear();
317 weak_factory_.InvalidateWeakPtrs();
318 if (!clients_.empty() || !clients_pending_on_restart_.empty())
319 RestartCapture();
320 break;
321 case VIDEO_CAPTURE_STATE_PAUSED:
322 for (ClientInfoMap::iterator it = clients_.begin();
323 it != clients_.end(); ++it) {
324 it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_PAUSED);
325 }
326 break;
327 case VIDEO_CAPTURE_STATE_ERROR:
328 DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_;
329 for (ClientInfoMap::iterator it = clients_.begin();
330 it != clients_.end(); ++it) {
331 it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_ERROR);
332 }
333 clients_.clear();
334 state_ = VIDEO_CAPTURE_STATE_ERROR;
335 break;
336 case VIDEO_CAPTURE_STATE_ENDED:
337 DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_;
338 for (ClientInfoMap::iterator it = clients_.begin();
339 it != clients_.end(); ++it) {
340 // We'll only notify the client that the stream has stopped.
341 it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STOPPED);
342 }
343 clients_.clear();
344 state_ = VIDEO_CAPTURE_STATE_ENDED;
345 break;
346 default:
347 break;
348 }
349 }
350
OnDeviceSupportedFormatsEnumerated(const media::VideoCaptureFormats & supported_formats)351 void VideoCaptureImpl::OnDeviceSupportedFormatsEnumerated(
352 const media::VideoCaptureFormats& supported_formats) {
353 DCHECK(thread_checker_.CalledOnValidThread());
354 for (size_t i = 0; i < device_formats_cb_queue_.size(); ++i)
355 device_formats_cb_queue_[i].Run(supported_formats);
356 device_formats_cb_queue_.clear();
357 }
358
OnDeviceFormatsInUseReceived(const media::VideoCaptureFormats & formats_in_use)359 void VideoCaptureImpl::OnDeviceFormatsInUseReceived(
360 const media::VideoCaptureFormats& formats_in_use) {
361 DCHECK(thread_checker_.CalledOnValidThread());
362 for (size_t i = 0; i < device_formats_in_use_cb_queue_.size(); ++i)
363 device_formats_in_use_cb_queue_[i].Run(formats_in_use);
364 device_formats_in_use_cb_queue_.clear();
365 }
366
OnDelegateAdded(int32 device_id)367 void VideoCaptureImpl::OnDelegateAdded(int32 device_id) {
368 DCHECK(thread_checker_.CalledOnValidThread());
369 DVLOG(1) << "OnDelegateAdded: device_id " << device_id;
370
371 device_id_ = device_id;
372 for (ClientInfoMap::iterator it = clients_pending_on_filter_.begin();
373 it != clients_pending_on_filter_.end(); ) {
374 int client_id = it->first;
375 VideoCaptureStateUpdateCB state_update_cb =
376 it->second.state_update_cb;
377 VideoCaptureDeliverFrameCB deliver_frame_cb =
378 it->second.deliver_frame_cb;
379 const media::VideoCaptureParams params = it->second.params;
380 clients_pending_on_filter_.erase(it++);
381 StartCapture(client_id, params, state_update_cb,
382 deliver_frame_cb);
383 }
384 }
385
StopDevice()386 void VideoCaptureImpl::StopDevice() {
387 DCHECK(thread_checker_.CalledOnValidThread());
388
389 if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
390 state_ = VIDEO_CAPTURE_STATE_STOPPING;
391 Send(new VideoCaptureHostMsg_Stop(device_id_));
392 params_.requested_format.frame_size.SetSize(0, 0);
393 }
394 }
395
RestartCapture()396 void VideoCaptureImpl::RestartCapture() {
397 DCHECK(thread_checker_.CalledOnValidThread());
398 DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPED);
399
400 int width = 0;
401 int height = 0;
402 clients_.insert(clients_pending_on_restart_.begin(),
403 clients_pending_on_restart_.end());
404 clients_pending_on_restart_.clear();
405 for (ClientInfoMap::iterator it = clients_.begin();
406 it != clients_.end(); ++it) {
407 width = std::max(width,
408 it->second.params.requested_format.frame_size.width());
409 height = std::max(height,
410 it->second.params.requested_format.frame_size.height());
411 }
412 params_.requested_format.frame_size.SetSize(width, height);
413 DVLOG(1) << "RestartCapture, "
414 << params_.requested_format.frame_size.ToString();
415 StartCaptureInternal();
416 }
417
StartCaptureInternal()418 void VideoCaptureImpl::StartCaptureInternal() {
419 DCHECK(thread_checker_.CalledOnValidThread());
420 DCHECK(device_id_);
421
422 Send(new VideoCaptureHostMsg_Start(device_id_, session_id_, params_));
423 state_ = VIDEO_CAPTURE_STATE_STARTED;
424 }
425
Send(IPC::Message * message)426 void VideoCaptureImpl::Send(IPC::Message* message) {
427 DCHECK(thread_checker_.CalledOnValidThread());
428 message_filter_->Send(message);
429 }
430
RemoveClient(int client_id,ClientInfoMap * clients)431 bool VideoCaptureImpl::RemoveClient(int client_id, ClientInfoMap* clients) {
432 DCHECK(thread_checker_.CalledOnValidThread());
433 bool found = false;
434
435 ClientInfoMap::iterator it = clients->find(client_id);
436 if (it != clients->end()) {
437 it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STOPPED);
438 clients->erase(it);
439 found = true;
440 }
441 return found;
442 }
443
444 } // namespace content
445