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 #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 
     51 namespace cricket {
     52 
     53 DeviceManagerInterface* DeviceManagerFactory::Create() {
     54   return new Win32DeviceManager();
     55 }
     56 
     57 class 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 
     76 static const char* kFilteredAudioDevicesName[] = {
     77     NULL,
     78 };
     79 static 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 };
     84 static const wchar_t kFriendlyName[] = L"FriendlyName";
     85 static const wchar_t kDevicePath[] = L"DevicePath";
     86 static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
     87 static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
     88 static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
     89 static bool GetWaveDevices(bool input, std::vector<Device>* devs);
     90 
     91 Win32DeviceManager::Win32DeviceManager()
     92     : need_couninitialize_(false) {
     93   set_watcher(new Win32DeviceWatcher(this));
     94 }
     95 
     96 Win32DeviceManager::~Win32DeviceManager() {
     97   if (initialized()) {
     98     Terminate();
     99   }
    100 }
    101 
    102 bool 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 
    120 void 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 
    131 bool 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 
    150 bool 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 
    164 bool 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 
    172 bool 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 
    219 HRESULT 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
    237 HRESULT 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 
    259 bool 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 
    305 bool 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 
    332 Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager)
    333     : DeviceWatcher(manager),
    334       manager_(manager),
    335       audio_notify_(NULL),
    336       video_notify_(NULL) {
    337 }
    338 
    339 Win32DeviceWatcher::~Win32DeviceWatcher() {
    340 }
    341 
    342 bool 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 
    363 void Win32DeviceWatcher::Stop() {
    364   UnregisterDeviceNotification(video_notify_);
    365   video_notify_ = NULL;
    366   UnregisterDeviceNotification(audio_notify_);
    367   audio_notify_ = NULL;
    368   Destroy();
    369 }
    370 
    371 HDEVNOTIFY 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 
    381 void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) {
    382   UnregisterDeviceNotification(handle);
    383 }
    384 
    385 bool 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