summarylogtreecommitdiffstats
path: root/v4l2_by-path.patch
blob: 26fd180f29863007f0d119423b962de5b0048a4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
From f19d4c3f3cd630f676d07e889e25e7c9250cfddc Mon Sep 17 00:00:00 2001
From: Grzegorz Godlewski <gg@gitgis.com>
Date: Mon, 16 May 2022 18:29:16 +0200
Subject: [PATCH] linux-v4l2: Save device by id or path

---
 plugins/linux-v4l2/v4l2-input.c | 208 +++++++++++++++++++++++---------
 1 file changed, 151 insertions(+), 57 deletions(-)

diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c
index 9b931ea88113..8e98ea469b46 100644
--- a/plugins/linux-v4l2/v4l2-input.c
+++ b/plugins/linux-v4l2/v4l2-input.c
@@ -69,6 +69,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 struct v4l2_data {
 	/* settings */
 	char *device_id;
+	char *real_path;
 	int input;
 	int pixfmt;
 	int standard;
@@ -344,37 +345,71 @@ static void v4l2_props_set_enabled(obs_properties_t *props,
 }
 
 /*
- * List available devices
+ * Get capabilities of device
  */
-static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
+static bool get_device_capabilities(char *dev_name,
+				    struct v4l2_capability *video_cap)
+{
+	int fd;
+	uint32_t caps;
+	if ((fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK)) == -1) {
+		const char *errstr = strerror(errno);
+		blog(LOG_WARNING, "Unable to open %s: %s", dev_name, errstr);
+		return false;
+	}
+
+	if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, video_cap) == -1) {
+		blog(LOG_WARNING, "Failed to query capabilities for %s",
+		     dev_name);
+		v4l2_close(fd);
+		return false;
+	}
+	v4l2_close(fd);
+
+#ifndef V4L2_CAP_DEVICE_CAPS
+	caps = video_cap->capabilities;
+#else
+	/* ... since Linux 3.3 */
+	caps = (video_cap->capabilities & V4L2_CAP_DEVICE_CAPS)
+		       ? video_cap->device_caps
+		       : video_cap->capabilities;
+#endif
+
+	if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
+		blog(LOG_WARNING, "%s seems to not support video capture",
+		     dev_name);
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Build list of video devices available at path
+ */
+static void v4l2_device_add_devices_from(obs_property_t *prop,
+					 const char *basedir,
+					 bool allow_duplicates)
 {
 	DIR *dirp;
 	struct dirent *dp;
 	struct dstr device;
-	bool cur_device_found;
-	size_t cur_device_index;
-	const char *cur_device_name;
 
 #ifdef __FreeBSD__
-	dirp = opendir("/dev");
+	dirp = opendir(basedir);
 #else
-	dirp = opendir("/sys/class/video4linux");
+	if (0 == strcmp("/dev/", basedir)) {
+		dirp = opendir("/sys/class/video4linux");
+	} else {
+		dirp = opendir(basedir);
+	}
 #endif
 	if (!dirp)
 		return;
 
-	cur_device_found = false;
-	cur_device_name = obs_data_get_string(settings, "device_id");
-
-	obs_property_list_clear(prop);
-
-	dstr_init_copy(&device, "/dev/");
+	dstr_init(&device);
 
 	while ((dp = readdir(dirp)) != NULL) {
-		int fd;
-		uint32_t caps;
-		struct v4l2_capability video_cap;
-
 #ifdef __FreeBSD__
 		if (strstr(dp->d_name, "video") == NULL)
 			continue;
@@ -383,51 +418,87 @@ static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
 		if (dp->d_type == DT_DIR)
 			continue;
 
-		dstr_resize(&device, 5);
+		dstr_copy(&device, basedir);
 		dstr_cat(&device, dp->d_name);
 
-		if ((fd = v4l2_open(device.array, O_RDWR | O_NONBLOCK)) == -1) {
-			blog(LOG_INFO, "Unable to open %s", device.array);
+		struct v4l2_capability video_cap;
+		if (!get_device_capabilities(device.array, &video_cap)) {
 			continue;
 		}
 
-		if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) {
-			blog(LOG_INFO, "Failed to query capabilities for %s",
-			     device.array);
-			v4l2_close(fd);
-			continue;
+		bool device_already_added = false;
+
+		if (!allow_duplicates) {
+			size_t listidx = 0;
+			const char *item_name;
+			while (NULL != (item_name = obs_property_list_item_name(
+						prop, listidx++))) {
+				if (NULL !=
+				    strstr(item_name, video_cap.bus_info)) {
+					device_already_added = true;
+					break;
+				}
+			}
 		}
 
-#ifndef V4L2_CAP_DEVICE_CAPS
-		caps = video_cap.capabilities;
+		if (!device_already_added) {
+			char unique_device_name[PATH_MAX];
+			if (0 == strcmp("/dev/v4l/by-path/", basedir)) {
+				snprintf(unique_device_name, PATH_MAX,
+					 "Bus: %s (%s)", video_cap.bus_info,
+					 video_cap.card);
+			} else {
+				snprintf(unique_device_name, PATH_MAX,
+					 "%s (%s)", video_cap.card,
+					 video_cap.bus_info);
+			}
+			obs_property_list_add_string(prop, unique_device_name,
+						     device.array);
+			blog(LOG_INFO, "Found device '%s' at %s",
+			     video_cap.card, device.array);
+		}
+	}
+
+	closedir(dirp);
+	dstr_free(&device);
+}
+
+/*
+ * List available devices
+ */
+static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
+{
+	bool cur_device_found;
+	size_t cur_device_index;
+	const char *cur_device_name;
+
+	obs_property_list_clear(prop);
+
+#ifdef __FreeBSD__
+	v4l2_device_add_devices_from(prop, "/dev/", false);
 #else
-		/* ... since Linux 3.3 */
-		caps = (video_cap.capabilities & V4L2_CAP_DEVICE_CAPS)
-			       ? video_cap.device_caps
-			       : video_cap.capabilities;
+	v4l2_device_add_devices_from(prop, "/dev/v4l/by-id/", true);
+	obs_property_list_item_disable(
+		prop,
+		obs_property_list_add_string(
+			prop,
+			"Advanced. Select device by connection:", "by_path"),
+		true);
+	v4l2_device_add_devices_from(prop, "/dev/v4l/by-path/", true);
+	v4l2_device_add_devices_from(prop, "/dev/", false);
 #endif
 
-		if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
-			blog(LOG_INFO, "%s seems to not support video capture",
-			     device.array);
-			v4l2_close(fd);
-			continue;
-		}
+	cur_device_found = false;
+	cur_device_name = obs_data_get_string(settings, "device_id");
 
-		/* make sure device names are unique */
-		char unique_device_name[68];
-		sprintf(unique_device_name, "%s (%s)", video_cap.card,
-			video_cap.bus_info);
-		obs_property_list_add_string(prop, unique_device_name,
-					     device.array);
-		blog(LOG_INFO, "Found device '%s' at %s", video_cap.card,
-		     device.array);
-
-		/* check if this is the currently used device */
-		if (cur_device_name && !strcmp(cur_device_name, device.array))
+	size_t listidx = 0;
+	const char *item_name;
+	while (NULL !=
+	       (item_name = obs_property_list_item_string(prop, listidx++))) {
+		if (0 == strcmp(item_name, cur_device_name)) {
 			cur_device_found = true;
-
-		v4l2_close(fd);
+			break;
+		}
 	}
 
 	/* add currently selected device if not present, but disable it ... */
@@ -436,9 +507,6 @@ static void v4l2_device_list(obs_property_t *prop, obs_data_t *settings)
 			prop, cur_device_name, cur_device_name);
 		obs_property_list_item_disable(prop, cur_device_index, true);
 	}
