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