blob: 071f1117911df9154c0a9fe4dd48325db79e433a [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
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 "talk/base/logging.h"
42#include "talk/base/stringutils.h"
43#include "talk/base/thread.h"
44#include "talk/base/win32.h" // ToUtf8
45#include "talk/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 talk_base::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 (talk_base::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 = talk_base::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 = talk_base::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 = talk_base::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(talk_base::ToUtf8(caps.szPname),
316 talk_base::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(talk_base::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