Device monitoring in Mac via AVFoundation.

Relanding a couple of CLs with intelligent (humand based) rebase:

- Relanding http://crrev.com/24615005/, first introduction of the
DeviceMonitorMacImpl and associate AVFoundationGlue:
-- AVFoundation glue is cleaned up following posterior comments
to bemasc@ in http://crrev.com/50133002.
-- Including http://crrev.com/43633002, silly typo causing a bug.
-- Merges bemasc@ treatment of suspended devices as not available,
only for QTKit http://crrev.com/55183002. .
-- Adapted the treatment from bemasc@ sorting out the insidious bug,
see below and http://crrev.com/51703003. In this case we add a dummy
entry in the list of devices, and mark it as |kInvalid|.

- Relanding http://crrev.com/40493003, avi@ fix for MEDIA_EXPORT.

- Relanding http://crrev.com/50213004, putting AVFoundation behind flag.
Note that we still are forcing enumeration of devices on startup, but
since it's behind a flag, no penalty is expected in the short term
and in the mid term the whole AVFoundation bringup will be moved to
getUserMedia() runtime.

A very insidious bug was uncovered by bemasc@, when, for performance
reasons we didn't enumerate devices on startup; it could be that a
Mac box would have one camera, not enumerated, and it gets disconnected,
since the init value of the |cached_devices_| is empty, the code would
see no difference and take thus take no action. bemasc@ solved it by
init'ing the device count to -1 both for video and audio.

BUG=288562

Review URL: https://codereview.chromium.org/63103004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233583 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/DEPS b/content/browser/DEPS
index 73e4d4a..98370cb 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -5,6 +5,7 @@
   "+media/audio",  # For audio input for speech input feature.
   "+media/base",  # For Android JNI registration.
   "+media/midi",  # For Web MIDI API
+  "+media/video",  # For Video Device monitoring in Mac.
   "+sql",
   "+ui/webui",
   "+win8/util",
diff --git a/content/browser/device_monitor_mac.h b/content/browser/device_monitor_mac.h
index bab522f..6def493 100644
--- a/content/browser/device_monitor_mac.h
+++ b/content/browser/device_monitor_mac.h
@@ -8,19 +8,29 @@
 #include "base/basictypes.h"
 #include "base/system_monitor/system_monitor.h"
 
+namespace {
+class DeviceMonitorMacImpl;
+}
+
 namespace content {
 
+// Class to track audio/video devices removal or addition via callback to
+// base::SystemMonitor ProcessDevicesChanged(). A single object of this class
+// is created from the browser main process and lives as long as this one.
 class DeviceMonitorMac {
  public:
   DeviceMonitorMac();
   ~DeviceMonitorMac();
 
- private:
-  // Forward the notifications to system monitor.
+  // Method called by the internal DeviceMonitorMacImpl object
+  // |device_monitor_impl_| when a device of type |type| has been added to or
+  // removed from the system. This code executes in the notification thread
+  // (QTKit or AVFoundation).
   void NotifyDeviceChanged(base::SystemMonitor::DeviceType type);
 
-  class QTMonitorImpl;
-  scoped_ptr<DeviceMonitorMac::QTMonitorImpl> qt_monitor_;
+ private:
+  scoped_ptr<DeviceMonitorMacImpl> device_monitor_impl_;
+
   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMac);
 };
 
diff --git a/content/browser/device_monitor_mac.mm b/content/browser/device_monitor_mac.mm
index e30cd5b5..92d3b66 100644
--- a/content/browser/device_monitor_mac.mm
+++ b/content/browser/device_monitor_mac.mm
@@ -7,42 +7,136 @@
 #import <QTKit/QTKit.h>
 
 #include "base/logging.h"
+#import "media/video/capture/mac/avfoundation_glue.h"
 
