1/*
2 * libjingle
3 * Copyright 2004 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/media/devices/macdevicemanager.h"
29
30#include <CoreAudio/CoreAudio.h>
31#include <QuickTime/QuickTime.h>
32
33#include "talk/media/base/mediacommon.h"
34#include "webrtc/base/logging.h"
35#include "webrtc/base/stringutils.h"
36#include "webrtc/base/thread.h"
37
38class DeviceWatcherImpl;
39
40namespace cricket {
41
42DeviceManagerInterface* DeviceManagerFactory::Create() {
43  return new MacDeviceManager();
44}
45
46class MacDeviceWatcher : public DeviceWatcher {
47 public:
48  explicit MacDeviceWatcher(DeviceManagerInterface* dm);
49  virtual ~MacDeviceWatcher();
50  virtual bool Start();
51  virtual void Stop();
52
53 private:
54  DeviceManagerInterface* manager_;
55  DeviceWatcherImpl* impl_;
56};
57
58static const char* kFilteredAudioDevicesName[] = {
59    NULL,
60};
61// TODO(tommyw): Try to get hold of a copy of Final Cut to understand why we
62//               crash while scanning their components on OS X.
63static const char* const kFilteredVideoDevicesName[] =  {
64    "DVCPRO HD",               // Final cut
65    "Sonix SN9C201p",          // Crashes in OpenAComponent and CloseComponent
66    NULL,
67};
68static const UInt32 kAudioDeviceNameLength = 64;
69// Obj-C functions defined in macdevicemanagermm.mm
70// TODO(ronghuawu): have a shared header for these function defines.
71extern DeviceWatcherImpl* CreateDeviceWatcherCallback(
72    DeviceManagerInterface* dm);
73extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl);
74extern bool GetAVFoundationVideoDevices(std::vector<Device>* out);
75static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
76static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
77
78MacDeviceManager::MacDeviceManager() {
79  set_watcher(new MacDeviceWatcher(this));
80}
81
82MacDeviceManager::~MacDeviceManager() {
83}
84
85bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
86  devices->clear();
87  if (!GetAVFoundationVideoDevices(devices)) {
88    return false;
89  }
90  return FilterDevices(devices, kFilteredVideoDevicesName);
91}
92
93bool MacDeviceManager::GetAudioDevices(bool input,
94                                       std::vector<Device>* devs) {
95  devs->clear();
96  std::vector<AudioDeviceID> dev_ids;
97  bool ret = GetAudioDeviceIDs(input, &dev_ids);
98  if (!ret) {
99    return false;
100  }
101  for (size_t i = 0; i < dev_ids.size(); ++i) {
102    std::string name;
103    if (GetAudioDeviceName(dev_ids[i], input, &name)) {
104      devs->push_back(Device(name, dev_ids[i]));
105    }
106  }
107  return FilterDevices(devs, kFilteredAudioDevicesName);
108}
109
110static bool GetAudioDeviceIDs(bool input,
111                              std::vector<AudioDeviceID>* out_dev_ids) {
112  UInt32 propsize;
113  OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
114                                           &propsize, NULL);
115  if (0 != err) {
116    LOG(LS_ERROR) << "Couldn't get information about property, "
117                  << "so no device list acquired.";
118    return false;
119  }
120
121  size_t num_devices = propsize / sizeof(AudioDeviceID);
122  rtc::scoped_ptr<AudioDeviceID[]> device_ids(
123      new AudioDeviceID[num_devices]);
124
125  err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
126                                 &propsize, device_ids.get());
127  if (0 != err) {
128    LOG(LS_ERROR) << "Failed to get device ids, "
129                  << "so no device listing acquired.";
130    return false;
131  }
132
133  for (size_t i = 0; i < num_devices; ++i) {
134    AudioDeviceID an_id = device_ids[i];
135    // find out the number of channels for this direction
136    // (input/output) on this device -
137    // we'll ignore anything with no channels.
138    err = AudioDeviceGetPropertyInfo(an_id, 0, input,
139                                     kAudioDevicePropertyStreams,
140                                     &propsize, NULL);
141    if (0 == err) {
142      unsigned num_channels = propsize / sizeof(AudioStreamID);
143      if (0 < num_channels) {
144        out_dev_ids->push_back(an_id);
145      }
146    } else {
147      LOG(LS_ERROR) << "No property info for stream property for device id "
148                    << an_id << "(is_input == " << input
149                    << "), so not including it in the list.";
150    }
151  }
152
153  return true;
154}
155
156static bool GetAudioDeviceName(AudioDeviceID id,
157                               bool input,
158                               std::string* out_name) {
159  UInt32 nameLength = kAudioDeviceNameLength;
160  char name[kAudioDeviceNameLength + 1];
161  OSErr err = AudioDeviceGetProperty(id, 0, input,
162                                     kAudioDevicePropertyDeviceName,
163                                     &nameLength, name);
164  if (0 != err) {
165    LOG(LS_ERROR) << "No name acquired for device id " << id;
166    return false;
167  }
168
169  *out_name = name;
170  return true;
171}
172
173MacDeviceWatcher::MacDeviceWatcher(DeviceManagerInterface* manager)
174    : DeviceWatcher(manager),
175      manager_(manager),
176      impl_(NULL) {
177}
178
179MacDeviceWatcher::~MacDeviceWatcher() {
180}
181
182bool MacDeviceWatcher::Start() {
183  if (!impl_) {
184    impl_ = CreateDeviceWatcherCallback(manager_);
185  }
186  return impl_ != NULL;
187}
188
189void MacDeviceWatcher::Stop() {
190  if (impl_) {
191    ReleaseDeviceWatcherCallback(impl_);
192    impl_ = NULL;
193  }
194}
195
196};  // namespace cricket
197