Ring Daemon 16.0.0
Loading...
Searching...
No Matches
winvideo/video_device_monitor_impl.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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
37 = {0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44};
38
39class VideoDeviceMonitorImpl
40{
41public:
44
45 void start();
46
47private:
49
50 VideoDeviceMonitor* monitor_;
51
52 void run();
53
54 std::vector<std::string> enumerateVideoInputDevices();
55
56 std::thread thread_;
57 HWND hWnd_;
58 static LRESULT CALLBACK WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
59};
60
62 : monitor_(monitor)
63 , thread_()
64{}
65
66void
68{
69 // Enumerate the initial capture device list.
70 auto captureDeviceList = enumerateVideoInputDevices();
71 for (auto node : captureDeviceList) {
72 monitor_->addDevice(node);
73 }
74 thread_ = std::thread(&VideoDeviceMonitorImpl::run, this);
75}
76
78{
79 SendMessage(hWnd_, WM_DESTROY, 0, 0);
80 if (thread_.joinable())
81 thread_.join();
82}
83
84std::string
86{
87 std::string unique_name = pbdi->dbcc_name;
88
89 std::transform(unique_name.begin(), unique_name.end(), unique_name.begin(), [](unsigned char c) {
90 return std::tolower(c);
91 });
92
93 auto pos = unique_name.find_last_of("#");
94 unique_name = unique_name.substr(0, pos);
95
96 return unique_name;
97}
98
99bool
101{
102 // Use a guid for cameras specifically in order to not get spammed
103 // with device messages.
104 // These are pertinent GUIDs for media devices:
105 //
106 // usb interfaces { 0xa5dcbf10l, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51,
107 // 0xed }; image devices { 0x6bdd1fc6, 0x810f, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2,
108 // 0x09, 0x2f }; capture devices { 0x65e8773d, 0x8f56, 0x11d0, 0xa3, 0xb9, 0x00, 0xa0, 0xc9,
109 // 0x22, 0x31, 0x96 }; camera devices { 0xe5323777, 0xf976, 0x4f5b, 0x9b, 0x55, 0xb9, 0x46,
110 // 0x99, 0xc4, 0x6e, 0x44 }; audio devices { 0x6994ad04, 0x93ef, 0x11d0, 0xa3, 0xcc, 0x00,
111 // 0xa0, 0xc9, 0x22, 0x31, 0x96 };
112
114
118 NotificationFilter.dbcc_classguid = guidCamera;
119
123
124 if (nullptr == *hDeviceNotify) {
125 return false;
126 }
127
128 return true;
129}
130
132VideoDeviceMonitorImpl::WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
133{
134 LRESULT lRet = 1;
136 VideoDeviceMonitorImpl* pThis;
137
138 switch (message) {
139 case WM_CREATE: {
140 // Store object pointer passed from CreateWindowEx.
141 auto createParams = reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams;
142 pThis = static_cast<VideoDeviceMonitorImpl*>(createParams);
143 SetLastError(0);
144 SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
145
147 JAMI_ERR() << "Cannot register for device change notifications";
149 }
150 } break;
151
152 case WM_DEVICECHANGE: {
153 switch (wParam) {
155 case DBT_DEVICEARRIVAL: {
158 if (!unique_name.empty()) {
160 << ((wParam == DBT_DEVICEARRIVAL) ? " plugged" : " unplugged");
161 if (pThis = reinterpret_cast<VideoDeviceMonitorImpl*>(
163 if (wParam == DBT_DEVICEARRIVAL) {
164 auto captureDeviceList = pThis->enumerateVideoInputDevices();
165 for (auto id : captureDeviceList) {
166 if (id.find(unique_name) != std::string::npos)
167 pThis->monitor_->addDevice(id);
168 }
169 } else if (wParam == DBT_DEVICEREMOVECOMPLETE) {
170 pThis->monitor_->removeDevice(unique_name);
171 }
172 }
173 }
174 } break;
175 default:
176 break;
177 }
178 break;
179 } break;
180
181 case WM_CLOSE:
184 break;
185
186 case WM_DESTROY:
188 break;
189
190 default:
191 lRet = DefWindowProc(hWnd, message, wParam, lParam);
192 break;
193 }
194
195 return lRet;
196}
197
198void
199VideoDeviceMonitorImpl::run()
200{
201 // Create a dummy window with the sole purpose to receive device change messages.
202 static const char* className = "Message";
203 WNDCLASSEX wx = {};
204 wx.cbSize = sizeof(WNDCLASSEX);
205 wx.lpfnWndProc = WinProcCallback;
206 wx.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
207 wx.lpszClassName = className;
208 if (RegisterClassEx(&wx)) {
209 // Pass this as lpParam so WinProcCallback can access members of VideoDeviceMonitorImpl.
210 hWnd_ = CreateWindowEx(
211 0, className, "devicenotifications", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, this);
212 }
213
214 // Run the message loop that will finish once a WM_DESTROY message
215 // has been sent, allowing the thread to join.
216 MSG msg;
217 int retVal;
218 while ((retVal = GetMessage(&msg, NULL, 0, 0)) != 0) {
219 if (retVal != -1) {
222 }
223 }
224}
225
226std::vector<std::string>
227VideoDeviceMonitorImpl::enumerateVideoInputDevices()
228{
229 std::vector<std::string> deviceList;
230
233 NULL,
236
237 if (FAILED(hr)) {
238 JAMI_ERR() << "Unable to enumerate webcams";
239 return {};
240 }
241
242 IEnumMoniker* pEnum = nullptr;
243 hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
244 if (hr == S_FALSE) {
246 }
247 pDevEnum->Release();
248 if (FAILED(hr) || pEnum == nullptr) {
249 JAMI_ERR() << "No webcam found";
250 return {};
251 }
252
254 while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
256 HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
257 if (FAILED(hr)) {
258 pMoniker->Release();
259 continue;
260 }
261
264
266 if (hr != S_OK) {
267 continue;
268 }
269 hr = pMoniker->GetDisplayName(bind_ctx, NULL, &olestr);
270 if (hr != S_OK) {
271 continue;
272 }
274 if (!unique_name.empty()) {
275 // replace ':' with '_' since ffmpeg uses : to delineate between sources
276 std::replace(unique_name.begin(), unique_name.end(), ':', '_');
277 deviceList.push_back(std::string("video=") + unique_name);
278 }
279
280 pPropBag->Release();
281 }
282 pEnum->Release();
283
284 return deviceList;
285}
286
288 : preferences_()
289 , devices_()
290 , monitorImpl_(new VideoDeviceMonitorImpl(this))
291{
292 monitorImpl_->start();
294}
295
296VideoDeviceMonitor::~VideoDeviceMonitor() {}
297
298} // namespace video
299} // 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:218
#define JAMI_DBG(...)
Definition logger.h:216
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 ring_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