20#ifndef MEDIASUBTYPE_I420
22 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}};
30 if (value !=
nullptr) {
37 using ComPtr = std::unique_ptr<T, ComReleaser<T>>;
42 :
result(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)),
52 [[nodiscard]]
bool ready()
const {
53 return SUCCEEDED(
result) ||
result == RPC_E_CHANGED_MODE;
68 if (value ==
nullptr || *value == L
'\0') {
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,
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) {
87 [[nodiscard]] std::string
camera_name(IPropertyBag *property_bag) {
88 if (property_bag ==
nullptr) {
91 for (
const wchar_t *property : {L
"FriendlyName", L
"Description"}) {
94 const HRESULT result =
95 property_bag->Read(property, &value,
nullptr);
97 if (SUCCEEDED(result) && value.vt == VT_BSTR) {
100 VariantClear(&value);
109 ICreateDevEnum *device_enumerator_raw =
nullptr;
110 if (FAILED(CoCreateInstance(
111 CLSID_SystemDeviceEnum,
nullptr, CLSCTX_INPROC_SERVER,
113 reinterpret_cast<void **
>(&device_enumerator_raw)))) {
118 IEnumMoniker *enumerator_raw =
nullptr;
119 if (device_enumerator->CreateClassEnumerator(
120 CLSID_VideoInputDeviceCategory, &enumerator_raw, 0) !=
122 enumerator_raw ==
nullptr) {
127 std::vector<CameraDevice> devices;
128 IMoniker *moniker_raw =
nullptr;
131 while (enumerator->Next(1, &moniker_raw, &fetched) == S_OK) {
133 moniker_raw =
nullptr;
135 IPropertyBag *property_bag_raw =
nullptr;
136 if (SUCCEEDED(moniker->BindToStorage(
137 nullptr,
nullptr, IID_IPropertyBag,
138 reinterpret_cast<void **
>(&property_bag_raw)))) {
143 name =
"Camera " + std::to_string(index);
145 devices.push_back({index, std::move(name), std::move(moniker)});
152 if (media_type ==
nullptr) {
155 if (media_type->pbFormat !=
nullptr) {
156 CoTaskMemFree(media_type->pbFormat);
158 if (media_type->pUnk !=
nullptr) {
159 media_type->pUnk->Release();
161 CoTaskMemFree(media_type);
164 [[nodiscard]] ComPtr<IAMStreamConfig>
166 IBaseFilter *filter_raw =
nullptr;
167 if (moniker ==
nullptr ||
168 FAILED(moniker->BindToObject(
169 nullptr,
nullptr, IID_IBaseFilter,
170 reinterpret_cast<void **
>(&filter_raw)))) {
174 IEnumPins *pins_raw =
nullptr;
175 if (FAILED(filter->EnumPins(&pins_raw)) || pins_raw ==
nullptr) {
179 IPin *pin_raw =
nullptr;
181 while (pins->Next(1, &pin_raw, &fetched) == S_OK) {
184 PIN_DIRECTION direction = PINDIR_INPUT;
185 if (FAILED(pin->QueryDirection(&direction)) ||
186 direction != PINDIR_OUTPUT) {
189 IAMStreamConfig *config_raw =
nullptr;
190 if (SUCCEEDED(pin->QueryInterface(
192 reinterpret_cast<void **
>(&config_raw))) &&
193 config_raw !=
nullptr) {
201 if (subtype == MEDIASUBTYPE_YUY2)
203 if (subtype == MEDIASUBTYPE_UYVY)
205 if (subtype == MEDIASUBTYPE_YV12)
207 if (subtype == MEDIASUBTYPE_NV12)
211 if (subtype == MEDIASUBTYPE_MJPG)
213 if (subtype == MEDIASUBTYPE_RGB24)
215 if (subtype == MEDIASUBTYPE_RGB32)
221 int &width,
int &height,
222 double &frame_rate) {
223 if (media_type ==
nullptr || media_type->pbFormat ==
nullptr) {
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;
241 if (bitmap ==
nullptr || bitmap->biWidth <= 0 ||
242 bitmap->biHeight == 0) {
245 width = bitmap->biWidth;
246 height = std::abs(bitmap->biHeight);
247 frame_rate = interval > 0
248 ? 10000000.0 /
static_cast<double>(interval)
254 if (!std::isfinite(value) || value <= 0.0) {
257 if (std::none_of(rates.begin(), rates.end(), [value](
double rate) {
258 return std::abs(rate - value) < 0.05;
260 rates.push_back(value);
266 std::ostream &error) {
268 if (!com_scope.ready()) {
269 error <<
"acmxvk: unable to initialize COM for camera probing\n";
273 for (
const CameraDevice &device : devices) {
274 output << device.index <<
'\t' << device.name <<
'\n';
276 if (devices.empty()) {
277 error <<
"acmxvk: no DirectShow capture devices found\n";
284 std::ostream &output,
285 std::ostream &error) {
287 if (!com_scope.ready()) {
288 error <<
"acmxvk: unable to initialize COM for camera probing\n";
292 const auto device = std::find_if(
293 devices.begin(), devices.end(), [device_index](
const CameraDevice &entry) {
294 return entry.index == device_index;
296 if (device == devices.end()) {
297 error <<
"acmxvk: DirectShow camera " << device_index
298 <<
" was not found\n";
301 const ComPtr<IAMStreamConfig> config =
304 error <<
"acmxvk: camera " << device_index
305 <<
" exposes no DirectShow capture configuration\n";
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";
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) {
332 media_type_guard(media_type, &free_media_type);
335 double frame_rate = 0.0;
336 if (!
video_format(media_type, width, height, frame_rate)) {
339 std::vector<double> &rates =
340 formats[
subtype_name(media_type->subtype)][{width, height}];
342 if (capability_size >=
343 static_cast<int>(
sizeof(VIDEO_STREAM_CONFIG_CAPS))) {
345 reinterpret_cast<const VIDEO_STREAM_CONFIG_CAPS *
>(
346 capability_buffer.data());
347 if (caps->MinFrameInterval > 0) {
350 static_cast<double>(caps->MinFrameInterval));
352 if (caps->MaxFrameInterval > 0) {
355 static_cast<double>(caps->MaxFrameInterval));
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;
371 for (
const double rate : rates) {
372 output << (first ?
" @ " :
", ") << std::fixed
373 << std::setprecision(1) << rate <<
" fps";
379 if (formats.empty()) {
380 error <<
"acmxvk: camera " << device_index
381 <<
" exposes no DirectShow capture formats\n";
static const GUID MEDIASUBTYPE_I420
std::vector< CameraDevice > enumerate_devices()
bool video_format(const AM_MEDIA_TYPE *media_type, int &width, int &height, double &frame_rate)
std::unique_ptr< T, ComReleaser< T > > ComPtr
std::string subtype_name(const GUID &subtype)
std::string camera_name(IPropertyBag *property_bag)
ComPtr< IAMStreamConfig > stream_config(IMoniker *moniker)
void append_frame_rate(std::vector< double > &rates, double value)
std::string wide_to_utf8(const wchar_t *value)
void free_media_type(AM_MEDIA_TYPE *media_type)
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.
ComPtr< IMoniker > moniker
void operator()(T *value) const