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 "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