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
22namespace content {
23
24class 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;
33
34 private:
35  friend class base::RefCountedThreadSafe<ClientBuffer>;
36
37  virtual ~ClientBuffer() {}
38
39  DISALLOW_COPY_AND_ASSIGN(ClientBuffer);
40};
41
42VideoCaptureImpl::ClientInfo::ClientInfo() {}
43VideoCaptureImpl::ClientInfo::~ClientInfo() {}
44
45VideoCaptureImpl::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
58VideoCaptureImpl::~VideoCaptureImpl() {
59  DCHECK(thread_checker_.CalledOnValidThread());
60}
61
62void VideoCaptureImpl::Init() {
63  DCHECK(thread_checker_.CalledOnValidThread());
64  message_filter_->AddDelegate(this);
65}
66
67void 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
74void VideoCaptureImpl::SuspendCapture(bool suspend) {
75  DCHECK(thread_checker_.CalledOnValidThread());
76  suspended_ = suspend;
77}
78
79void 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
131void 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
151void 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
160void 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
169void 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
195void 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
207void 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
258static void NullReadPixelsCB(const SkBitmap& bitmap) { NOTIMPLEMENTED(); }
259
260void 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
296void 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
305void 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
351void 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
359void 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
367void 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
386void 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
396void 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
418void 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
426void VideoCaptureImpl::Send(IPC::Message* message) {
427  DCHECK(thread_checker_.CalledOnValidThread());
428  message_filter_->Send(message);
429}
430
431bool 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