ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
camera_probe.cpp
Go to the documentation of this file.
1#include "camera_probe.hpp"
2
3#include <algorithm>
4#include <array>
5#include <cerrno>
6#include <cmath>
7#include <cstdint>
8#include <cstring>
9#include <filesystem>
10#include <fstream>
11#include <functional>
12#include <iomanip>
13#include <ostream>
14#include <string>
15#include <vector>
16
17#ifdef __linux__
18#include <fcntl.h>
19#include <linux/videodev2.h>
20#include <sys/ioctl.h>
21#include <unistd.h>
22#endif
23
24namespace acmxvk {
25 namespace fs = std::filesystem;
26 namespace {
27#ifdef __linux__
28 [[nodiscard]] std::string sanitizeDeviceText(std::string text) {
29 for (char &character : text) {
30 const unsigned char value = static_cast<unsigned char>(character);
31 if (value < 0x20U || value == 0x7FU) {
32 character = ' ';
33 }
34 }
35 return text;
36 }
37
38 template <std::size_t Size> [[nodiscard]] std::string v4l2Text(const __u8 (&value)[Size]) {
39 const char *text = reinterpret_cast<const char *>(value);
40 return sanitizeDeviceText(std::string(text, ::strnlen(text, Size)));
41 }
42
43 [[nodiscard]] int cameraIoctl(int descriptor, unsigned long request, void *argument) {
44 int result = -1;
45 do {
46 result = ioctl(descriptor, request, argument);
47 } while (result < 0 && errno == EINTR);
48 return result;
49 }
50
51 void appendFrameRate(std::vector<double> &frame_rates, double frame_rate) {
52 if (!std::isfinite(frame_rate) || frame_rate <= 0.0) {
53 return;
54 }
55 const auto existing = std::find_if(frame_rates.begin(), frame_rates.end(), [frame_rate](double value) { return std::abs(value - frame_rate) < 0.05; });
56 if (existing == frame_rates.end()) {
57 frame_rates.push_back(frame_rate);
58 }
59 }
60
61 [[nodiscard]] double frameRate(const v4l2_fract &interval) {
62 if (interval.numerator == 0U) {
63 return 0.0;
64 }
65 return static_cast<double>(interval.denominator) / static_cast<double>(interval.numerator);
66 }
67
68 [[nodiscard]] std::string fourccName(std::uint32_t fourcc) {
69 std::array<char, 5> name{static_cast<char>(fourcc & 0xFFU), static_cast<char>((fourcc >> 8U) & 0xFFU), static_cast<char>((fourcc >> 16U) & 0xFFU), static_cast<char>((fourcc >> 24U) & 0xFFU), '\0'};
70 for (std::size_t index = 0; index < 4U; ++index) {
71 const unsigned char character = static_cast<unsigned char>(name[index]);
72 if (character < 0x20U || character > 0x7EU) {
73 name[index] = '?';
74 }
75 }
76 return name.data();
77 }
78
79 [[nodiscard]] bool enumerateCaptureType(int descriptor, v4l2_buf_type capture_type, bool loopback_device, double current_fps, std::ostream &output) {
80 bool found_format = false;
81 v4l2_fmtdesc format{};
82 format.type = capture_type;
83 for (format.index = 0U; cameraIoctl(descriptor, VIDIOC_ENUM_FMT, &format) == 0; ++format.index) {
84 found_format = true;
85 output << "\n Format: " << fourccName(format.pixelformat) << " (" << v4l2Text(format.description) << ")\n";
86
87 v4l2_frmsizeenum frame_size{};
88 frame_size.pixel_format = format.pixelformat;
89 for (frame_size.index = 0U; cameraIoctl(descriptor, VIDIOC_ENUM_FRAMESIZES, &frame_size) == 0; ++frame_size.index) {
90 if (frame_size.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
91 std::vector<double> frame_rates;
92 v4l2_frmivalenum interval{};
93 interval.pixel_format = format.pixelformat;
94 interval.width = frame_size.discrete.width;
95 interval.height = frame_size.discrete.height;
96 for (interval.index = 0U; cameraIoctl(descriptor, VIDIOC_ENUM_FRAMEINTERVALS, &interval) == 0; ++interval.index) {
97 if (interval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
98 appendFrameRate(frame_rates, frameRate(interval.discrete));
99 } else if (interval.type == V4L2_FRMIVAL_TYPE_STEPWISE || interval.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
100 appendFrameRate(frame_rates, frameRate(interval.stepwise.min));
101 appendFrameRate(frame_rates, frameRate(interval.stepwise.max));
102 break;
103 }
104 }
105
106 if (frame_rates.empty()) {
107 appendFrameRate(frame_rates, current_fps);
108 }
109 if (loopback_device) {
110 constexpr std::array<double, 9> LOOPBACK_FRAME_RATES{24.0, 25.0, 30.0, 50.0, 60.0, 90.0, 120.0, 144.0, 240.0};
111 for (double frame_rate : LOOPBACK_FRAME_RATES) {
112 appendFrameRate(frame_rates, frame_rate);
113 }
114 }
115 std::sort(frame_rates.begin(), frame_rates.end(), std::greater<double>());
116
117 output << " " << frame_size.discrete.width << 'x' << frame_size.discrete.height;
118 bool first = true;
119 for (double frame_rate : frame_rates) {
120 output << (first ? " @ " : ", ") << std::fixed << std::setprecision(1) << frame_rate << " fps";
121 first = false;
122 }
123 output << '\n';
124 } else if (frame_size.type == V4L2_FRMSIZE_TYPE_STEPWISE || frame_size.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
125 output << " " << frame_size.stepwise.min_width << 'x' << frame_size.stepwise.min_height << " to " << frame_size.stepwise.max_width << 'x' << frame_size.stepwise.max_height << " (step " << frame_size.stepwise.step_width << 'x' << frame_size.stepwise.step_height << ")\n";
126 break;
127 }
128 }
129 }
130 return found_format;
131 }
132
133 [[nodiscard]] bool primaryVideoNode(int device_index) {
134 std::ifstream index_file("/sys/class/video4linux/video" + std::to_string(device_index) + "/index");
135 int stream_index = 0;
136 return !(index_file >> stream_index) || stream_index == 0;
137 }
138#endif
139 } // namespace
140
141 [[nodiscard]] bool listCameraDevices(std::ostream &output, std::ostream &error) {
142#ifdef __linux__
143 std::vector<int> device_indices;
144 std::error_code directory_error;
145 for (const fs::directory_entry &entry : fs::directory_iterator("/dev", directory_error)) {
146 const std::string name = entry.path().filename().string();
147 if (!name.starts_with("video") || name.size() <= 5U || !std::all_of(name.begin() + 5, name.end(), [](char character) { return character >= '0' && character <= '9'; })) {
148 continue;
149 }
150 try {
151 const int index = std::stoi(name.substr(5));
152 if (index >= 0 && primaryVideoNode(index)) {
153 device_indices.push_back(index);
154 }
155 } catch (const std::exception &) {
156 continue;
157 }
158 }
159 if (directory_error) {
160 error << "acmxvk: cannot enumerate /dev video devices: " << directory_error.message() << '\n';
161 return false;
162 }
163 std::sort(device_indices.begin(), device_indices.end());
164 device_indices.erase(std::unique(device_indices.begin(), device_indices.end()), device_indices.end());
165
166 bool found_device = false;
167 for (int device_index : device_indices) {
168 const std::string path = "/dev/video" + std::to_string(device_index);
169 int descriptor = open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
170 if (descriptor < 0) {
171 continue;
172 }
173 v4l2_capability capability{};
174 const bool queried = cameraIoctl(descriptor, VIDIOC_QUERYCAP, &capability) == 0;
175 close(descriptor);
176 if (!queried) {
177 continue;
178 }
179 const std::uint32_t capabilities = (capability.capabilities & V4L2_CAP_DEVICE_CAPS) != 0U ? capability.device_caps : capability.capabilities;
180 if ((capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) == 0U) {
181 continue;
182 }
183 output << device_index << '\t' << v4l2Text(capability.card) << '\n';
184 found_device = true;
185 }
186 if (!found_device) {
187 error << "acmxvk: no V4L2 capture devices found\n";
188 }
189 return found_device;
190#else
191 static_cast<void>(output);
192 error << "acmxvk: --list-camera-devices is supported on Linux and macOS\n";
193 return false;
194#endif
195 }
196
197 [[nodiscard]] bool probeCameraDevice(int device_index, std::ostream &output, std::ostream &error) {
198#ifdef __linux__
199 const std::string device_path = "/dev/video" + std::to_string(device_index);
200 int descriptor = open(device_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
201 if (descriptor < 0) {
202 descriptor = open(device_path.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
203 }
204 if (descriptor < 0) {
205 error << "acmxvk: cannot open " << device_path << ": " << std::strerror(errno) << '\n';
206 return false;
207 }
208
209 struct DescriptorGuard {
210 int value;
211 ~DescriptorGuard() {
212 if (value >= 0) {
213 close(value);
214 }
215 }
216 } guard{descriptor};
217
218 v4l2_capability capability{};
219 if (cameraIoctl(descriptor, VIDIOC_QUERYCAP, &capability) != 0) {
220 error << "acmxvk: cannot query " << device_path << ": " << std::strerror(errno) << '\n';
221 return false;
222 }
223
224 const std::uint32_t device_capabilities = (capability.capabilities & V4L2_CAP_DEVICE_CAPS) != 0U ? capability.device_caps : capability.capabilities;
225 const std::string driver = v4l2Text(capability.driver);
226 const bool loopback_device = driver.find("v4l2loopback") != std::string::npos || driver.find("v4l2 loopback") != std::string::npos;
227
228 output << "Device " << device_index << ": " << device_path << '\n' << " Driver : " << driver << '\n' << " Card : " << v4l2Text(capability.card) << '\n' << " Bus : " << v4l2Text(capability.bus_info) << '\n';
229
230 double current_fps = 0.0;
231 v4l2_streamparm stream_parameters{};
232 stream_parameters.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
233 if (cameraIoctl(descriptor, VIDIOC_G_PARM, &stream_parameters) == 0) {
234 current_fps = frameRate(stream_parameters.parm.capture.timeperframe);
235 }
236
237 bool found_format = false;
238 if ((device_capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0U) {
239 found_format = enumerateCaptureType(descriptor, V4L2_BUF_TYPE_VIDEO_CAPTURE, loopback_device, current_fps, output) || found_format;
240 }
241 if ((device_capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) != 0U) {
242 found_format = enumerateCaptureType(descriptor, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, loopback_device, current_fps, output) || found_format;
243 }
244 if (!found_format) {
245 error << "acmxvk: " << device_path << " exposes no capture formats\n";
246 }
247 return found_format;
248#else
249 static_cast<void>(device_index);
250 static_cast<void>(output);
251 error << "acmxvk: --enumerate-device is supported on Linux and macOS\n";
252 return false;
253#endif
254 }
255} // namespace acmxvk
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.