diff --git a/src/media/video/winvideo/video_device_monitor_impl.cpp b/src/media/video/winvideo/video_device_monitor_impl.cpp index 3983525cdcc5d4842c164872a4a77517754c4ce1..b51290982941eea0937c505daddd13bbda1ffe36 100644 --- a/src/media/video/winvideo/video_device_monitor_impl.cpp +++ b/src/media/video/winvideo/video_device_monitor_impl.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2015-2019 Savoir-faire Linux Inc. * * Author: Edric Milaret <edric.ladent-milaret@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,15 +20,10 @@ */ #include <algorithm> -#include <cerrno> -#include <cstdio> -#include <mutex> -#include <sstream> -#include <stdexcept> // for std::runtime_error #include <string> #include <thread> -#include <unistd.h> #include <vector> +#include <cctype> #include "../video_device_monitor.h" #include "logger.h" @@ -35,114 +31,329 @@ #include <dshow.h> #include <dbt.h> +#include <SetupAPI.h> -namespace ring { namespace video { +namespace ring { +namespace video { + +constexpr GUID guidCamera = { 0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44 }; class VideoDeviceMonitorImpl { - public: - /* - * This is the only restriction to the pImpl design: - * as the Linux implementation has a thread, it needs a way to notify - * devices addition and deletion. - * - * This class should maybe inherit from VideoDeviceMonitor instead of - * being its pImpl. - */ - VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor); - ~VideoDeviceMonitorImpl(); - - void start(); - - private: - NON_COPYABLE(VideoDeviceMonitorImpl); - - VideoDeviceMonitor* monitor_; - - void run(); - - mutable std::mutex mutex_; - bool probing_; - std::thread thread_; +public: + VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor); + ~VideoDeviceMonitorImpl(); + + void start(); + +private: + NON_COPYABLE(VideoDeviceMonitorImpl); + + VideoDeviceMonitor* monitor_; + + void run(); + + HRESULT enumerateVideoInputDevices(IEnumMoniker **ppEnum); + std::vector<std::string> enumerateVideoInputDevices(); + + std::thread thread_; + HWND hWnd_; + static LRESULT CALLBACK WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); }; VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor) : monitor_(monitor) - , mutex_() , thread_() {} void VideoDeviceMonitorImpl::start() { - probing_ = true; thread_ = std::thread(&VideoDeviceMonitorImpl::run, this); } VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl() { - probing_ = false; + SendMessage(hWnd_, WM_DESTROY, 0, 0); if (thread_.joinable()) thread_.join(); } +std::string +getDeviceFriendlyName(PDEV_BROADCAST_DEVICEINTERFACE pbdi) +{ + // As per: https://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal + // we need to convert the usb device descriptor string from: + // \\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed} + // to something like: + // USB\Vid_04e8&Pid_503b\0002F9A9828E0F06 + // in order to match the device's registry entry, and finally obtain the friendly name. (-_-)? + + std::string friendlyName; + std::string name = pbdi->dbcc_name; + name = name.substr(4); + auto pos = name.find("#"); + name.replace(pos, 1, "\\"); + pos = name.find_last_of("#"); + name = name.substr(0, pos); + pos = name.find("#"); + name.replace(pos, 1, "\\"); + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return std::toupper(c); } + ); + + DWORD dwFlag = DIGCF_ALLCLASSES; + HDEVINFO hDevInfo = SetupDiGetClassDevs(&guidCamera, 0, NULL, dwFlag); + if (INVALID_HANDLE_VALUE == hDevInfo) { + return {}; + } + + SP_DEVINFO_DATA* pspDevInfoData = + (SP_DEVINFO_DATA*)HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA)); + pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA); + + for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, pspDevInfoData); i++) { + GUID guid; + guid = pspDevInfoData->ClassGuid; + + DWORD DataT; + DWORD nSize = 0; + TCHAR buf[260]; + + if (!SetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize)) { + break; + } + + std::string strBuf(&buf[0]); + if (strBuf.find(name) != std::string::npos) { + if (SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, + SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, sizeof(buf), &nSize)) { + friendlyName = std::string(buf); + break; + } + } + } + + if (pspDevInfoData) { + HeapFree(GetProcessHeap(), 0, pspDevInfoData); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + + return friendlyName; +} + +bool +registerDeviceInterfaceToHwnd(HWND hWnd, HDEVNOTIFY *hDeviceNotify) +{ + // Use a guid for cameras specifically in order to not get spammed + // with device messages. + // These are pertinent GUIDs for media devices: + // + // usb interfaces { 0xa5dcbf10l, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed }; + // image devices { 0x6bdd1fc6, 0x810f, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f }; + // capture devices { 0x65e8773d, 0x8f56, 0x11d0, 0xa3, 0xb9, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96 }; + // camera devices { 0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44 }; + // audio devices { 0x6994ad04, 0x93ef, 0x11d0, 0xa3, 0xcc, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96 }; + + DEV_BROADCAST_DEVICEINTERFACE NotificationFilter; + + ZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); + NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); + NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + NotificationFilter.dbcc_classguid = guidCamera; + + *hDeviceNotify = RegisterDeviceNotification( + hWnd, + &NotificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE + ); + + if (nullptr == *hDeviceNotify) { + return false; + } + + return true; +} + +LRESULT CALLBACK +VideoDeviceMonitorImpl::WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + LRESULT lRet = 1; + static HDEVNOTIFY hDeviceNotify; + VideoDeviceMonitorImpl *pThis; + + switch (message) { + case WM_CREATE: + { + // Store object pointer passed from CreateWindowEx. + auto createParams = reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams; + pThis = static_cast<VideoDeviceMonitorImpl*>(createParams); + SetLastError(0); + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis)); + + if (!registerDeviceInterfaceToHwnd(hWnd, &hDeviceNotify)) { + RING_ERR() << "Cannot register for device change notifications"; + SendMessage(hWnd, WM_DESTROY, 0, 0); + } + } + break; + + case WM_DEVICECHANGE: + { + switch (wParam) { + case DBT_DEVICEREMOVECOMPLETE: + case DBT_DEVICEARRIVAL: + { + PDEV_BROADCAST_DEVICEINTERFACE p = (PDEV_BROADCAST_DEVICEINTERFACE)lParam; + auto friendlyName = getDeviceFriendlyName(p); + if (!friendlyName.empty()) { + RING_DBG() << friendlyName << ((wParam == DBT_DEVICEARRIVAL) ? " plugged" : " unplugged"); + if (pThis = reinterpret_cast<VideoDeviceMonitorImpl*>(GetWindowLongPtr(hWnd, GWLP_USERDATA))) { + if (wParam == DBT_DEVICEARRIVAL) { + pThis->monitor_->addDevice(friendlyName); + } else if (wParam == DBT_DEVICEREMOVECOMPLETE) { + pThis->monitor_->removeDevice(friendlyName); + } + } + } + } + break; + default: + break; + } + break; + } + break; + + case WM_CLOSE: + UnregisterDeviceNotification(hDeviceNotify); + DestroyWindow(hWnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + lRet = DefWindowProc(hWnd, message, wParam, lParam); + break; + } + + return lRet; +} + void VideoDeviceMonitorImpl::run() { - //FIX ME : That's one shot - std::list<std::string> deviceList; + // Enumerate the initial capture device list. + auto captureDeviceList = enumerateVideoInputDevices(); + for (auto node : captureDeviceList) { + monitor_->addDevice(node); + } + + // Create a dummy window with the sole purpose to receive device change messages. + static const char* className = "Message"; + WNDCLASSEX wx = {}; + wx.cbSize = sizeof(WNDCLASSEX); + wx.lpfnWndProc = WinProcCallback; + wx.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0)); + wx.lpszClassName = className; + if (RegisterClassEx(&wx)) { + // Pass this as lpParam so WinProcCallback can access members of VideoDeviceMonitorImpl. + hWnd_ = CreateWindowEx(0, className, "devicenotifications", 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, NULL, this); + } + + // Run the message loop that will finish once a WM_DESTROY message + // has been sent, allowing the thread to join. + MSG msg; + int retVal; + while ((retVal = GetMessage(&msg, NULL, 0, 0)) != 0) { + if (retVal != -1) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +HRESULT +VideoDeviceMonitorImpl::enumerateVideoInputDevices(IEnumMoniker **ppEnum) +{ + ICreateDevEnum *pDevEnum; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum)); + + if (SUCCEEDED(hr)) { + hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, ppEnum, 0); + if (hr == S_FALSE) { + hr = VFW_E_NOT_FOUND; + } + pDevEnum->Release(); + } + return hr; +} + +std::vector<std::string> +VideoDeviceMonitorImpl::enumerateVideoInputDevices() +{ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hr)) { + return {}; + } + + std::vector<std::string> deviceList; + ICreateDevEnum *pDevEnum; hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum)); + if (FAILED(hr)) { + RING_ERR() << "Can't enumerate webcams"; + return {}; + } + IEnumMoniker *pEnum = nullptr; - if (SUCCEEDED(hr)) { - hr = pDevEnum->CreateClassEnumerator( - CLSID_VideoInputDeviceCategory, - &pEnum, 0); - pDevEnum->Release(); - if (FAILED(hr) || pEnum == nullptr) { - RING_ERR("No webcam found."); - hr = VFW_E_NOT_FOUND; + hr = enumerateVideoInputDevices(&pEnum); + if (FAILED(hr) || pEnum == nullptr) { + RING_ERR() << "No webcam found"; + return {}; + } + + IMoniker *pMoniker = NULL; + unsigned deviceID = 0; + while (pEnum->Next(1, &pMoniker, NULL) == S_OK) { + IPropertyBag *pPropBag; + HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag)); + if (FAILED(hr)) { + pMoniker->Release(); + continue; } - if (hr != VFW_E_NOT_FOUND && pEnum != nullptr) { - IMoniker *pMoniker = NULL; - unsigned deviceID = 0; - while (pEnum->Next(1, &pMoniker, NULL) == S_OK) - { - IPropertyBag *pPropBag; - HRESULT hr = pMoniker->BindToStorage( - 0, - 0, - IID_PPV_ARGS(&pPropBag)); - if (FAILED(hr)) { - pMoniker->Release(); - continue; - } - VARIANT var; - VariantInit(&var); - hr = pPropBag->Read(L"Description", &var, 0); - if (FAILED(hr)) { - hr = pPropBag->Read(L"FriendlyName", &var, 0); - } - if (SUCCEEDED(hr)) { - deviceList.push_back(std::to_string(deviceID)); - VariantClear(&var); - } + VARIANT var; + VariantInit(&var); + hr = pPropBag->Read(L"Description", &var, 0); + if (FAILED(hr)) { + hr = pPropBag->Read(L"FriendlyName", &var, 0); + } - hr = pPropBag->Write(L"FriendlyName", &var); - pPropBag->Release(); - pMoniker->Release(); - deviceID++; - } - pEnum->Release(); - for (auto device : deviceList) { - //FIXME: Custom id is a weak way to do that - monitor_->addDevice(device); + if (SUCCEEDED(hr)) { + auto deviceName = bstrToStdString(var.bstrVal); + if (!deviceName.empty()) { + deviceList.push_back(deviceName); } + VariantClear(&var); } + + hr = pPropBag->Write(L"FriendlyName", &var); + + pPropBag->Release(); + pMoniker->Release(); + + deviceID++; } + pEnum->Release(); + CoUninitialize(); + + return deviceList; } VideoDeviceMonitor::VideoDeviceMonitor() @@ -156,4 +367,5 @@ VideoDeviceMonitor::VideoDeviceMonitor() VideoDeviceMonitor::~VideoDeviceMonitor() {} -}} // namespace ring::video +} +} // namespace ring::video diff --git a/src/string_utils.cpp b/src/string_utils.cpp index b11db5210bea4dd8a6bd81c49183befd21bbe132..8a7f12c5f2738eff0ebe7236f99a6b87e6dfb23e 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -27,6 +27,7 @@ #include <stdexcept> #ifdef _WIN32 #include <windows.h> +#include <oleauto.h> #endif #include <ciso646> // fix windows compiler bug @@ -57,6 +58,16 @@ decodeMultibyteString(const std::string& s) return std::string(wstr.begin(), wstr.end()); } +std::string +bstrToStdString(BSTR bstr) +{ + int wslen = ::SysStringLen(bstr); + if (wslen != 0) { + std::wstring wstr(bstr, wslen); + return std::string(wstr.begin(), wstr.end()); + } + return {}; +} #endif std::string diff --git a/src/string_utils.h b/src/string_utils.h index 62ad643ae344dd4d969dddd14170cc43a3c116f0..25b09957538dc4d4b883d101a2df082960efacbc 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -24,6 +24,9 @@ #include <string> #include <vector> +#ifdef _WIN32 +#include <WTypes.h> +#endif namespace ring { @@ -41,6 +44,7 @@ std::string to_string(double value); #ifdef _WIN32 std::wstring to_wstring(const std::string& s); std::string decodeMultibyteString(const std::string& s); +std::string bstrToStdString(BSTR bstr); #endif template <typename T>