diff options
Diffstat (limited to 'talk/media/devices/win32devicemanager.cc')
-rw-r--r-- | talk/media/devices/win32devicemanager.cc | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/talk/media/devices/win32devicemanager.cc b/talk/media/devices/win32devicemanager.cc new file mode 100644 index 0000000000..1b9e9d86f6 --- /dev/null +++ b/talk/media/devices/win32devicemanager.cc @@ -0,0 +1,414 @@ +/* + * 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> +#include <mmdeviceapi.h> +#include <mmsystem.h> +#include <functiondiscoverykeys_devpkey.h> +#include <uuids.h> + +// PKEY_AudioEndpoint_GUID isn't included in uuid.lib and we don't want +// to define INITGUID in order to define all the uuids in this object file +// as it will conflict with uuid.lib (multiply defined symbols). +// So our workaround is to define this one missing symbol here manually. +// See: https://code.google.com/p/webrtc/issues/detail?id=3996 +EXTERN_C const PROPERTYKEY PKEY_AudioEndpoint_GUID = { { + 0x1da5d803, 0xd492, 0x4edd, { + 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e + } }, 4 +}; + +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32.h" // ToUtf8 +#include "webrtc/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 rtc::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 (rtc::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 = rtc::ToUtf8(name.bstrVal); + // Get the device id if one exists. + if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) && + path.vt == VT_BSTR) { + path_str = rtc::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 = rtc::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(rtc::ToUtf8(caps.szPname), + rtc::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(rtc::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 |