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/browser/renderer_host/media/video_capture_controller.h" 6 7 #include <map> 8 #include <set> 9 10 #include "base/bind.h" 11 #include "base/debug/trace_event.h" 12 #include "base/metrics/histogram.h" 13 #include "base/metrics/sparse_histogram.h" 14 #include "base/stl_util.h" 15 #include "content/browser/renderer_host/media/media_stream_manager.h" 16 #include "content/browser/renderer_host/media/video_capture_manager.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "gpu/command_buffer/common/mailbox_holder.h" 19 #include "media/base/video_frame.h" 20 #include "media/base/video_util.h" 21 #include "media/base/yuv_convert.h" 22 #include "third_party/libyuv/include/libyuv.h" 23 24 using media::VideoCaptureFormat; 25 26 namespace content { 27 28 namespace { 29 30 static const int kInfiniteRatio = 99999; 31 32 #define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \ 33 UMA_HISTOGRAM_SPARSE_SLOWLY( \ 34 name, \ 35 (height) ? ((width) * 100) / (height) : kInfiniteRatio); 36 37 // The number of buffers that VideoCaptureBufferPool should allocate. 38 const int kNoOfBuffers = 3; 39 40 class PoolBuffer : public media::VideoCaptureDevice::Client::Buffer { 41 public: 42 PoolBuffer(const scoped_refptr<VideoCaptureBufferPool>& pool, 43 int buffer_id, 44 void* data, 45 size_t size) 46 : Buffer(buffer_id, data, size), pool_(pool) { 47 DCHECK(pool_); 48 } 49 50 private: 51 virtual ~PoolBuffer() { pool_->RelinquishProducerReservation(id()); } 52 53 const scoped_refptr<VideoCaptureBufferPool> pool_; 54 }; 55 56 } // anonymous namespace 57 58 struct VideoCaptureController::ControllerClient { 59 ControllerClient(const VideoCaptureControllerID& id, 60 VideoCaptureControllerEventHandler* handler, 61 base::ProcessHandle render_process, 62 media::VideoCaptureSessionId session_id, 63 const media::VideoCaptureParams& params) 64 : controller_id(id), 65 event_handler(handler), 66 render_process_handle(render_process), 67 session_id(session_id), 68 parameters(params), 69 session_closed(false) {} 70 71 ~ControllerClient() {} 72 73 // ID used for identifying this object. 74 const VideoCaptureControllerID controller_id; 75 VideoCaptureControllerEventHandler* const event_handler; 76 77 // Handle to the render process that will receive the capture buffers. 78 const base::ProcessHandle render_process_handle; 79 const media::VideoCaptureSessionId session_id; 80 const media::VideoCaptureParams parameters; 81 82 // Buffers that are currently known to this client. 83 std::set<int> known_buffers; 84 85 // Buffers currently held by this client, and syncpoint callback to call when 86 // they are returned from the client. 87 typedef std::map<int, scoped_refptr<media::VideoFrame> > ActiveBufferMap; 88 ActiveBufferMap active_buffers; 89 90 // State of capture session, controlled by VideoCaptureManager directly. This 91 // transitions to true as soon as StopSession() occurs, at which point the 92 // client is sent an OnEnded() event. However, because the client retains a 93 // VideoCaptureController* pointer, its ControllerClient entry lives on until 94 // it unregisters itself via RemoveClient(), which may happen asynchronously. 95 // 96 // TODO(nick): If we changed the semantics of VideoCaptureHost so that 97 // OnEnded() events were processed synchronously (with the RemoveClient() done 98 // implicitly), we could avoid tracking this state here in the Controller, and 99 // simplify the code in both places. 100 bool session_closed; 101 }; 102 103 // Receives events from the VideoCaptureDevice and posts them to a 104 // VideoCaptureController on the IO thread. An instance of this class may safely 105 // outlive its target VideoCaptureController. 106 // 107 // Methods of this class may be called from any thread, and in practice will 108 // often be called on some auxiliary thread depending on the platform and the 109 // device type; including, for example, the DirectShow thread on Windows, the 110 // v4l2_thread on Linux, and the UI thread for tab capture. 111 class VideoCaptureController::VideoCaptureDeviceClient 112 : public media::VideoCaptureDevice::Client { 113 public: 114 explicit VideoCaptureDeviceClient( 115 const base::WeakPtr<VideoCaptureController>& controller, 116 const scoped_refptr<VideoCaptureBufferPool>& buffer_pool); 117 virtual ~VideoCaptureDeviceClient(); 118 119 // VideoCaptureDevice::Client implementation. 120 virtual scoped_refptr<Buffer> ReserveOutputBuffer( 121 media::VideoFrame::Format format, 122 const gfx::Size& size) OVERRIDE; 123 virtual void OnIncomingCapturedData(const uint8* data, 124 int length, 125 const VideoCaptureFormat& frame_format, 126 int rotation, 127 base::TimeTicks timestamp) OVERRIDE; 128 virtual void OnIncomingCapturedVideoFrame( 129 const scoped_refptr<Buffer>& buffer, 130 const VideoCaptureFormat& buffer_format, 131 const scoped_refptr<media::VideoFrame>& frame, 132 base::TimeTicks timestamp) OVERRIDE; 133 virtual void OnError(const std::string& reason) OVERRIDE; 134 virtual void OnLog(const std::string& message) OVERRIDE; 135 136 private: 137 scoped_refptr<Buffer> DoReserveOutputBuffer(media::VideoFrame::Format format, 138 const gfx::Size& dimensions); 139 140 // The controller to which we post events. 141 const base::WeakPtr<VideoCaptureController> controller_; 142 143 // The pool of shared-memory buffers used for capturing. 144 const scoped_refptr<VideoCaptureBufferPool> buffer_pool_; 145 146 bool first_frame_; 147 }; 148 149 VideoCaptureController::VideoCaptureController() 150 : buffer_pool_(new VideoCaptureBufferPool(kNoOfBuffers)), 151 state_(VIDEO_CAPTURE_STATE_STARTED), 152 weak_ptr_factory_(this) { 153 } 154 155 VideoCaptureController::VideoCaptureDeviceClient::VideoCaptureDeviceClient( 156 const base::WeakPtr<VideoCaptureController>& controller, 157 const scoped_refptr<VideoCaptureBufferPool>& buffer_pool) 158 : controller_(controller), buffer_pool_(buffer_pool), first_frame_(true) {} 159 160 VideoCaptureController::VideoCaptureDeviceClient::~VideoCaptureDeviceClient() {} 161 162 base::WeakPtr<VideoCaptureController> VideoCaptureController::GetWeakPtr() { 163 return weak_ptr_factory_.GetWeakPtr(); 164 } 165 166 scoped_ptr<media::VideoCaptureDevice::Client> 167 VideoCaptureController::NewDeviceClient() { 168 scoped_ptr<media::VideoCaptureDevice::Client> result( 169 new VideoCaptureDeviceClient(this->GetWeakPtr(), buffer_pool_)); 170 return result.Pass(); 171 } 172 173 void VideoCaptureController::AddClient( 174 const VideoCaptureControllerID& id, 175 VideoCaptureControllerEventHandler* event_handler, 176 base::ProcessHandle render_process, 177 media::VideoCaptureSessionId session_id, 178 const media::VideoCaptureParams& params) { 179 DCHECK_CURRENTLY_ON(BrowserThread::IO); 180 DVLOG(1) << "VideoCaptureController::AddClient, id " << id.device_id 181 << ", " << params.requested_format.frame_size.ToString() 182 << ", " << params.requested_format.frame_rate 183 << ", " << session_id 184 << ")"; 185 186 // If this is the first client added to the controller, cache the parameters. 187 if (!controller_clients_.size()) 188 video_capture_format_ = params.requested_format; 189 190 // Signal error in case device is already in error state. 191 if (state_ == VIDEO_CAPTURE_STATE_ERROR) { 192 event_handler->OnError(id); 193 return; 194 } 195 196 // Do nothing if this client has called AddClient before. 197 if (FindClient(id, event_handler, controller_clients_)) 198 return; 199 200 ControllerClient* client = new ControllerClient( 201 id, event_handler, render_process, session_id, params); 202 // If we already have gotten frame_info from the device, repeat it to the new 203 // client. 204 if (state_ == VIDEO_CAPTURE_STATE_STARTED) { 205 controller_clients_.push_back(client); 206 return; 207 } 208 } 209 210 int VideoCaptureController::RemoveClient( 211 const VideoCaptureControllerID& id, 212 VideoCaptureControllerEventHandler* event_handler) { 213 DCHECK_CURRENTLY_ON(BrowserThread::IO); 214 DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id.device_id; 215 216 ControllerClient* client = FindClient(id, event_handler, controller_clients_); 217 if (!client) 218 return kInvalidMediaCaptureSessionId; 219 220 // Take back all buffers held by the |client|. 221 for (ControllerClient::ActiveBufferMap::iterator buffer_it = 222 client->active_buffers.begin(); 223 buffer_it != client->active_buffers.end(); 224 ++buffer_it) { 225 buffer_pool_->RelinquishConsumerHold(buffer_it->first, 1); 226 } 227 client->active_buffers.clear(); 228 229 int session_id = client->session_id; 230 controller_clients_.remove(client); 231 delete client; 232 233 return session_id; 234 } 235 236 void VideoCaptureController::StopSession(int session_id) { 237 DCHECK_CURRENTLY_ON(BrowserThread::IO); 238 DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id; 239 240 ControllerClient* client = FindClient(session_id, controller_clients_); 241 242 if (client) { 243 client->session_closed = true; 244 client->event_handler->OnEnded(client->controller_id); 245 } 246 } 247 248 void VideoCaptureController::ReturnBuffer( 249 const VideoCaptureControllerID& id, 250 VideoCaptureControllerEventHandler* event_handler, 251 int buffer_id, 252 const std::vector<uint32>& sync_points) { 253 DCHECK_CURRENTLY_ON(BrowserThread::IO); 254 255 ControllerClient* client = FindClient(id, event_handler, controller_clients_); 256 257 // If this buffer is not held by this client, or this client doesn't exist 258 // in controller, do nothing. 259 ControllerClient::ActiveBufferMap::iterator iter; 260 if (!client || (iter = client->active_buffers.find(buffer_id)) == 261 client->active_buffers.end()) { 262 NOTREACHED(); 263 return; 264 } 265 scoped_refptr<media::VideoFrame> frame = iter->second; 266 client->active_buffers.erase(iter); 267 268 if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { 269 for (size_t i = 0; i < sync_points.size(); i++) 270 frame->AppendReleaseSyncPoint(sync_points[i]); 271 } 272 273 buffer_pool_->RelinquishConsumerHold(buffer_id, 1); 274 } 275 276 const media::VideoCaptureFormat& 277 VideoCaptureController::GetVideoCaptureFormat() const { 278 DCHECK_CURRENTLY_ON(BrowserThread::IO); 279 return video_capture_format_; 280 } 281 282 scoped_refptr<media::VideoCaptureDevice::Client::Buffer> 283 VideoCaptureController::VideoCaptureDeviceClient::ReserveOutputBuffer( 284 media::VideoFrame::Format format, 285 const gfx::Size& size) { 286 return DoReserveOutputBuffer(format, size); 287 } 288 289 void VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedData( 290 const uint8* data, 291 int length, 292 const VideoCaptureFormat& frame_format, 293 int rotation, 294 base::TimeTicks timestamp) { 295 TRACE_EVENT0("video", "VideoCaptureController::OnIncomingCapturedData"); 296 297 if (!frame_format.IsValid()) 298 return; 299 300 // Chopped pixels in width/height in case video capture device has odd 301 // numbers for width/height. 302 int chopped_width = 0; 303 int chopped_height = 0; 304 int new_unrotated_width = frame_format.frame_size.width(); 305 int new_unrotated_height = frame_format.frame_size.height(); 306 307 if (new_unrotated_width & 1) { 308 --new_unrotated_width; 309 chopped_width = 1; 310 } 311 if (new_unrotated_height & 1) { 312 --new_unrotated_height; 313 chopped_height = 1; 314 } 315 316 int destination_width = new_unrotated_width; 317 int destination_height = new_unrotated_height; 318 if (rotation == 90 || rotation == 270) { 319 destination_width = new_unrotated_height; 320 destination_height = new_unrotated_width; 321 } 322 const gfx::Size dimensions(destination_width, destination_height); 323 if (!media::VideoFrame::IsValidConfig(media::VideoFrame::I420, 324 dimensions, 325 gfx::Rect(dimensions), 326 dimensions)) { 327 return; 328 } 329 330 scoped_refptr<Buffer> buffer = 331 DoReserveOutputBuffer(media::VideoFrame::I420, dimensions); 332 333 if (!buffer) 334 return; 335 uint8* yplane = NULL; 336 bool flip = false; 337 yplane = reinterpret_cast<uint8*>(buffer->data()); 338 uint8* uplane = 339 yplane + 340 media::VideoFrame::PlaneAllocationSize( 341 media::VideoFrame::I420, media::VideoFrame::kYPlane, dimensions); 342 uint8* vplane = 343 uplane + 344 media::VideoFrame::PlaneAllocationSize( 345 media::VideoFrame::I420, media::VideoFrame::kUPlane, dimensions); 346 int yplane_stride = dimensions.width(); 347 int uv_plane_stride = yplane_stride / 2; 348 int crop_x = 0; 349 int crop_y = 0; 350 libyuv::FourCC origin_colorspace = libyuv::FOURCC_ANY; 351 352 libyuv::RotationMode rotation_mode = libyuv::kRotate0; 353 if (rotation == 90) 354 rotation_mode = libyuv::kRotate90; 355 else if (rotation == 180) 356 rotation_mode = libyuv::kRotate180; 357 else if (rotation == 270) 358 rotation_mode = libyuv::kRotate270; 359 360 switch (frame_format.pixel_format) { 361 case media::PIXEL_FORMAT_UNKNOWN: // Color format not set. 362 break; 363 case media::PIXEL_FORMAT_I420: 364 DCHECK(!chopped_width && !chopped_height); 365 origin_colorspace = libyuv::FOURCC_I420; 366 break; 367 case media::PIXEL_FORMAT_YV12: 368 DCHECK(!chopped_width && !chopped_height); 369 origin_colorspace = libyuv::FOURCC_YV12; 370 break; 371 case media::PIXEL_FORMAT_NV21: 372 DCHECK(!chopped_width && !chopped_height); 373 origin_colorspace = libyuv::FOURCC_NV21; 374 break; 375 case media::PIXEL_FORMAT_YUY2: 376 DCHECK(!chopped_width && !chopped_height); 377 origin_colorspace = libyuv::FOURCC_YUY2; 378 break; 379 case media::PIXEL_FORMAT_UYVY: 380 DCHECK(!chopped_width && !chopped_height); 381 origin_colorspace = libyuv::FOURCC_UYVY; 382 break; 383 case media::PIXEL_FORMAT_RGB24: 384 origin_colorspace = libyuv::FOURCC_24BG; 385 #if defined(OS_WIN) 386 // TODO(wjia): Currently, for RGB24 on WIN, capture device always 387 // passes in positive src_width and src_height. Remove this hardcoded 388 // value when nagative src_height is supported. The negative src_height 389 // indicates that vertical flipping is needed. 390 flip = true; 391 #endif 392 break; 393 case media::PIXEL_FORMAT_ARGB: 394 origin_colorspace = libyuv::FOURCC_ARGB; 395 break; 396 case media::PIXEL_FORMAT_MJPEG: 397 origin_colorspace = libyuv::FOURCC_MJPG; 398 break; 399 default: 400 NOTREACHED(); 401 } 402 403 libyuv::ConvertToI420(data, 404 length, 405 yplane, 406 yplane_stride, 407 uplane, 408 uv_plane_stride, 409 vplane, 410 uv_plane_stride, 411 crop_x, 412 crop_y, 413 frame_format.frame_size.width(), 414 (flip ? -frame_format.frame_size.height() : 415 frame_format.frame_size.height()), 416 new_unrotated_width, 417 new_unrotated_height, 418 rotation_mode, 419 origin_colorspace); 420 scoped_refptr<media::VideoFrame> frame = 421 media::VideoFrame::WrapExternalPackedMemory( 422 media::VideoFrame::I420, 423 dimensions, 424 gfx::Rect(dimensions), 425 dimensions, 426 yplane, 427 media::VideoFrame::AllocationSize(media::VideoFrame::I420, 428 dimensions), 429 base::SharedMemory::NULLHandle(), 430 base::TimeDelta(), 431 base::Closure()); 432 DCHECK(frame); 433 434 VideoCaptureFormat format( 435 dimensions, frame_format.frame_rate, media::PIXEL_FORMAT_I420); 436 BrowserThread::PostTask( 437 BrowserThread::IO, 438 FROM_HERE, 439 base::Bind( 440 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread, 441 controller_, 442 buffer, 443 format, 444 frame, 445 timestamp)); 446 447 if (first_frame_) { 448 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width", 449 frame_format.frame_size.width()); 450 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height", 451 frame_format.frame_size.height()); 452 UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio", 453 frame_format.frame_size.width(), 454 frame_format.frame_size.height()); 455 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate", 456 frame_format.frame_rate); 457 UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.PixelFormat", 458 frame_format.pixel_format, 459 media::PIXEL_FORMAT_MAX); 460 first_frame_ = false; 461 } 462 } 463 464 void 465 VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedVideoFrame( 466 const scoped_refptr<Buffer>& buffer, 467 const VideoCaptureFormat& buffer_format, 468 const scoped_refptr<media::VideoFrame>& frame, 469 base::TimeTicks timestamp) { 470 BrowserThread::PostTask( 471 BrowserThread::IO, 472 FROM_HERE, 473 base::Bind( 474 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread, 475 controller_, 476 buffer, 477 buffer_format, 478 frame, 479 timestamp)); 480 } 481 482 void VideoCaptureController::VideoCaptureDeviceClient::OnError( 483 const std::string& reason) { 484 MediaStreamManager::SendMessageToNativeLog( 485 "Error on video capture: " + reason); 486 BrowserThread::PostTask(BrowserThread::IO, 487 FROM_HERE, 488 base::Bind(&VideoCaptureController::DoErrorOnIOThread, controller_)); 489 } 490 491 void VideoCaptureController::VideoCaptureDeviceClient::OnLog( 492 const std::string& message) { 493 MediaStreamManager::SendMessageToNativeLog("Video capture: " + message); 494 } 495 496 scoped_refptr<media::VideoCaptureDevice::Client::Buffer> 497 VideoCaptureController::VideoCaptureDeviceClient::DoReserveOutputBuffer( 498 media::VideoFrame::Format format, 499 const gfx::Size& dimensions) { 500 size_t frame_bytes = 0; 501 if (format == media::VideoFrame::NATIVE_TEXTURE) { 502 DCHECK_EQ(dimensions.width(), 0); 503 DCHECK_EQ(dimensions.height(), 0); 504 } else { 505 // The capture pipeline expects I420 for now. 506 DCHECK_EQ(format, media::VideoFrame::I420) 507 << "Non-I420 output buffer format " << format << " requested"; 508 frame_bytes = media::VideoFrame::AllocationSize(format, dimensions); 509 } 510 511 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; 512 int buffer_id = 513 buffer_pool_->ReserveForProducer(frame_bytes, &buffer_id_to_drop); 514 if (buffer_id == VideoCaptureBufferPool::kInvalidId) 515 return NULL; 516 void* data; 517 size_t size; 518 buffer_pool_->GetBufferInfo(buffer_id, &data, &size); 519 520 scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer( 521 new PoolBuffer(buffer_pool_, buffer_id, data, size)); 522 523 if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) { 524 BrowserThread::PostTask(BrowserThread::IO, 525 FROM_HERE, 526 base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread, 527 controller_, buffer_id_to_drop)); 528 } 529 530 return output_buffer; 531 } 532 533 VideoCaptureController::~VideoCaptureController() { 534 STLDeleteContainerPointers(controller_clients_.begin(), 535 controller_clients_.end()); 536 } 537 538 void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread( 539 const scoped_refptr<media::VideoCaptureDevice::Client::Buffer>& buffer, 540 const media::VideoCaptureFormat& buffer_format, 541 const scoped_refptr<media::VideoFrame>& frame, 542 base::TimeTicks timestamp) { 543 DCHECK_CURRENTLY_ON(BrowserThread::IO); 544 DCHECK_NE(buffer->id(), VideoCaptureBufferPool::kInvalidId); 545 546 int count = 0; 547 if (state_ == VIDEO_CAPTURE_STATE_STARTED) { 548 for (ControllerClients::iterator client_it = controller_clients_.begin(); 549 client_it != controller_clients_.end(); ++client_it) { 550 ControllerClient* client = *client_it; 551 if (client->session_closed) 552 continue; 553 554 if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { 555 client->event_handler->OnMailboxBufferReady(client->controller_id, 556 buffer->id(), 557 *frame->mailbox_holder(), 558 buffer_format, 559 timestamp); 560 } else { 561 bool is_new_buffer = client->known_buffers.insert(buffer->id()).second; 562 if (is_new_buffer) { 563 // On the first use of a buffer on a client, share the memory handle. 564 size_t memory_size = 0; 565 base::SharedMemoryHandle remote_handle = buffer_pool_->ShareToProcess( 566 buffer->id(), client->render_process_handle, &memory_size); 567 client->event_handler->OnBufferCreated( 568 client->controller_id, remote_handle, memory_size, buffer->id()); 569 } 570 571 client->event_handler->OnBufferReady( 572 client->controller_id, buffer->id(), buffer_format, timestamp); 573 } 574 575 bool inserted = 576 client->active_buffers.insert(std::make_pair(buffer->id(), frame)) 577 .second; 578 DCHECK(inserted) << "Unexpected duplicate buffer: " << buffer->id(); 579 count++; 580 } 581 } 582 583 buffer_pool_->HoldForConsumers(buffer->id(), count); 584 } 585 586 void VideoCaptureController::DoErrorOnIOThread() { 587 DCHECK_CURRENTLY_ON(BrowserThread::IO); 588 state_ = VIDEO_CAPTURE_STATE_ERROR; 589 590 for (ControllerClients::iterator client_it = controller_clients_.begin(); 591 client_it != controller_clients_.end(); ++client_it) { 592 ControllerClient* client = *client_it; 593 if (client->session_closed) 594 continue; 595 596 client->event_handler->OnError(client->controller_id); 597 } 598 } 599 600 void VideoCaptureController::DoBufferDestroyedOnIOThread( 601 int buffer_id_to_drop) { 602 DCHECK_CURRENTLY_ON(BrowserThread::IO); 603 604 for (ControllerClients::iterator client_it = controller_clients_.begin(); 605 client_it != controller_clients_.end(); ++client_it) { 606 ControllerClient* client = *client_it; 607 if (client->session_closed) 608 continue; 609 610 if (client->known_buffers.erase(buffer_id_to_drop)) { 611 client->event_handler->OnBufferDestroyed(client->controller_id, 612 buffer_id_to_drop); 613 } 614 } 615 } 616 617 VideoCaptureController::ControllerClient* 618 VideoCaptureController::FindClient( 619 const VideoCaptureControllerID& id, 620 VideoCaptureControllerEventHandler* handler, 621 const ControllerClients& clients) { 622 for (ControllerClients::const_iterator client_it = clients.begin(); 623 client_it != clients.end(); ++client_it) { 624 if ((*client_it)->controller_id == id && 625 (*client_it)->event_handler == handler) { 626 return *client_it; 627 } 628 } 629 return NULL; 630 } 631 632 VideoCaptureController::ControllerClient* 633 VideoCaptureController::FindClient( 634 int session_id, 635 const ControllerClients& clients) { 636 for (ControllerClients::const_iterator client_it = clients.begin(); 637 client_it != clients.end(); ++client_it) { 638 if ((*client_it)->session_id == session_id) { 639 return *client_it; 640 } 641 } 642 return NULL; 643 } 644 645 int VideoCaptureController::GetClientCount() { 646 DCHECK_CURRENTLY_ON(BrowserThread::IO); 647 return controller_clients_.size(); 648 } 649 650 } // namespace content 651