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