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