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/compositor/image_transport_factory.h"
30 using media::VideoCaptureFormat
;
36 static const int kInfiniteRatio
= 99999;
38 #define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \
39 UMA_HISTOGRAM_SPARSE_SLOWLY( \
41 (height) ? ((width) * 100) / (height) : kInfiniteRatio);
43 class PoolBuffer
: public media::VideoCaptureDevice::Client::Buffer
{
45 PoolBuffer(const scoped_refptr
<VideoCaptureBufferPool
>& pool
,
49 : Buffer(buffer_id
, data
, size
), pool_(pool
) {
54 ~PoolBuffer() override
{ pool_
->RelinquishProducerReservation(id()); }
56 const scoped_refptr
<VideoCaptureBufferPool
> pool_
;
59 class SyncPointClientImpl
: public media::VideoFrame::SyncPointClient
{
61 explicit SyncPointClientImpl(GLHelper
* gl_helper
) : gl_helper_(gl_helper
) {}
62 ~SyncPointClientImpl() override
{}
63 uint32
InsertSyncPoint() override
{ return gl_helper_
->InsertSyncPoint(); }
64 void WaitSyncPoint(uint32 sync_point
) override
{
65 gl_helper_
->WaitSyncPoint(sync_point
);
72 void ReturnVideoFrame(const scoped_refptr
<media::VideoFrame
>& video_frame
,
74 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
75 #if defined(OS_ANDROID)
78 GLHelper
* gl_helper
= ImageTransportFactory::GetInstance()->GetGLHelper();
79 // UpdateReleaseSyncPoint() creates a new sync_point using |gl_helper|, so
80 // wait the given |sync_point| using |gl_helper|.
82 gl_helper
->WaitSyncPoint(sync_point
);
83 SyncPointClientImpl
client(gl_helper
);
84 video_frame
->UpdateReleaseSyncPoint(&client
);
89 } // anonymous namespace
91 struct VideoCaptureController::ControllerClient
{
92 ControllerClient(const VideoCaptureControllerID
& id
,
93 VideoCaptureControllerEventHandler
* handler
,
94 base::ProcessHandle render_process
,
95 media::VideoCaptureSessionId session_id
,
96 const media::VideoCaptureParams
& params
)
98 event_handler(handler
),
99 render_process_handle(render_process
),
100 session_id(session_id
),
102 session_closed(false),
105 ~ControllerClient() {}
107 // ID used for identifying this object.
108 const VideoCaptureControllerID controller_id
;
109 VideoCaptureControllerEventHandler
* const event_handler
;
111 // Handle to the render process that will receive the capture buffers.
112 const base::ProcessHandle render_process_handle
;
113 const media::VideoCaptureSessionId session_id
;
114 const media::VideoCaptureParams parameters
;
116 // Buffers that are currently known to this client.
117 std::set
<int> known_buffers
;
119 // Buffers currently held by this client, and syncpoint callback to call when
120 // they are returned from the client.
121 typedef std::map
<int, scoped_refptr
<media::VideoFrame
> > ActiveBufferMap
;
122 ActiveBufferMap active_buffers
;
124 // State of capture session, controlled by VideoCaptureManager directly. This
125 // transitions to true as soon as StopSession() occurs, at which point the
126 // client is sent an OnEnded() event. However, because the client retains a
127 // VideoCaptureController* pointer, its ControllerClient entry lives on until
128 // it unregisters itself via RemoveClient(), which may happen asynchronously.
130 // TODO(nick): If we changed the semantics of VideoCaptureHost so that
131 // OnEnded() events were processed synchronously (with the RemoveClient() done
132 // implicitly), we could avoid tracking this state here in the Controller, and
133 // simplify the code in both places.
136 // Indicates whether the client is paused, if true, VideoCaptureController
137 // stops updating its buffer.
141 // Receives events from the VideoCaptureDevice and posts them to a
142 // VideoCaptureController on the IO thread. An instance of this class may safely
143 // outlive its target VideoCaptureController.
145 // Methods of this class may be called from any thread, and in practice will
146 // often be called on some auxiliary thread depending on the platform and the
147 // device type; including, for example, the DirectShow thread on Windows, the
148 // v4l2_thread on Linux, and the UI thread for tab capture.
149 class VideoCaptureController::VideoCaptureDeviceClient
150 : public media::VideoCaptureDevice::Client
{
152 explicit VideoCaptureDeviceClient(
153 const base::WeakPtr
<VideoCaptureController
>& controller
,
154 const scoped_refptr
<VideoCaptureBufferPool
>& buffer_pool
);
155 ~VideoCaptureDeviceClient() override
;
157 // VideoCaptureDevice::Client implementation.
158 scoped_refptr
<Buffer
> ReserveOutputBuffer(media::VideoFrame::Format format
,
159 const gfx::Size
& size
) override
;
160 void OnIncomingCapturedData(const uint8
* data
,
162 const VideoCaptureFormat
& frame_format
,
164 base::TimeTicks timestamp
) override
;
165 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 void OnError(const std::string
& reason
) override
;
171 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 has_received_frames_(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::PauseOrResumeClient(
273 const VideoCaptureControllerID
& id
,
274 VideoCaptureControllerEventHandler
* event_handler
,
276 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
277 DVLOG(1) << "VideoCaptureController::PauseOrResumeClient, id "
278 << id
.device_id
<< ", " << pause
;
280 ControllerClient
* client
= FindClient(id
, event_handler
, controller_clients_
);
284 DCHECK(client
->paused
!= pause
);
285 client
->paused
= pause
;
288 void VideoCaptureController::StopSession(int session_id
) {
289 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
290 DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id
;
292 ControllerClient
* client
= FindClient(session_id
, controller_clients_
);
295 client
->session_closed
= true;
296 client
->event_handler
->OnEnded(client
->controller_id
);
300 void VideoCaptureController::ReturnBuffer(
301 const VideoCaptureControllerID
& id
,
302 VideoCaptureControllerEventHandler
* event_handler
,
305 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
307 ControllerClient
* client
= FindClient(id
, event_handler
, controller_clients_
);
309 // If this buffer is not held by this client, or this client doesn't exist
310 // in controller, do nothing.
311 ControllerClient::ActiveBufferMap::iterator iter
;
312 if (!client
|| (iter
= client
->active_buffers
.find(buffer_id
)) ==
313 client
->active_buffers
.end()) {
317 scoped_refptr
<media::VideoFrame
> frame
= iter
->second
;
318 client
->active_buffers
.erase(iter
);
319 buffer_pool_
->RelinquishConsumerHold(buffer_id
, 1);
321 #if defined(OS_ANDROID)
322 DCHECK_EQ(0u, sync_point
);
325 BrowserThread::PostTask(BrowserThread::UI
,
327 base::Bind(&ReturnVideoFrame
, frame
, sync_point
));
330 const media::VideoCaptureFormat
&
331 VideoCaptureController::GetVideoCaptureFormat() const {
332 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
333 return video_capture_format_
;
336 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>
337 VideoCaptureController::VideoCaptureDeviceClient::ReserveOutputBuffer(
338 media::VideoFrame::Format format
,
339 const gfx::Size
& size
) {
340 return DoReserveOutputBuffer(format
, size
);
343 void VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedData(
346 const VideoCaptureFormat
& frame_format
,
348 base::TimeTicks timestamp
) {
349 TRACE_EVENT0("video", "VideoCaptureController::OnIncomingCapturedData");
351 if (!frame_format
.IsValid())
354 // Chopped pixels in width/height in case video capture device has odd
355 // numbers for width/height.
356 int chopped_width
= 0;
357 int chopped_height
= 0;
358 int new_unrotated_width
= frame_format
.frame_size
.width();
359 int new_unrotated_height
= frame_format
.frame_size
.height();
361 if (new_unrotated_width
& 1) {
362 --new_unrotated_width
;
365 if (new_unrotated_height
& 1) {
366 --new_unrotated_height
;
370 int destination_width
= new_unrotated_width
;
371 int destination_height
= new_unrotated_height
;
372 if (rotation
== 90 || rotation
== 270) {
373 destination_width
= new_unrotated_height
;
374 destination_height
= new_unrotated_width
;
376 const gfx::Size
dimensions(destination_width
, destination_height
);
377 if (!media::VideoFrame::IsValidConfig(media::VideoFrame::I420
,
379 gfx::Rect(dimensions
),
384 scoped_refptr
<Buffer
> buffer
=
385 DoReserveOutputBuffer(media::VideoFrame::I420
, dimensions
);
389 uint8
* yplane
= NULL
;
391 yplane
= reinterpret_cast<uint8
*>(buffer
->data());
394 media::VideoFrame::PlaneAllocationSize(
395 media::VideoFrame::I420
, media::VideoFrame::kYPlane
, dimensions
);
398 media::VideoFrame::PlaneAllocationSize(
399 media::VideoFrame::I420
, media::VideoFrame::kUPlane
, dimensions
);
400 int yplane_stride
= dimensions
.width();
401 int uv_plane_stride
= yplane_stride
/ 2;
404 libyuv::FourCC origin_colorspace
= libyuv::FOURCC_ANY
;
406 libyuv::RotationMode rotation_mode
= libyuv::kRotate0
;
408 rotation_mode
= libyuv::kRotate90
;
409 else if (rotation
== 180)
410 rotation_mode
= libyuv::kRotate180
;
411 else if (rotation
== 270)
412 rotation_mode
= libyuv::kRotate270
;
414 switch (frame_format
.pixel_format
) {
415 case media::PIXEL_FORMAT_UNKNOWN
: // Color format not set.
417 case media::PIXEL_FORMAT_I420
:
418 DCHECK(!chopped_width
&& !chopped_height
);
419 origin_colorspace
= libyuv::FOURCC_I420
;
421 case media::PIXEL_FORMAT_YV12
:
422 DCHECK(!chopped_width
&& !chopped_height
);
423 origin_colorspace
= libyuv::FOURCC_YV12
;
425 case media::PIXEL_FORMAT_NV12
:
426 DCHECK(!chopped_width
&& !chopped_height
);
427 origin_colorspace
= libyuv::FOURCC_NV12
;
429 case media::PIXEL_FORMAT_NV21
:
430 DCHECK(!chopped_width
&& !chopped_height
);
431 origin_colorspace
= libyuv::FOURCC_NV21
;
433 case media::PIXEL_FORMAT_YUY2
:
434 DCHECK(!chopped_width
&& !chopped_height
);
435 origin_colorspace
= libyuv::FOURCC_YUY2
;
437 case media::PIXEL_FORMAT_UYVY
:
438 DCHECK(!chopped_width
&& !chopped_height
);
439 origin_colorspace
= libyuv::FOURCC_UYVY
;
441 case media::PIXEL_FORMAT_RGB24
:
442 origin_colorspace
= libyuv::FOURCC_24BG
;
444 // TODO(wjia): Currently, for RGB24 on WIN, capture device always
445 // passes in positive src_width and src_height. Remove this hardcoded
446 // value when nagative src_height is supported. The negative src_height
447 // indicates that vertical flipping is needed.
451 case media::PIXEL_FORMAT_ARGB
:
452 origin_colorspace
= libyuv::FOURCC_ARGB
;
454 case media::PIXEL_FORMAT_MJPEG
:
455 origin_colorspace
= libyuv::FOURCC_MJPG
;
461 libyuv::ConvertToI420(data
,
471 frame_format
.frame_size
.width(),
472 (flip
? -frame_format
.frame_size
.height() :
473 frame_format
.frame_size
.height()),
475 new_unrotated_height
,
478 scoped_refptr
<media::VideoFrame
> frame
=
479 media::VideoFrame::WrapExternalPackedMemory(
480 media::VideoFrame::I420
,
482 gfx::Rect(dimensions
),
485 media::VideoFrame::AllocationSize(media::VideoFrame::I420
,
487 base::SharedMemory::NULLHandle(),
492 VideoCaptureFormat
format(
493 dimensions
, frame_format
.frame_rate
, media::PIXEL_FORMAT_I420
);
494 BrowserThread::PostTask(
498 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread
,
507 VideoCaptureController::VideoCaptureDeviceClient::OnIncomingCapturedVideoFrame(
508 const scoped_refptr
<Buffer
>& buffer
,
509 const VideoCaptureFormat
& buffer_format
,
510 const scoped_refptr
<media::VideoFrame
>& frame
,
511 base::TimeTicks timestamp
) {
512 BrowserThread::PostTask(
516 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread
,
524 void VideoCaptureController::VideoCaptureDeviceClient::OnError(
525 const std::string
& reason
) {
526 const std::string log_message
= base::StringPrintf(
527 "Error on video capture: %s, OS message: %s",
529 logging::SystemErrorCodeToString(
530 logging::GetLastSystemErrorCode()).c_str());
531 DLOG(ERROR
) << log_message
;
532 MediaStreamManager::SendMessageToNativeLog(log_message
);
533 BrowserThread::PostTask(BrowserThread::IO
,
535 base::Bind(&VideoCaptureController::DoErrorOnIOThread
, controller_
));
538 void VideoCaptureController::VideoCaptureDeviceClient::OnLog(
539 const std::string
& message
) {
540 MediaStreamManager::SendMessageToNativeLog("Video capture: " + message
);
543 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>
544 VideoCaptureController::VideoCaptureDeviceClient::DoReserveOutputBuffer(
545 media::VideoFrame::Format format
,
546 const gfx::Size
& dimensions
) {
547 size_t frame_bytes
= 0;
548 if (format
== media::VideoFrame::NATIVE_TEXTURE
) {
549 DCHECK_EQ(dimensions
.width(), 0);
550 DCHECK_EQ(dimensions
.height(), 0);
552 // The capture pipeline expects I420 for now.
553 DCHECK_EQ(format
, media::VideoFrame::I420
)
554 << "Non-I420 output buffer format " << format
<< " requested";
555 frame_bytes
= media::VideoFrame::AllocationSize(format
, dimensions
);
558 int buffer_id_to_drop
= VideoCaptureBufferPool::kInvalidId
;
560 buffer_pool_
->ReserveForProducer(frame_bytes
, &buffer_id_to_drop
);
561 if (buffer_id
== VideoCaptureBufferPool::kInvalidId
)
565 buffer_pool_
->GetBufferInfo(buffer_id
, &data
, &size
);
567 scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
> output_buffer(
568 new PoolBuffer(buffer_pool_
, buffer_id
, data
, size
));
570 if (buffer_id_to_drop
!= VideoCaptureBufferPool::kInvalidId
) {
571 BrowserThread::PostTask(BrowserThread::IO
,
573 base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread
,
574 controller_
, buffer_id_to_drop
));
577 return output_buffer
;
580 VideoCaptureController::~VideoCaptureController() {
581 STLDeleteContainerPointers(controller_clients_
.begin(),
582 controller_clients_
.end());
585 void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread(
586 const scoped_refptr
<media::VideoCaptureDevice::Client::Buffer
>& buffer
,
587 const media::VideoCaptureFormat
& buffer_format
,
588 const scoped_refptr
<media::VideoFrame
>& frame
,
589 base::TimeTicks timestamp
) {
590 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
591 DCHECK_NE(buffer
->id(), VideoCaptureBufferPool::kInvalidId
);
594 if (state_
== VIDEO_CAPTURE_STATE_STARTED
) {
595 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
596 client_it
!= controller_clients_
.end(); ++client_it
) {
597 ControllerClient
* client
= *client_it
;
598 if (client
->session_closed
|| client
->paused
)
601 if (frame
->format() == media::VideoFrame::NATIVE_TEXTURE
) {
602 client
->event_handler
->OnMailboxBufferReady(client
->controller_id
,
604 *frame
->mailbox_holder(),
608 bool is_new_buffer
= client
->known_buffers
.insert(buffer
->id()).second
;
610 // On the first use of a buffer on a client, share the memory handle.
611 size_t memory_size
= 0;
612 base::SharedMemoryHandle remote_handle
= buffer_pool_
->ShareToProcess(
613 buffer
->id(), client
->render_process_handle
, &memory_size
);
614 client
->event_handler
->OnBufferCreated(
615 client
->controller_id
, remote_handle
, memory_size
, buffer
->id());
618 client
->event_handler
->OnBufferReady(
619 client
->controller_id
, buffer
->id(), buffer_format
,
620 frame
->visible_rect(), timestamp
);
624 client
->active_buffers
.insert(std::make_pair(buffer
->id(), frame
))
626 DCHECK(inserted
) << "Unexpected duplicate buffer: " << buffer
->id();
631 if (!has_received_frames_
) {
632 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width",
633 buffer_format
.frame_size
.width());
634 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height",
635 buffer_format
.frame_size
.height());
636 UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio",
637 buffer_format
.frame_size
.width(),
638 buffer_format
.frame_size
.height());
639 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate",
640 buffer_format
.frame_rate
);
641 has_received_frames_
= true;
644 buffer_pool_
->HoldForConsumers(buffer
->id(), count
);
647 void VideoCaptureController::DoErrorOnIOThread() {
648 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
649 state_
= VIDEO_CAPTURE_STATE_ERROR
;
651 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
652 client_it
!= controller_clients_
.end(); ++client_it
) {
653 ControllerClient
* client
= *client_it
;
654 if (client
->session_closed
)
657 client
->event_handler
->OnError(client
->controller_id
);
661 void VideoCaptureController::DoBufferDestroyedOnIOThread(
662 int buffer_id_to_drop
) {
663 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
665 for (ControllerClients::iterator client_it
= controller_clients_
.begin();
666 client_it
!= controller_clients_
.end(); ++client_it
) {
667 ControllerClient
* client
= *client_it
;
668 if (client
->session_closed
)
671 if (client
->known_buffers
.erase(buffer_id_to_drop
)) {
672 client
->event_handler
->OnBufferDestroyed(client
->controller_id
,
678 VideoCaptureController::ControllerClient
*
679 VideoCaptureController::FindClient(
680 const VideoCaptureControllerID
& id
,
681 VideoCaptureControllerEventHandler
* handler
,
682 const ControllerClients
& clients
) {
683 for (ControllerClients::const_iterator client_it
= clients
.begin();
684 client_it
!= clients
.end(); ++client_it
) {
685 if ((*client_it
)->controller_id
== id
&&
686 (*client_it
)->event_handler
== handler
) {
693 VideoCaptureController::ControllerClient
*
694 VideoCaptureController::FindClient(
696 const ControllerClients
& clients
) {
697 for (ControllerClients::const_iterator client_it
= clients
.begin();
698 client_it
!= clients
.end(); ++client_it
) {
699 if ((*client_it
)->session_id
== session_id
) {
706 int VideoCaptureController::GetClientCount() const {
707 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
708 return controller_clients_
.size();
711 int VideoCaptureController::GetActiveClientCount() const {
712 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
713 int active_client_count
= 0;
714 for (ControllerClient
* client
: controller_clients_
) {
716 ++active_client_count
;
718 return active_client_count
;
721 } // namespace content