cc: Make picture pile base thread safe.
[chromium-blink-merge.git] / content / renderer / media / video_capture_impl.cc
blob9688b464680fb8271cbec4a7882ef16391324f5d
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.
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"
22 namespace content {
24 class VideoCaptureImpl::ClientBuffer
25 : public base::RefCountedThreadSafe<ClientBuffer> {
26 public:
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;
34 private:
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),
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();
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());
76 Send(suspend ?
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(
83 int client_id,
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;
101 } else {
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.";
116 } else {
117 clients_[client_id] = client_info;
118 if (state_ == VIDEO_CAPTURE_STATE_STARTED)
119 return;
120 params_ = params;
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 ...";
148 StopDevice();
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_,
160 session_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)
168 Send(
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);
181 return;
184 scoped_ptr<base::SharedMemory> shm(new base::SharedMemory(handle, false));
185 if (!shm->Map(length)) {
186 DLOG(ERROR) << "OnBufferCreated: Map failed.";
187 return;
190 bool inserted =
191 client_buffers_.insert(std::make_pair(
192 buffer_id,
193 new ClientBuffer(shm.Pass(),
194 length))).second;
195 DCHECK(inserted);
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())
203 return;
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));
221 return;
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,
242 visible_rect,
243 gfx::Size(visible_rect.width(), visible_rect.height()),
244 reinterpret_cast<uint8*>(buffer->buffer->memory()),
245 buffer->buffer_size,
246 buffer->buffer->handle(),
247 timestamp - first_frame_timestamp_,
248 media::BindToCurrentLoop(
249 base::Bind(&VideoCaptureImpl::OnClientBufferFinished,
250 weak_factory_.GetWeakPtr(),
251 buffer_id,
252 buffer,
253 0)));
255 for (ClientInfoMap::iterator it = clients_.begin(); it != clients_.end();
256 ++it) {
257 it->second.deliver_frame_cb.Run(frame, format, timestamp);
261 static void NullReadPixelsCB(const SkBitmap& bitmap) { NOTIMPLEMENTED(); }
263 void VideoCaptureImpl::OnMailboxBufferReceived(
264 int buffer_id,
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));
272 return;
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(),
284 buffer_id,
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();
293 ++it) {
294 it->second.deliver_frame_cb.Run(frame, format, timestamp);
298 void VideoCaptureImpl::OnClientBufferFinished(
299 int buffer_id,
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());
310 switch (state) {
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.
314 break;
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())
321 RestartCapture();
322 break;
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);
328 break;
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);
335 clients_.clear();
336 state_ = VIDEO_CAPTURE_STATE_ERROR;
337 break;
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);
345 clients_.clear();
346 state_ = VIDEO_CAPTURE_STATE_ENDED;
347 break;
348 default:
349 break;
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,
384 deliver_frame_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);
402 int width = 0;
403 int height = 0;
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());
422 DCHECK(device_id_);
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());
435 bool found = false;
437 ClientInfoMap::iterator it = clients->find(client_id);
438 if (it != clients->end()) {
439 it->second.state_update_cb.Run(VIDEO_CAPTURE_STATE_STOPPED);
440 clients->erase(it);
441 found = true;
443 return found;
446 } // namespace content