-namespace content {
+namespace {
 
-class DeviceMonitorMac::QTMonitorImpl {
+// This class is used to keep track of system devices names and their types.
+class DeviceInfo {
  public:
-  explicit QTMonitorImpl(DeviceMonitorMac* monitor);
-  virtual ~QTMonitorImpl() {}
+  enum DeviceType {
+    kAudio,
+    kVideo,
+    kMuxed,
+    kUnknown,
+    kInvalid
+  };
 
-  void Start();
-  void Stop();
+  DeviceInfo(std::string unique_id, DeviceType type)
+      : unique_id_(unique_id), type_(type) {}
 
+  // Operator== is needed here to use this class in a std::find. A given
+  // |unique_id_| always has the same |type_| so for comparison purposes the
+  // latter can be safely ignored.
+  bool operator==(const DeviceInfo& device) const {
+    return unique_id_ == device.unique_id_;
+  }
+
+  const std::string& unique_id() const { return unique_id_; }
+  DeviceType type() const { return type_; }
+
+ private:
+  std::string unique_id_;
+  DeviceType type_;
+  // Allow generated copy constructor and assignment.
+};
+
+// Base abstract class used by DeviceMonitorMac to interact with either a QTKit
+// or an AVFoundation implementation of events and notifications.
+class DeviceMonitorMacImpl {
+ public:
+  explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
+      : monitor_(monitor),
+        cached_devices_(),
+        device_arrival_(nil),
+        device_removal_(nil) {
+    DCHECK(monitor);
+  }
+  virtual ~DeviceMonitorMacImpl() {}
+
+  virtual void OnDeviceChanged() = 0;
+
+  // Method called by the default notification center when a device is removed
+  // or added to the system. It will compare the |cached_devices_| with the
+  // current situation, update it, and, if there's an update, signal to
+  // |monitor_| with the appropriate device type.
+  void ConsolidateDevicesListAndNotify(
+      const std::vector<DeviceInfo>& snapshot_devices);
+
+ protected:
+  content::DeviceMonitorMac* monitor_;
+  std::vector<DeviceInfo> cached_devices_;
+
+  // Handles to NSNotificationCenter block observers.
+  id device_arrival_;
+  id device_removal_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
+};
+
+void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
+    const std::vector<DeviceInfo>& snapshot_devices) {
+  bool video_device_added = false;
+  bool audio_device_added = false;
+  bool video_device_removed = false;
+  bool audio_device_removed = false;
+
+  // Compare the current system devices snapshot with the ones cached to detect
+  // additions, present in the former but not in the latter. If we find a device
+  // in snapshot_devices entry also present in cached_devices, we remove it from
+  // the latter vector.
+  std::vector<DeviceInfo>::const_iterator it;
+  for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
+    std::vector<DeviceInfo>::iterator cached_devices_iterator =
+        std::find(cached_devices_.begin(), cached_devices_.end(), *it);
+    if (cached_devices_iterator == cached_devices_.end()) {
+      video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
+                             (it->type() == DeviceInfo::kMuxed));
+      audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
+                             (it->type() == DeviceInfo::kMuxed));
+      DVLOG(1) << "Device has been added, id: " << it->unique_id();
+    } else {
+      cached_devices_.erase(cached_devices_iterator);
+    }
+  }
+  // All the remaining entries in cached_devices are removed devices.
+  for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
+    video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
+                             (it->type() == DeviceInfo::kMuxed) ||
+                             (it->type() == DeviceInfo::kInvalid));
+    audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
+                             (it->type() == DeviceInfo::kMuxed) ||
+                             (it->type() == DeviceInfo::kInvalid));
+    DVLOG(1) << "Device has been removed, id: " << it->unique_id();
+  }
+  // Update the cached devices with the current system snapshot.
+  cached_devices_ = snapshot_devices;
+
+  if (video_device_added || video_device_removed)
+    monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
+  if (audio_device_added || audio_device_removed)
+    monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
+}
+
+class QTKitMonitorImpl : public DeviceMonitorMacImpl {
+ public:
+  explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
+  virtual ~QTKitMonitorImpl();
+
+  virtual void OnDeviceChanged() OVERRIDE;
  private:
   void CountDevices();
   void OnAttributeChanged(NSNotification* notification);
-  void OnDeviceChanged();
 
-  DeviceMonitorMac* monitor_;
-  int number_audio_devices_;
-  int number_video_devices_;
-  id device_arrival_;
-  id device_removal_;
   id device_change_;
-
-  DISALLOW_COPY_AND_ASSIGN(QTMonitorImpl);
 };
 
-DeviceMonitorMac::QTMonitorImpl::QTMonitorImpl(DeviceMonitorMac* monitor)
-    : monitor_(monitor),
-      number_audio_devices_(-1),
-      number_video_devices_(-1),
-      device_arrival_(nil),
-      device_removal_(nil) {
-  DCHECK(monitor);
-}
+QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
+    : DeviceMonitorMacImpl(monitor) {
+  // Initialise the devices_cache_ with a not-valid entry.
+  cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
 
-void DeviceMonitorMac::QTMonitorImpl::Start() {
   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
   device_arrival_ =
       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
@@ -50,14 +144,12 @@
                        queue:nil
                   usingBlock:^(NSNotification* notification) {
                       OnDeviceChanged();}];
-
   device_removal_ =
       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
                       object:nil
                        queue:nil
                   usingBlock:^(NSNotification* notification) {
                       OnDeviceChanged();}];
