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