• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
12namespace {
13
14// This class is used to keep track of system devices names and their types.
15class 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.
46class 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
83void 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
127class 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
140QTKitMonitorImpl::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
163QTKitMonitorImpl::~QTKitMonitorImpl() {
164  NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
165  [nc removeObserver:device_arrival_];
166  [nc removeObserver:device_removal_];
167  [nc removeObserver:device_change_];
168}
169
170void QTKitMonitorImpl::OnAttributeChanged(
171    NSNotification* notification) {
172  if ([[[notification userInfo]
173         objectForKey:QTCaptureDeviceChangedAttributeKey]
174      isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
175    OnDeviceChanged();
176  }
177}
178
179void 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
206class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
207 public:
208  explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
209  virtual ~AVFoundationMonitorImpl();
210
211  virtual void OnDeviceChanged() OVERRIDE;
212};
213
214AVFoundationMonitorImpl::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
234AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
235  NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
236  [nc removeObserver:device_arrival_];
237  [nc removeObserver:device_removal_];
238}
239
240void 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
261namespace content {
262
263DeviceMonitorMac::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
278DeviceMonitorMac::~DeviceMonitorMac() {}
279
280void 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