Commit 1cf88276 authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Kateryna Kostiuk

winvideo: implement video device monitor

- Creates a window to use RegisterDeviceNotification then converts
  the device name to a friendly name using the windows setup api.

Change-Id: Ia0946b5febdbd48b8633931b8902e6aca824907c
parent e928f99e
......@@ -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
......@@ -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
......
......@@ -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>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment