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