Ring Daemon
Loading...
Searching...
No Matches
winvideo/video_device_monitor_impl.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "../video_device_monitor.h"
19#include "logger.h"
20#include "noncopyable.h"
21#include "string_utils.h"
22
23#include <dshow.h>
24#include <dbt.h>
25#include <SetupAPI.h>
26
27#include <algorithm>
28#include <string>
29#include <thread>
30#include <vector>
31#include <cctype>
32
33namespace jami {
34namespace video {
35
36constexpr GUID guidCamera = {0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44};
37
38class VideoDeviceMonitorImpl
39{
40public:
43
44 void start();
45
46private:
48
49 VideoDeviceMonitor* monitor_;
50
51 void run();
52
53 std::vector<std::string> enumerateVideoInputDevices();
54
55 std::thread thread_;
56 HWND hWnd_;
57 static LRESULT CALLBACK WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
58};
59
61 : monitor_(monitor)
62 , thread_()
63{}
64
65void
67{
68 // Enumerate the initial capture device list.
69 auto captureDeviceList = enumerateVideoInputDevices();
70 for (auto node : captureDeviceList) {
71 monitor_->addDevice(node);
72 }
73 thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
74}
75
77{
78 SendMessage(hWnd_, WM_DESTROY, 0, 0);
79 if (thread_.joinable())
80 thread_.join();
81}
82
83std::string
85{
86 std::string unique_name = pbdi->dbcc_name;
87
88 std::transform(unique_name.begin(), unique_name.end(), unique_name.begin(), [](unsigned char c) {
89 return std::tolower(c);
90 });
91
92 auto pos = unique_name.find_last_of("#");
93 unique_name = unique_name.substr(0, pos);
94
95 return unique_name;
96}
97
98bool
100{
101 // Use a guid for cameras specifically in order to not get spammed
102 // with device messages.
103 // These are pertinent GUIDs for media devices:
104 //
105 // usb interfaces { 0xa5dcbf10l, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51,
106 // 0xed }; image devices { 0x6bdd1fc6, 0x810f, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2,
107 // 0x09, 0x2f }; capture devices { 0x65e8773d, 0x8f56, 0x11d0, 0xa3, 0xb9, 0x00, 0xa0, 0xc9,
108 // 0x22, 0x31, 0x96 }; camera devices { 0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46,
109 // 0x99, 0xc4, 0x6e, 0x44 }; audio devices { 0x6994ad04, 0x93ef, 0x11d0, 0xa3, 0xcc, 0x00,
110 // 0xa0, 0xc9, 0x22, 0x31, 0x96 };
111
113
117 NotificationFilter.dbcc_classguid = guidCamera;
118
120
121 if (nullptr == *hDeviceNotify) {
122 return false;
123 }
124
125 return true;
126}
127
129VideoDeviceMonitorImpl::WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
130{
131 LRESULT lRet = 1;
133 VideoDeviceMonitorImpl* pThis;
134
135 switch (message) {
136 case WM_CREATE: {
137 // Store object pointer passed from CreateWindowEx.
138 auto createParams = reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams;
139 pThis = static_cast<VideoDeviceMonitorImpl*>(createParams);
140 SetLastError(0);
141 SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
142
144 JAMI_ERR() << "Cannot register for device change notifications";
146 }
147 } break;
148
149 case WM_DEVICECHANGE: {
150 switch (wParam) {
152 case DBT_DEVICEARRIVAL: {
155 if (!unique_name.empty()) {
156 JAMI_DBG() << unique_name << ((wParam == DBT_DEVICEARRIVAL) ? " plugged" : " unplugged");
157 if (pThis = reinterpret_cast<VideoDeviceMonitorImpl*>(GetWindowLongPtr(hWnd, GWLP_USERDATA))) {
158 if (wParam == DBT_DEVICEARRIVAL) {
159 auto captureDeviceList = pThis->enumerateVideoInputDevices();
160 for (auto id : captureDeviceList) {
161 if (id.find(unique_name) != std::string::npos)
162 pThis->monitor_->addDevice(id);
163 }
164 } else if (wParam == DBT_DEVICEREMOVECOMPLETE) {
165 pThis->monitor_->removeDevice(unique_name);
166 }
167 }
168 }
169 } break;
170 default:
171 break;
172 }
173 break;
174 } break;
175
176 case WM_CLOSE:
179 break;
180
181 case WM_DESTROY:
183 break;
184
185 default:
186 lRet = DefWindowProc(hWnd, message, wParam, lParam);
187 break;
188 }
189
190 return lRet;
191}
192
193void
194VideoDeviceMonitorImpl::run()
195{
196 // Create a dummy window with the sole purpose to receive device change messages.
197 static const char* className = "Message";
198 WNDCLASSEX wx = {};
199 wx.cbSize = sizeof(WNDCLASSEX);
200 wx.lpfnWndProc = WinProcCallback;
201 wx.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
202 wx.lpszClassName = className;
203 if (RegisterClassEx(&wx)) {
204 // Pass this as lpParam so WinProcCallback can access members of VideoDeviceMonitorImpl.
205 hWnd_ = CreateWindowEx(0, className, "devicenotifications", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, this);
206 }
207
208 // Run the message loop that will finish once a WM_DESTROY message
209 // has been sent, allowing the thread to join.
210 MSG msg;
211 int retVal;
212 while ((retVal = GetMessage(&msg, NULL, 0, 0)) != 0) {
213 if (retVal != -1) {
216 }
217 }
218}
219
220std::vector<std::string>
221VideoDeviceMonitorImpl::enumerateVideoInputDevices()
222{
223 std::vector<std::string> deviceList;
224
227
228 if (FAILED(hr)) {
229 JAMI_ERR() << "Unable to enumerate webcams";
230 return {};
231 }
232
233 IEnumMoniker* pEnum = nullptr;
234 hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
235 if (hr == S_FALSE) {
237 }
238 pDevEnum->Release();
239 if (FAILED(hr) || pEnum == nullptr) {
240 JAMI_ERR() << "No webcam found";
241 return {};
242 }
243
245 while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
247 HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
248 if (FAILED(hr)) {
249 pMoniker->Release();
250 continue;
251 }
252
255
257 if (hr != S_OK) {
258 continue;
259 }
260 hr = pMoniker->GetDisplayName(bind_ctx, NULL, &olestr);
261 if (hr != S_OK) {
262 continue;
263 }
265 if (!unique_name.empty()) {
266 // replace ':' with '_' since ffmpeg uses : to delineate between sources
267 std::replace(unique_name.begin(), unique_name.end(), ':', '_');
268 deviceList.push_back(std::string("video=") + unique_name);
269 }
270
271 pPropBag->Release();
272 }
273 pEnum->Release();
274
275 return deviceList;
276}
277
279 : preferences_()
280 , devices_()
281 , monitorImpl_(new VideoDeviceMonitorImpl(this))
282{
283 monitorImpl_->start();
285}
286
287VideoDeviceMonitor::~VideoDeviceMonitor() {}
288
289} // namespace video
290} // namespace jami
VideoDeviceMonitorImpl(VideoDeviceMonitor *monitor)
bool addDevice(const std::string &node, const std::vector< std::map< std::string, std::string > > &devInfo={})
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
bool registerDeviceInterfaceToHwnd(HWND hWnd, HDEVNOTIFY *hDeviceNotify)
static constexpr const char DEVICE_DESKTOP[]
std::string getDeviceUniqueName(PDEV_BROADCAST_DEVICEINTERFACE_A pbdi)
void emitSignal(Args... args)
Definition jami_signal.h:64
std::string to_string(double value)
int32_t addDevice(const std::string &accountId, const std::string &uri)
Simple macro to hide class' copy constructor and assignment operator.
#define NON_COPYABLE(ClassName)
Definition noncopyable.h:30