Home | History | Annotate | Download | only in media
      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:
     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 
     42 VideoCaptureImpl::ClientInfo::ClientInfo() {}
     43 VideoCaptureImpl::ClientInfo::~ClientInfo() {}
     44 
     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 
     58 VideoCaptureImpl::~VideoCaptureImpl() {
     59   DCHECK(thread_checker_.CalledOnValidThread());
     60 }
     61 
     62 void VideoCaptureImpl::Init() {
     63   DCHECK(thread_checker_.CalledOnValidThread());
     64   message_filter_->AddDelegate(this);
     65 }
     66 
     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 
     74 void VideoCaptureImpl::SuspendCapture(bool suspend) {
     75   DCHECK(thread_checker_.CalledOnValidThread());
     76   suspended_ = suspend;
     77 }
     78 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    258 static void NullReadPixelsCB(const SkBitmap& bitmap) { NOTIMPLEMENTED(); }
    259 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    426 void VideoCaptureImpl::Send(IPC::Message* message) {
    427   DCHECK(thread_checker_.CalledOnValidThread());
    428   message_filter_->Send(message);
    429 }
    430 
    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