Home | History | Annotate | Download | only in devices
      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