9
9
#include < sys/ioctl.h>
10
10
#include < linux/videodev2.h>
11
11
12
+ #include < string>
12
13
#include < vector>
13
14
#include < set>
14
15
#include < stdexcept>
21
22
// Obviously, this won't help if multiple processes are used
22
23
// or if devices are opened by other tools.
23
24
// In this case, explicitly specifying the device seems the only solution.
24
- static std::set<size_t > ACTIVE_DEVICES;
25
+ static std::set<std::string > ACTIVE_DEVICES;
25
26
26
27
class VirtualOutput {
27
28
private:
28
29
bool _output_running = false ;
29
30
int _camera_fd;
30
31
std::string _camera_device;
31
- size_t _camera_device_idx;
32
32
uint32_t _frame_fourcc;
33
33
uint32_t _native_fourcc;
34
34
uint32_t _frame_width;
@@ -37,7 +37,8 @@ class VirtualOutput {
37
37
std::vector<uint8_t > _buffer_output;
38
38
39
39
public:
40
- VirtualOutput (uint32_t width, uint32_t height, uint32_t fourcc) {
40
+ VirtualOutput (uint32_t width, uint32_t height, uint32_t fourcc,
41
+ std::optional<std::string> device_) {
41
42
_frame_width = width;
42
43
_frame_height = height;
43
44
_frame_fourcc = libyuv::CanonicalFourCC (fourcc);
@@ -82,50 +83,78 @@ class VirtualOutput {
82
83
throw std::runtime_error (" Unsupported image format." );
83
84
}
84
85
85
- char device_name[14 ];
86
- int device_idx = -1 ;
87
-
88
- for (size_t i = 0 ; i < 100 ; i++) {
89
- if (ACTIVE_DEVICES.count (i)) {
90
- continue ;
86
+ auto try_open = [&](const std::string& device_name) {
87
+ if (ACTIVE_DEVICES.count (device_name)) {
88
+ throw std::invalid_argument (
89
+ " Device " + device_name + " is already in use."
90
+ );
91
91
}
92
- sprintf (device_name, " /dev/video%zu" , i);
93
- _camera_fd = open (device_name, O_WRONLY | O_SYNC);
92
+ _camera_fd = open (device_name.c_str (), O_WRONLY | O_SYNC);
94
93
if (_camera_fd == -1 ) {
95
94
if (errno == EACCES) {
96
95
throw std::runtime_error (
97
- " Could not access " + std::string ( device_name) + " due to missing permissions. "
96
+ " Could not access " + device_name + " due to missing permissions. "
98
97
" Did you add your user to the 'video' group? "
99
98
" Run 'usermod -a -G video myusername' and log out and in again."
100
99
);
100
+ } else if (errno == ENOENT) {
101
+ throw std::invalid_argument (
102
+ " Device " + device_name + " does not exist."
103
+ );
104
+ } else {
105
+ throw std::invalid_argument (
106
+ " Device " + device_name + " could not be opened: " +
107
+ std::string (strerror (errno))
108
+ );
101
109
}
102
- continue ;
103
110
}
104
111
105
112
struct v4l2_capability camera_cap;
106
113
107
114
if (ioctl (_camera_fd, VIDIOC_QUERYCAP, &camera_cap) == -1 ) {
108
- continue ;
115
+ throw std::invalid_argument (
116
+ " Device capabilities of " + device_name + " could not be queried."
117
+ );
109
118
}
110
119
if (!(camera_cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) {
111
- continue ;
120
+ throw std::invalid_argument (
121
+ " Device " + device_name + " is not a video output device."
122
+ );
112
123
}
113
124
if (strcmp ((const char *)camera_cap.driver , " v4l2 loopback" ) != 0 ) {
114
- continue ;
125
+ throw std::invalid_argument (
126
+ " Device " + device_name + " is not a V4L2 device."
127
+ );
128
+ }
129
+ };
130
+
131
+ std::string device_name;
132
+
133
+ if (device_.has_value ()) {
134
+ device_name = device_.value ();
135
+ try_open (device_name);
136
+ } else {
137
+ bool found = false ;
138
+ for (size_t i = 0 ; i < 100 ; i++) {
139
+ std::ostringstream device_name_s;
140
+ device_name_s << " /dev/video" << i;
141
+ device_name = device_name_s.str ();
142
+ try {
143
+ try_open (device_name);
144
+ } catch (std::invalid_argument&) {
145
+ continue ;
146
+ }
147
+ found = true ;
148
+ break ;
149
+ }
150
+ if (!found) {
151
+ throw std::runtime_error (
152
+ " No v4l2 loopback device found at /dev/video[0-99]. "
153
+ " Did you run 'modprobe v4l2loopback'? "
154
+ " See also pyvirtualcam's documentation." );
115
155
}
116
- device_idx = i;
117
- break ;
118
- }
119
- if (device_idx == -1 ) {
120
- throw std::runtime_error (
121
- " No v4l2 loopback device found at /dev/video[0-99]. "
122
- " Did you run 'modprobe v4l2loopback'? "
123
- " See also pyvirtualcam's documentation." );
124
156
}
125
157
126
- uint32_t half_width = width / 2 ;
127
- uint32_t half_height = height / 2 ;
128
-
129
158
v4l2_format v4l2_fmt;
130
159
memset (&v4l2_fmt, 0 , sizeof (v4l2_fmt));
131
160
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
@@ -139,16 +168,15 @@ class VirtualOutput {
139
168
if (ioctl (_camera_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1 ) {
140
169
close (_camera_fd);
141
170
throw std::runtime_error (
142
- " Virtual camera device " + std::string ( device_name) +
171
+ " Virtual camera device " + device_name +
143
172
" could not be configured: " + std::string (strerror (errno))
144
173
);
145
174
}
146
175
147
176
_output_running = true ;
148
- _camera_device = std::string (device_name);
149
- _camera_device_idx = device_idx;
177
+ _camera_device = device_name;
150
178
151
- ACTIVE_DEVICES.insert (device_idx );
179
+ ACTIVE_DEVICES.insert (_camera_device );
152
180
}
153
181
154
182
void stop () {
@@ -159,7 +187,7 @@ class VirtualOutput {
159
187
close (_camera_fd);
160
188
161
189
_output_running = false ;
162
- ACTIVE_DEVICES.erase (_camera_device_idx );
190
+ ACTIVE_DEVICES.erase (_camera_device );
163
191
}
164
192
165
193
void send (const uint8_t * frame) {
0 commit comments