From 0a37c63d00240eea7a4f03ce31ae413734809d3e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 2 Sep 2022 20:21:11 +0200 Subject: [PATCH] Enumerate v4l2 devices --- cmd/list-devices/main.c | 43 ++++++++++++++++ device/camera/camera.c | 4 +- device/camera/camera.h | 2 + device/camera/camera_input.c | 1 + device/camera/camera_isp.c | 10 +++- device/camera/camera_output.c | 41 +++++++++++++-- device/device_list.c | 68 ++++++++++++++++++++++++ device/device_list.h | 30 +++++++++++ device/v4l2/device_list.c | 97 +++++++++++++++++++++++++++++++++++ 9 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 cmd/list-devices/main.c create mode 100644 device/device_list.c create mode 100644 device/device_list.h create mode 100644 device/v4l2/device_list.c diff --git a/cmd/list-devices/main.c b/cmd/list-devices/main.c new file mode 100644 index 0000000..6299032 --- /dev/null +++ b/cmd/list-devices/main.c @@ -0,0 +1,43 @@ +#include "util/opts/log.h" +#include "util/opts/fourcc.h" +#include "device/device.h" +#include "device/device_list.h" + +#include +#include + +log_options_t log_options = { + .debug = true, + .verbose = true +}; + +void print_formats(const char *type, device_info_formats_t *formats) +{ + if (!formats->n) { + return; + } + + printf("- %s: ", type); + for (int j = 0; j < formats->n; j++) { + printf("%s ", fourcc_to_string(formats->formats[j]).buf); + } + printf("\n"); +} + +int main(int argc, const char *argv[]) +{ + device_list_t *list = device_list_v4l2(); + + printf("Found %d devices\n", list->ndevices); + + for (int i = 0; i < list->ndevices; i++) { + device_info_t * info = &list->devices[i]; + + printf("Device %d: %s / %s / camera: %d, m2m: %d\n", i, info->name, info->path, info->camera, info->m2m); + print_formats("Output / IN", &info->output_formats); + print_formats("Capture / OUT", &info->capture_formats); + } + + device_list_free(list); + return 0; +} diff --git a/device/camera/camera.c b/device/camera/camera.c index 5a9710b..323244a 100644 --- a/device/camera/camera.c +++ b/device/camera/camera.c @@ -1,6 +1,7 @@ #include "camera.h" #include "device/device.h" +#include "device/device_list.h" #include "device/buffer_list.h" #include "device/links.h" #include "util/opts/log.h" @@ -11,6 +12,7 @@ camera_t *camera_open(camera_options_t *options) camera_t *camera = calloc(1, sizeof(camera_t)); camera->name = "CAMERA"; camera->options = *options; + camera->device_list = device_list_v4l2(); if (camera_configure_input(camera) < 0) { goto error; @@ -50,7 +52,7 @@ void camera_close(camera_t **camerap) } } - memset(camera->links, 0, sizeof(camera->links)); + device_list_free(camera->device_list); free(camera); } diff --git a/device/camera/camera.h b/device/camera/camera.h index 5d16ade..c185564 100644 --- a/device/camera/camera.h +++ b/device/camera/camera.h @@ -66,6 +66,8 @@ typedef struct camera_s { }; }; + struct device_list_s *device_list; + link_t links[MAX_DEVICES]; int nlinks; } camera_t; diff --git a/device/camera/camera_input.c b/device/camera/camera_input.c index 9f6d82f..57b3bc1 100644 --- a/device/camera/camera_input.c +++ b/device/camera/camera_input.c @@ -3,6 +3,7 @@ #include "device/buffer.h" #include "device/buffer_list.h" #include "device/device.h" +#include "device/device_list.h" #include "device/links.h" #include "util/opts/log.h" #include "util/opts/fourcc.h" diff --git a/device/camera/camera_isp.c b/device/camera/camera_isp.c index 458826f..9d17775 100644 --- a/device/camera/camera_isp.c +++ b/device/camera/camera_isp.c @@ -3,6 +3,7 @@ #include "device/buffer.h" #include "device/buffer_list.h" #include "device/device.h" +#include "device/device_list.h" #include "device/links.h" #include "util/opts/log.h" #include "util/opts/fourcc.h" @@ -43,7 +44,14 @@ static const char *isp_names[2] = { int camera_configure_legacy_isp(camera_t *camera, buffer_list_t *src_capture, float div, int res) { - camera->legacy_isp[res] = device_v4l2_open(isp_names[res], "/dev/video12"); + device_info_t *device = device_list_find_m2m_format(camera->device_list, src_capture->fmt.format, V4L2_PIX_FMT_YUYV); + + if (!device) { + LOG_INFO(camera, "Cannot find ISP to scale from '%s' to 'YUYV'", fourcc_to_string(src_capture->fmt.format).buf); + return -1; + } + + camera->legacy_isp[res] = device_v4l2_open(isp_names[res], device->path); buffer_list_t *isp_output = device_open_buffer_list_output( camera->legacy_isp[res], src_capture); diff --git a/device/camera/camera_output.c b/device/camera/camera_output.c index 477688c..68dbd3d 100644 --- a/device/camera/camera_output.c +++ b/device/camera/camera_output.c @@ -3,6 +3,7 @@ #include "device/buffer.h" #include "device/buffer_list.h" #include "device/device.h" +#include "device/device_list.h" #include "device/links.h" #include "util/opts/log.h" #include "util/opts/fourcc.h" @@ -55,7 +56,14 @@ static int camera_configure_h264_output(camera_t *camera, buffer_list_t *src_cap return 0; } - camera->codec_h264[res] = device_v4l2_open(h264_names[res], "/dev/video11"); + device_info_t *device = device_list_find_m2m_format(camera->device_list, src_capture->fmt.format, V4L2_PIX_FMT_H264); + + if (!device) { + LOG_INFO(camera, "Cannot find H264 encoder to convert from '%s'", fourcc_to_string(src_capture->fmt.format).buf); + return -1; + } + + camera->codec_h264[res] = device_v4l2_open(h264_names[res], device->path); buffer_list_t *output = device_open_buffer_list_output(camera->codec_h264[res], src_capture); buffer_list_t *capture = device_open_buffer_list_capture(camera->codec_h264[res], output, 1.0, V4L2_PIX_FMT_H264, true); @@ -76,7 +84,18 @@ static int camera_configure_jpeg_output(camera_t *camera, buffer_list_t *src_cap return 0; } - camera->codec_jpeg[res] = device_v4l2_open(jpeg_names[res], "/dev/video31"); + device_info_t *device = device_list_find_m2m_format(camera->device_list, src_capture->fmt.format, V4L2_PIX_FMT_JPEG); + + if (!device) { + device = device_list_find_m2m_format(camera->device_list, src_capture->fmt.format, V4L2_PIX_FMT_MJPEG); + } + + if (!device) { + LOG_INFO(camera, "Cannot find JPEG encoder to convert from '%s'", fourcc_to_string(src_capture->fmt.format).buf); + return -1; + } + + camera->codec_jpeg[res] = device_v4l2_open(jpeg_names[res], device->path); buffer_list_t *output = device_open_buffer_list_output(camera->codec_jpeg[res], src_capture); buffer_list_t *capture = device_open_buffer_list_capture(camera->codec_jpeg[res], output, 1.0, V4L2_PIX_FMT_JPEG, true); @@ -123,9 +142,25 @@ int camera_configure_output_rescaler(camera_t *camera, buffer_list_t *src_captur int camera_configure_decoder(camera_t *camera, buffer_list_t *src_capture) { + unsigned decode_formats[] = { + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + 0 + }; + unsigned chosen_format = 0; + device_info_t *device = device_list_find_m2m_formats(camera->device_list, src_capture->fmt.format, decode_formats, &chosen_format); + + if (!device) { + LOG_INFO(camera, "Cannot find '%s' decoder", fourcc_to_string(src_capture->fmt.format).buf); + return -1; + } + device_video_force_key(camera->camera); - camera->decoder = device_v4l2_open("DECODER", "/dev/video10"); + camera->decoder = device_v4l2_open("DECODER", device->path); buffer_list_t *decoder_output = device_open_buffer_list_output( camera->decoder, src_capture); diff --git a/device/device_list.c b/device/device_list.c new file mode 100644 index 0000000..e3b11e2 --- /dev/null +++ b/device/device_list.c @@ -0,0 +1,68 @@ +#include "device/device_list.h" + +#include +#include + +bool device_info_has_format(device_info_t *info, bool capture, unsigned format) +{ + if (!info) { + return false; + } + + device_info_formats_t *formats = capture ? &info->capture_formats : &info->output_formats; + + for (int i = 0; i < formats->n; i++) { + if (formats->formats[i] == format) { + return true; + } + } + + return false; +} + +device_info_t *device_list_find_m2m_format(device_list_t *list, unsigned output, unsigned capture) +{ + if (!list) + return NULL; + + for (int i = 0; i < list->ndevices; i++) { + device_info_t *info = &list->devices[i]; + + if (info->m2m && device_info_has_format(info, false, output) && device_info_has_format(info, true, capture)) { + return info; + } + } + + return NULL; +} + +device_info_t *device_list_find_m2m_formats(device_list_t *list, unsigned output, unsigned capture_formats[], unsigned *found_format) +{ + for (int i = 0; capture_formats[i]; i++) { + device_info_t *info = device_list_find_m2m_format(list, output, capture_formats[i]); + if (info) { + if (found_format) { + *found_format = capture_formats[i]; + } + return info; + } + } + + return NULL; +} + +void device_list_free(device_list_t *list) +{ + if (!list) + return; + + for (int i = 0; i < list->ndevices; i++) { + device_info_t *info = &list->devices[i]; + free(info->name); + free(info->path); + free(info->output_formats.formats); + free(info->capture_formats.formats); + } + + free(list); +} diff --git a/device/device_list.h b/device/device_list.h new file mode 100644 index 0000000..c577bd0 --- /dev/null +++ b/device/device_list.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +typedef struct device_info_formats_s { + unsigned *formats; + unsigned n; +} device_info_formats_t; + +typedef struct device_info_s { + char *name; + char *path; + + bool camera; + bool m2m; + + device_info_formats_t output_formats; + device_info_formats_t capture_formats; +} device_info_t; + +typedef struct device_list_s { + device_info_t *devices; + int ndevices; +} device_list_t; + +device_list_t *device_list_v4l2(); +bool device_info_has_format(device_info_t *info, bool capture, unsigned format); +device_info_t *device_list_find_m2m_format(device_list_t *list, unsigned output, unsigned capture); +device_info_t *device_list_find_m2m_formats(device_list_t *list, unsigned output, unsigned capture_formats[], unsigned *found_format); +void device_list_free(device_list_t *list); diff --git a/device/v4l2/device_list.c b/device/v4l2/device_list.c new file mode 100644 index 0000000..865f3cb --- /dev/null +++ b/device/v4l2/device_list.c @@ -0,0 +1,97 @@ +#include "v4l2.h" +#include "device/device_list.h" +#include "util/opts/log.h" + +#include +#include +#include +#include + +static void device_list_read_formats(int fd, device_info_formats_t *formats, enum v4l2_buf_type buf_type) +{ + for (int i = 0; ; ++i) { + struct v4l2_fmtdesc format_desc; + memset(&format_desc, 0, sizeof(format_desc)); + format_desc.type = (enum v4l2_buf_type) buf_type; + format_desc.index = i; + + if (-1 == ioctl(fd, VIDIOC_ENUM_FMT, &format_desc)) { + break; + } + + formats->n++; + formats->formats = realloc(formats->formats, sizeof(formats->formats[0]) * formats->n); + formats->formats[formats->n - 1] = format_desc.pixelformat; + } +} + +static bool device_list_read_dev(device_info_t *info, const char *name) +{ + asprintf(&info->path, "/dev/%s", name); + + int fd = open(info->path, O_RDWR|O_NONBLOCK); + if (fd < 0) { + LOG_ERROR(NULL, "Can't open device: %s", info->path); + } + + struct v4l2_capability v4l2_cap; + ERR_IOCTL(info, fd, VIDIOC_QUERYCAP, &v4l2_cap, "Can't query device capabilities"); + info->name = strdup((const char *)v4l2_cap.card); + + if (!(v4l2_cap.capabilities & V4L2_CAP_STREAMING)) { + LOG_VERBOSE(info, "Device (%s) does not support streaming (skipping)", info->path); + goto error; + } else if ((v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) && !(v4l2_cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) { + info->camera = true; + } else if (!(v4l2_cap.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_M2M_MPLANE))) { + LOG_VERBOSE(info, "Device (%s) does not support capture (skipping)", info->path); + goto error; + } else if (!(v4l2_cap.capabilities & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_VIDEO_M2M_MPLANE))) { + LOG_VERBOSE(info, "Device (%s) does not support output (skipping)", info->path); + goto error; + } else if ((v4l2_cap.capabilities & V4L2_CAP_VIDEO_M2M) || (v4l2_cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE)) { + info->m2m = true; + } + + device_list_read_formats(fd, &info->capture_formats, V4L2_BUF_TYPE_VIDEO_CAPTURE); + device_list_read_formats(fd, &info->capture_formats, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + device_list_read_formats(fd, &info->output_formats, V4L2_BUF_TYPE_VIDEO_OUTPUT); + device_list_read_formats(fd, &info->output_formats, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + close(fd); + + return true; + +error: + free(info->name); + free(info->path); + close(fd); + return false; +} + +device_list_t *device_list_v4l2() +{ + DIR *dev = opendir("/dev"); + if (!dev) { + return NULL; + } + + device_list_t *list = calloc(1, sizeof(device_list_t)); + struct dirent *ent; + + while ((ent = readdir(dev)) != NULL) { + if (strstr(ent->d_name, "video") != ent->d_name) { + continue; + } + + device_info_t info = {NULL}; + if (device_list_read_dev(&info, ent->d_name)) { + list->ndevices++; + list->devices = realloc(list->devices, sizeof(info) * list->ndevices); + list->devices[list->ndevices-1] = info; + } + } + + closedir(dev); + + return list; +}