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) {
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)));
43 [[nodiscard]]
int cameraIoctl(
int descriptor,
unsigned long request,
void *argument) {
46 result = ioctl(descriptor, request, argument);
47 }
while (result < 0 && errno == EINTR);
51 void appendFrameRate(std::vector<double> &frame_rates,
double frame_rate) {
52 if (!std::isfinite(frame_rate) || frame_rate <= 0.0) {
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);
61 [[nodiscard]]
double frameRate(
const v4l2_fract &interval) {
62 if (interval.numerator == 0U) {
65 return static_cast<double>(interval.denominator) /
static_cast<double>(interval.numerator);
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) {
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) {
85 output <<
"\n Format: " << fourccName(format.pixelformat) <<
" (" << v4l2Text(format.description) <<
")\n";
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));
106 if (frame_rates.empty()) {
107 appendFrameRate(frame_rates, current_fps);
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);
115 std::sort(frame_rates.begin(), frame_rates.end(), std::greater<double>());
117 output <<
" " << frame_size.discrete.width <<
'x' << frame_size.discrete.height;
119 for (
double frame_rate : frame_rates) {
120 output << (first ?
" @ " :
", ") << std::fixed << std::setprecision(1) << frame_rate <<
" fps";
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";
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;
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'; })) {
151 const int index = std::stoi(name.substr(5));
152 if (index >= 0 && primaryVideoNode(index)) {
153 device_indices.push_back(index);
155 }
catch (
const std::exception &) {
159 if (directory_error) {
160 error <<
"acmxvk: cannot enumerate /dev video devices: " << directory_error.message() <<
'\n';
163 std::sort(device_indices.begin(), device_indices.end());
164 device_indices.erase(std::unique(device_indices.begin(), device_indices.end()), device_indices.end());
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) {
173 v4l2_capability capability{};
174 const bool queried = cameraIoctl(descriptor, VIDIOC_QUERYCAP, &capability) == 0;
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) {
183 output << device_index <<
'\t' << v4l2Text(capability.card) <<
'\n';
187 error <<
"acmxvk: no V4L2 capture devices found\n";
191 static_cast<void>(output);
192 error <<
"acmxvk: --list-camera-devices is supported on Linux and macOS\n";
197 [[nodiscard]]
bool probeCameraDevice(
int device_index, std::ostream &output, std::ostream &error) {
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);
204 if (descriptor < 0) {
205 error <<
"acmxvk: cannot open " << device_path <<
": " << std::strerror(errno) <<
'\n';
209 struct DescriptorGuard {
218 v4l2_capability capability{};
219 if (cameraIoctl(descriptor, VIDIOC_QUERYCAP, &capability) != 0) {
220 error <<
"acmxvk: cannot query " << device_path <<
": " << std::strerror(errno) <<
'\n';
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;
228 output <<
"Device " << device_index <<
": " << device_path <<
'\n' <<
" Driver : " << driver <<
'\n' <<
" Card : " << v4l2Text(capability.card) <<
'\n' <<
" Bus : " << v4l2Text(capability.bus_info) <<
'\n';
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);
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;
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;
245 error <<
"acmxvk: " << device_path <<
" exposes no capture formats\n";
249 static_cast<void>(device_index);
250 static_cast<void>(output);
251 error <<
"acmxvk: --enumerate-device is supported on Linux and macOS\n";