Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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/memory/singleton.h" | |
| 8 #include "content/common/child_process.h" | |
| 9 #include "content/common/child_thread.h" | |
| 10 #include "content/common/video_capture_messages.h" | |
| 11 #include "content/common/view_messages.h" | |
| 12 #include "media/base/message_loop_factory_impl.h" | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // VideoCaptureMessageFilterCreator is to be used as a singleton so we can get | |
| 17 // access to a shared VideoCaptureMessageFilter. | |
| 18 // Example usage: | |
| 19 // VideoCaptureMessageFilter* filter = | |
| 20 // VideoCaptureMessageFilterCreator::SharedFilter(); | |
| 21 | |
| 22 class VideoCaptureMessageFilterCreator { | |
| 23 public: | |
| 24 VideoCaptureMessageFilterCreator() { | |
| 25 int routing_id; | |
| 26 ChildThread::current()->Send( | |
| 27 new ViewHostMsg_GenerateRoutingID(&routing_id)); | |
| 28 filter_ = new VideoCaptureMessageFilter(routing_id); | |
| 29 filter_->AddFilter(); | |
| 30 } | |
| 31 | |
| 32 static VideoCaptureMessageFilter* SharedFilter() { | |
| 33 return GetInstance()->filter_.get(); | |
| 34 } | |
| 35 | |
| 36 static VideoCaptureMessageFilterCreator* GetInstance() { | |
| 37 return Singleton<VideoCaptureMessageFilterCreator>::get(); | |
| 38 } | |
| 39 | |
| 40 private: | |
| 41 scoped_refptr<VideoCaptureMessageFilter> filter_; | |
| 42 }; | |
| 43 | |
| 44 } // namespace | |
| 45 | |
| 46 VideoCaptureImplManager::VideoCaptureImplManager() { | |
| 47 ml_factory_.reset(new media::MessageLoopFactoryImpl()); | |
| 48 ml_proxy_ = ml_factory_->GetMessageLoopProxy("VC manager"); | |
| 49 } | |
| 50 | |
| 51 // static | |
| 52 media::VideoCapture* VideoCaptureImplManager::AddDevice( | |
| 53 media::VideoCaptureSessionId id, | |
| 54 media::VideoCapture::EventHandler* handler) { | |
| 55 DCHECK(handler); | |
| 56 VideoCaptureImplManager* manager = GetInstance(); | |
| 57 | |
| 58 base::AutoLock auto_lock(manager->lock_); | |
| 59 Devices::iterator it = manager->devices_.find(id); | |
| 60 if (it == manager->devices_.end()) { | |
| 61 VideoCaptureImpl* vc = | |
| 62 new VideoCaptureImpl(id, manager->ml_proxy_, | |
| 63 VideoCaptureMessageFilterCreator::SharedFilter()); | |
| 64 if (vc) { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
not sure how valuable this check is
chrome regist
wjia(left Chromium)
2011/05/17 04:00:25
removed the checking. What if some module has a ba
| |
| 65 manager->devices_[id] = Device(vc, handler); | |
| 66 vc->Init(); | |
| 67 } | |
| 68 return vc; | |
| 69 } | |
| 70 | |
| 71 manager->devices_[id].clients.push_front(handler); | |
| 72 return it->second.vc; | |
| 73 } | |
| 74 | |
| 75 // static | |
| 76 void VideoCaptureImplManager::RemoveDevice( | |
| 77 media::VideoCaptureSessionId id, | |
| 78 media::VideoCapture::EventHandler* handler) { | |
| 79 DCHECK(handler); | |
| 80 VideoCaptureImplManager* manager = GetInstance(); | |
| 81 | |
| 82 base::AutoLock auto_lock(manager->lock_); | |
| 83 Devices::iterator it = manager->devices_.find(id); | |
| 84 if (it == manager->devices_.end()) | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
is it valid to call RemoveDevice w/ invalid id?
i
wjia(left Chromium)
2011/05/17 04:00:25
The idea is to allow anyone to call RemoveDevice w
| |
| 85 return; | |
| 86 | |
| 87 size_t size = it->second.clients.size(); | |
| 88 it->second.clients.remove(handler); | |
| 89 | |
| 90 if (size == it->second.clients.size() || size > 1) | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
is it valid to remove pass in an invalid handler p
wjia(left Chromium)
2011/05/17 04:00:25
same explanation as above.
| |
| 91 return; | |
| 92 | |
| 93 manager->devices_[id].vc->DeInit(NewRunnableMethod(manager, | |
| 94 &VideoCaptureImplManager::FreeDevice, manager->devices_[id].vc)); | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
are you transferring ownership of vc to this task
wjia(left Chromium)
2011/05/17 04:00:25
yes, because |vc| has to be removed from MessageFi
| |
| 95 manager->devices_.erase(id); | |
| 96 return; | |
| 97 } | |
| 98 | |
| 99 // static | |
| 100 VideoCaptureImplManager* VideoCaptureImplManager::GetInstance() { | |
| 101 return Singleton<VideoCaptureImplManager>::get(); | |
| 102 } | |
| 103 | |
| 104 void VideoCaptureImplManager::FreeDevice(VideoCaptureImpl* vc) { | |
| 105 delete vc; | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
looks like this method only gets called on the IO
wjia(left Chromium)
2011/05/17 04:00:25
VideoCaptureImplManager::AddDevice() can be called
| |
| 106 } | |
| 107 | |
| 108 VideoCaptureImpl::VideoCaptureImpl( | |
| 109 const media::VideoCaptureSessionId id, | |
| 110 scoped_refptr<base::MessageLoopProxy> ml_proxy, | |
| 111 VideoCaptureMessageFilter* filter) | |
| 112 : VideoCapture(), | |
| 113 filter_(filter), | |
| 114 session_id_(id), | |
| 115 ml_proxy_(ml_proxy), | |
| 116 device_id_(0), | |
| 117 width_(0), | |
| 118 height_(0), | |
| 119 frame_rate_(0), | |
| 120 video_type_(media::VideoFrame::I420), | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
not initializing new_width_ / new_height_
wjia(left Chromium)
2011/05/17 04:00:25
Done.
| |
| 121 state_(kStopped) { | |
| 122 DCHECK(filter); | |
| 123 } | |
| 124 | |
| 125 VideoCaptureImpl::~VideoCaptureImpl() { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
nit: collapse {} onto one line if it's <80chars
wjia(left Chromium)
2011/05/17 04:00:25
Done.
| |
| 126 } | |
| 127 | |
| 128 void VideoCaptureImpl::Init() { | |
| 129 AddDelegateOnIOThread(); | |
| 130 } | |
| 131 | |
| 132 void VideoCaptureImpl::DeInit(Task* task) { | |
| 133 StopDevice(); | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
hrmmmm... so again I'm not 100% on which thread is
wjia(left Chromium)
2011/05/17 04:00:25
You are right. Thanks for pointing this out. I nee
| |
| 134 RemoveDelegateOnIOThread(task); | |
| 135 } | |
| 136 | |
| 137 void VideoCaptureImpl::StartCapture( | |
| 138 media::VideoCapture::EventHandler* handler, | |
| 139 const VideoCaptureCapability& capability) { | |
| 140 DCHECK_EQ(capability.raw_type, media::VideoFrame::I420); | |
| 141 | |
| 142 if (!ml_proxy_->BelongsToCurrentThread()) { | |
| 143 ml_proxy_->PostTask(FROM_HERE, | |
| 144 NewRunnableMethod(this, &VideoCaptureImpl::StartCapture, handler, | |
| 145 capability)); | |
| 146 return; | |
| 147 } | |
| 148 | |
| 149 if (capability.resolution_fixed && master_clients_.size() && | |
| 150 (capability.width != width_ || capability.height != height_)) { | |
| 151 // Can't have 2 master clients with different resolutions. | |
| 152 handler->OnError(this, 1); | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 clients_[handler] = capability; | |
| 157 if (capability.resolution_fixed) { | |
| 158 master_clients_.push_back(handler); | |
| 159 if (master_clients_.size() > 1) | |
| 160 return; | |
| 161 } | |
| 162 | |
| 163 if (state_ == kStarted) { | |
| 164 // Take the resolution of master client. | |
| 165 if (capability.resolution_fixed && | |
| 166 (capability.width != width_ || capability.height != height_)) { | |
| 167 new_width_ = capability.width; | |
| 168 new_height_ = capability.height; | |
| 169 DLOG(INFO) << "StartCapture: Got master client with new resolution " | |
| 170 "during started, try to restart."; | |
| 171 StopDevice(); | |
| 172 } | |
| 173 handler->OnStarted(this); | |
| 174 return; | |
| 175 } | |
| 176 | |
| 177 if (state_ == kStopping) { | |
| 178 if (capability.resolution_fixed || !pending_start()) { | |
| 179 new_width_ = capability.width; | |
| 180 new_height_ = capability.height; | |
| 181 DLOG(INFO) << "StartCapture: Got new resolution, already in stopping."; | |
| 182 } | |
| 183 handler->OnStarted(this); | |
| 184 return; | |
| 185 } | |
| 186 | |
| 187 DCHECK_EQ(clients_.size(), 1ul); | |
| 188 video_type_ = capability.raw_type; | |
| 189 new_width_ = 0; | |
| 190 new_height_ = 0; | |
| 191 width_ = capability.width; | |
| 192 height_ = capability.height; | |
| 193 | |
| 194 StartCaptureInternal(); | |
| 195 } | |
| 196 | |
| 197 void VideoCaptureImpl::StopCapture(media::VideoCapture::EventHandler* handler) { | |
| 198 if (!ml_proxy_->BelongsToCurrentThread()) { | |
| 199 ml_proxy_->PostTask(FROM_HERE, | |
| 200 NewRunnableMethod(this, &VideoCaptureImpl::StopCapture, handler)); | |
| 201 return; | |
| 202 } | |
| 203 | |
| 204 if (clients_.find(handler) == clients_.end()) | |
| 205 return; | |
| 206 | |
| 207 handler->OnStopped(this); | |
| 208 clients_.erase(handler); | |
| 209 master_clients_.remove(handler); | |
| 210 | |
| 211 // Still have at least one master client. | |
| 212 if (master_clients_.size() > 0) | |
| 213 return; | |
| 214 | |
| 215 // TODO(wjia): Is it really needed to handle resolution change for non-master | |
| 216 // clients, except no client case? | |
| 217 if (clients_.size() > 0) { | |
| 218 DLOG(INFO) << "StopCapture: No master client."; | |
| 219 int maxw = 0; | |
| 220 int maxh = 0; | |
| 221 for (ClientInfo::iterator it = clients_.begin(); | |
| 222 it != clients_.end(); it++) { | |
| 223 if (it->second.width > maxw && it->second.height > maxh) { | |
| 224 maxw = it->second.width; | |
| 225 maxh = it->second.height; | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 if (state_ == kStarted) { | |
| 230 // Only handle resolution reduction. | |
| 231 if (maxw < width_ && maxh < height_) { | |
| 232 new_width_ = maxw; | |
| 233 new_height_ = maxh; | |
| 234 DLOG(INFO) << "StopCapture: New smaller resolution, stopping ..."; | |
| 235 StopDevice(); | |
| 236 } | |
| 237 return; | |
| 238 } | |
| 239 | |
| 240 if (state_ == kStopping) { | |
| 241 new_width_ = maxw; | |
| 242 new_height_ = maxh; | |
| 243 DLOG(INFO) << "StopCapture: New resolution, during stopping."; | |
| 244 return; | |
| 245 } | |
| 246 } else { | |
| 247 new_width_ = width_ = 0; | |
| 248 new_height_ = height_ = 0; | |
| 249 DLOG(INFO) << "StopCapture: No more client, stopping ..."; | |
| 250 StopDevice(); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 void VideoCaptureImpl::OnBufferReceived(TransportDIB::Handle handle, | |
| 255 base::Time timestamp) { | |
| 256 if (!ml_proxy_->BelongsToCurrentThread()) { | |
| 257 ml_proxy_->PostTask(FROM_HERE, | |
| 258 NewRunnableMethod(this, &VideoCaptureImpl::OnBufferReceived, | |
| 259 handle, timestamp)); | |
| 260 return; | |
| 261 } | |
| 262 | |
| 263 if (state_ != kStarted) { | |
| 264 filter_->Send( | |
| 265 new VideoCaptureHostMsg_BufferReady(0, device_id_, handle)); | |
| 266 return; | |
| 267 } | |
| 268 | |
| 269 media::VideoCapture::VideoFrameBuffer* buffer; | |
| 270 CachedDIB::iterator it; | |
| 271 for (it = cached_dibs_.begin(); it != cached_dibs_.end(); it++) { | |
| 272 if ((*it)->dib->handle() == handle) | |
| 273 break; | |
| 274 } | |
| 275 if (it == cached_dibs_.end()) { | |
| 276 TransportDIB* dib = TransportDIB::Map(handle); | |
| 277 buffer = new VideoFrameBuffer; | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
add ()
wjia(left Chromium)
2011/05/17 04:00:25
Done.
| |
| 278 buffer->memory_pointer = dib->memory(); | |
| 279 buffer->buffer_size = dib->size(); | |
| 280 buffer->width = width_; | |
| 281 buffer->height = height_; | |
| 282 | |
| 283 DIBBuffer* dib_buffer = new DIBBuffer(dib, buffer); | |
| 284 cached_dibs_.push_back(dib_buffer); | |
| 285 } else { | |
| 286 buffer = (*it)->mapped_memory; | |
| 287 } | |
| 288 | |
| 289 // TODO(wjia): handle buffer sharing with downstream modules. | |
| 290 for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); it++) { | |
| 291 it->first->OnBufferReady(this, buffer); | |
| 292 } | |
| 293 | |
| 294 filter_->Send( | |
| 295 new VideoCaptureHostMsg_BufferReady(0, device_id_, handle)); | |
| 296 } | |
| 297 | |
| 298 void VideoCaptureImpl::OnStateChanged( | |
| 299 const media::VideoCapture::State& state) { | |
| 300 if (!ml_proxy_->BelongsToCurrentThread()) { | |
| 301 ml_proxy_->PostTask(FROM_HERE, | |
| 302 NewRunnableMethod(this, &VideoCaptureImpl::OnStateChanged, state)); | |
| 303 return; | |
| 304 } | |
| 305 | |
| 306 switch (state) { | |
| 307 case media::VideoCapture::kStarted: | |
| 308 for (ClientInfo::iterator it = clients_.begin(); | |
| 309 it != clients_.end(); it++) { | |
| 310 it->first->OnStarted(this); | |
| 311 } | |
| 312 break; | |
| 313 case media::VideoCapture::kStopped: | |
| 314 state_ = kStopped; | |
| 315 DLOG(INFO) << "OnStateChanged: stopped!, device_id = " << device_id_; | |
| 316 if (pending_start()) | |
| 317 RestartCapture(); | |
| 318 break; | |
| 319 case media::VideoCapture::kPaused: | |
| 320 for (ClientInfo::iterator it = clients_.begin(); | |
| 321 it != clients_.end(); it++) { | |
| 322 it->first->OnPaused(this); | |
| 323 } | |
| 324 break; | |
| 325 case media::VideoCapture::kError: | |
| 326 for (ClientInfo::iterator it = clients_.begin(); | |
| 327 it != clients_.end(); it++) { | |
| 328 // TODO(wjia): browser process would send error code. | |
| 329 it->first->OnError(this, 1); | |
| 330 } | |
| 331 break; | |
| 332 default: | |
| 333 break; | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 void VideoCaptureImpl::OnDeviceInfoReceived( | |
| 338 const media::VideoCaptureParams& device_info) { | |
| 339 if (!ml_proxy_->BelongsToCurrentThread()) { | |
| 340 ml_proxy_->PostTask(FROM_HERE, | |
| 341 NewRunnableMethod(this, &VideoCaptureImpl::OnDeviceInfoReceived, | |
| 342 device_info)); | |
| 343 return; | |
| 344 } | |
| 345 | |
| 346 for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); it++) { | |
| 347 it->first->OnDeviceInfoReceived(this, device_info); | |
| 348 } | |
| 349 } | |
| 350 | |
| 351 void VideoCaptureImpl::StopDevice() { | |
| 352 if (!ml_proxy_->BelongsToCurrentThread()) { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
the idea behind using the proxy is that you don't
| |
| 353 ml_proxy_->PostTask(FROM_HERE, | |
| 354 NewRunnableMethod(this, &VideoCaptureImpl::StopDevice)); | |
| 355 return; | |
| 356 } | |
| 357 | |
| 358 if (state_ == kStarted) { | |
| 359 state_ = kStopping; | |
| 360 filter_->Send(new VideoCaptureHostMsg_Stop(0, device_id_)); | |
| 361 width_ = height_ = 0; | |
| 362 } | |
| 363 } | |
| 364 | |
| 365 void VideoCaptureImpl::RestartCapture() { | |
| 366 DCHECK(ml_proxy_->BelongsToCurrentThread()); | |
| 367 DCHECK_EQ(state_, kStopped); | |
| 368 | |
| 369 width_ = new_width_; | |
| 370 height_ = new_height_; | |
| 371 new_width_ = 0; | |
| 372 new_height_ = 0; | |
| 373 | |
| 374 DLOG(INFO) << "RestartCapture, " << width_ << ", " << height_; | |
| 375 StartCaptureInternal(); | |
| 376 } | |
| 377 | |
| 378 void VideoCaptureImpl::StartCaptureInternal() { | |
| 379 DCHECK(ml_proxy_->BelongsToCurrentThread()); | |
| 380 | |
| 381 media::VideoCaptureParams params; | |
| 382 params.width = width_; | |
| 383 params.height = height_; | |
| 384 params.session_id = session_id_; | |
| 385 | |
| 386 int try_times = 100; | |
| 387 bool result = false; | |
| 388 for (int i = 0; i < try_times; i++) { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
why do we have to try 100 times?
wjia(left Chromium)
2011/05/17 04:00:25
I just pick up this number for timeout. The reason
| |
| 389 if (device_id_ && | |
| 390 filter_->Send(new VideoCaptureHostMsg_Start(0, device_id_, params))) { | |
| 391 result = true; | |
| 392 break; | |
| 393 } | |
| 394 | |
| 395 base::PlatformThread::Sleep(1000); | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
???
wjia(left Chromium)
2011/05/17 04:00:25
When either |channel_| or |device_id_| is not read
| |
| 396 } | |
| 397 | |
| 398 if (!result) { | |
| 399 state_ = kStopped; | |
| 400 width_ = height_ = 0; | |
| 401 for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); | |
| 402 it++) { | |
| 403 it->first->OnError(this, 2); | |
| 404 } | |
| 405 } else { | |
| 406 state_ = kStarted; | |
| 407 for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); | |
| 408 it++) { | |
| 409 it->first->OnStarted(this); | |
| 410 } | |
| 411 } | |
| 412 } | |
| 413 | |
| 414 void VideoCaptureImpl::AddDelegateOnIOThread() { | |
| 415 DCHECK(ChildProcess::current()) << "Must be in the renderer"; | |
| 416 base::MessageLoopProxy* message_loop_proxy = | |
| 417 ChildProcess::current()->io_message_loop_proxy(); | |
| 418 DCHECK(message_loop_proxy); | |
| 419 | |
| 420 if (!message_loop_proxy->BelongsToCurrentThread()) { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
considering you only have one caller of AddDelegat
wjia(left Chromium)
2011/05/17 04:00:25
Done.
| |
| 421 message_loop_proxy->PostTask(FROM_HERE, | |
| 422 NewRunnableMethod(this, &VideoCaptureImpl::AddDelegateOnIOThread)); | |
| 423 return; | |
| 424 } | |
| 425 | |
| 426 device_id_ = filter_->AddDelegate(this); | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
I think there's a race between StartCaptureInterna
wjia(left Chromium)
2011/05/17 04:00:25
The |device_id_| can be only modified on IO thread
| |
| 427 } | |
| 428 | |
| 429 void VideoCaptureImpl::RemoveDelegateOnIOThread(Task* task) { | |
| 430 DCHECK(ChildProcess::current()) << "Must be in the renderer"; | |
| 431 base::MessageLoopProxy* message_loop_proxy = | |
| 432 ChildProcess::current()->io_message_loop_proxy(); | |
| 433 DCHECK(message_loop_proxy); | |
| 434 | |
| 435 if (!message_loop_proxy->BelongsToCurrentThread()) { | |
|
scherkus (not reviewing)
2011/05/14 23:33:42
ditto
wjia(left Chromium)
2011/05/17 04:00:25
Done.
| |
| 436 message_loop_proxy->PostTask(FROM_HERE, | |
| 437 NewRunnableMethod(this, &VideoCaptureImpl::RemoveDelegateOnIOThread, | |
| 438 task)); | |
| 439 return; | |
| 440 } | |
| 441 | |
| 442 filter_->RemoveDelegate(this); | |
| 443 media::AutoTaskRunner auto_runner(task); | |
| 444 } | |
| OLD | NEW |