-
   device_change_ =
       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
                       object:nil
@@ -66,61 +158,127 @@
                       OnAttributeChanged(notification);}];
 }
 
-void DeviceMonitorMac::QTMonitorImpl::Stop() {
-  if (!monitor_)
-    return;
-
+QTKitMonitorImpl::~QTKitMonitorImpl() {
   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
   [nc removeObserver:device_arrival_];
   [nc removeObserver:device_removal_];
   [nc removeObserver:device_change_];
 }
 
-void DeviceMonitorMac::QTMonitorImpl::OnAttributeChanged(
+void QTKitMonitorImpl::OnAttributeChanged(
     NSNotification* notification) {
-  if ([[[notification userInfo] objectForKey:QTCaptureDeviceChangedAttributeKey]
-          isEqualToString:QTCaptureDeviceSuspendedAttribute])
+  if ([[[notification userInfo]
+         objectForKey:QTCaptureDeviceChangedAttributeKey]
+      isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
     OnDeviceChanged();
-}
-
-void DeviceMonitorMac::QTMonitorImpl::CountDevices() {
-  NSArray* devices = [QTCaptureDevice inputDevices];
-  number_video_devices_ = 0;
-  number_audio_devices_ = 0;
-  for (QTCaptureDevice* device in devices) {
-    // Act as if suspended video capture devices are not attached.  For
-    // example, a laptop's internal webcam is suspended when the lid is closed.
-    if (([device hasMediaType:QTMediaTypeVideo] ||
-         [device hasMediaType:QTMediaTypeMuxed]) &&
-        ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue])
-      ++number_video_devices_;
-
-    if ([device hasMediaType:QTMediaTypeSound] ||
-        [device hasMediaType:QTMediaTypeMuxed])
-      ++number_audio_devices_;
   }
 }
 
-void DeviceMonitorMac::QTMonitorImpl::OnDeviceChanged() {
-  int number_video_devices = number_video_devices_;
-  int number_audio_devices = number_audio_devices_;
-  CountDevices();
+void QTKitMonitorImpl::OnDeviceChanged() {
+  std::vector<DeviceInfo> snapshot_devices;
 
-  if (number_video_devices_ != number_video_devices)
-    monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
-
-  if (number_audio_devices_ != number_audio_devices)
-    monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
+  NSArray* devices = [QTCaptureDevice inputDevices];
+  for (QTCaptureDevice* device in devices) {
+    DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
+    // Act as if suspended video capture devices are not attached.  For
+    // example, a laptop's internal webcam is suspended when the lid is closed.
+    if ([device hasMediaType:QTMediaTypeVideo] &&
+        ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
+        boolValue]) {
+      device_type = DeviceInfo::kVideo;
+    } else if ([device hasMediaType:QTMediaTypeMuxed] &&
+        ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
+        boolValue]) {
+      device_type = DeviceInfo::kMuxed;
+    } else if ([device hasMediaType:QTMediaTypeSound] &&
+        ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
+        boolValue]) {
+      device_type = DeviceInfo::kAudio;
+    }
+    snapshot_devices.push_back(
+        DeviceInfo([[device uniqueID] UTF8String], device_type));
+  }
+  ConsolidateDevicesListAndNotify(snapshot_devices);
 }
 
+class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
+ public:
+  explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
+  virtual ~AVFoundationMonitorImpl();
+
+  virtual void OnDeviceChanged() OVERRIDE;
+};
+
+AVFoundationMonitorImpl::AVFoundationMonitorImpl(
+    content::DeviceMonitorMac* monitor)
+    : DeviceMonitorMacImpl(monitor) {
+  NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
+  device_arrival_ =
+      [nc addObserverForName:AVFoundationGlue::
+          AVCaptureDeviceWasConnectedNotification()
+                      object:nil
+                       queue:nil
+                  usingBlock:^(NSNotification* notification) {
+                      OnDeviceChanged();}];
+  device_removal_ =
+      [nc addObserverForName:AVFoundationGlue::
+          AVCaptureDeviceWasDisconnectedNotification()
+                      object:nil
+                       queue:nil
+                  usingBlock:^(NSNotification* notification) {
+                      OnDeviceChanged();}];
+}
+
+AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
+  NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
+  [nc removeObserver:device_arrival_];
+  [nc removeObserver:device_removal_];
+}
+
+void AVFoundationMonitorImpl::OnDeviceChanged() {
+  std::vector<DeviceInfo> snapshot_devices;
+
+  NSArray* devices = [AVCaptureDeviceGlue devices];
+  for (CrAVCaptureDevice* device in devices) {
+    DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
+    if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
+      device_type = DeviceInfo::kVideo;
+    } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
+      device_type = DeviceInfo::kMuxed;
+    } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
+      device_type = DeviceInfo::kAudio;
+    }
+    snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
+                                          device_type));
+  }
+  ConsolidateDevicesListAndNotify(snapshot_devices);
+}
+
+}  // namespace
+
+namespace content {
+
 DeviceMonitorMac::DeviceMonitorMac() {
-  qt_monitor_.reset(new QTMonitorImpl(this));
-  qt_monitor_->Start();
+  if (AVFoundationGlue::IsAVFoundationSupported()) {
+    DVLOG(1) << "Monitoring via AVFoundation";
+    device_monitor_impl_.reset(new AVFoundationMonitorImpl(this));
+    // Force device enumeration to correctly list those already in the system,
+    // and more importantly to force AVFoundation NSBundle to create the devices
+    // so they can send notifications. This operation seems to take in the range
+    // of hundred of ms. and represent a startup penalty, so should be moved to
+    // the point when is needed.
+    // TODO(mcasas): Once the whole video capture moves to AVFoundation, the
+    // NSBundle devices will be created during JavaScript getUserMedia() call,
+    // so we should be able to remove this line let the AVFoundation loading
+    // happen then.
+    device_monitor_impl_->OnDeviceChanged();
+  } else {
+    DVLOG(1) << "Monitoring via QTKit";
+    device_monitor_impl_.reset(new QTKitMonitorImpl(this));
+  }
 }
 
-DeviceMonitorMac::~DeviceMonitorMac() {
-  qt_monitor_->Stop();
-}
+DeviceMonitorMac::~DeviceMonitorMac() {}
 
 void DeviceMonitorMac::NotifyDeviceChanged(
     base::SystemMonitor::DeviceType type) {
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 435b031..84974f3 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -56,6 +56,13 @@
 // tested.  See http://crbug.com/158170.
 // TODO(dalecurtis): Remove this once we're sure nothing has exploded.
 const char kDisableMainThreadAudio[] = "disable-main-thread-audio";
+// AVFoundation is available in versions 10.7 and onwards, and is to be used
+// http://crbug.com/288562 for both audio and video device monitoring and for
+// video capture. Being a dynamically loaded NSBundle and library, it hits the
+// Chrome startup time (http://crbug.com/311325 and http://crbug.com/311437);
+// until development is finished and the library load time issue is solved, the
+// usage of this library is hidden behind this flag.
+const char kEnableAVFoundation[] = "enable-avfoundation";
 #endif
 
 #if defined(OS_WIN)
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 3618cb9..a2c0c91 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -40,6 +40,7 @@
 
 #if defined(OS_MACOSX)
 MEDIA_EXPORT extern const char kDisableMainThreadAudio[];
+MEDIA_EXPORT extern const char kEnableAVFoundation[];
 #endif
 
 #if defined(OS_WIN)
diff --git a/media/media.gyp b/media/media.gyp
index ec59412..95490d6 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -398,6 +398,8 @@
         'video/capture/fake_video_capture_device.h',
         'video/capture/linux/video_capture_device_linux.cc',
         'video/capture/linux/video_capture_device_linux.h',
+        'video/capture/mac/avfoundation_glue.h',
+        'video/capture/mac/avfoundation_glue.mm',
         'video/capture/mac/platform_video_capturing_mac.h',
         'video/capture/mac/video_capture_device_mac.h',
         'video/capture/mac/video_capture_device_mac.mm',
diff --git a/media/video/capture/mac/avfoundation_glue.h b/media/video/capture/mac/avfoundation_glue.h
new file mode 100644
index 0000000..885489b
--- /dev/null
+++ b/media/video/capture/mac/avfoundation_glue.h
@@ -0,0 +1,60 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// AVFoundation API is only introduced in Mac OS X > 10.6, and there is only one
+// build of Chromium, so the (potential) linking with AVFoundation has to happen
+// in runtime. For this to be clean, a AVFoundationGlue class is defined to try
+// and load these AVFoundation system libraries. If it succeeds, subsequent
+// clients can use AVFoundation via the rest of the classes declared in this
+// file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_MAC_AVFOUNDATION_GLUE_H_
+#define MEDIA_VIDEO_CAPTURE_MAC_AVFOUNDATION_GLUE_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+class MEDIA_EXPORT AVFoundationGlue {
+ public:
+  // This method returns true if the OS version supports AVFoundation and the
+  // AVFoundation bundle could be loaded correctly, or false otherwise.
+  static bool IsAVFoundationSupported();
+
+  static NSBundle const* AVFoundationBundle();
+
+  static void* AVFoundationLibraryHandle();
+
+  // Originally coming from AVCaptureDevice.h but in global namespace.
+  static NSString* AVCaptureDeviceWasConnectedNotification();
+  static NSString* AVCaptureDeviceWasDisconnectedNotification();
+
+  // Originally coming from AVMediaFormat.h but in global namespace.
+  static NSString* AVMediaTypeVideo();
+  static NSString* AVMediaTypeAudio();
+  static NSString* AVMediaTypeMuxed();
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(AVFoundationGlue);
+};
+
+// Originally AVCaptureDevice and coming from AVCaptureDevice.h
+MEDIA_EXPORT
+@interface CrAVCaptureDevice : NSObject
+
+- (BOOL)hasMediaType:(NSString*)mediaType;
+
+- (NSString*)uniqueID;
+
+@end
+
+MEDIA_EXPORT
+@interface AVCaptureDeviceGlue : NSObject
+
++ (NSArray*)devices;
+
+@end
+
+#endif  // MEDIA_VIDEO_CAPTURE_MAC_AVFOUNDATION_GLUE_H_
diff --git a/media/video/capture/mac/avfoundation_glue.mm b/media/video/capture/mac/avfoundation_glue.mm
new file mode 100644
index 0000000..95a7bcc
--- /dev/null
+++ b/media/video/capture/mac/avfoundation_glue.mm
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "media/video/capture/mac/avfoundation_glue.h"
+
+#include <dlfcn.h>
+
+#include "base/command_line.h"
+#include "base/mac/mac_util.h"
+#include "media/base/media_switches.h"
+
+namespace {
+
+NSString* ReadNSStringPtr(const char* symbol) {
+  NSString** string_pointer = reinterpret_cast<NSString**>(
+      dlsym(AVFoundationGlue::AVFoundationLibraryHandle(), symbol));
+  DCHECK(string_pointer) << dlerror();
+  return *string_pointer;
+}
+
+}  // namespace
+
+
+bool AVFoundationGlue::IsAVFoundationSupported() {
+  const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+  return cmd_line->HasSwitch(switches::kEnableAVFoundation) &&
+      base::mac::IsOSLionOrLater() && [AVFoundationBundle() load];
+}
+
+NSBundle const* AVFoundationGlue::AVFoundationBundle() {
+  static NSBundle* bundle = [NSBundle
+      bundleWithPath:@"/System/Library/Frameworks/AVFoundation.framework"];
+  return bundle;
+}
+
+void* AVFoundationGlue::AVFoundationLibraryHandle() {
+  const char* library_path =
+      [[AVFoundationBundle() executablePath] fileSystemRepresentation];
+  if (library_path == NULL) {
+    DCHECK(false);
+    return NULL;
+  }
+  static void* library_handle = dlopen(library_path, RTLD_LAZY | RTLD_LOCAL);
+  DCHECK(library_handle) << dlerror();
+  return library_handle;
+}
+
+NSString* AVFoundationGlue::AVCaptureDeviceWasConnectedNotification() {
+  return ReadNSStringPtr("AVCaptureDeviceWasConnectedNotification");
+}
+
+NSString* AVFoundationGlue::AVCaptureDeviceWasDisconnectedNotification() {
+  return ReadNSStringPtr("AVCaptureDeviceWasDisconnectedNotification");
+}
+
+NSString* AVFoundationGlue::AVMediaTypeVideo() {
+  return ReadNSStringPtr("AVMediaTypeVideo");
+}
+
+NSString* AVFoundationGlue::AVMediaTypeAudio() {
+  return ReadNSStringPtr("AVMediaTypeAudio");
+}
+
+NSString* AVFoundationGlue::AVMediaTypeMuxed() {
+  return ReadNSStringPtr("AVMediaTypeMuxed");
+}
+
+@implementation AVCaptureDeviceGlue
+
++ (NSArray*)devices {
+  Class avcClass =
+      [AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDevice"];
+  if ([avcClass respondsToSelector:@selector(devices)]) {
+    return [avcClass performSelector:@selector(devices)];
+  }
+  return nil;
+}
+
+@end  // @implementation AVCaptureDeviceGlue