video_capture_controller.cc revision 010d83a9304c5a91596085d917d248abff47903a
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
24using media::VideoCaptureFormat;
25
26namespace content {
27
28namespace {
29
30static 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.
38const int kNoOfBuffers = 3;
39
40class 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
58struct 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.
111class 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
135 private:
136  scoped_refptr<Buffer> DoReserveOutputBuffer(media::VideoFrame::Format format,
137                                              const gfx::Size& dimensions);
138
139  // The controller to which we post events.
140  const base::WeakPtr<VideoCaptureController> controller_;
141
142  // The pool of shared-memory buffers used for capturing.
143  const scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
144
145  bool first_frame_;
146};
147
148VideoCaptureController::VideoCaptureController()
149    : buffer_pool_(new VideoCaptureBufferPool(kNoOfBuffers)),
150      state_(VIDEO_CAPTURE_STATE_STARTED),
151      weak_ptr_factory_(this) {
152}
153
154VideoCaptureController::VideoCaptureDeviceClient::VideoCaptureDeviceClient(
155    const base::WeakPtr<VideoCaptureController>& controller,
156    const scoped_refptr<VideoCaptureBufferPool>& buffer_pool)
157    : controller_(controller), buffer_pool_(buffer_pool), first_frame_(true) {}
158
159VideoCaptureController::VideoCaptureDeviceClient::~VideoCaptureDeviceClient() {}
160
161base::WeakPtr<VideoCaptureController> VideoCaptureController::GetWeakPtr() {
162  return weak_ptr_factory_.GetWeakPtr();
163}
164
165scoped_ptr<media::VideoCaptureDevice::Client>
166VideoCaptureController::NewDeviceClient() {
167  scoped_ptr<media::VideoCaptureDevice::Client> result(
168      new VideoCaptureDeviceClient(this->GetWeakPtr(), buffer_pool_));
169  return result.Pass();
170}
171
172void VideoCaptureController::AddClient(
173    const VideoCaptureControllerID& id,
174    VideoCaptureControllerEventHandler* event_handler,
175    base::ProcessHandle render_process,
176    media::VideoCaptureSessionId session_id,
177    const media::VideoCaptureParams& params) {
178  DCHECK_CURRENTLY_ON(BrowserThread::IO);
179  DVLOG(1) << "VideoCaptureController::AddClient, id " << id.device_id
180           << ", " << params.requested_format.frame_size.ToString()
181           << ", " << params.requested_format.frame_rate
182           << ", " << session_id
183           << ")";
184
185  // If this is the first client added to the controller, cache the parameters.
186  if (!controller_clients_.size())
187    video_capture_format_ = params.requested_format;
188
189  // Signal error in case device is already in error state.
190  if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
191    event_handler->OnError(id);
192    return;
193  }
194
195  // Do nothing if this client has called AddClient before.
196  if (FindClient(id, event_handler, controller_clients_))
197    return;
198
199  ControllerClient* client = new ControllerClient(
200      id, event_handler, render_process, session_id, params);
201  // If we already have gotten frame_info from the device, repeat it to the new
202  // client.
203  if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
204    controller_clients_.push_back(client);
205    return;
206  }
207}
208
209int VideoCaptureController::RemoveClient(
210    const VideoCaptureControllerID& id,
211    VideoCaptureControllerEventHandler* event_handler) {
212  DCHECK_CURRENTLY_ON(BrowserThread::IO);
213  DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id.device_id;
214
215  ControllerClient* client = FindClient(id, event_handler, controller_clients_);
216  if (!client)
217    return kInvalidMediaCaptureSessionId;
218
219  // Take back all buffers held by the |client|.
220  for (ControllerClient::ActiveBufferMap::iterator buffer_it =
221           client->active_buffers.begin();
222       buffer_it != client->active_buffers.end();
223       ++buffer_it) {
224    buffer_pool_->RelinquishConsumerHold(buffer_it->first, 1);
225  }
226  client->active_buffers.clear();
227
228  int session_id = client->session_id;
229  controller_clients_.remove(client);
230  delete client;
231
232  return session_id;
233}
234
235void VideoCaptureController::StopSession(int session_id) {
236  DCHECK_CURRENTLY_ON(BrowserThread::IO);
237  DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id;
238
239  ControllerClient* client = FindClient(session_id, controller_clients_);
240
241  if (client) {
242    client->session_closed = true;
243    client->event_handler->OnEnded(client->controller_id);
244  }
245}
246
247void VideoCaptureController::ReturnBuffer(
248    const VideoCaptureControllerID& id,
249    VideoCaptureControllerEventHandler* event_handler,
250    int buffer_id,
251    const std::vector<uint32>& sync_points) {
252  DCHECK_CURRENTLY_ON(BrowserThread::IO);
253
254  ControllerClient* client = FindClient(id, event_handler, controller_clients_);
255
256  // If this buffer is not held by this client, or this client doesn't exist
257  // in controller, do nothing.
258  ControllerClient::ActiveBufferMap::iterator iter;
259  if (!client || (iter = client->active_buffers.find(buffer_id)) ==
260                     client->active_buffers.end()) {
261    NOTREACHED();
262    return;
263  }
264  scoped_refptr<media::VideoFrame> frame = iter->second;
265  client->active_buffers.erase(iter);
266
267  if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) {
268    for (size_t i = 0; i < sync_points.size(); i++)
269      frame->AppendReleaseSyncPoint(sync_points[i]);
270  }
271
272  buffer_pool_->RelinquishConsumerHold(buffer_id, 1);
273}
274
275const media::VideoCaptureFormat&
276VideoCaptureController::GetVideoCaptureFormat() const {
277  DCHECK_CURRENTLY_ON(BrowserThread::IO);
278  return video_capture_format_;
279}
280
281scoped_refptr<media::VideoCaptureDevice::Client::Buffer>
282VideoCaptureController::VideoCaptureDeviceClient::ReserveOutputBuffer(
283    media::VideoFrame::Format format,
284    const gfx::Size& size) {
285  return DoReserveOutputBuffer(format, size);
286}
287
288void VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedData(
289    const uint8* data,
290    int length,
291    const VideoCaptureFormat& frame_format,
292    int rotation,
293    base::TimeTicks timestamp) {
294  TRACE_EVENT0("video", "VideoCaptureController::OnIncomingCapturedData");
295
296  if (!frame_format.IsValid())
297    return;
298
299  // Chopped pixels in width/height in case video capture device has odd
300  // numbers for width/height.
301  int chopped_width = 0;
302  int chopped_height = 0;
303  int new_unrotated_width = frame_format.frame_size.width();
304  int new_unrotated_height = frame_format.frame_size.height();
305
306  if (new_unrotated_width & 1) {
307    --new_unrotated_width;
308    chopped_width = 1;
309  }
310  if (new_unrotated_height & 1) {
311    --new_unrotated_height;
312    chopped_height = 1;
313  }
314
315  int destination_width = new_unrotated_width;
316  int destination_height = new_unrotated_height;
317  if (rotation == 90 || rotation == 270) {
318    destination_width = new_unrotated_height;
319    destination_height = new_unrotated_width;
320  }
321  const gfx::Size dimensions(destination_width, destination_height);
322  if (!media::VideoFrame::IsValidConfig(media::VideoFrame::I420,
323                                        dimensions,
324                                        gfx::Rect(dimensions),
325                                        dimensions)) {
326    return;
327  }
328
329  scoped_refptr<Buffer> buffer =
330      DoReserveOutputBuffer(media::VideoFrame::I420, dimensions);
331
332  if (!buffer)
333    return;
334  uint8* yplane = NULL;
335  bool flip = false;
336  yplane = reinterpret_cast<uint8*>(buffer->data());
337  uint8* uplane =
338      yplane +
339      media::VideoFrame::PlaneAllocationSize(
340          media::VideoFrame::I420, media::VideoFrame::kYPlane, dimensions);
341  uint8* vplane =
342      uplane +
343      media::VideoFrame::PlaneAllocationSize(
344          media::VideoFrame::I420, media::VideoFrame::kUPlane, dimensions);
345  int yplane_stride = dimensions.width();
346  int uv_plane_stride = yplane_stride / 2;
347  int crop_x = 0;
348  int crop_y = 0;
349  libyuv::FourCC origin_colorspace = libyuv::FOURCC_ANY;
350
351  libyuv::RotationMode rotation_mode = libyuv::kRotate0;
352  if (rotation == 90)
353    rotation_mode = libyuv::kRotate90;
354  else if (rotation == 180)
355    rotation_mode = libyuv::kRotate180;
356  else if (rotation == 270)
357    rotation_mode = libyuv::kRotate270;
358
359  switch (frame_format.pixel_format) {
360    case media::PIXEL_FORMAT_UNKNOWN:  // Color format not set.
361      break;
362    case media::PIXEL_FORMAT_I420:
363      DCHECK(!chopped_width && !chopped_height);
364      origin_colorspace = libyuv::FOURCC_I420;
365      break;
366    case media::PIXEL_FORMAT_YV12:
367      DCHECK(!chopped_width && !chopped_height);
368      origin_colorspace = libyuv::FOURCC_YV12;
369      break;
370    case media::PIXEL_FORMAT_NV21:
371      DCHECK(!chopped_width && !chopped_height);
372      origin_colorspace = libyuv::FOURCC_NV21;
373      break;
374    case media::PIXEL_FORMAT_YUY2:
375      DCHECK(!chopped_width && !chopped_height);
376      origin_colorspace = libyuv::FOURCC_YUY2;
377      break;
378    case media::PIXEL_FORMAT_UYVY:
379      DCHECK(!chopped_width && !chopped_height);
380      origin_colorspace = libyuv::FOURCC_UYVY;
381      break;
382    case media::PIXEL_FORMAT_RGB24:
383      origin_colorspace = libyuv::FOURCC_24BG;
384#if defined(OS_WIN)
385      // TODO(wjia): Currently, for RGB24 on WIN, capture device always
386      // passes in positive src_width and src_height. Remove this hardcoded
387      // value when nagative src_height is supported. The negative src_height
388      // indicates that vertical flipping is needed.
389      flip = true;
390#endif
391      break;
392    case media::PIXEL_FORMAT_ARGB:
393      origin_colorspace = libyuv::FOURCC_ARGB;
394      break;
395    case media::PIXEL_FORMAT_MJPEG:
396      origin_colorspace = libyuv::FOURCC_MJPG;
397      break;
398    default:
399      NOTREACHED();
400  }
401
402  libyuv::ConvertToI420(data,
403                        length,
404                        yplane,
405                        yplane_stride,
406                        uplane,
407                        uv_plane_stride,
408                        vplane,
409                        uv_plane_stride,
410                        crop_x,
411                        crop_y,
412                        frame_format.frame_size.width(),
413                        (flip ? -frame_format.frame_size.height() :
414                                frame_format.frame_size.height()),
415                        new_unrotated_width,
416                        new_unrotated_height,
417                        rotation_mode,
418                        origin_colorspace);
419  scoped_refptr<media::VideoFrame> frame =
420      media::VideoFrame::WrapExternalPackedMemory(
421          media::VideoFrame::I420,
422          dimensions,
423          gfx::Rect(dimensions),
424          dimensions,
425          yplane,
426          media::VideoFrame::AllocationSize(media::VideoFrame::I420,
427                                            dimensions),
428          base::SharedMemory::NULLHandle(),
429          base::TimeDelta(),
430          base::Closure());
431  DCHECK(frame);
432
433  VideoCaptureFormat format(
434      dimensions, frame_format.frame_rate, media::PIXEL_FORMAT_I420);
435  BrowserThread::PostTask(
436      BrowserThread::IO,
437      FROM_HERE,
438      base::Bind(
439          &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread,
440          controller_,
441          buffer,
442          format,
443          frame,
444          timestamp));
445
446  if (first_frame_) {
447    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width",
448                         frame_format.frame_size.width());
449    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height",
450                         frame_format.frame_size.height());
451    UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio",
452                               frame_format.frame_size.width(),
453                               frame_format.frame_size.height());
454    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate",
455                         frame_format.frame_rate);
456    UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.PixelFormat",
457                              frame_format.pixel_format,
458                              media::PIXEL_FORMAT_MAX);
459    first_frame_ = false;
460  }
461}
462
463void
464VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedVideoFrame(
465    const scoped_refptr<Buffer>& buffer,
466    const VideoCaptureFormat& buffer_format,
467    const scoped_refptr<media::VideoFrame>& frame,
468    base::TimeTicks timestamp) {
469  BrowserThread::PostTask(
470      BrowserThread::IO,
471      FROM_HERE,
472      base::Bind(
473          &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread,
474          controller_,
475          buffer,
476          buffer_format,
477          frame,
478          timestamp));
479}
480
481void VideoCaptureController::VideoCaptureDeviceClient::OnError(
482    const std::string& reason) {
483  MediaStreamManager::SendMessageToNativeLog(
484      "Error on video capture: " + reason);
485  BrowserThread::PostTask(BrowserThread::IO,
486      FROM_HERE,
487      base::Bind(&VideoCaptureController::DoErrorOnIOThread, controller_));
488}
489
490scoped_refptr<media::VideoCaptureDevice::Client::Buffer>
491VideoCaptureController::VideoCaptureDeviceClient::DoReserveOutputBuffer(
492    media::VideoFrame::Format format,
493    const gfx::Size& dimensions) {
494  size_t frame_bytes = 0;
495  if (format == media::VideoFrame::NATIVE_TEXTURE) {
496    DCHECK_EQ(dimensions.width(), 0);
497    DCHECK_EQ(dimensions.height(), 0);
498  } else {
499    // The capture pipeline expects I420 for now.
500    DCHECK_EQ(format, media::VideoFrame::I420)
501        << "Non-I420 output buffer format " << format << " requested";
502    frame_bytes = media::VideoFrame::AllocationSize(format, dimensions);
503  }
504
505  int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId;
506  int buffer_id =
507      buffer_pool_->ReserveForProducer(frame_bytes, &buffer_id_to_drop);
508  if (buffer_id == VideoCaptureBufferPool::kInvalidId)
509    return NULL;
510  void* data;
511  size_t size;
512  buffer_pool_->GetBufferInfo(buffer_id, &data, &size);
513
514  scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer(
515      new PoolBuffer(buffer_pool_, buffer_id, data, size));
516
517  if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) {
518    BrowserThread::PostTask(BrowserThread::IO,
519        FROM_HERE,
520        base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread,
521                   controller_, buffer_id_to_drop));
522  }
523
524  return output_buffer;
525}
526
527VideoCaptureController::~VideoCaptureController() {
528  STLDeleteContainerPointers(controller_clients_.begin(),
529                             controller_clients_.end());
530}
531
532void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread(
533    const scoped_refptr<media::VideoCaptureDevice::Client::Buffer>& buffer,
534    const media::VideoCaptureFormat& buffer_format,
535    const scoped_refptr<media::VideoFrame>& frame,
536    base::TimeTicks timestamp) {
537  DCHECK_CURRENTLY_ON(BrowserThread::IO);
538  DCHECK_NE(buffer->id(), VideoCaptureBufferPool::kInvalidId);
539
540  int count = 0;
541  if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
542    for (ControllerClients::iterator client_it = controller_clients_.begin();
543         client_it != controller_clients_.end(); ++client_it) {
544      ControllerClient* client = *client_it;
545      if (client->session_closed)
546        continue;
547
548      if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) {
549        client->event_handler->OnMailboxBufferReady(client->controller_id,
550                                                    buffer->id(),
551                                                    *frame->mailbox_holder(),
552                                                    buffer_format,
553                                                    timestamp);
554      } else {
555        bool is_new_buffer = client->known_buffers.insert(buffer->id()).second;
556        if (is_new_buffer) {
557          // On the first use of a buffer on a client, share the memory handle.
558          size_t memory_size = 0;
559          base::SharedMemoryHandle remote_handle = buffer_pool_->ShareToProcess(
560              buffer->id(), client->render_process_handle, &memory_size);
561          client->event_handler->OnBufferCreated(
562              client->controller_id, remote_handle, memory_size, buffer->id());
563        }
564
565        client->event_handler->OnBufferReady(
566            client->controller_id, buffer->id(), buffer_format, timestamp);
567      }
568
569      bool inserted =
570          client->active_buffers.insert(std::make_pair(buffer->id(), frame))
571              .second;
572      DCHECK(inserted) << "Unexpected duplicate buffer: " << buffer->id();
573      count++;
574    }
575  }
576
577  buffer_pool_->HoldForConsumers(buffer->id(), count);
578}
579
580void VideoCaptureController::DoErrorOnIOThread() {
581  DCHECK_CURRENTLY_ON(BrowserThread::IO);
582  state_ = VIDEO_CAPTURE_STATE_ERROR;
583
584  for (ControllerClients::iterator client_it = controller_clients_.begin();
585       client_it != controller_clients_.end(); ++client_it) {
586    ControllerClient* client = *client_it;
587    if (client->session_closed)
588       continue;
589
590    client->event_handler->OnError(client->controller_id);
591  }
592}
593
594void VideoCaptureController::DoBufferDestroyedOnIOThread(
595    int buffer_id_to_drop) {
596  DCHECK_CURRENTLY_ON(BrowserThread::IO);
597
598  for (ControllerClients::iterator client_it = controller_clients_.begin();
599       client_it != controller_clients_.end(); ++client_it) {
600    ControllerClient* client = *client_it;
601    if (client->session_closed)
602      continue;
603
604    if (client->known_buffers.erase(buffer_id_to_drop)) {
605      client->event_handler->OnBufferDestroyed(client->controller_id,
606                                               buffer_id_to_drop);
607    }
608  }
609}
610
611VideoCaptureController::ControllerClient*
612VideoCaptureController::FindClient(
613    const VideoCaptureControllerID& id,
614    VideoCaptureControllerEventHandler* handler,
615    const ControllerClients& clients) {
616  for (ControllerClients::const_iterator client_it = clients.begin();
617       client_it != clients.end(); ++client_it) {
618    if ((*client_it)->controller_id == id &&
619        (*client_it)->event_handler == handler) {
620      return *client_it;
621    }
622  }
623  return NULL;
624}
625
626VideoCaptureController::ControllerClient*
627VideoCaptureController::FindClient(
628    int session_id,
629    const ControllerClients& clients) {
630  for (ControllerClients::const_iterator client_it = clients.begin();
631       client_it != clients.end(); ++client_it) {
632    if ((*client_it)->session_id == session_id) {
633      return *client_it;
634    }
635  }
636  return NULL;
637}
638
639int VideoCaptureController::GetClientCount() {
640  DCHECK_CURRENTLY_ON(BrowserThread::IO);
641  return controller_clients_.size();
642}
643
644}  // namespace content
645