win32devicemanager.cc revision d4e598d57aed714a599444a7eab5e8fdde52a950
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/win32devicemanager.h"
29
30#include <atlbase.h>
31#include <dbt.h>
32#include <strmif.h>  // must come before ks.h
33#include <ks.h>
34#include <ksmedia.h>
35#define INITGUID  // For PKEY_AudioEndpoint_GUID
36#include <mmdeviceapi.h>
37#include <mmsystem.h>
38#include <functiondiscoverykeys_devpkey.h>
39#include <uuids.h>
40
41#include "webrtc/base/logging.h"
42#include "webrtc/base/stringutils.h"
43#include "webrtc/base/thread.h"
44#include "webrtc/base/win32.h"  // ToUtf8
45#include "webrtc/base/win32window.h"
46#include "talk/media/base/mediacommon.h"
47#ifdef HAVE_LOGITECH_HEADERS
48#include "third_party/logitech/files/logitechquickcam.h"
49#endif
50
51namespace cricket {
52
53DeviceManagerInterface* DeviceManagerFactory::Create() {
54  return new Win32DeviceManager();
55}
56
57class Win32DeviceWatcher
58    : public DeviceWatcher,
59      public rtc::Win32Window {
60 public:
61  explicit Win32DeviceWatcher(Win32DeviceManager* dm);
62  virtual ~Win32DeviceWatcher();
63  virtual bool Start();
64  virtual void Stop();
65
66 private:
67  HDEVNOTIFY Register(REFGUID guid);
68  void Unregister(HDEVNOTIFY notify);
69  virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
70
71  Win32DeviceManager* manager_;
72  HDEVNOTIFY audio_notify_;
73  HDEVNOTIFY video_notify_;
74};
75
76static const char* kFilteredAudioDevicesName[] = {
77    NULL,
78};
79static const char* const kFilteredVideoDevicesName[] =  {
80    "Asus virtual Camera",     // Bad Asus desktop virtual cam
81    "Bluetooth Video",         // Bad Sony viao bluetooth sharing driver
82    NULL,
83};
84static const wchar_t kFriendlyName[] = L"FriendlyName";
85static const wchar_t kDevicePath[] = L"DevicePath";
86static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
87static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
88static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
89static bool GetWaveDevices(bool input, std::vector<Device>* devs);
90
91Win32DeviceManager::Win32DeviceManager()
92    : need_couninitialize_(false) {
93  set_watcher(new Win32DeviceWatcher(this));
94}
95
96Win32DeviceManager::~Win32DeviceManager() {
97  if (initialized()) {
98    Terminate();
99  }
100}
101
102bool Win32DeviceManager::Init() {
103  if (!initialized()) {
104    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
105    need_couninitialize_ = SUCCEEDED(hr);
106    if (FAILED(hr)) {
107      LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
108      if (hr != RPC_E_CHANGED_MODE) {
109        return false;
110      }
111    }
112    if (!watcher()->Start()) {
113      return false;
114    }
115    set_initialized(true);
116  }
117  return true;
118}
119
120void Win32DeviceManager::Terminate() {
121  if (initialized()) {
122    watcher()->Stop();
123    if (need_couninitialize_) {
124      CoUninitialize();
125      need_couninitialize_ = false;
126    }
127    set_initialized(false);
128  }
129}
130
131bool Win32DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
132  bool ret = false;
133  // If there are multiple capture devices, we want the first USB one.
134  // This avoids issues with defaulting to virtual cameras or grabber cards.
135  std::vector<Device> devices;
136  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
137  if (ret) {
138    *device = devices[0];
139    for (size_t i = 0; i < devices.size(); ++i) {
140      if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
141                   ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
142        *device = devices[i];
143        break;
144      }
145    }
146  }
147  return ret;
148}
149
150bool Win32DeviceManager::GetAudioDevices(bool input,
151                                         std::vector<Device>* devs) {
152  devs->clear();
153
154  if (rtc::IsWindowsVistaOrLater()) {
155    if (!GetCoreAudioDevices(input, devs))
156      return false;
157  } else {
158    if (!GetWaveDevices(input, devs))
159      return false;
160  }
161  return FilterDevices(devs, kFilteredAudioDevicesName);
162}
163
164bool Win32DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
165  devices->clear();
166  if (!GetDevices(CLSID_VideoInputDeviceCategory, devices)) {
167    return false;
168  }
169  return FilterDevices(devices, kFilteredVideoDevicesName);
170}
171
172bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
173  HRESULT hr;
174
175  // CComPtr is a scoped pointer that will be auto released when going
176  // out of scope. CoUninitialize must not be called before the
177  // release.
178  CComPtr<ICreateDevEnum> sys_dev_enum;
179  CComPtr<IEnumMoniker> cam_enum;
180  if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
181      FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
182    LOG(LS_ERROR) << "Failed to create device enumerator, hr="  << hr;
183    return false;
184  }
185
186  // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
187  // devices available, S_FALSE will be returned, but enumMk will be NULL.
188  if (hr == S_OK) {
189    CComPtr<IMoniker> mk;
190    while (cam_enum->Next(1, &mk, NULL) == S_OK) {
191#ifdef HAVE_LOGITECH_HEADERS
192      // Initialize Logitech device if applicable
193      MaybeLogitechDeviceReset(mk);
194#endif
195      CComPtr<IPropertyBag> bag;
196      if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
197          __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
198        CComVariant name, path;
199        std::string name_str, path_str;
200        if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
201            name.vt == VT_BSTR) {
202          name_str = rtc::ToUtf8(name.bstrVal);
203          // Get the device id if one exists.
204          if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
205              path.vt == VT_BSTR) {
206            path_str = rtc::ToUtf8(path.bstrVal);
207          }
208
209          devices->push_back(Device(name_str, path_str));
210        }
211      }
212      mk = NULL;
213    }
214  }
215
216  return true;
217}
218
219HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
220  out->clear();
221  PROPVARIANT var;
222  PropVariantInit(&var);
223
224  HRESULT hr = bag->GetValue(key, &var);
225  if (SUCCEEDED(hr)) {
226    if (var.pwszVal)
227      *out = rtc::ToUtf8(var.pwszVal);
228    else
229      hr = E_FAIL;
230  }
231
232  PropVariantClear(&var);
233  return hr;
234}
235
236// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
237HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
238  CComPtr<IPropertyStore> props;
239
240  HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
241  if (FAILED(hr)) {
242    return hr;
243  }
244
245  // Get the endpoint's name and id.
246  std::string name, guid;
247  hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
248  if (SUCCEEDED(hr)) {
249    hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
250
251    if (SUCCEEDED(hr)) {
252      out->name = name;
253      out->id = guid;
254    }
255  }
256  return hr;
257}
258
259bool GetCoreAudioDevices(
260    bool input, std::vector<Device>* devs) {
261  HRESULT hr = S_OK;
262  CComPtr<IMMDeviceEnumerator> enumerator;
263
264  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
265      __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
266  if (SUCCEEDED(hr)) {
267    CComPtr<IMMDeviceCollection> devices;
268    hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
269                                        DEVICE_STATE_ACTIVE, &devices);
270    if (SUCCEEDED(hr)) {
271      unsigned int count;
272      hr = devices->GetCount(&count);
273
274      if (SUCCEEDED(hr)) {
275        for (unsigned int i = 0; i < count; i++) {
276          CComPtr<IMMDevice> device;
277
278          // Get pointer to endpoint number i.
279          hr = devices->Item(i, &device);
280          if (FAILED(hr)) {
281            break;
282          }
283
284          Device dev;
285          hr = CricketDeviceFromImmDevice(device, &dev);
286          if (SUCCEEDED(hr)) {
287            devs->push_back(dev);
288          } else {
289            LOG(LS_WARNING) << "Unable to query IMM Device, skipping.  HR="
290                            << hr;
291            hr = S_FALSE;
292          }
293        }
294      }
295    }
296  }
297
298  if (FAILED(hr)) {
299    LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
300    return false;
301  }
302  return true;
303}
304
305bool GetWaveDevices(bool input, std::vector<Device>* devs) {
306  // Note, we don't use the System Device Enumerator interface here since it
307  // adds lots of pseudo-devices to the list, such as DirectSound and Wave
308  // variants of the same device.
309  if (input) {
310    int num_devs = waveInGetNumDevs();
311    for (int i = 0; i < num_devs; ++i) {
312      WAVEINCAPS caps;
313      if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
314          caps.wChannels > 0) {
315        devs->push_back(Device(rtc::ToUtf8(caps.szPname),
316                               rtc::ToString(i)));
317      }
318    }
319  } else {
320    int num_devs = waveOutGetNumDevs();
321    for (int i = 0; i < num_devs; ++i) {
322      WAVEOUTCAPS caps;
323      if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
324          caps.wChannels > 0) {
325        devs->push_back(Device(rtc::ToUtf8(caps.szPname), i));
326      }
327    }
328  }
329  return true;
330}
331
332Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager)
333    : DeviceWatcher(manager),
334      manager_(manager),
335      audio_notify_(NULL),
336      video_notify_(NULL) {
337}
338
339Win32DeviceWatcher::~Win32DeviceWatcher() {
340}
341
342bool Win32DeviceWatcher::Start() {
343  if (!Create(NULL, _T("libjingle Win32DeviceWatcher Window"),
344              0, 0, 0, 0, 0, 0)) {
345    return false;
346  }
347
348  audio_notify_ = Register(KSCATEGORY_AUDIO);
349  if (!audio_notify_) {
350    Stop();
351    return false;
352  }
353
354  video_notify_ = Register(KSCATEGORY_VIDEO);
355  if (!video_notify_) {
356    Stop();
357    return false;
358  }
359
360  return true;
361}
362
363void Win32DeviceWatcher::Stop() {
364  UnregisterDeviceNotification(video_notify_);
365  video_notify_ = NULL;
366  UnregisterDeviceNotification(audio_notify_);
367  audio_notify_ = NULL;
368  Destroy();
369}
370
371HDEVNOTIFY Win32DeviceWatcher::Register(REFGUID guid) {
372  DEV_BROADCAST_DEVICEINTERFACE dbdi;
373  dbdi.dbcc_size = sizeof(dbdi);
374  dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
375  dbdi.dbcc_classguid = guid;
376  dbdi.dbcc_name[0] = '\0';
377  return RegisterDeviceNotification(handle(), &dbdi,
378                                    DEVICE_NOTIFY_WINDOW_HANDLE);
379}
380
381void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) {
382  UnregisterDeviceNotification(handle);
383}
384
385bool Win32DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
386                              LRESULT& result) {
387  if (uMsg == WM_DEVICECHANGE) {
388    if (wParam == DBT_DEVICEARRIVAL ||
389        wParam == DBT_DEVICEREMOVECOMPLETE) {
390      DEV_BROADCAST_DEVICEINTERFACE* dbdi =
391          reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
392      if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
393        dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
394        manager_->SignalDevicesChange();
395      }
396    }
397    result = 0;
398    return true;
399  }
400
401  return false;
402}
403
404};  // namespace cricket
405