-
-	closedir(dirp);
-	dstr_free(&device);
 }
 
 /*
@@ -795,12 +863,19 @@ static void device_added(void *vptr, calldata_t *calldata)
 	const char *dev;
 	calldata_get_string(calldata, "device", &dev);
 
-	if (strcmp(data->device_id, dev))
+	char *real_path = realpath(data->device_id, NULL);
+
+	if (real_path == NULL)
 		return;
+	if (strcmp(real_path, dev))
+		goto fail;
 
-	blog(LOG_INFO, "Device %s reconnected", dev);
+	blog(LOG_INFO, "Device %s reconnected, is opened as %s", dev,
+	     data->device_id);
 
 	v4l2_init(data);
+fail:
+	free(real_path);
 }
 /**
  * Device removed callback
@@ -816,10 +891,13 @@ static void device_removed(void *vptr, calldata_t *calldata)
 	const char *dev;
 	calldata_get_string(calldata, "device", &dev);
 
-	if (strcmp(data->device_id, dev))
+	if (data->real_path == NULL)
+		return;
+	if (strcmp(data->real_path, dev))
 		return;
 
-	blog(LOG_INFO, "Device %s disconnected", dev);
+	blog(LOG_INFO, "Device %s disconnected, was opened as %s", dev,
+	     data->device_id);
 
 	v4l2_terminate(data);
 }
@@ -965,12 +1043,23 @@ static void v4l2_init(struct v4l2_data *data)
 	int fps_num, fps_denom;
 
 	blog(LOG_INFO, "Start capture from %s", data->device_id);
+	if (data->real_path) {
+		bfree(data->real_path);
+		data->real_path = NULL;
+	}
 	data->dev = v4l2_open(data->device_id, O_RDWR | O_NONBLOCK);
 	if (data->dev == -1) {
 		blog(LOG_ERROR, "Unable to open device");
 		goto fail;
 	}
 
+	char *real_path = realpath(data->device_id, NULL);
+	if (NULL != real_path) {
+		data->real_path = real_path;
+	} else {
+		data->real_path = bstrdup(data->device_id);
+	}
+
 	/* set input */
 	if (v4l2_set_input(data->dev, &data->input) < 0) {
 		blog(LOG_ERROR, "Unable to set input %d", data->input);
@@ -1135,6 +1224,11 @@ static void v4l2_update(void *vptr, obs_data_t *settings)
 	if (data->device_id)
 		bfree(data->device_id);
 
+	if (data->real_path) {
+		bfree(data->real_path);
+		data->real_path = NULL;
+	}
+
 	data->device_id = bstrdup(obs_data_get_string(settings, "device_id"));
 	data->input = obs_data_get_int(settings, "input");
 	data->pixfmt = obs_data_get_int(settings, "pixelformat");