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/device_monitor_mac.h"
7 #import <QTKit/QTKit.h>
11 #include "base/bind_helpers.h"
12 #include "base/logging.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/threading/thread_checker.h"
15 #include "content/public/browser/browser_thread.h"
16 #import "media/video/capture/mac/avfoundation_glue.h"
20 // This class is used to keep track of system devices names and their types.
31 DeviceInfo(std::string unique_id, DeviceType type)
32 : unique_id_(unique_id), type_(type) {}
34 // Operator== is needed here to use this class in a std::find. A given
35 // |unique_id_| always has the same |type_| so for comparison purposes the
36 // latter can be safely ignored.
37 bool operator==(const DeviceInfo& device) const {
38 return unique_id_ == device.unique_id_;
41 const std::string& unique_id() const { return unique_id_; }
42 DeviceType type() const { return type_; }
45 std::string unique_id_;
47 // Allow generated copy constructor and assignment.
50 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
51 // or an AVFoundation implementation of events and notifications.
52 class DeviceMonitorMacImpl {
54 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
58 device_removal_(nil) {
60 // Initialise the devices_cache_ with a not-valid entry. For the case in
61 // which there is one single device in the system and we get notified when
62 // it gets removed, this will prevent the system from thinking that no
63 // devices were added nor removed and not notifying the |monitor_|.
64 cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
66 virtual ~DeviceMonitorMacImpl() {}
68 virtual void OnDeviceChanged() = 0;
70 // Method called by the default notification center when a device is removed
71 // or added to the system. It will compare the |cached_devices_| with the
72 // current situation, update it, and, if there's an update, signal to
73 // |monitor_| with the appropriate device type.
74 void ConsolidateDevicesListAndNotify(
75 const std::vector<DeviceInfo>& snapshot_devices);
78 content::DeviceMonitorMac* monitor_;
79 std::vector<DeviceInfo> cached_devices_;
81 // Handles to NSNotificationCenter block observers.
86 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
89 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
90 const std::vector<DeviceInfo>& snapshot_devices) {
91 bool video_device_added = false;
92 bool audio_device_added = false;
93 bool video_device_removed = false;
94 bool audio_device_removed = false;
96 // Compare the current system devices snapshot with the ones cached to detect
97 // additions, present in the former but not in the latter. If we find a device
98 // in snapshot_devices entry also present in cached_devices, we remove it from
100 std::vector<DeviceInfo>::const_iterator it;
101 for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
102 std::vector<DeviceInfo>::iterator cached_devices_iterator =
103 std::find(cached_devices_.begin(), cached_devices_.end(), *it);
104 if (cached_devices_iterator == cached_devices_.end()) {
105 video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
106 (it->type() == DeviceInfo::kMuxed));
107 audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
108 (it->type() == DeviceInfo::kMuxed));
109 DVLOG(1) << "Device has been added, id: " << it->unique_id();
111 cached_devices_.erase(cached_devices_iterator);
114 // All the remaining entries in cached_devices are removed devices.
115 for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
116 video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
117 (it->type() == DeviceInfo::kMuxed) ||
118 (it->type() == DeviceInfo::kInvalid));
119 audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
120 (it->type() == DeviceInfo::kMuxed) ||
121 (it->type() == DeviceInfo::kInvalid));
122 DVLOG(1) << "Device has been removed, id: " << it->unique_id();
124 // Update the cached devices with the current system snapshot.
125 cached_devices_ = snapshot_devices;
127 if (video_device_added || video_device_removed)
128 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
129 if (audio_device_added || audio_device_removed)
130 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
133 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
135 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
136 virtual ~QTKitMonitorImpl();
138 virtual void OnDeviceChanged() OVERRIDE;
141 void OnAttributeChanged(NSNotification* notification);
146 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
147 : DeviceMonitorMacImpl(monitor) {
148 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
150 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
153 usingBlock:^(NSNotification* notification) {
154 OnDeviceChanged();}];
156 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
159 usingBlock:^(NSNotification* notification) {
160 OnDeviceChanged();}];
162 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
165 usingBlock:^(NSNotification* notification) {
166 OnAttributeChanged(notification);}];
169 QTKitMonitorImpl::~QTKitMonitorImpl() {
170 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
171 [nc removeObserver:device_arrival_];
172 [nc removeObserver:device_removal_];
173 [nc removeObserver:device_change_];
176 void QTKitMonitorImpl::OnAttributeChanged(
177 NSNotification* notification) {
178 if ([[[notification userInfo]
179 objectForKey:QTCaptureDeviceChangedAttributeKey]
180 isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
185 void QTKitMonitorImpl::OnDeviceChanged() {
186 std::vector<DeviceInfo> snapshot_devices;
188 NSArray* devices = [QTCaptureDevice inputDevices];
189 for (QTCaptureDevice* device in devices) {
190 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
191 // Act as if suspended video capture devices are not attached. For
192 // example, a laptop's internal webcam is suspended when the lid is closed.
193 if ([device hasMediaType:QTMediaTypeVideo] &&
194 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
196 device_type = DeviceInfo::kVideo;
197 } else if ([device hasMediaType:QTMediaTypeMuxed] &&
198 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
200 device_type = DeviceInfo::kMuxed;
201 } else if ([device hasMediaType:QTMediaTypeSound] &&
202 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
204 device_type = DeviceInfo::kAudio;
206 snapshot_devices.push_back(
207 DeviceInfo([[device uniqueID] UTF8String], device_type));
209 ConsolidateDevicesListAndNotify(snapshot_devices);
212 // Forward declaration for use by CrAVFoundationDeviceObserver.
213 class SuspendObserverDelegate;
217 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
218 // classes cannot observe Key-Values directly. Created, manipulated, and
219 // destroyed on the Device Thread by SuspendedObserverDelegate.
220 @interface CrAVFoundationDeviceObserver : NSObject {
222 SuspendObserverDelegate* receiver_; // weak
223 // Member to keep track of the devices we are already monitoring.
224 std::set<CrAVCaptureDevice*> monitoredDevices_;
227 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver;
228 - (void)startObserving:(CrAVCaptureDevice*)device;
229 - (void)stopObserving:(CrAVCaptureDevice*)device;
235 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver.
236 // Provides a callback for this device observer to indicate that there has been
237 // a device change of some kind. Created by AVFoundationMonitorImpl in UI thread
238 // but living in Device Thread.
239 class SuspendObserverDelegate :
240 public base::RefCountedThreadSafe<SuspendObserverDelegate> {
242 explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor)
243 : avfoundation_monitor_impl_(monitor) {
244 device_thread_checker_.DetachFromThread();
247 void OnDeviceChanged();
248 void StartObserver();
249 void ResetDeviceMonitorOnUIThread();
252 friend class base::RefCountedThreadSafe<SuspendObserverDelegate>;
254 virtual ~SuspendObserverDelegate() {}
256 void OnDeviceChangedOnUIThread(
257 const std::vector<DeviceInfo>& snapshot_devices);
259 base::ThreadChecker device_thread_checker_;
260 base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
261 DeviceMonitorMacImpl* avfoundation_monitor_impl_;
264 void SuspendObserverDelegate::OnDeviceChanged() {
265 DCHECK(device_thread_checker_.CalledOnValidThread());
266 NSArray* devices = [AVCaptureDeviceGlue devices];
267 std::vector<DeviceInfo> snapshot_devices;
268 for (CrAVCaptureDevice* device in devices) {
269 [suspend_observer_ startObserving:device];
270 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
271 [device isSuspended];
272 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
273 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
276 device_type = DeviceInfo::kVideo;
277 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
278 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
279 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
280 device_type = DeviceInfo::kAudio;
282 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
285 // Post the consolidation of enumerated devices to be done on UI thread.
286 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
287 base::Bind(&SuspendObserverDelegate::OnDeviceChangedOnUIThread,
288 this, snapshot_devices));
291 void SuspendObserverDelegate::StartObserver() {
292 DCHECK(device_thread_checker_.CalledOnValidThread());
293 suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
294 initWithChangeReceiver:this]);
295 for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
296 [suspend_observer_ startObserving:device];
299 void SuspendObserverDelegate::ResetDeviceMonitorOnUIThread() {
300 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
301 avfoundation_monitor_impl_ = NULL;
304 void SuspendObserverDelegate::OnDeviceChangedOnUIThread(
305 const std::vector<DeviceInfo>& snapshot_devices) {
306 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
307 // |avfoundation_monitor_impl_| might have been NULLed asynchronously before
308 // arriving at this line.
309 if (avfoundation_monitor_impl_) {
310 avfoundation_monitor_impl_->ConsolidateDevicesListAndNotify(
315 // AVFoundation implementation of the Mac Device Monitor, registers as a global
316 // device connect/disconnect observer and plugs suspend/wake up device observers
317 // per device. Owns a SuspendObserverDelegate living in |device_task_runner_|
318 // and gets notified when a device is suspended/resumed. This class is created
319 // and lives in UI thread;
320 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
322 AVFoundationMonitorImpl(
323 content::DeviceMonitorMac* monitor,
324 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
325 virtual ~AVFoundationMonitorImpl();
327 virtual void OnDeviceChanged() OVERRIDE;
330 base::ThreadChecker thread_checker_;
332 // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
333 // posting tasks to |suspend_observer_delegate_|; valid after
334 // MediaStreamManager calls StartMonitoring().
335 const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
337 scoped_refptr<SuspendObserverDelegate> suspend_observer_delegate_;
339 DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
342 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
343 content::DeviceMonitorMac* monitor,
344 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
345 : DeviceMonitorMacImpl(monitor),
346 device_task_runner_(device_task_runner),
347 suspend_observer_delegate_(new SuspendObserverDelegate(this)) {
348 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
350 [nc addObserverForName:AVFoundationGlue::
351 AVCaptureDeviceWasConnectedNotification()
354 usingBlock:^(NSNotification* notification) {
355 OnDeviceChanged();}];
357 [nc addObserverForName:AVFoundationGlue::
358 AVCaptureDeviceWasDisconnectedNotification()
361 usingBlock:^(NSNotification* notification) {
362 OnDeviceChanged();}];
363 device_task_runner_->PostTask(FROM_HERE,
364 base::Bind(&SuspendObserverDelegate::StartObserver,
365 suspend_observer_delegate_));
368 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
369 DCHECK(thread_checker_.CalledOnValidThread());
370 suspend_observer_delegate_->ResetDeviceMonitorOnUIThread();
371 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
372 [nc removeObserver:device_arrival_];
373 [nc removeObserver:device_removal_];
376 void AVFoundationMonitorImpl::OnDeviceChanged() {
377 DCHECK(thread_checker_.CalledOnValidThread());
378 device_task_runner_->PostTask(FROM_HERE,
379 base::Bind(&SuspendObserverDelegate::OnDeviceChanged,
380 suspend_observer_delegate_));
385 @implementation CrAVFoundationDeviceObserver
387 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver {
388 if ((self = [super init])) {
389 DCHECK(receiver != NULL);
390 receiver_ = receiver;
396 std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin();
397 while (it != monitoredDevices_.end())
398 [self stopObserving:*it++];
402 - (void)startObserving:(CrAVCaptureDevice*)device {
403 DCHECK(device != nil);
404 // Skip this device if there are already observers connected to it.
405 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
406 monitoredDevices_.end()) {
409 [device addObserver:self
410 forKeyPath:@"suspended"
413 [device addObserver:self
414 forKeyPath:@"connected"
417 monitoredDevices_.insert(device);
420 - (void)stopObserving:(CrAVCaptureDevice*)device {
421 DCHECK(device != nil);
422 std::set<CrAVCaptureDevice*>::iterator found =
423 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
424 DCHECK(found != monitoredDevices_.end());
425 // Every so seldom, |device| might be gone when getting here, in that case
426 // removing the observer causes a crash. Try to avoid it by checking sanity of
427 // the |device| via its -observationInfo. http://crbug.com/371271.
428 if ([device observationInfo]) {
429 [device removeObserver:self
430 forKeyPath:@"suspended"];
431 [device removeObserver:self
432 forKeyPath:@"connected"];
434 monitoredDevices_.erase(found);
437 - (void)observeValueForKeyPath:(NSString*)keyPath
439 change:(NSDictionary*)change
440 context:(void*)context {
441 if ([keyPath isEqual:@"suspended"])
442 receiver_->OnDeviceChanged();
443 if ([keyPath isEqual:@"connected"])
444 [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
447 @end // @implementation CrAVFoundationDeviceObserver
451 DeviceMonitorMac::DeviceMonitorMac() {
452 // Both QTKit and AVFoundation do not need to be fired up until the user
453 // exercises a GetUserMedia. Bringing up either library and enumerating the
454 // devices in the system is an operation taking in the range of hundred of ms,
455 // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
458 DeviceMonitorMac::~DeviceMonitorMac() {}
460 void DeviceMonitorMac::StartMonitoring(
461 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
462 DCHECK(thread_checker_.CalledOnValidThread());
463 if (AVFoundationGlue::IsAVFoundationSupported()) {
464 DVLOG(1) << "Monitoring via AVFoundation";
465 device_monitor_impl_.reset(new AVFoundationMonitorImpl(this,
466 device_task_runner));
468 DVLOG(1) << "Monitoring via QTKit";
469 device_monitor_impl_.reset(new QTKitMonitorImpl(this));
473 void DeviceMonitorMac::NotifyDeviceChanged(
474 base::SystemMonitor::DeviceType type) {
475 DCHECK(thread_checker_.CalledOnValidThread());
476 // TODO(xians): Remove the global variable for SystemMonitor.
477 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
480 } // namespace content