/* * libjingle * Copyright 2004 Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "talk/media/devices/win32devicemanager.h" #include <atlbase.h> #include <dbt.h> #include <strmif.h> // must come before ks.h #include <ks.h> #include <ksmedia.h> #define INITGUID // For PKEY_AudioEndpoint_GUID #include <mmdeviceapi.h> #include <mmsystem.h> #include <functiondiscoverykeys_devpkey.h> #include <uuids.h> #include "talk/base/logging.h" #include "talk/base/stringutils.h" #include "talk/base/thread.h" #include "talk/base/win32.h" // ToUtf8 #include "talk/base/win32window.h" #include "talk/media/base/mediacommon.h" #ifdef HAVE_LOGITECH_HEADERS #include "third_party/logitech/files/logitechquickcam.h" #endif namespace cricket { DeviceManagerInterface* DeviceManagerFactory::Create() { return new Win32DeviceManager(); } class Win32DeviceWatcher : public DeviceWatcher, public talk_base::Win32Window { public: explicit Win32DeviceWatcher(Win32DeviceManager* dm); virtual ~Win32DeviceWatcher(); virtual bool Start(); virtual void Stop(); private: HDEVNOTIFY Register(REFGUID guid); void Unregister(HDEVNOTIFY notify); virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result); Win32DeviceManager* manager_; HDEVNOTIFY audio_notify_; HDEVNOTIFY video_notify_; }; static const char* kFilteredAudioDevicesName[] = { NULL, }; static const char* const kFilteredVideoDevicesName[] = { "Asus virtual Camera", // Bad Asus desktop virtual cam "Bluetooth Video", // Bad Sony viao bluetooth sharing driver NULL, }; static const wchar_t kFriendlyName[] = L"FriendlyName"; static const wchar_t kDevicePath[] = L"DevicePath"; static const char kUsbDevicePathPrefix[] = "\\\\?\\usb"; static bool GetDevices(const CLSID& catid, std::vector<Device>* out); static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs); static bool GetWaveDevices(bool input, std::vector<Device>* devs); Win32DeviceManager::Win32DeviceManager() : need_couninitialize_(false) { set_watcher(new Win32DeviceWatcher(this)); } Win32DeviceManager::~Win32DeviceManager() { if (initialized()) { Terminate(); } } bool Win32DeviceManager::Init() { if (!initialized()) { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); need_couninitialize_ = SUCCEEDED(hr); if (FAILED(hr)) { LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr; if (hr != RPC_E_CHANGED_MODE) { return false; } } if (!watcher()->Start()) { return false; } set_initialized(true); } return true; } void Win32DeviceManager::Terminate() { if (initialized()) { watcher()->Stop(); if (need_couninitialize_) { CoUninitialize(); need_couninitialize_ = false; } set_initialized(false); } } bool Win32DeviceManager::GetDefaultVideoCaptureDevice(Device* device) { bool ret = false; // If there are multiple capture devices, we want the first USB one. // This avoids issues with defaulting to virtual cameras or grabber cards. std::vector<Device> devices; ret = (GetVideoCaptureDevices(&devices) && !devices.empty()); if (ret) { *device = devices[0]; for (size_t i = 0; i < devices.size(); ++i) { if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix, ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) { *device = devices[i]; break; } } } return ret; } bool Win32DeviceManager::GetAudioDevices(bool input, std::vector<Device>* devs) { devs->clear(); if (talk_base::IsWindowsVistaOrLater()) { if (!GetCoreAudioDevices(input, devs)) return false; } else { if (!GetWaveDevices(input, devs)) return false; } return FilterDevices(devs, kFilteredAudioDevicesName); } bool Win32DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { devices->clear(); if (!GetDevices(CLSID_VideoInputDeviceCategory, devices)) { return false; } return FilterDevices(devices, kFilteredVideoDevicesName); } bool GetDevices(const CLSID& catid, std::vector<Device>* devices) { HRESULT hr; // CComPtr is a scoped pointer that will be auto released when going // out of scope. CoUninitialize must not be called before the // release. CComPtr<ICreateDevEnum> sys_dev_enum; CComPtr<IEnumMoniker> cam_enum; if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) || FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) { LOG(LS_ERROR) << "Failed to create device enumerator, hr=" << hr; return false; } // Only enum devices if CreateClassEnumerator returns S_OK. If there are no // devices available, S_FALSE will be returned, but enumMk will be NULL. if (hr == S_OK) { CComPtr<IMoniker> mk; while (cam_enum->Next(1, &mk, NULL) == S_OK) { #ifdef HAVE_LOGITECH_HEADERS // Initialize Logitech device if applicable MaybeLogitechDeviceReset(mk); #endif CComPtr<IPropertyBag> bag; if (SUCCEEDED(mk->BindToStorage(NULL, NULL, __uuidof(bag), reinterpret_cast<void**>(&bag)))) { CComVariant name, path; std::string name_str, path_str; if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) && name.vt == VT_BSTR) { name_str = talk_base::ToUtf8(name.bstrVal); // Get the device id if one exists. if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) && path.vt == VT_BSTR) { path_str = talk_base::ToUtf8(path.bstrVal); } devices->push_back(Device(name_str, path_str)); } } mk = NULL; } } return true; } HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) { out->clear(); PROPVARIANT var; PropVariantInit(&var); HRESULT hr = bag->GetValue(key, &var); if (SUCCEEDED(hr)) { if (var.pwszVal) *out = talk_base::ToUtf8(var.pwszVal); else hr = E_FAIL; } PropVariantClear(&var); return hr; } // Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) { CComPtr<IPropertyStore> props; HRESULT hr = device->OpenPropertyStore(STGM_READ, &props); if (FAILED(hr)) { return hr; } // Get the endpoint's name and id. std::string name, guid; hr = GetStringProp(props, PKEY_Device_FriendlyName, &name); if (SUCCEEDED(hr)) { hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid); if (SUCCEEDED(hr)) { out->name = name; out->id = guid; } } return hr; } bool GetCoreAudioDevices( bool input, std::vector<Device>* devs) { HRESULT hr = S_OK; CComPtr<IMMDeviceEnumerator> enumerator; hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator)); if (SUCCEEDED(hr)) { CComPtr<IMMDeviceCollection> devices; hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender), DEVICE_STATE_ACTIVE, &devices); if (SUCCEEDED(hr)) { unsigned int count; hr = devices->GetCount(&count); if (SUCCEEDED(hr)) { for (unsigned int i = 0; i < count; i++) { CComPtr<IMMDevice> device; // Get pointer to endpoint number i. hr = devices->Item(i, &device); if (FAILED(hr)) { break; } Device dev; hr = CricketDeviceFromImmDevice(device, &dev); if (SUCCEEDED(hr)) { devs->push_back(dev); } else { LOG(LS_WARNING) << "Unable to query IMM Device, skipping. HR=" << hr; hr = S_FALSE; } } } } } if (FAILED(hr)) { LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr; return false; } return true; } bool GetWaveDevices(bool input, std::vector<Device>* devs) { // Note, we don't use the System Device Enumerator interface here since it // adds lots of pseudo-devices to the list, such as DirectSound and Wave // variants of the same device. if (input) { int num_devs = waveInGetNumDevs(); for (int i = 0; i < num_devs; ++i) { WAVEINCAPS caps; if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR && caps.wChannels > 0) { devs->push_back(Device(talk_base::ToUtf8(caps.szPname), talk_base::ToString(i))); } } } else { int num_devs = waveOutGetNumDevs(); for (int i = 0; i < num_devs; ++i) { WAVEOUTCAPS caps; if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR && caps.wChannels > 0) { devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i)); } } } return true; } Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager) : DeviceWatcher(manager), manager_(manager), audio_notify_(NULL), video_notify_(NULL) { } Win32DeviceWatcher::~Win32DeviceWatcher() { } bool Win32DeviceWatcher::Start() { if (!Create(NULL, _T("libjingle Win32DeviceWatcher Window"), 0, 0, 0, 0, 0, 0)) { return false; } audio_notify_ = Register(KSCATEGORY_AUDIO); if (!audio_notify_) { Stop(); return false; } video_notify_ = Register(KSCATEGORY_VIDEO); if (!video_notify_) { Stop(); return false; } return true; } void Win32DeviceWatcher::Stop() { UnregisterDeviceNotification(video_notify_); video_notify_ = NULL; UnregisterDeviceNotification(audio_notify_); audio_notify_ = NULL; Destroy(); } HDEVNOTIFY Win32DeviceWatcher::Register(REFGUID guid) { DEV_BROADCAST_DEVICEINTERFACE dbdi; dbdi.dbcc_size = sizeof(dbdi); dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dbdi.dbcc_classguid = guid; dbdi.dbcc_name[0] = '\0'; return RegisterDeviceNotification(handle(), &dbdi, DEVICE_NOTIFY_WINDOW_HANDLE); } void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) { UnregisterDeviceNotification(handle); } bool Win32DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result) { if (uMsg == WM_DEVICECHANGE) { if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) { DEV_BROADCAST_DEVICEINTERFACE* dbdi = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam); if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO || dbdi->dbcc_classguid == KSCATEGORY_VIDEO) { manager_->SignalDevicesChange(); } } result = 0; return true; } return false; } }; // namespace cricket