Home | History | Annotate | Download | only in browser
      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