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/device_monitor_mac.h" 6 7 #import <QTKit/QTKit.h> 8 9 #include "base/logging.h" 10 #import "media/video/capture/mac/avfoundation_glue.h" 11 12 namespace { 13 14 // This class is used to keep track of system devices names and their types. 15 class DeviceInfo { 16 public: 17 enum DeviceType { 18 kAudio, 19 kVideo, 20 kMuxed, 21 kUnknown, 22 kInvalid 23 }; 24 25 DeviceInfo(std::string unique_id, DeviceType type) 26 : unique_id_(unique_id), type_(type) {} 27 28 // Operator== is needed here to use this class in a std::find. A given 29 // |unique_id_| always has the same |type_| so for comparison purposes the 30 // latter can be safely ignored. 31 bool operator==(const DeviceInfo& device) const { 32 return unique_id_ == device.unique_id_; 33 } 34 35 const std::string& unique_id() const { return unique_id_; } 36 DeviceType type() const { return type_; } 37 38 private: 39 std::string unique_id_; 40 DeviceType type_; 41 // Allow generated copy constructor and assignment. 42 }; 43 44 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit 45 // or an AVFoundation implementation of events and notifications. 46 class DeviceMonitorMacImpl { 47 public: 48 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor) 49 : monitor_(monitor), 50 cached_devices_(), 51 device_arrival_(nil), 52 device_removal_(nil) { 53 DCHECK(monitor); 54 // Initialise the devices_cache_ with a not-valid entry. For the case in 55 // which there is one single device in the system and we get notified when 56 // it gets removed, this will prevent the system from thinking that no 57 // devices were added nor removed and not notifying the |monitor_|. 58 cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid)); 59 } 60 virtual ~DeviceMonitorMacImpl() {} 61 62 virtual void OnDeviceChanged() = 0; 63 64 // Method called by the default notification center when a device is removed 65 // or added to the system. It will compare the |cached_devices_| with the 66 // current situation, update it, and, if there's an update, signal to 67 // |monitor_| with the appropriate device type. 68 void ConsolidateDevicesListAndNotify( 69 const std::vector<DeviceInfo>& snapshot_devices); 70 71 protected: 72 content::DeviceMonitorMac* monitor_; 73 std::vector<DeviceInfo> cached_devices_; 74 75 // Handles to NSNotificationCenter block observers. 76 id device_arrival_; 77 id device_removal_; 78 79 private: 80 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl); 81 }; 82 83 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify( 84 const std::vector<DeviceInfo>& snapshot_devices) { 85 bool video_device_added = false; 86 bool audio_device_added = false; 87 bool video_device_removed = false; 88 bool audio_device_removed = false; 89 90 // Compare the current system devices snapshot with the ones cached to detect 91 // additions, present in the former but not in the latter. If we find a device 92 // in snapshot_devices entry also present in cached_devices, we remove it from 93 // the latter vector. 94 std::vector<DeviceInfo>::const_iterator it; 95 for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) { 96 std::vector<DeviceInfo>::iterator cached_devices_iterator = 97 std::find(cached_devices_.begin(), cached_devices_.end(), *it); 98 if (cached_devices_iterator == cached_devices_.end()) { 99 video_device_added |= ((it->type() == DeviceInfo::kVideo) || 100 (it->type() == DeviceInfo::kMuxed)); 101 audio_device_added |= ((it->type() == DeviceInfo::kAudio) || 102 (it->type() == DeviceInfo::kMuxed)); 103 DVLOG(1) << "Device has been added, id: " << it->unique_id(); 104 } else { 105 cached_devices_.erase(cached_devices_iterator); 106 } 107 } 108 // All the remaining entries in cached_devices are removed devices. 109 for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) { 110 video_device_removed |= ((it->type() == DeviceInfo::kVideo) || 111 (it->type() == DeviceInfo::kMuxed) || 112 (it->type() == DeviceInfo::kInvalid)); 113 audio_device_removed |= ((it->type() == DeviceInfo::kAudio) || 114 (it->type() == DeviceInfo::kMuxed) || 115 (it->type() == DeviceInfo::kInvalid)); 116 DVLOG(1) << "Device has been removed, id: " << it->unique_id(); 117 } 118 // Update the cached devices with the current system snapshot. 119 cached_devices_ = snapshot_devices; 120 121 if (video_device_added || video_device_removed) 122 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE); 123 if (audio_device_added || audio_device_removed) 124 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE); 125 } 126 127 class QTKitMonitorImpl : public DeviceMonitorMacImpl { 128 public: 129 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor); 130 virtual ~QTKitMonitorImpl(); 131 132 virtual void OnDeviceChanged() OVERRIDE; 133 private: 134 void CountDevices(); 135 void OnAttributeChanged(NSNotification* notification); 136 137 id device_change_; 138 }; 139 140 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor) 141 : DeviceMonitorMacImpl(monitor) { 142 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 143 device_arrival_ = 144 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification 145 object:nil 146 queue:nil 147 usingBlock:^(NSNotification* notification) { 148 OnDeviceChanged();}]; 149 device_removal_ = 150 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification 151 object:nil 152 queue:nil 153 usingBlock:^(NSNotification* notification) { 154 OnDeviceChanged();}]; 155 device_change_ = 156 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification 157 object:nil 158 queue:nil 159 usingBlock:^(NSNotification* notification) { 160 OnAttributeChanged(notification);}]; 161 } 162 163 QTKitMonitorImpl::~QTKitMonitorImpl() { 164 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 165 [nc removeObserver:device_arrival_]; 166 [nc removeObserver:device_removal_]; 167 [nc removeObserver:device_change_]; 168 } 169 170 void QTKitMonitorImpl::OnAttributeChanged( 171 NSNotification* notification) { 172 if ([[[notification userInfo] 173 objectForKey:QTCaptureDeviceChangedAttributeKey] 174 isEqualToString:QTCaptureDeviceSuspendedAttribute]) { 175 OnDeviceChanged(); 176 } 177 } 178 179 void QTKitMonitorImpl::OnDeviceChanged() { 180 std::vector<DeviceInfo> snapshot_devices; 181 182 NSArray* devices = [QTCaptureDevice inputDevices]; 183 for (QTCaptureDevice* device in devices) { 184 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown; 185 // Act as if suspended video capture devices are not attached. For 186 // example, a laptop's internal webcam is suspended when the lid is closed. 187 if ([device hasMediaType:QTMediaTypeVideo] && 188 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] 189 boolValue]) { 190 device_type = DeviceInfo::kVideo; 191 } else if ([device hasMediaType:QTMediaTypeMuxed] && 192 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] 193 boolValue]) { 194 device_type = DeviceInfo::kMuxed; 195 } else if ([device hasMediaType:QTMediaTypeSound] && 196 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] 197 boolValue]) { 198 device_type = DeviceInfo::kAudio; 199 } 200 snapshot_devices.push_back( 201 DeviceInfo([[device uniqueID] UTF8String], device_type)); 202 } 203 ConsolidateDevicesListAndNotify(snapshot_devices); 204 } 205 206 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl { 207 public: 208 explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor); 209 virtual ~AVFoundationMonitorImpl(); 210 211 virtual void OnDeviceChanged() OVERRIDE; 212 }; 213 214 AVFoundationMonitorImpl::AVFoundationMonitorImpl( 215 content::DeviceMonitorMac* monitor) 216 : DeviceMonitorMacImpl(monitor) { 217 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 218 device_arrival_ = 219 [nc addObserverForName:AVFoundationGlue:: 220 AVCaptureDeviceWasConnectedNotification() 221 object:nil 222 queue:nil 223 usingBlock:^(NSNotification* notification) { 224 OnDeviceChanged();}]; 225 device_removal_ = 226 [nc addObserverForName:AVFoundationGlue:: 227 AVCaptureDeviceWasDisconnectedNotification() 228 object:nil 229 queue:nil 230 usingBlock:^(NSNotification* notification) { 231 OnDeviceChanged();}]; 232 } 233 234 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() { 235 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 236 [nc removeObserver:device_arrival_]; 237 [nc removeObserver:device_removal_]; 238 } 239 240 void AVFoundationMonitorImpl::OnDeviceChanged() { 241 std::vector<DeviceInfo> snapshot_devices; 242 243 NSArray* devices = [AVCaptureDeviceGlue devices]; 244 for (CrAVCaptureDevice* device in devices) { 245 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown; 246 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) { 247 device_type = DeviceInfo::kVideo; 248 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) { 249 device_type = DeviceInfo::kMuxed; 250 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) { 251 device_type = DeviceInfo::kAudio; 252 } 253 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String], 254 device_type)); 255 } 256 ConsolidateDevicesListAndNotify(snapshot_devices); 257 } 258 259 } // namespace 260 261 namespace content { 262 263 DeviceMonitorMac::DeviceMonitorMac() { 264 if (AVFoundationGlue::IsAVFoundationSupported()) { 265 DVLOG(1) << "Monitoring via AVFoundation"; 266 device_monitor_impl_.reset(new AVFoundationMonitorImpl(this)); 267 // For the AVFoundation to start sending connect/disconnect notifications, 268 // the AVFoundation NSBundle has to be loaded and the devices enumerated. 269 // This operation seems to take in the range of hundred of ms. so should be 270 // moved to the point when is needed, and that is during 271 // DeviceVideoCaptureMac +getDeviceNames. 272 } else { 273 DVLOG(1) << "Monitoring via QTKit"; 274 device_monitor_impl_.reset(new QTKitMonitorImpl(this)); 275 } 276 } 277 278 DeviceMonitorMac::~DeviceMonitorMac() {} 279 280 void DeviceMonitorMac::NotifyDeviceChanged( 281 base::SystemMonitor::DeviceType type) { 282 // TODO(xians): Remove the global variable for SystemMonitor. 283 base::SystemMonitor::Get()->ProcessDevicesChanged(type); 284 } 285 286 } // namespace content 287