diff options
author | pommicket <pommicket@gmail.com> | 2025-02-21 14:25:50 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-02-21 14:25:50 -0500 |
commit | 66e0461583b1711b4ebc3a94f1c5f2aed7c0bd81 (patch) | |
tree | 5f6ca7aa152960a4d0c3d6e7ba7980cb2c754eca | |
parent | 49028d60bb50473dd4a3aa583a8ca5db7c938d1d (diff) |
connecting/disconnecting devices
-rw-r--r-- | camera.c | 14 | ||||
-rw-r--r-- | camera.h | 1 | ||||
-rw-r--r-- | ds.h | 2 | ||||
-rw-r--r-- | main.c | 188 |
4 files changed, 139 insertions, 66 deletions
@@ -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; +} @@ -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); @@ -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) @@ -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; |