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 <set> 10 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" 17 18 namespace { 19 20 // This class is used to keep track of system devices names and their types. 21 class DeviceInfo { 22 public: 23 enum DeviceType { 24 kAudio, 25 kVideo, 26 kMuxed, 27 kUnknown, 28 kInvalid 29 }; 30 31 DeviceInfo(std::string unique_id, DeviceType type) 32 : unique_id_(unique_id), type_(type) {} 33 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_; 39 } 40 41 const std::string& unique_id() const { return unique_id_; } 42 DeviceType type() const { return type_; } 43 44 private: 45 std::string unique_id_; 46 DeviceType type_; 47 // Allow generated copy constructor and assignment. 48 }; 49 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 { 53 public: 54 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor) 55 : monitor_(monitor), 56 cached_devices_(), 57 device_arrival_(nil), 58 device_removal_(nil) { 59 DCHECK(monitor); 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)); 65 } 66 virtual ~DeviceMonitorMacImpl() {} 67 68 virtual void OnDeviceChanged() = 0; 69 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); 76 77 protected: 78 content::DeviceMonitorMac* monitor_; 79 std::vector<DeviceInfo> cached_devices_; 80 81 // Handles to NSNotificationCenter block observers. 82 id device_arrival_; 83 id device_removal_; 84 85 private: 86 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl); 87 }; 88 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; 95 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 99 // the latter vector. 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(); 110 } else { 111 cached_devices_.erase(cached_devices_iterator); 112 } 113 } 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(); 123 } 124 // Update the cached devices with the current system snapshot. 125 cached_devices_ = snapshot_devices; 126 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); 131 } 132 133 class QTKitMonitorImpl : public DeviceMonitorMacImpl { 134 public: 135 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor); 136 virtual ~QTKitMonitorImpl(); 137 138 virtual void OnDeviceChanged() OVERRIDE; 139 private: 140 void CountDevices(); 141 void OnAttributeChanged(NSNotification* notification); 142 143 id device_change_; 144 }; 145 146 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor) 147 : DeviceMonitorMacImpl(monitor) { 148 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 149 device_arrival_ = 150 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification 151 object:nil 152 queue:nil 153 usingBlock:^(NSNotification* notification) { 154 OnDeviceChanged();}]; 155 device_removal_ = 156 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification 157 object:nil 158 queue:nil 159 usingBlock:^(NSNotification* notification) { 160 OnDeviceChanged();}]; 161 device_change_ = 162 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification 163 object:nil 164 queue:nil 165 usingBlock:^(NSNotification* notification) { 166 OnAttributeChanged(notification);}]; 167 } 168 169 QTKitMonitorImpl::~QTKitMonitorImpl() { 170 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 171 [nc removeObserver:device_arrival_]; 172 [nc removeObserver:device_removal_]; 173 [nc removeObserver:device_change_]; 174 } 175 176 void QTKitMonitorImpl::OnAttributeChanged( 177 NSNotification* notification) { 178 if ([[[notification userInfo] 179 objectForKey:QTCaptureDeviceChangedAttributeKey] 180 isEqualToString:QTCaptureDeviceSuspendedAttribute]) { 181 OnDeviceChanged(); 182 } 183 } 184 185 void QTKitMonitorImpl::OnDeviceChanged() { 186 std::vector<DeviceInfo> snapshot_devices; 187 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] 195 boolValue]) { 196 device_type = DeviceInfo::kVideo; 197 } else if ([device hasMediaType:QTMediaTypeMuxed] && 198 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] 199 boolValue]) { 200 device_type = DeviceInfo::kMuxed; 201 } else if ([device hasMediaType:QTMediaTypeSound] && 202 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] 203 boolValue]) { 204 device_type = DeviceInfo::kAudio; 205 } 206 snapshot_devices.push_back( 207 DeviceInfo([[device uniqueID] UTF8String], device_type)); 208 } 209 ConsolidateDevicesListAndNotify(snapshot_devices); 210 } 211 212 // Forward declaration for use by CrAVFoundationDeviceObserver. 213 class SuspendObserverDelegate; 214 215 } // namespace 216 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 { 221 @private 222 SuspendObserverDelegate* receiver_; // weak 223 // Member to keep track of the devices we are already monitoring. 224 std::set<CrAVCaptureDevice*> monitoredDevices_; 225 } 226 227 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver; 228 - (void)startObserving:(CrAVCaptureDevice*)device; 229 - (void)stopObserving:(CrAVCaptureDevice*)device; 230 231 @end 232 233 namespace { 234 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> { 241 public: 242 explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor) 243 : avfoundation_monitor_impl_(monitor) { 244 device_thread_checker_.DetachFromThread(); 245 } 246 247 void OnDeviceChanged(); 248 void StartObserver(); 249 void ResetDeviceMonitorOnUIThread(); 250 251 private: 252 friend class base::RefCountedThreadSafe<SuspendObserverDelegate>; 253 254 virtual ~SuspendObserverDelegate() {} 255 256 void OnDeviceChangedOnUIThread( 257 const std::vector<DeviceInfo>& snapshot_devices); 258 259 base::ThreadChecker device_thread_checker_; 260 base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_; 261 DeviceMonitorMacImpl* avfoundation_monitor_impl_; 262 }; 263 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()]) { 274 if (suspended) 275 continue; 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; 281 } 282 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String], 283 device_type)); 284 } 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)); 289 } 290 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]; 297 } 298 299 void SuspendObserverDelegate::ResetDeviceMonitorOnUIThread() { 300 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 301 avfoundation_monitor_impl_ = NULL; 302 } 303 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( 311 snapshot_devices); 312 } 313 } 314 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 { 321 public: 322 AVFoundationMonitorImpl( 323 content::DeviceMonitorMac* monitor, 324 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner); 325 virtual ~AVFoundationMonitorImpl(); 326 327 virtual void OnDeviceChanged() OVERRIDE; 328 329 private: 330 base::ThreadChecker thread_checker_; 331 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_; 336 337 scoped_refptr<SuspendObserverDelegate> suspend_observer_delegate_; 338 339 DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl); 340 }; 341 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]; 349 device_arrival_ = 350 [nc addObserverForName:AVFoundationGlue:: 351 AVCaptureDeviceWasConnectedNotification() 352 object:nil 353 queue:nil 354 usingBlock:^(NSNotification* notification) { 355 OnDeviceChanged();}]; 356 device_removal_ = 357 [nc addObserverForName:AVFoundationGlue:: 358 AVCaptureDeviceWasDisconnectedNotification() 359 object:nil 360 queue:nil 361 usingBlock:^(NSNotification* notification) { 362 OnDeviceChanged();}]; 363 device_task_runner_->PostTask(FROM_HERE, 364 base::Bind(&SuspendObserverDelegate::StartObserver, 365 suspend_observer_delegate_)); 366 } 367 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_]; 374 } 375 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_)); 381 } 382 383 } // namespace 384 385 @implementation CrAVFoundationDeviceObserver 386 387 - (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver { 388 if ((self = [super init])) { 389 DCHECK(receiver != NULL); 390 receiver_ = receiver; 391 } 392 return self; 393 } 394 395 - (void)dealloc { 396 std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin(); 397 while (it != monitoredDevices_.end()) 398 [self stopObserving:*it++]; 399 [super dealloc]; 400 } 401 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()) { 407 return; 408 } 409 [device addObserver:self 410 forKeyPath:@"suspended" 411 options:0 412 context:device]; 413 [device addObserver:self 414 forKeyPath:@"connected" 415 options:0 416 context:device]; 417 monitoredDevices_.insert(device); 418 } 419 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"]; 433 } 434 monitoredDevices_.erase(found); 435 } 436 437 - (void)observeValueForKeyPath:(NSString*)keyPath 438 ofObject:(id)object 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)]; 445 } 446 447 @end // @implementation CrAVFoundationDeviceObserver 448 449 namespace content { 450 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(). 456 } 457 458 DeviceMonitorMac::~DeviceMonitorMac() {} 459 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)); 467 } else { 468 DVLOG(1) << "Monitoring via QTKit"; 469 device_monitor_impl_.reset(new QTKitMonitorImpl(this)); 470 } 471 } 472 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); 478 } 479 480 } // namespace content 481