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 #import "media/video/capture/mac/avfoundation_glue.h"
18 // This class is used to keep track of system devices names and their types.
29 DeviceInfo(std::string unique_id, DeviceType type)
30 : unique_id_(unique_id), type_(type) {}
32 // Operator== is needed here to use this class in a std::find. A given
33 // |unique_id_| always has the same |type_| so for comparison purposes the
34 // latter can be safely ignored.
35 bool operator==(const DeviceInfo& device) const {
36 return unique_id_ == device.unique_id_;
39 const std::string& unique_id() const { return unique_id_; }
40 DeviceType type() const { return type_; }
43 std::string unique_id_;
45 // Allow generated copy constructor and assignment.
48 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
49 // or an AVFoundation implementation of events and notifications.
50 class DeviceMonitorMacImpl {
52 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
56 device_removal_(nil) {
58 // Initialise the devices_cache_ with a not-valid entry. For the case in
59 // which there is one single device in the system and we get notified when
60 // it gets removed, this will prevent the system from thinking that no
61 // devices were added nor removed and not notifying the |monitor_|.
62 cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
64 virtual ~DeviceMonitorMacImpl() {}
66 virtual void OnDeviceChanged() = 0;
68 // Method called by the default notification center when a device is removed
69 // or added to the system. It will compare the |cached_devices_| with the
70 // current situation, update it, and, if there's an update, signal to
71 // |monitor_| with the appropriate device type.
72 void ConsolidateDevicesListAndNotify(
73 const std::vector<DeviceInfo>& snapshot_devices);
76 content::DeviceMonitorMac* monitor_;
77 std::vector<DeviceInfo> cached_devices_;
79 // Handles to NSNotificationCenter block observers.
84 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
87 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
88 const std::vector<DeviceInfo>& snapshot_devices) {
89 bool video_device_added = false;
90 bool audio_device_added = false;
91 bool video_device_removed = false;
92 bool audio_device_removed = false;
94 // Compare the current system devices snapshot with the ones cached to detect
95 // additions, present in the former but not in the latter. If we find a device
96 // in snapshot_devices entry also present in cached_devices, we remove it from
98 std::vector<DeviceInfo>::const_iterator it;
99 for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
100 std::vector<DeviceInfo>::iterator cached_devices_iterator =
101 std::find(cached_devices_.begin(), cached_devices_.end(), *it);
102 if (cached_devices_iterator == cached_devices_.end()) {
103 video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
104 (it->type() == DeviceInfo::kMuxed));
105 audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
106 (it->type() == DeviceInfo::kMuxed));
107 DVLOG(1) << "Device has been added, id: " << it->unique_id();
109 cached_devices_.erase(cached_devices_iterator);
112 // All the remaining entries in cached_devices are removed devices.
113 for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
114 video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
115 (it->type() == DeviceInfo::kMuxed) ||
116 (it->type() == DeviceInfo::kInvalid));
117 audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
118 (it->type() == DeviceInfo::kMuxed) ||
119 (it->type() == DeviceInfo::kInvalid));
120 DVLOG(1) << "Device has been removed, id: " << it->unique_id();
122 // Update the cached devices with the current system snapshot.
123 cached_devices_ = snapshot_devices;
125 if (video_device_added || video_device_removed)
126 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
127 if (audio_device_added || audio_device_removed)
128 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
131 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
133 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
134 virtual ~QTKitMonitorImpl();
136 virtual void OnDeviceChanged() OVERRIDE;
139 void OnAttributeChanged(NSNotification* notification);
144 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
145 : DeviceMonitorMacImpl(monitor) {
146 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
148 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
151 usingBlock:^(NSNotification* notification) {
152 OnDeviceChanged();}];
154 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
157 usingBlock:^(NSNotification* notification) {
158 OnDeviceChanged();}];
160 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
163 usingBlock:^(NSNotification* notification) {
164 OnAttributeChanged(notification);}];
167 QTKitMonitorImpl::~QTKitMonitorImpl() {
168 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
169 [nc removeObserver:device_arrival_];
170 [nc removeObserver:device_removal_];
171 [nc removeObserver:device_change_];
174 void QTKitMonitorImpl::OnAttributeChanged(
175 NSNotification* notification) {
176 if ([[[notification userInfo]
177 objectForKey:QTCaptureDeviceChangedAttributeKey]
178 isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
183 void QTKitMonitorImpl::OnDeviceChanged() {
184 std::vector<DeviceInfo> snapshot_devices;
186 NSArray* devices = [QTCaptureDevice inputDevices];
187 for (QTCaptureDevice* device in devices) {
188 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
189 // Act as if suspended video capture devices are not attached. For
190 // example, a laptop's internal webcam is suspended when the lid is closed.
191 if ([device hasMediaType:QTMediaTypeVideo] &&
192 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
194 device_type = DeviceInfo::kVideo;
195 } else if ([device hasMediaType:QTMediaTypeMuxed] &&
196 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
198 device_type = DeviceInfo::kMuxed;
199 } else if ([device hasMediaType:QTMediaTypeSound] &&
200 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
202 device_type = DeviceInfo::kAudio;
204 snapshot_devices.push_back(
205 DeviceInfo([[device uniqueID] UTF8String], device_type));
207 ConsolidateDevicesListAndNotify(snapshot_devices);
210 // Forward declaration for use by CrAVFoundationDeviceObserver.
211 class AVFoundationMonitorImpl;
215 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
216 // classes cannot observe Key-Values directly. This class is used by
217 // AVfoundationMonitorImpl and executed in its |device_task_runner_|, a.k.a.
218 // "Device Thread". -stopObserving is called dutifully on -dealloc on UI thread.
219 @interface CrAVFoundationDeviceObserver : NSObject {
221 AVFoundationMonitorImpl* receiver_;
222 // Member to keep track of the devices we are already monitoring.
223 std::set<CrAVCaptureDevice*> monitoredDevices_;
226 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver;
227 - (void)startObserving:(CrAVCaptureDevice*)device;
228 - (void)stopObserving:(CrAVCaptureDevice*)device;
234 // AVFoundation implementation of the Mac Device Monitor, registers as a global
235 // device connect/disconnect observer and plugs suspend/wake up device observers
236 // per device. This class is created and lives in UI thread; device enumeration
237 // and operations involving |suspend_observer_| happen on |device_task_runner_|.
238 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
240 AVFoundationMonitorImpl(
241 content::DeviceMonitorMac* monitor,
242 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
243 virtual ~AVFoundationMonitorImpl();
245 virtual void OnDeviceChanged() OVERRIDE;
248 void OnDeviceChangedOnDeviceThread(
249 const scoped_refptr<base::MessageLoopProxy>& ui_thread);
250 void StartObserverOnDeviceThread();
252 base::ThreadChecker thread_checker_;
254 // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
255 // device enumeration, valid after MediaStreamManager calls StartMonitoring().
256 const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
258 // Created and executed in |device_task_runnner_|.
259 base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
261 DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
264 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
265 content::DeviceMonitorMac* monitor,
266 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
267 : DeviceMonitorMacImpl(monitor),
268 device_task_runner_(device_task_runner) {
269 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
271 [nc addObserverForName:AVFoundationGlue::
272 AVCaptureDeviceWasConnectedNotification()
275 usingBlock:^(NSNotification* notification) {
276 OnDeviceChanged();}];
278 [nc addObserverForName:AVFoundationGlue::
279 AVCaptureDeviceWasDisconnectedNotification()
282 usingBlock:^(NSNotification* notification) {
283 OnDeviceChanged();}];
284 device_task_runner_->PostTask(FROM_HERE,
285 base::Bind(&AVFoundationMonitorImpl::StartObserverOnDeviceThread,
286 base::Unretained(this)));
289 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
290 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
291 [nc removeObserver:device_arrival_];
292 [nc removeObserver:device_removal_];
295 void AVFoundationMonitorImpl::OnDeviceChanged() {
296 DCHECK(thread_checker_.CalledOnValidThread());
297 device_task_runner_->PostTask(FROM_HERE,
298 base::Bind(&AVFoundationMonitorImpl::OnDeviceChangedOnDeviceThread,
299 base::Unretained(this),
300 base::MessageLoop::current()->message_loop_proxy()));
303 void AVFoundationMonitorImpl::OnDeviceChangedOnDeviceThread(
304 const scoped_refptr<base::MessageLoopProxy>& ui_thread) {
305 DCHECK(device_task_runner_->BelongsToCurrentThread());
306 NSArray* devices = [AVCaptureDeviceGlue devices];
307 std::vector<DeviceInfo> snapshot_devices;
308 for (CrAVCaptureDevice* device in devices) {
309 [suspend_observer_ startObserving:device];
310 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
311 [device isSuspended];
312 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
313 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
316 device_type = DeviceInfo::kVideo;
317 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
318 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
319 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
320 device_type = DeviceInfo::kAudio;
322 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
325 // Post the consolidation of enumerated devices to be done on UI thread.
326 ui_thread->PostTask(FROM_HERE,
327 base::Bind(&DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify,
328 base::Unretained(this), snapshot_devices));
331 void AVFoundationMonitorImpl::StartObserverOnDeviceThread() {
332 DCHECK(device_task_runner_->BelongsToCurrentThread());
333 suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
334 initWithChangeReceiver:this]);
335 for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
336 [suspend_observer_ startObserving:device];
341 @implementation CrAVFoundationDeviceObserver
343 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver {
344 if ((self = [super init])) {
345 DCHECK(receiver != NULL);
346 receiver_ = receiver;
352 std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin();
353 while (it != monitoredDevices_.end())
354 [self stopObserving:*it++];
358 - (void)startObserving:(CrAVCaptureDevice*)device {
359 DCHECK(device != nil);
360 // Skip this device if there are already observers connected to it.
361 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
362 monitoredDevices_.end()) {
365 [device addObserver:self
366 forKeyPath:@"suspended"
369 [device addObserver:self
370 forKeyPath:@"connected"
373 monitoredDevices_.insert(device);
376 - (void)stopObserving:(CrAVCaptureDevice*)device {
377 DCHECK(device != nil);
378 std::set<CrAVCaptureDevice*>::iterator found =
379 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
380 DCHECK(found != monitoredDevices_.end());
381 [device removeObserver:self
382 forKeyPath:@"suspended"];
383 [device removeObserver:self
384 forKeyPath:@"connected"];
385 monitoredDevices_.erase(found);
388 - (void)observeValueForKeyPath:(NSString*)keyPath
390 change:(NSDictionary*)change
391 context:(void*)context {
392 if ([keyPath isEqual:@"suspended"])
393 receiver_->OnDeviceChanged();
394 if ([keyPath isEqual:@"connected"])
395 [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
398 @end // @implementation CrAVFoundationDeviceObserver
402 DeviceMonitorMac::DeviceMonitorMac() {
403 // Both QTKit and AVFoundation do not need to be fired up until the user
404 // exercises a GetUserMedia. Bringing up either library and enumerating the
405 // devices in the system is an operation taking in the range of hundred of ms,
406 // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
409 DeviceMonitorMac::~DeviceMonitorMac() {}
411 void DeviceMonitorMac::StartMonitoring(
412 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
413 DCHECK(thread_checker_.CalledOnValidThread());
414 if (AVFoundationGlue::IsAVFoundationSupported()) {
415 DVLOG(1) << "Monitoring via AVFoundation";
416 device_monitor_impl_.reset(new AVFoundationMonitorImpl(this,
417 device_task_runner));
419 DVLOG(1) << "Monitoring via QTKit";
420 device_monitor_impl_.reset(new QTKitMonitorImpl(this));
424 void DeviceMonitorMac::NotifyDeviceChanged(
425 base::SystemMonitor::DeviceType type) {
426 DCHECK(thread_checker_.CalledOnValidThread());
427 // TODO(xians): Remove the global variable for SystemMonitor.
428 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
431 } // namespace content