summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-02-21 14:25:50 -0500
committerpommicket <pommicket@gmail.com>2025-02-21 14:25:50 -0500
commit66e0461583b1711b4ebc3a94f1c5f2aed7c0bd81 (patch)
tree5f6ca7aa152960a4d0c3d6e7ba7980cb2c754eca
parent49028d60bb50473dd4a3aa583a8ca5db7c938d1d (diff)
connecting/disconnecting devices
-rw-r--r--camera.c14
-rw-r--r--camera.h1
-rw-r--r--ds.h2
-rw-r--r--main.c188
4 files changed, 139 insertions, 66 deletions
diff --git a/camera.c b/camera.c
index b5e3a93..2c8d5bb 100644
--- a/camera.c
+++ b/camera.c
@@ -13,7 +13,8 @@
#define CAMERA_MAX_BUFFERS 4
struct Camera {
- char *dev_path;
+ // e.g. "/dev/video0"
+ char *devnode;
char *name;
uint32_t input_idx;
struct v4l2_format curr_format;
@@ -290,7 +291,7 @@ static bool camera_stop_io(Camera *camera) {
// Re-opening doesn't seem to be necessary for read-based access for me,
// but idk if that's true on all cameras.
v4l2_close(camera->fd);
- camera->fd = v4l2_open(camera->dev_path, O_RDWR);
+ camera->fd = v4l2_open(camera->devnode, O_RDWR);
if (camera->fd < 0) {
perror("v4l2_open");
return false;
@@ -702,6 +703,7 @@ void camera_close(Camera *camera) {
void camera_free(Camera *camera) {
camera_close(camera);
+ free(camera->devnode);
free(camera);
}
@@ -768,7 +770,7 @@ bool camera_open(Camera *camera) {
assert(!camera->read_frame);
assert(!camera->mmap_frames[0]);
assert(!camera->userp_frames[0]);
- camera->fd = v4l2_open(camera->dev_path, O_RDWR);
+ camera->fd = v4l2_open(camera->devnode, O_RDWR);
if (camera->fd < 0) {
perror("v4l2_open");
return false;
@@ -850,7 +852,7 @@ static void cameras_from_device_with_fd(const char *dev_path, const char *serial
arr_set_len(camera->formats, o);
}
camera->input_idx = input_idx;
- camera->dev_path = strdup(dev_path);
+ camera->devnode = strdup(dev_path);
// select best format
PictureFormat best_format = {0};
uint32_t desired_format = V4L2_PIX_FMT_RGB24;
@@ -904,3 +906,7 @@ void camera_hash_str(Camera *camera, char str[HASH_SIZE * 2 + 1]) {
sprintf(&str[2*i], "%02x", camera->hash.hash[i]);
}
}
+
+const char *camera_devnode(Camera *camera) {
+ return camera->devnode;
+}
diff --git a/camera.h b/camera.h
index d09c9f9..a47509e 100644
--- a/camera.h
+++ b/camera.h
@@ -113,6 +113,7 @@ bool camera_save_png(Camera *camera, const char *path);
bool camera_next_frame(Camera *camera);
void camera_update_gl_textures(Camera *camera, const GLuint textures[3]);
const char *camera_name(Camera *camera);
+const char *camera_devnode(Camera *camera);
uint32_t camera_pixel_format(Camera *camera);
CameraAccessMethod camera_access_method(Camera *camera);
void camera_close(Camera *camera);
diff --git a/ds.h b/ds.h
index 2d23742..3c4d006 100644
--- a/ds.h
+++ b/ds.h
@@ -315,7 +315,7 @@ static void *arr_copy_(const void *arr, size_t member_size) {
#define arr_remove_item(a, item) do { for (u32 _i = 0; _i < arr_len((a)); ++_i) if ((a)[_i] == item) { arr_remove((a), _i); break; } } while (0);
#define arr_index_of(a, item) (sizeof((a)[0] == (item)), arr_index_of_((a), sizeof *(a), &(item)))
#define arr_remove_multiple(a, i, n) (void)((a) = arr_remove_multiple_((a), sizeof *(a), (i), (n)))
-#define arr_insert(a, i, x) do { u32 _index = (i); (a) = arr_cast_typeof(a) arr_grow1_((a), sizeof *(a)); \
+#define arr_insert(a, i, x) do { uint32_t _index = (i); (a) = arr_cast_typeof(a) arr_grow1_((a), sizeof *(a)); \
if (a) { memmove((a) + _index + 1, (a) + _index, (arr_len(a) - _index) * sizeof *(a));\
(a)[_index] = x; \
++arr_hdr_(a)->len; } } while (0)
diff --git a/main.c b/main.c
index 4987e45..12f2619 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,5 @@
/*
TODO
--handle dis/connecting devices
-timer
-video
-adjustable camera framerate
@@ -364,23 +363,55 @@ static void menu_select(State *state) {
}
static void select_camera(State *state) {
- arr_foreach_ptr(state->camera_precedence, const Hash, h) {
- arr_foreach_ptr(state->cameras, Camera *const, pcamera) {
- Camera *c = *pcamera;
- if (hash_eq(camera_hash(c), *h)) {
- if (state->camera == c)
- return;
- state->camera = c;
+ bool *cameras_working = calloc(1, arr_len(state->cameras));
+ memset(cameras_working, 1, arr_len(state->cameras));
+ while (true) {
+ int camera_idx = -1;
+ // find highest-precedence possibly-working camera
+ arr_foreach_ptr(state->camera_precedence, const Hash, h) {
+ arr_foreach_ptr(state->cameras, Camera *const, pcamera) {
+ Camera *c = *pcamera;
+ if (hash_eq(camera_hash(c), *h)) {
+ if (state->camera == c) {
+ // already have best camera selected
+ free(cameras_working);
+ return;
+ }
+ camera_idx = (int)(pcamera - state->cameras);
+ if (cameras_working[camera_idx]) {
+ state->camera = c;
+ break;
+ }
+ }
+ }
+ if (state->camera) break;
+ }
+ if (!state->camera) {
+ // nothing in precedence list works- find first possibly-working camera
+ for (camera_idx = 0; camera_idx < (int)arr_len(state->cameras); camera_idx++)
+ if (cameras_working[camera_idx])
+ break;
+ if (camera_idx >= (int)arr_len(state->cameras)) {
+ // no cameras work
break;
}
+ state->camera = state->cameras[camera_idx];
+ }
+ if (camera_open(state->camera)) {
+ // move to highest precedence
+ arr_foreach_ptr(state->camera_precedence, Hash, h) {
+ if (hash_eq(*h, camera_hash(state->camera))) {
+ arr_remove(state->camera_precedence, h - state->camera_precedence);
+ }
+ }
+ arr_insert(state->camera_precedence, 0, camera_hash(state->camera));
+ break;
+ } else {
+ cameras_working[camera_idx] = false;
+ state->camera = NULL;
}
- if (state->camera) break;
- }
- if (!state->camera) {
- state->camera = state->cameras[0];
- arr_add(state->camera_precedence, camera_hash(state->camera));
}
- camera_open(state->camera);
+ free(cameras_working);
}
int menu_get_option_at_pos(State *state, int x, int y) {
@@ -423,6 +454,64 @@ bool mkdir_with_parents(const char *path) {
}
}
+static void debug_print_device_attrs(struct udev_device *dev) {
+ printf("----%s----\n",udev_device_get_devnode(dev));
+ struct udev_list_entry *attr = NULL, *attrs = udev_device_get_sysattr_list_entry(dev);
+ udev_list_entry_foreach(attr, attrs) {
+ const char *val = udev_device_get_sysattr_value(dev, udev_list_entry_get_name(attr));
+ printf("%s = %s\n", udev_list_entry_get_name(attr),
+ val ? val : "NULL");
+ }
+}
+
+static void get_cameras_from_udev_device(State *state, struct udev_device *dev) {
+ const char *devnode = udev_device_get_devnode(dev);
+ if (!devnode) return;
+ const char *subsystem = udev_device_get_sysattr_value(dev, "subsystem");
+ if (!subsystem || strcmp(subsystem, "video4linux") != 0) {
+ // not a v4l device
+ return;
+ }
+ int status = access(devnode, R_OK);
+ if (status != 0 && errno == EACCES) {
+ // can't read from this device
+ return;
+ }
+ if (status != 0) {
+ perror("access");
+ return;
+ }
+ /*
+ build up a serial number for the camera by taking its "serial" value,
+ together with the serial of its ancestors
+ (my personal camera doesn't have a serial on the video4linux device,
+ but does have one on its grandparent- this makes sense since a single
+ physical device can have multiple cameras, as well as microphones, etc.)
+ */
+ // NOTE: we don't need to unref the return value of udev_device_get_parent
+ struct udev_device *parent = udev_device_get_parent(dev);
+ const char *serial_str = udev_device_get_sysattr_value(dev, "serial");
+ StrBuilder serial = str_builder_new();
+ if (serial_str && *serial_str)
+ str_builder_appendf(&serial, "%s;", serial_str);
+ for (int k = 0; k < 100 /* prevent infinite loop due to some fucked up device state */; k++) {
+ const char *parent_serial = udev_device_get_sysattr_value(parent, "serial");
+ if (parent_serial && strlen(parent_serial) >= 12 &&
+ parent_serial[4] == ':' && parent_serial[7] == ':' && parent_serial[10] == '.') {
+ // this is actually a USB interface! e.g. 0000:06:00.3
+ // so it is not tied to the camera
+ break;
+ }
+ if (parent_serial && *parent_serial)
+ str_builder_appendf(&serial, "%s;", parent_serial);
+ struct udev_device *grandparent = udev_device_get_parent(parent);
+ if (!grandparent) break;
+ parent = grandparent;
+ }
+ cameras_from_device(devnode, serial.str, &state->cameras);
+ str_builder_free(&serial);
+}
+
int main(void) {
static State state_data;
State *state = &state_data;
@@ -700,7 +789,8 @@ void main() {\n\
}
struct udev *udev = udev_new();
struct udev_monitor *udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
- udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "video4linux", NULL);
+ // subsystems don't seem to be set for "remove" events, so we shouldn't do this:
+ // udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "video4linux", NULL);
if (!udev_monitor) {
perror("udev_monitor_new_from_netlink");
}
@@ -723,46 +813,7 @@ void main() {\n\
udev_list_entry_foreach(device, devices) {
struct udev_device *dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(device));
if (!dev) continue;
- const char *devnode = udev_device_get_devnode(dev);
- if (!devnode) continue;
- const char *subsystem = udev_device_get_sysattr_value(dev, "subsystem");
- if (!subsystem || strcmp(subsystem, "video4linux") != 0) goto cont1;
- int status = access(devnode, R_OK);
- if (status != 0 && errno == EACCES) {
- // can't read from this device
- goto cont1;
- }
- if (status != 0) goto cont1;
- /*
- build up a serial number for the camera by taking its "serial" value,
- together with the serial of its ancestors
- (my personal camera doesn't have a serial on the video4linux device,
- but does have one on its grandparent- this makes sense since a single
- physical device can have multiple cameras, as well as microphones, etc.)
- */
- // NOTE: we don't need to unref the return value of udev_device_get_parent
- struct udev_device *parent = udev_device_get_parent(dev);
- const char *serial_str = udev_device_get_sysattr_value(dev, "serial");
- StrBuilder serial = str_builder_new();
- if (serial_str && *serial_str)
- str_builder_appendf(&serial, "%s;", serial_str);
- for (int k = 0; k < 100 /* prevent infinite loop due to some fucked up device state */; k++) {
- const char *parent_serial = udev_device_get_sysattr_value(parent, "serial");
- if (parent_serial && strlen(parent_serial) >= 12 &&
- parent_serial[4] == ':' && parent_serial[7] == ':' && parent_serial[10] == '.') {
- // this is actually a USB interface! e.g. 0000:06:00.3
- // so it is not tied to the camera
- break;
- }
- if (parent_serial && *parent_serial)
- str_builder_appendf(&serial, "%s;", parent_serial);
- struct udev_device *grandparent = udev_device_get_parent(parent);
- if (!grandparent) break;
- parent = grandparent;
- }
- cameras_from_device(devnode, serial.str, &state->cameras);
- str_builder_free(&serial);
- cont1:
+ get_cameras_from_udev_device(state, dev);
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
@@ -787,16 +838,31 @@ void main() {\n\
while (!state->quit) {
state->menu_needs_rerendering = false;
struct udev_device *dev = NULL;
+ bool any_new_cameras = false;
while (udev_monitor && (dev = udev_monitor_receive_device(udev_monitor))) {
- printf("%s %s\n",udev_device_get_sysname(dev),udev_device_get_action(dev));
- struct udev_list_entry *attr = NULL, *attrs = udev_device_get_sysattr_list_entry(dev);
-
- udev_list_entry_foreach(attr, attrs) {
- printf("%s = %s\n", udev_list_entry_get_name(attr),
- udev_device_get_sysattr_value(dev, udev_list_entry_get_name(attr)));
+ const char *devnode = udev_device_get_devnode(dev);
+ const char *action = udev_device_get_action(dev);
+ const char *subsystem = udev_device_get_sysattr_value(dev, "subsystem");
+ if (strcmp(action, "remove") == 0) {
+ if (state->camera && strcmp(devnode, camera_devnode(state->camera)) == 0) {
+ // our special camera got disconnected ):
+ state->camera = NULL;
+ }
+ for (size_t i = 0; i < arr_len(state->cameras); ) {
+ if (strcmp(camera_devnode(state->cameras[i]), devnode) == 0) {
+ arr_remove(state->cameras, i);
+ } else {
+ i++;
+ }
+ }
+ } else if (strcmp(action, "add") == 0 && subsystem && strcmp(subsystem, "video4linux") == 0) {
+ get_cameras_from_udev_device(state, dev);
+ any_new_cameras = true;
}
udev_device_unref(dev);
}
+ if (!state->camera || any_new_cameras)
+ select_camera(state);
SDL_Event event = {0};
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) goto quit;