ACMX 2.136.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
camera_probe_windows.cpp
Go to the documentation of this file.
1#include "camera_probe.hpp"
2
3#ifndef NOMINMAX
4#define NOMINMAX
5#endif
6#include <dshow.h>
7#include <dvdmedia.h>
8#include <olectl.h>
9#include <windows.h>
10
11#include <algorithm>
12#include <cmath>
13#include <iomanip>
14#include <map>
15#include <memory>
16#include <ostream>
17#include <string>
18#include <vector>
19
20#ifndef MEDIASUBTYPE_I420
21static const GUID MEDIASUBTYPE_I420 = {
22 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}};
23#endif
24
25namespace acmxvk {
26 namespace {
27 template <typename T>
28 struct ComReleaser {
29 void operator()(T *value) const {
30 if (value != nullptr) {
31 value->Release();
32 }
33 }
34 };
35
36 template <typename T>
37 using ComPtr = std::unique_ptr<T, ComReleaser<T>>;
38
39 class ComScope {
40 public:
42 : result(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)),
43 should_uninitialize(SUCCEEDED(result)) {
44 }
45
48 CoUninitialize();
49 }
50 }
51
52 [[nodiscard]] bool ready() const {
53 return SUCCEEDED(result) || result == RPC_E_CHANGED_MODE;
54 }
55
56 private:
57 HRESULT result;
59 };
60
61 struct CameraDevice {
62 int index = -1;
63 std::string name;
65 };
66
67 [[nodiscard]] std::string wide_to_utf8(const wchar_t *value) {
68 if (value == nullptr || *value == L'\0') {
69 return {};
70 }
71 const int source_length = static_cast<int>(wcslen(value));
72 const int length = WideCharToMultiByte(
73 CP_UTF8, WC_ERR_INVALID_CHARS, value, source_length, nullptr,
74 0, nullptr, nullptr);
75 if (length <= 0) {
76 return {};
77 }
78 std::string result(static_cast<std::size_t>(length), '\0');
79 if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value,
80 source_length, result.data(), length,
81 nullptr, nullptr) != length) {
82 return {};
83 }
84 return result;
85 }
86
87 [[nodiscard]] std::string camera_name(IPropertyBag *property_bag) {
88 if (property_bag == nullptr) {
89 return {};
90 }
91 for (const wchar_t *property : {L"FriendlyName", L"Description"}) {
92 VARIANT value;
93 VariantInit(&value);
94 const HRESULT result =
95 property_bag->Read(property, &value, nullptr);
96 std::string name;
97 if (SUCCEEDED(result) && value.vt == VT_BSTR) {
98 name = wide_to_utf8(value.bstrVal);
99 }
100 VariantClear(&value);
101 if (!name.empty()) {
102 return name;
103 }
104 }
105 return {};
106 }
107
108 [[nodiscard]] std::vector<CameraDevice> enumerate_devices() {
109 ICreateDevEnum *device_enumerator_raw = nullptr;
110 if (FAILED(CoCreateInstance(
111 CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER,
112 IID_ICreateDevEnum,
113 reinterpret_cast<void **>(&device_enumerator_raw)))) {
114 return {};
115 }
116 ComPtr<ICreateDevEnum> device_enumerator(device_enumerator_raw);
117
118 IEnumMoniker *enumerator_raw = nullptr;
119 if (device_enumerator->CreateClassEnumerator(
120 CLSID_VideoInputDeviceCategory, &enumerator_raw, 0) !=
121 S_OK ||
122 enumerator_raw == nullptr) {
123 return {};
124 }
125 ComPtr<IEnumMoniker> enumerator(enumerator_raw);
126
127 std::vector<CameraDevice> devices;
128 IMoniker *moniker_raw = nullptr;
129 ULONG fetched = 0;
130 int index = 0;
131 while (enumerator->Next(1, &moniker_raw, &fetched) == S_OK) {
132 ComPtr<IMoniker> moniker(moniker_raw);
133 moniker_raw = nullptr;
134 std::string name;
135 IPropertyBag *property_bag_raw = nullptr;
136 if (SUCCEEDED(moniker->BindToStorage(
137 nullptr, nullptr, IID_IPropertyBag,
138 reinterpret_cast<void **>(&property_bag_raw)))) {
139 ComPtr<IPropertyBag> property_bag(property_bag_raw);
140 name = camera_name(property_bag.get());
141 }
142 if (name.empty()) {
143 name = "Camera " + std::to_string(index);
144 }
145 devices.push_back({index, std::move(name), std::move(moniker)});
146 ++index;
147 }
148 return devices;
149 }
150
151 void free_media_type(AM_MEDIA_TYPE *media_type) {
152 if (media_type == nullptr) {
153 return;
154 }
155 if (media_type->pbFormat != nullptr) {
156 CoTaskMemFree(media_type->pbFormat);
157 }
158 if (media_type->pUnk != nullptr) {
159 media_type->pUnk->Release();
160 }
161 CoTaskMemFree(media_type);
162 }
163
164 [[nodiscard]] ComPtr<IAMStreamConfig>
165 stream_config(IMoniker *moniker) {
166 IBaseFilter *filter_raw = nullptr;
167 if (moniker == nullptr ||
168 FAILED(moniker->BindToObject(
169 nullptr, nullptr, IID_IBaseFilter,
170 reinterpret_cast<void **>(&filter_raw)))) {
171 return {};
172 }
173 ComPtr<IBaseFilter> filter(filter_raw);
174 IEnumPins *pins_raw = nullptr;
175 if (FAILED(filter->EnumPins(&pins_raw)) || pins_raw == nullptr) {
176 return {};
177 }
178 ComPtr<IEnumPins> pins(pins_raw);
179 IPin *pin_raw = nullptr;
180 ULONG fetched = 0;
181 while (pins->Next(1, &pin_raw, &fetched) == S_OK) {
182 ComPtr<IPin> pin(pin_raw);
183 pin_raw = nullptr;
184 PIN_DIRECTION direction = PINDIR_INPUT;
185 if (FAILED(pin->QueryDirection(&direction)) ||
186 direction != PINDIR_OUTPUT) {
187 continue;
188 }
189 IAMStreamConfig *config_raw = nullptr;
190 if (SUCCEEDED(pin->QueryInterface(
191 IID_IAMStreamConfig,
192 reinterpret_cast<void **>(&config_raw))) &&
193 config_raw != nullptr) {
194 return ComPtr<IAMStreamConfig>(config_raw);
195 }
196 }
197 return {};
198 }
199
200 [[nodiscard]] std::string subtype_name(const GUID &subtype) {
201 if (subtype == MEDIASUBTYPE_YUY2)
202 return "YUY2";
203 if (subtype == MEDIASUBTYPE_UYVY)
204 return "UYVY";
205 if (subtype == MEDIASUBTYPE_YV12)
206 return "YV12";
207 if (subtype == MEDIASUBTYPE_NV12)
208 return "NV12";
209 if (subtype == MEDIASUBTYPE_I420)
210 return "I420";
211 if (subtype == MEDIASUBTYPE_MJPG)
212 return "MJPG";
213 if (subtype == MEDIASUBTYPE_RGB24)
214 return "RGB24";
215 if (subtype == MEDIASUBTYPE_RGB32)
216 return "RGB32";
217 return "OTHER";
218 }
219
220 [[nodiscard]] bool video_format(const AM_MEDIA_TYPE *media_type,
221 int &width, int &height,
222 double &frame_rate) {
223 if (media_type == nullptr || media_type->pbFormat == nullptr) {
224 return false;
225 }
226 const BITMAPINFOHEADER *bitmap = nullptr;
227 REFERENCE_TIME interval = 0;
228 if (media_type->formattype == FORMAT_VideoInfo &&
229 media_type->cbFormat >= sizeof(VIDEOINFOHEADER)) {
230 const auto *info = reinterpret_cast<const VIDEOINFOHEADER *>(
231 media_type->pbFormat);
232 bitmap = &info->bmiHeader;
233 interval = info->AvgTimePerFrame;
234 } else if (media_type->formattype == FORMAT_VideoInfo2 &&
235 media_type->cbFormat >= sizeof(VIDEOINFOHEADER2)) {
236 const auto *info = reinterpret_cast<const VIDEOINFOHEADER2 *>(
237 media_type->pbFormat);
238 bitmap = &info->bmiHeader;
239 interval = info->AvgTimePerFrame;
240 }
241 if (bitmap == nullptr || bitmap->biWidth <= 0 ||
242 bitmap->biHeight == 0) {
243 return false;
244 }
245 width = bitmap->biWidth;
246 height = std::abs(bitmap->biHeight);
247 frame_rate = interval > 0
248 ? 10000000.0 / static_cast<double>(interval)
249 : 0.0;
250 return true;
251 }
252
253 void append_frame_rate(std::vector<double> &rates, double value) {
254 if (!std::isfinite(value) || value <= 0.0) {
255 return;
256 }
257 if (std::none_of(rates.begin(), rates.end(), [value](double rate) {
258 return std::abs(rate - value) < 0.05;
259 })) {
260 rates.push_back(value);
261 }
262 }
263 } // namespace
264
265 [[nodiscard]] bool listCameraDevices(std::ostream &output,
266 std::ostream &error) {
267 ComScope com_scope;
268 if (!com_scope.ready()) {
269 error << "acmxvk: unable to initialize COM for camera probing\n";
270 return false;
271 }
272 const std::vector<CameraDevice> devices = enumerate_devices();
273 for (const CameraDevice &device : devices) {
274 output << device.index << '\t' << device.name << '\n';
275 }
276 if (devices.empty()) {
277 error << "acmxvk: no DirectShow capture devices found\n";
278 return false;
279 }
280 return true;
281 }
282
283 [[nodiscard]] bool probeCameraDevice(int device_index,
284 std::ostream &output,
285 std::ostream &error) {
286 ComScope com_scope;
287 if (!com_scope.ready()) {
288 error << "acmxvk: unable to initialize COM for camera probing\n";
289 return false;
290 }
291 std::vector<CameraDevice> devices = enumerate_devices();
292 const auto device = std::find_if(
293 devices.begin(), devices.end(), [device_index](const CameraDevice &entry) {
294 return entry.index == device_index;
295 });
296 if (device == devices.end()) {
297 error << "acmxvk: DirectShow camera " << device_index
298 << " was not found\n";
299 return false;
300 }
301 const ComPtr<IAMStreamConfig> config =
302 stream_config(device->moniker.get());
303 if (!config) {
304 error << "acmxvk: camera " << device_index
305 << " exposes no DirectShow capture configuration\n";
306 return false;
307 }
308
309 int capability_count = 0;
310 int capability_size = 0;
311 if (FAILED(config->GetNumberOfCapabilities(&capability_count,
312 &capability_size)) ||
313 capability_count <= 0 || capability_size <= 0) {
314 error << "acmxvk: camera " << device_index
315 << " exposes no DirectShow capture formats\n";
316 return false;
317 }
318
319 using ResolutionMap =
320 std::map<std::pair<int, int>, std::vector<double>>;
321 std::map<std::string, ResolutionMap> formats;
322 std::vector<BYTE> capability_buffer(
323 static_cast<std::size_t>(capability_size));
324 for (int index = 0; index < capability_count; ++index) {
325 AM_MEDIA_TYPE *media_type = nullptr;
326 if (FAILED(config->GetStreamCaps(index, &media_type,
327 capability_buffer.data())) ||
328 media_type == nullptr) {
329 continue;
330 }
331 const std::unique_ptr<AM_MEDIA_TYPE, decltype(&free_media_type)>
332 media_type_guard(media_type, &free_media_type);
333 int width = 0;
334 int height = 0;
335 double frame_rate = 0.0;
336 if (!video_format(media_type, width, height, frame_rate)) {
337 continue;
338 }
339 std::vector<double> &rates =
340 formats[subtype_name(media_type->subtype)][{width, height}];
341 append_frame_rate(rates, frame_rate);
342 if (capability_size >=
343 static_cast<int>(sizeof(VIDEO_STREAM_CONFIG_CAPS))) {
344 const auto *caps =
345 reinterpret_cast<const VIDEO_STREAM_CONFIG_CAPS *>(
346 capability_buffer.data());
347 if (caps->MinFrameInterval > 0) {
349 rates, 10000000.0 /
350 static_cast<double>(caps->MinFrameInterval));
351 }
352 if (caps->MaxFrameInterval > 0) {
354 rates, 10000000.0 /
355 static_cast<double>(caps->MaxFrameInterval));
356 }
357 }
358 }
359
360 output << "Device " << device_index << ": DirectShow\n"
361 << " Driver : DirectShow\n"
362 << " Card : " << device->name << '\n'
363 << " Bus : Windows\n";
364 for (auto &[format, resolutions] : formats) {
365 output << "\n Format: " << format << '\n';
366 for (auto &[resolution, rates] : resolutions) {
367 std::sort(rates.begin(), rates.end());
368 output << " " << resolution.first << 'x'
369 << resolution.second;
370 bool first = true;
371 for (const double rate : rates) {
372 output << (first ? " @ " : ", ") << std::fixed
373 << std::setprecision(1) << rate << " fps";
374 first = false;
375 }
376 output << '\n';
377 }
378 }
379 if (formats.empty()) {
380 error << "acmxvk: camera " << device_index
381 << " exposes no DirectShow capture formats\n";
382 return false;
383 }
384 return true;
385 }
386} // namespace acmxvk
GLsizei GLsizei * length
static const GUID MEDIASUBTYPE_I420
bool video_format(const AM_MEDIA_TYPE *media_type, int &width, int &height, double &frame_rate)
std::string camera_name(IPropertyBag *property_bag)
ComPtr< IAMStreamConfig > stream_config(IMoniker *moniker)
void append_frame_rate(std::vector< double > &rates, double value)
bool listCameraDevices(std::ostream &output, std::ostream &error)
Print native camera device indices and names as tab-separated records.
bool probeCameraDevice(int device_index, std::ostream &output, std::ostream &error)
Print the native capture formats, resolutions, and frame rates exposed by a camera device.