Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(339)

Side by Side Diff: media/capture/device_monitor_mac.mm

Issue 2224643002: Move device monitors out of //media/capture into //media/device_monitors (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « media/capture/device_monitor_mac.h ('k') | media/capture/device_monitor_udev.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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 "media/capture/device_monitor_mac.h"
6
7 #include <set>
8
9 #include "base/bind_helpers.h"
10 #include "base/logging.h"
11 #include "base/mac/bind_objc_block.h"
12 #include "base/mac/scoped_nsobject.h"
13 #include "base/macros.h"
14 #include "base/profiler/scoped_tracker.h"
15 #include "base/task_runner_util.h"
16 #include "base/threading/thread_checker.h"
17 #import "media/base/mac/avfoundation_glue.h"
18
19 namespace {
20
21 // This class is used to keep track of system devices names and their types.
22 class DeviceInfo {
23 public:
24 enum DeviceType { kAudio, kVideo, kMuxed, kUnknown, kInvalid };
25
26 DeviceInfo(const std::string& unique_id, DeviceType type)
27 : unique_id_(unique_id), type_(type) {}
28
29 // Operator== is needed here to use this class in a std::find. A given
30 // |unique_id_| always has the same |type_| so for comparison purposes the
31 // latter can be safely ignored.
32 bool operator==(const DeviceInfo& device) const {
33 return unique_id_ == device.unique_id_;
34 }
35
36 const std::string& unique_id() const { return unique_id_; }
37 DeviceType type() const { return type_; }
38
39 private:
40 std::string unique_id_;
41 DeviceType type_;
42 // Allow generated copy constructor and assignment.
43 };
44
45 // Base abstract class used by DeviceMonitorMac.
46 class DeviceMonitorMacImpl {
47 public:
48 explicit DeviceMonitorMacImpl(media::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 media::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 // Forward declaration for use by CrAVFoundationDeviceObserver.
128 class SuspendObserverDelegate;
129
130 } // namespace
131
132 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
133 // classes cannot observe Key-Values directly. Created, manipulated, and
134 // destroyed on the UI Thread by SuspendObserverDelegate.
135 @interface CrAVFoundationDeviceObserver : NSObject {
136 @private
137 // Callback for device changed, has to run on Device Thread.
138 base::Closure onDeviceChangedCallback_;
139
140 // Member to keep track of the devices we are already monitoring.
141 std::set<base::scoped_nsobject<CrAVCaptureDevice>> monitoredDevices_;
142
143 // Pegged to the "main" thread -- usually content::BrowserThread::UI.
144 base::ThreadChecker mainThreadChecker_;
145 }
146
147 - (id)initWithOnChangedCallback:(const base::Closure&)callback;
148 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device;
149 - (void)stopObserving:(CrAVCaptureDevice*)device;
150 - (void)clearOnDeviceChangedCallback;
151
152 @end
153
154 namespace {
155
156 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver.
157 // It is created and destroyed on AVFoundationMonitorImpl's main thread (usually
158 // browser's UI thread), and it operates on this thread except for the expensive
159 // device enumerations which are run on Device Thread.
160 class SuspendObserverDelegate
161 : public base::RefCountedThreadSafe<SuspendObserverDelegate> {
162 public:
163 explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor);
164
165 // Create |suspend_observer_| for all devices and register OnDeviceChanged()
166 // as its change callback. Schedule bottom half in DoStartObserver().
167 void StartObserver(
168 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread);
169 // Enumerate devices in |device_thread| and run the bottom half in
170 // DoOnDeviceChange(). |suspend_observer_| calls back here on suspend event,
171 // and our parent AVFoundationMonitorImpl calls on connect/disconnect device.
172 void OnDeviceChanged(
173 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread);
174 // Remove the device monitor's weak reference. Remove ourselves as suspend
175 // notification observer from |suspend_observer_|.
176 void ResetDeviceMonitor();
177
178 private:
179 friend class base::RefCountedThreadSafe<SuspendObserverDelegate>;
180
181 virtual ~SuspendObserverDelegate();
182
183 // Bottom half of StartObserver(), starts |suspend_observer_| for all devices.
184 // Assumes that |devices| has been retained prior to being called, and
185 // releases it internally.
186 void DoStartObserver(NSArray* devices);
187 // Bottom half of OnDeviceChanged(), starts |suspend_observer_| for current
188 // devices and composes a snapshot of them to send it to
189 // |avfoundation_monitor_impl_|. Assumes that |devices| has been retained
190 // prior to being called, and releases it internally.
191 void DoOnDeviceChanged(NSArray* devices);
192
193 base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
194 DeviceMonitorMacImpl* avfoundation_monitor_impl_;
195
196 // Pegged to the "main" thread -- usually content::BrowserThread::UI.
197 base::ThreadChecker main_thread_checker_;
198 };
199
200 SuspendObserverDelegate::SuspendObserverDelegate(DeviceMonitorMacImpl* monitor)
201 : avfoundation_monitor_impl_(monitor) {
202 DCHECK(main_thread_checker_.CalledOnValidThread());
203 }
204
205 void SuspendObserverDelegate::StartObserver(
206 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) {
207 DCHECK(main_thread_checker_.CalledOnValidThread());
208
209 base::Closure on_device_changed_callback = base::Bind(
210 &SuspendObserverDelegate::OnDeviceChanged, this, device_thread);
211 suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
212 initWithOnChangedCallback:on_device_changed_callback]);
213
214 // Enumerate the devices in Device thread and post the observers start to be
215 // done on UI thread. The devices array is retained in |device_thread| and
216 // released in DoStartObserver().
217 base::PostTaskAndReplyWithResult(
218 device_thread.get(), FROM_HERE, base::BindBlock(^{
219 return [[AVCaptureDeviceGlue devices] retain];
220 }),
221 base::Bind(&SuspendObserverDelegate::DoStartObserver, this));
222 }
223
224 void SuspendObserverDelegate::OnDeviceChanged(
225 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) {
226 DCHECK(main_thread_checker_.CalledOnValidThread());
227 // Enumerate the devices in Device thread and post the consolidation of the
228 // new devices and the old ones to be done on main thread. The devices array
229 // is retained in |device_thread| and released in DoOnDeviceChanged().
230 PostTaskAndReplyWithResult(
231 device_thread.get(), FROM_HERE, base::BindBlock(^{
232 return [[AVCaptureDeviceGlue devices] retain];
233 }),
234 base::Bind(&SuspendObserverDelegate::DoOnDeviceChanged, this));
235 }
236
237 void SuspendObserverDelegate::ResetDeviceMonitor() {
238 DCHECK(main_thread_checker_.CalledOnValidThread());
239 avfoundation_monitor_impl_ = NULL;
240 [suspend_observer_ clearOnDeviceChangedCallback];
241 }
242
243 SuspendObserverDelegate::~SuspendObserverDelegate() {
244 DCHECK(main_thread_checker_.CalledOnValidThread());
245 }
246
247 void SuspendObserverDelegate::DoStartObserver(NSArray* devices) {
248 DCHECK(main_thread_checker_.CalledOnValidThread());
249 base::scoped_nsobject<NSArray> auto_release(devices);
250 for (CrAVCaptureDevice* device in devices) {
251 base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]);
252 [suspend_observer_ startObserving:device_ptr];
253 }
254 }
255
256 void SuspendObserverDelegate::DoOnDeviceChanged(NSArray* devices) {
257 DCHECK(main_thread_checker_.CalledOnValidThread());
258 base::scoped_nsobject<NSArray> auto_release(devices);
259 std::vector<DeviceInfo> snapshot_devices;
260 for (CrAVCaptureDevice* device in devices) {
261 base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]);
262 [suspend_observer_ startObserving:device_ptr];
263
264 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
265 [device isSuspended];
266 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
267 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
268 if (suspended)
269 continue;
270 device_type = DeviceInfo::kVideo;
271 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
272 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
273 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
274 device_type = DeviceInfo::kAudio;
275 }
276 snapshot_devices.push_back(
277 DeviceInfo([[device uniqueID] UTF8String], device_type));
278 }
279 // Make sure no references are held to |devices| when
280 // ConsolidateDevicesListAndNotify is called since the VideoCaptureManager
281 // and AudioCaptureManagers also enumerates the available devices but on
282 // another thread.
283 auto_release.reset();
284 // |avfoundation_monitor_impl_| might have been NULLed asynchronously before
285 // arriving at this line.
286 if (avfoundation_monitor_impl_) {
287 avfoundation_monitor_impl_->ConsolidateDevicesListAndNotify(
288 snapshot_devices);
289 }
290 }
291
292 // AVFoundation implementation of the Mac Device Monitor, registers as a global
293 // device connect/disconnect observer and plugs suspend/wake up device observers
294 // per device. This class is created and lives on the main Application thread
295 // (UI for content). Owns a SuspendObserverDelegate that notifies when a device
296 // is suspended/resumed.
297 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
298 public:
299 AVFoundationMonitorImpl(
300 media::DeviceMonitorMac* monitor,
301 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner);
302 ~AVFoundationMonitorImpl() override;
303
304 void OnDeviceChanged() override;
305
306 private:
307 // {Video,AudioInput}DeviceManager's "Device" thread task runner used for
308 // posting tasks to |suspend_observer_delegate_|;
309 const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
310
311 // Pegged to the "main" thread -- usually content::BrowserThread::UI.
312 base::ThreadChecker main_thread_checker_;
313
314 scoped_refptr<SuspendObserverDelegate> suspend_observer_delegate_;
315
316 DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
317 };
318
319 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
320 media::DeviceMonitorMac* monitor,
321 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
322 : DeviceMonitorMacImpl(monitor),
323 device_task_runner_(device_task_runner),
324 suspend_observer_delegate_(new SuspendObserverDelegate(this)) {
325 DCHECK(main_thread_checker_.CalledOnValidThread());
326 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
327 device_arrival_ =
328 [nc addObserverForName:AVFoundationGlue::
329 AVCaptureDeviceWasConnectedNotification()
330 object:nil
331 queue:nil
332 usingBlock:^(NSNotification* notification) {
333 OnDeviceChanged();
334 }];
335 device_removal_ =
336 [nc addObserverForName:AVFoundationGlue::
337 AVCaptureDeviceWasDisconnectedNotification()
338 object:nil
339 queue:nil
340 usingBlock:^(NSNotification* notification) {
341 OnDeviceChanged();
342 }];
343 suspend_observer_delegate_->StartObserver(device_task_runner_);
344 }
345
346 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
347 DCHECK(main_thread_checker_.CalledOnValidThread());
348 suspend_observer_delegate_->ResetDeviceMonitor();
349 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
350 [nc removeObserver:device_arrival_];
351 [nc removeObserver:device_removal_];
352 }
353
354 void AVFoundationMonitorImpl::OnDeviceChanged() {
355 DCHECK(main_thread_checker_.CalledOnValidThread());
356 suspend_observer_delegate_->OnDeviceChanged(device_task_runner_);
357 }
358
359 } // namespace
360
361 @implementation CrAVFoundationDeviceObserver
362
363 - (id)initWithOnChangedCallback:(const base::Closure&)callback {
364 DCHECK(mainThreadChecker_.CalledOnValidThread());
365 if ((self = [super init])) {
366 DCHECK(!callback.is_null());
367 onDeviceChangedCallback_ = callback;
368 }
369 return self;
370 }
371
372 - (void)dealloc {
373 DCHECK(mainThreadChecker_.CalledOnValidThread());
374 std::set<base::scoped_nsobject<CrAVCaptureDevice>>::iterator it =
375 monitoredDevices_.begin();
376 while (it != monitoredDevices_.end())
377 [self removeObservers:*(it++)];
378 [super dealloc];
379 }
380
381 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device {
382 DCHECK(mainThreadChecker_.CalledOnValidThread());
383 DCHECK(device != nil);
384 // Skip this device if there are already observers connected to it.
385 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
386 monitoredDevices_.end()) {
387 return;
388 }
389 [device addObserver:self
390 forKeyPath:@"suspended"
391 options:0
392 context:device.get()];
393 [device addObserver:self
394 forKeyPath:@"connected"
395 options:0
396 context:device.get()];
397 monitoredDevices_.insert(device);
398 }
399
400 - (void)stopObserving:(CrAVCaptureDevice*)device {
401 DCHECK(mainThreadChecker_.CalledOnValidThread());
402 DCHECK(device != nil);
403
404 std::set<base::scoped_nsobject<CrAVCaptureDevice>>::iterator found =
405 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
406 DCHECK(found != monitoredDevices_.end());
407 [self removeObservers:*found];
408 monitoredDevices_.erase(found);
409 }
410
411 - (void)clearOnDeviceChangedCallback {
412 DCHECK(mainThreadChecker_.CalledOnValidThread());
413 onDeviceChangedCallback_.Reset();
414 }
415
416 - (void)removeObservers:(CrAVCaptureDevice*)device {
417 DCHECK(mainThreadChecker_.CalledOnValidThread());
418 // Check sanity of |device| via its -observationInfo. http://crbug.com/371271.
419 if ([device observationInfo]) {
420 [device removeObserver:self
421 forKeyPath:@"suspended"];
422 [device removeObserver:self
423 forKeyPath:@"connected"];
424 }
425 }
426
427 - (void)observeValueForKeyPath:(NSString*)keyPath
428 ofObject:(id)object
429 change:(NSDictionary*)change
430 context:(void*)context {
431 DCHECK(mainThreadChecker_.CalledOnValidThread());
432 if ([keyPath isEqual:@"suspended"])
433 onDeviceChangedCallback_.Run();
434 if ([keyPath isEqual:@"connected"])
435 [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
436 }
437
438 @end // @implementation CrAVFoundationDeviceObserver
439
440 namespace media {
441
442 DeviceMonitorMac::DeviceMonitorMac() {
443 // AVFoundation do not need to be fired up until the user
444 // exercises a GetUserMedia. Bringing up either library and enumerating the
445 // devices in the system is an operation taking in the range of hundred of ms,
446 // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
447 }
448
449 DeviceMonitorMac::~DeviceMonitorMac() {}
450
451 void DeviceMonitorMac::StartMonitoring(
452 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
453 DCHECK(thread_checker_.CalledOnValidThread());
454
455 // We're on the UI thread so let's try to initialize AVFoundation.
456 AVFoundationGlue::InitializeAVFoundation();
457
458 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458404
459 // is fixed.
460 tracked_objects::ScopedTracker tracking_profile(
461 FROM_HERE_WITH_EXPLICIT_FUNCTION(
462 "458404 DeviceMonitorMac::StartMonitoring::AVFoundation"));
463 DVLOG(1) << "Monitoring via AVFoundation";
464 device_monitor_impl_.reset(
465 new AVFoundationMonitorImpl(this, device_task_runner));
466 }
467
468 void DeviceMonitorMac::NotifyDeviceChanged(
469 base::SystemMonitor::DeviceType type) {
470 DCHECK(thread_checker_.CalledOnValidThread());
471 // TODO(xians): Remove the global variable for SystemMonitor.
472 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
473 }
474
475 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/device_monitor_mac.h ('k') | media/capture/device_monitor_udev.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698