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.
5 #include "content/browser/renderer_host/media/video_capture_controller.h"
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 "base/strings/stringprintf.h"
16 #include "content/browser/renderer_host/media/media_stream_manager.h"
17 #include "content/browser/renderer_host/media/video_capture_manager.h"
18 #include "content/common/gpu/client/gl_helper.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "gpu/command_buffer/common/mailbox_holder.h"
21 #include "media/base/video_frame.h"
22 #include "media/base/video_util.h"
23 #include "media/base/yuv_convert.h"
24 #include "third_party/libyuv/include/libyuv.h"
26 #if defined(OS_ANDROID)
27 #include "content/browser/renderer_host/image_transport_factory_android.h"
29 #include "content/browser/compositor/image_transport_factory.h"
32 using media::VideoCaptureFormat
;
38 static const int kInfiniteRatio
= 99999;
40 #define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \
41 UMA_HISTOGRAM_SPARSE_SLOWLY( \
43 (height) ? ((width) * 100) / (height) : kInfiniteRatio);
45 class PoolBuffer
: public media::VideoCaptureDevice::Client::Buffer
{
47 PoolBuffer(const scoped_refptr
<VideoCaptureBufferPool
>& pool
,
51 : Buffer(buffer_id
, data
, size
), pool_(pool
) {
56 virtual ~PoolBuffer() { pool_
->RelinquishProducerReservation(id()); }
58 const scoped_refptr
<VideoCaptureBufferPool
> pool_
;
61 class SyncPointClientImpl
: public media::VideoFrame::SyncPointClient
{
63 explicit SyncPointClientImpl(GLHelper
* gl_helper
) : gl_helper_(gl_helper
) {}
64 virtual ~SyncPointClientImpl() {}
65 virtual uint32
InsertSyncPoint() OVERRIDE
{
66 return gl_helper_
->InsertSyncPoint();
68 virtual void WaitSyncPoint(uint32 sync_point
) OVERRIDE
{
69 gl_helper_
->WaitSyncPoint(sync_point
);
76 void ReturnVideoFrame(const scoped_refptr
<media::VideoFrame
>& video_frame
,
78 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
79 #if defined(OS_ANDROID)
81 ImageTransportFactoryAndroid::GetInstance()->GetGLHelper();
83 GLHelper
* gl_helper
= ImageTransportFactory::GetInstance()->GetGLHelper();
86 // UpdateReleaseSyncPoint() creates a new sync_point using |gl_helper|, so
87 // wait the given |sync_point| using |gl_helper|.
88 gl_helper
->WaitSyncPoint(sync_point
);
89 SyncPointClientImpl
client(gl_helper
);
90 video_frame
->UpdateReleaseSyncPoint(&client
);
93 } // anonymous namespace
95 struct VideoCaptureController::ControllerClient
{
96 ControllerClient(const VideoCaptureControllerID
& id
,
97 VideoCaptureControllerEventHandler
* handler
,
98 base::ProcessHandle render_process
,
99 media::VideoCaptureSessionId session_id
,
100 const media::VideoCaptureParams
& params
)
102 event_handler(handler
),
103 render_process_handle(render_process
),
104 session_id(session_id
),
106 session_closed(false) {}
108 ~ControllerClient() {}
110 // ID used for identifying this object.
111 const VideoCaptureControllerID controller_id
;
112 VideoCaptureControllerEventHandler
* const event_handler
;
114 // Handle to the render process that will receive the capture buffers.
115 const base::ProcessHandle render_process_handle
;
116 const media::VideoCaptureSessionId session_id
;
117 const media::VideoCaptureParams parameters
;
119 // Buffers that are currently known to this client.
120 std::set
<int> known_buffers
;
122 // Buffers currently held by this client, and syncpoint callback to call when
123 // they are returned from the client.
124 typedef std::map
<int, scoped_refptr
<media::VideoFrame
> > ActiveBufferMap
;
125 ActiveBufferMap active_buffers
;
127 // State of capture session, controlled by VideoCaptureManager directly. This
128 // transitions to true as soon as StopSession() occurs, at which point the
129 // client is sent an OnEnded() event. However, because the client retains a
130 // VideoCaptureController* pointer, its ControllerClient entry lives on until
131 // it unregisters itself via RemoveClient(), which may happen asynchronously.
133 // TODO(nick): If we changed the semantics of VideoCaptureHost so that
134 // OnEnded() events were processed synchronously (with the RemoveClient() done
135 // implicitly), we could avoid tracking this state here in the Controller, and
136 // simplify the code in both places.
140 // Receives events from the VideoCaptureDevice and posts them to a
141 // VideoCaptureController on the IO thread. An instance of this class may safely
142 // outlive its target VideoCaptureController.
144 // Methods of this class may be called from any thread, and in practice will
145 // often be called on some auxiliary thread depending on the platform and the
146 // device type; including, for example, the DirectShow thread on Windows, the
147 // v4l2_thread on Linux, and the UI thread for tab capture.
148 class VideoCaptureController::VideoCaptureDeviceClient
149 : public media::VideoCaptureDevice::Client
{
151 explicit VideoCaptureDeviceClient(
152 const base::WeakPtr
<VideoCaptureController
>& controller
,
153 const scoped_refptr
<VideoCaptureBufferPool
>& buffer_pool
);
154 virtual ~VideoCaptureDeviceClient();
156 // VideoCaptureDevice::Client implementation.
157 virtual scoped_refptr
<Buffer
> ReserveOutputBuffer(
158 media::VideoFrame::Format format
,
159 const gfx::Size
& size
) OVERRIDE
;
160 virtual void OnIncomingCapturedData(const uint8
* data
,
162 const VideoCaptureFormat
& frame_format
,
164 base::TimeTicks timestamp
) OVERRIDE
;
165 virtual void OnIncomingCapturedVideoFrame(
166 const scoped_refptr
<Buffer
>& buffer
,
167 const VideoCaptureFormat
& buffer_format
,
168 const scoped_refptr
<media::VideoFrame
>& frame
,
169 base::TimeTicks timestamp
) OVERRIDE
;
170 virtual void OnError(const std::string
& reason
) OVERRIDE
;
171 virtual void OnLog(const std::string
& message
) OVERRIDE
;
174 scoped_refptr
<Buffer
> DoReserveOutputBuffer(media::VideoFrame::Format format
,
175 const gfx::Size
& dimensions
);
177 // The controller to which we post events.
178 const base::WeakPtr
<VideoCaptureController
> controller_
;
180 // The pool of shared-memory buffers used for capturing.
181 const scoped_refptr
<VideoCaptureBufferPool
> buffer_pool_
;
184 VideoCaptureController::VideoCaptureController(int max_buffers
)
185 : buffer_pool_(new VideoCaptureBufferPool(max_buffers
)),
186 state_(VIDEO_CAPTURE_STATE_STARTED
),
187 frame_received_(false),
188 weak_ptr_factory_(this) {
191 VideoCaptureController::VideoCaptureDeviceClient::VideoCaptureDeviceClient(
192 const base::WeakPtr
<VideoCaptureController
>& controller
,
193 const scoped_refptr
<VideoCaptureBufferPool
>& buffer_pool
)
194 : controller_(controller
), buffer_pool_(buffer_pool
) {}
196 VideoCaptureController::VideoCaptureDeviceClient::~VideoCaptureDeviceClient() {}
198 base::WeakPtr
<VideoCaptureController
> VideoCaptureController::GetWeakPtr() {
199 return weak_ptr_factory_
.GetWeakPtr();
202 scoped_ptr
<media::VideoCaptureDevice::Client
>
203 VideoCaptureController::NewDeviceClient() {
204 scoped_ptr
<media::VideoCaptureDevice::Client
> result(
205 new VideoCaptureDeviceClient(this->GetWeakPtr(), buffer_pool_
));
206 return result
.Pass();
209 void VideoCaptureController::AddClient(
210 const VideoCaptureControllerID
& id
,
211 VideoCaptureControllerEventHandler
* event_handler
,
212 base::ProcessHandle render_process
,
213 media::VideoCaptureSessionId session_id
,
214 const media::VideoCaptureParams
& params
) {
215 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
216 DVLOG(1) << "VideoCaptureController::AddClient, id " << id
.device_id
217 << ", " << params
.requested_format
.frame_size
.ToString()
218 << ", " << params
.requested_format
.frame_rate
219 << ", " << session_id
222 // If this is the first client added to the controller, cache the parameters.
223 if (!controller_clients_
.size())
224 video_capture_format_
= params
.requested_format
;
226 // Signal error in case device is already in error state.
227 if (state_
== VIDEO_CAPTURE_STATE_ERROR
) {
228 event_handler
->OnError(id
);
232 // Do nothing if this client has called AddClient before.
233 if (FindClient(id
, event_handler
, controller_clients_
))
236 ControllerClient
* client
= new ControllerClient(
237 id
, event_handler
, render_process
, session_id
, params
);
238 // If we already have gotten frame_info from the device, repeat it to the new
240 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
241 controller_clients_
.push_back(client
);
246 int VideoCaptureController::RemoveClient(
247 const VideoCaptureControllerID
& id
,
248 VideoCaptureControllerEventHandler
* event_handler
) {
249 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
250 DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id
.device_id
;
252 ControllerClient
* client
= FindClient(id
, event_handler
, controller_clients_
);
254 return kInvalidMediaCaptureSessionId
;
256 // Take back all buffers held by the |client|.
257 for (ControllerClient::ActiveBufferMap::iterator buffer_it
=
258 client
->active_buffers
.begin();
259 buffer_it
!= client
->active_buffers
.end();
261 buffer_pool_
->RelinquishConsumerHold(buffer_it
->first
, 1);
263 client
->active_buffers
.clear();
265 int session_id
= client
->session_id
;
266 controller_clients_
.remove(client
);
272 void VideoCaptureController::StopSession(int session_id
) {
273 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
274 DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id
;
276 ControllerClient
* client
= FindClient(session_id
, controller_clients_
);
279 client
->session_closed
= true;
280 client
->event_handler
->OnEnded(client
->controller_id
);
284 void VideoCaptureController::ReturnBuffer(
285 const VideoCaptureControllerID
& id
,
286 VideoCaptureControllerEventHandler
* event_handler
,
289 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
291 ControllerClient
* client
= FindClient(id
, event_handler
, controller_clients_
);
293 // If this buffer is not held by this client, or this client doesn't exist
294 // in controller, do nothing.
295 ControllerClient::ActiveBufferMap::iterator iter
;
296 if (!client
|| (iter
= client
->active_buffers
.find(buffer_id
)) ==
297 client
->active_buffers
.end()) {
301 scoped_refptr
<media::VideoFrame
> frame
= iter
->second
;
302 client
->active_buffers
.erase(iter
);
303 buffer_pool_
->RelinquishConsumerHold(buffer_id
, 1);
306 BrowserThread::PostTask(BrowserThread::UI
,
308 base::Bind(&ReturnVideoFrame
, frame
, sync_point
));
311 const media::VideoCaptureFormat
&
312 VideoCaptureController::GetVideoCaptureFormat() const {
313 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
314 return video_capture_format_
;
317 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>
318 VideoCaptureController::VideoCaptureDeviceClient::ReserveOutputBuffer(
319 media::VideoFrame::Format format
,
320 const gfx::Size
& size
) {
321 return DoReserveOutputBuffer(format
, size
);
324 void VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedData(
327 const VideoCaptureFormat
& frame_format
,
329 base::TimeTicks timestamp
) {
330 TRACE_EVENT0("video", "VideoCaptureController::OnIncomingCapturedData");
332 if (!frame_format
.IsValid())
335 // Chopped pixels in width/height in case video capture device has odd
336 // numbers for width/height.
337 int chopped_width
= 0;
338 int chopped_height
= 0;
339 int new_unrotated_width
= frame_format
.frame_size
.width();
340 int new_unrotated_height
= frame_format
.frame_size
.height();
342 if (new_unrotated_width
& 1) {
343 --new_unrotated_width
;
346 if (new_unrotated_height
& 1) {
347 --new_unrotated_height
;
351 int destination_width
= new_unrotated_width
;
352 int destination_height
= new_unrotated_height
;
353 if (rotation
== 90 || rotation
== 270) {
354 destination_width
= new_unrotated_height
;
355 destination_height
= new_unrotated_width
;
357 const gfx::Size
dimensions(destination_width
, destination_height
);
358 if (!media::VideoFrame::IsValidConfig(media::VideoFrame::I420
,
360 gfx::Rect(dimensions
),
365 scoped_refptr
<Buffer
> buffer
=
366 DoReserveOutputBuffer(media::VideoFrame::I420
, dimensions
);
370 uint8
* yplane
= NULL
;
372 yplane
= reinterpret_cast<uint8
*>(buffer
->data());
375 media::VideoFrame::PlaneAllocationSize(
376 media::VideoFrame::I420
, media::VideoFrame::kYPlane
, dimensions
);
379 media::VideoFrame::PlaneAllocationSize(
380 media::VideoFrame::I420
, media::VideoFrame::kUPlane
, dimensions
);
381 int yplane_stride
= dimensions
.width();
382 int uv_plane_stride
= yplane_stride
/ 2;
385 libyuv::FourCC origin_colorspace
= libyuv::FOURCC_ANY
;
387 libyuv::RotationMode rotation_mode
= libyuv::kRotate0
;
389 rotation_mode
= libyuv::kRotate90
;
390 else if (rotation
== 180)
391 rotation_mode
= libyuv::kRotate180
;
392 else if (rotation
== 270)
393 rotation_mode
= libyuv::kRotate270
;
395 switch (frame_format
.pixel_format
) {
396 case media::PIXEL_FORMAT_UNKNOWN
: // Color format not set.
398 case media::PIXEL_FORMAT_I420
:
399 DCHECK(!chopped_width
&& !chopped_height
);
400 origin_colorspace
= libyuv::FOURCC_I420
;
402 case media::PIXEL_FORMAT_YV12
:
403 DCHECK(!chopped_width
&& !chopped_height
);
404 origin_colorspace
= libyuv::FOURCC_YV12
;
406 case media::PIXEL_FORMAT_NV21
:
407 DCHECK(!chopped_width
&& !chopped_height
);
408 origin_colorspace
= libyuv::FOURCC_NV21
;
410 case media::PIXEL_FORMAT_YUY2
:
411 DCHECK(!chopped_width
&& !chopped_height
);
412 origin_colorspace
= libyuv::FOURCC_YUY2
;
414 case media::PIXEL_FORMAT_UYVY
:
415 DCHECK(!chopped_width
&& !chopped_height
);
416 origin_colorspace
= libyuv::FOURCC_UYVY
;
418 case media::PIXEL_FORMAT_RGB24
:
419 origin_colorspace
= libyuv::FOURCC_24BG
;
421 // TODO(wjia): Currently, for RGB24 on WIN, capture device always
422 // passes in positive src_width and src_height. Remove this hardcoded
423 // value when nagative src_height is supported. The negative src_height
424 // indicates that vertical flipping is needed.
428 case media::PIXEL_FORMAT_ARGB
:
429 origin_colorspace
= libyuv::FOURCC_ARGB
;
431 case media::PIXEL_FORMAT_MJPEG
:
432 origin_colorspace
= libyuv::FOURCC_MJPG
;
438 libyuv::ConvertToI420(data
,
448 frame_format
.frame_size
.width(),
449 (flip
? -frame_format
.frame_size
.height() :
450 frame_format
.frame_size
.height()),
452 new_unrotated_height
,
455 scoped_refptr
<media::VideoFrame
> frame
=
456 media::VideoFrame::WrapExternalPackedMemory(
457 media::VideoFrame::I420
,
459 gfx::Rect(dimensions
),
462 media::VideoFrame::AllocationSize(media::VideoFrame::I420
,
464 base::SharedMemory::NULLHandle(),
469 VideoCaptureFormat
format(
470 dimensions
, frame_format
.frame_rate
, media::PIXEL_FORMAT_I420
);
471 BrowserThread::PostTask(
475 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread
,
484 VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedVideoFrame(
485 const scoped_refptr
<Buffer
>& buffer
,
486 const VideoCaptureFormat
& buffer_format
,
487 const scoped_refptr
<media::VideoFrame
>& frame
,
488 base::TimeTicks timestamp
) {
489 BrowserThread::PostTask(
493 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread
,
501 void VideoCaptureController::VideoCaptureDeviceClient::OnError(
502 const std::string
& reason
) {
503 const std::string log_message
= base::StringPrintf(
504 "Error on video capture: %s, OS message: %s",
506 logging::SystemErrorCodeToString(
507 logging::GetLastSystemErrorCode()).c_str());
508 DLOG(ERROR
) << log_message
;
509 MediaStreamManager::SendMessageToNativeLog(log_message
);
510 BrowserThread::PostTask(BrowserThread::IO
,
512 base::Bind(&VideoCaptureController::DoErrorOnIOThread
, controller_
));
515 void VideoCaptureController::VideoCaptureDeviceClient::OnLog(
516 const std::string
& message
) {
517 MediaStreamManager::SendMessageToNativeLog("Video capture: " + message
);
520 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>
521 VideoCaptureController::VideoCaptureDeviceClient::DoReserveOutputBuffer(
522 media::VideoFrame::Format format
,
523 const gfx::Size
& dimensions
) {
524 size_t frame_bytes
= 0;
525 if (format
== media::VideoFrame::NATIVE_TEXTURE
) {
526 DCHECK_EQ(dimensions
.width(), 0);
527 DCHECK_EQ(dimensions
.height(), 0);
529 // The capture pipeline expects I420 for now.
530 DCHECK_EQ(format
, media::VideoFrame::I420
)
531 << "Non-I420 output buffer format " << format
<< " requested";
532 frame_bytes
= media::VideoFrame::AllocationSize(format
, dimensions
);
535 int buffer_id_to_drop
= VideoCaptureBufferPool::kInvalidId
;
537 buffer_pool_
->ReserveForProducer(frame_bytes
, &buffer_id_to_drop
);
538 if (buffer_id
== VideoCaptureBufferPool::kInvalidId
)
542 buffer_pool_
->GetBufferInfo(buffer_id
, &data
, &size
);
544 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
> output_buffer(
545 new PoolBuffer(buffer_pool_
, buffer_id
, data
, size
));
547 if (buffer_id_to_drop
!= VideoCaptureBufferPool::kInvalidId
) {
548 BrowserThread::PostTask(BrowserThread::IO
,
550 base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread
,
551 controller_
, buffer_id_to_drop
));
554 return output_buffer
;
557 VideoCaptureController::~VideoCaptureController() {
558 STLDeleteContainerPointers(controller_clients_
.begin(),
559 controller_clients_
.end());
560 UMA_HISTOGRAM_BOOLEAN("Media.VideoCapture.FramesReceived", frame_received_
);
563 void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread(
564 const scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>& buffer
,
565 const media::VideoCaptureFormat
& buffer_format
,
566 const scoped_refptr
<media::VideoFrame
>& frame
,
567 base::TimeTicks timestamp
) {
568 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
569 DCHECK_NE(buffer
->id(), VideoCaptureBufferPool::kInvalidId
);
572 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
573 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
574 client_it
!= controller_clients_
.end(); ++client_it
) {
575 ControllerClient
* client
= *client_it
;
576 if (client
->session_closed
)
579 if (frame
->format() == media::VideoFrame::NATIVE_TEXTURE
) {
580 client
->event_handler
->OnMailboxBufferReady(client
->controller_id
,
582 *frame
->mailbox_holder(),
586 bool is_new_buffer
= client
->known_buffers
.insert(buffer
->id()).second
;
588 // On the first use of a buffer on a client, share the memory handle.
589 size_t memory_size
= 0;
590 base::SharedMemoryHandle remote_handle
= buffer_pool_
->ShareToProcess(
591 buffer
->id(), client
->render_process_handle
, &memory_size
);
592 client
->event_handler
->OnBufferCreated(
593 client
->controller_id
, remote_handle
, memory_size
, buffer
->id());
596 client
->event_handler
->OnBufferReady(
597 client
->controller_id
, buffer
->id(), buffer_format
,
598 frame
->visible_rect(), timestamp
);
602 client
->active_buffers
.insert(std::make_pair(buffer
->id(), frame
))
604 DCHECK(inserted
) << "Unexpected duplicate buffer: " << buffer
->id();
609 if (!frame_received_
) {
610 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width",
611 buffer_format
.frame_size
.width());
612 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height",
613 buffer_format
.frame_size
.height());
614 UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio",
615 buffer_format
.frame_size
.width(),
616 buffer_format
.frame_size
.height());
617 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate",
618 buffer_format
.frame_rate
);
619 UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.PixelFormat",
620 buffer_format
.pixel_format
,
621 media::PIXEL_FORMAT_MAX
);
622 frame_received_
= true;
625 buffer_pool_
->HoldForConsumers(buffer
->id(), count
);
628 void VideoCaptureController::DoErrorOnIOThread() {
629 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
630 state_
= VIDEO_CAPTURE_STATE_ERROR
;
632 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
633 client_it
!= controller_clients_
.end(); ++client_it
) {
634 ControllerClient
* client
= *client_it
;
635 if (client
->session_closed
)
638 client
->event_handler
->OnError(client
->controller_id
);
642 void VideoCaptureController::DoBufferDestroyedOnIOThread(
643 int buffer_id_to_drop
) {
644 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
646 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
647 client_it
!= controller_clients_
.end(); ++client_it
) {
648 ControllerClient
* client
= *client_it
;
649 if (client
->session_closed
)
652 if (client
->known_buffers
.erase(buffer_id_to_drop
)) {
653 client
->event_handler
->OnBufferDestroyed(client
->controller_id
,
659 VideoCaptureController::ControllerClient
*
660 VideoCaptureController::FindClient(
661 const VideoCaptureControllerID
& id
,
662 VideoCaptureControllerEventHandler
* handler
,
663 const ControllerClients
& clients
) {
664 for (ControllerClients::const_iterator client_it
= clients
.begin();
665 client_it
!= clients
.end(); ++client_it
) {
666 if ((*client_it
)->controller_id
== id
&&
667 (*client_it
)->event_handler
== handler
) {
674 VideoCaptureController::ControllerClient
*
675 VideoCaptureController::FindClient(
677 const ControllerClients
& clients
) {
678 for (ControllerClients::const_iterator client_it
= clients
.begin();
679 client_it
!= clients
.end(); ++client_it
) {
680 if ((*client_it
)->session_id
== session_id
) {
687 int VideoCaptureController::GetClientCount() {
688 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
689 return controller_clients_
.size();
692 } // namespace content