/* * Copyright The Zephyr Project Contributors * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "video_ctrls.h" #define VIDEO_FRMIVAL_FPS(frmival) DIV_ROUND_CLOSEST((frmival)->denominator, (frmival)->numerator) #define VIDEO_FRMIVAL_MSEC(frmival) (MSEC_PER_SEC * (frmival)->numerator / (frmival)->denominator) /* Helper to allow completion of sub-command with content that depends on previous selection */ #define VIDEO_SHELL_COMPLETE_DEFINE(n, name) \ static void complete_##name##n(size_t idx, struct shell_static_entry *entry) \ { \ complete_##name(idx, entry, n); \ } \ SHELL_DYNAMIC_CMD_CREATE(dsub_##name##n, complete_##name##n) /* Current video device, used for tab completion */ static const struct device *video_shell_dev; /* Current video control, used for tab completion */ static struct video_ctrl_query video_shell_cq; /* Current buffer direction, used for tab completion */ static enum video_buf_type video_shell_buf_type = VIDEO_BUF_TYPE_OUTPUT; /* A global variable holding the buffer set to entry->syntax for control identifiers */ static char video_shell_ctrl_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE]; /* A global variable holding the buffer set to entry->syntax for control menu values */ static char video_shell_menu_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE]; static bool device_is_video_and_ready(const struct device *dev) { return device_is_ready(dev) && DEVICE_API_IS(video, dev); } static int video_shell_check_device(const struct shell *sh, const struct device *dev) { if (dev == NULL) { shell_error(sh, "Could not find a video device with that name"); return -ENODEV; } if (!DEVICE_API_IS(video, dev)) { shell_error(sh, "%s is not a video device", dev->name); return -EINVAL; } if (!device_is_ready(dev)) { shell_error(sh, "Device %s not ready", dev->name); return -ENODEV; } return 0; } static const struct device *video_shell_get_dev_by_num(int num) { const struct device *dev_list; size_t n = z_device_get_all_static(&dev_list); for (size_t i = 0; i < n; i++) { if (!DEVICE_API_IS(video, (&dev_list[i]))) { continue; } if (num == 0) { return &dev_list[i]; } num--; } return NULL; } static int video_shell_parse_in_out(const struct shell *sh, char const *arg_in_out, enum video_buf_type *type) { if (strcmp(arg_in_out, "in") == 0) { *type = VIDEO_BUF_TYPE_INPUT; } else if (strcmp(arg_in_out, "out") == 0) { *type = VIDEO_BUF_TYPE_OUTPUT; } else { shell_error(sh, "Endpoint direction must be 'in' or 'out', not '%s'", arg_in_out); return -EINVAL; } return 0; } static int video_shell_parse_on_off(const struct shell *sh, char const *arg_on_off, int32_t *value) { if (strcmp(arg_on_off, "on") == 0 || strcmp(arg_on_off, "1") == 0) { *value = true; } else if (strcmp(arg_on_off, "off") == 0 || strcmp(arg_on_off, "0") == 0) { *value = false; } else { shell_error(sh, "Endpoint direction must be 'on' or 'off', not '%s'", arg_on_off); return -EINVAL; } return 0; } static int cmd_video_start(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; char *arg_device = argv[1]; enum video_buf_type type; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } /* Only starting of output is supported for now */ type = VIDEO_BUF_TYPE_OUTPUT; ret = video_stream_start(dev, type); if (ret < 0) { shell_error(sh, "Failed to start %s", dev->name); return ret; } shell_print(sh, "Started %s video stream", dev->name); return 0; } static int cmd_video_stop(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; char *arg_device = argv[1]; enum video_buf_type type; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } /* Only starting of output is supported for now */ type = VIDEO_BUF_TYPE_OUTPUT; ret = video_stream_stop(dev, type); if (ret < 0) { shell_error(sh, "Failed to stop %s", dev->name); return ret; } shell_print(sh, "Stopped %s video stream", dev->name); return 0; } static void video_shell_print_buffer(const struct shell *sh, struct video_buffer *vbuf, struct video_format *fmt, int i, uint32_t num_buffer, uint32_t frmrate_fps, uint32_t frmival_msec) { uint32_t line_offset = vbuf->line_offset; uint32_t byte_offset = line_offset * fmt->pitch; uint32_t bytes_in_buf = vbuf->bytesused; uint32_t lines_in_buf = vbuf->bytesused / fmt->pitch; shell_print(sh, "Buffer %u/%u at %u ms, Bytes %u-%u/%u, Lines %u-%u/%u, Rate %u FPS %u ms", /* Buffer */ i + 1, num_buffer, vbuf->timestamp, /* Bytes */ byte_offset, byte_offset + bytes_in_buf, fmt->height * fmt->pitch, /* Lines */ line_offset, line_offset + lines_in_buf, fmt->height, /* Rate */ frmrate_fps, frmival_msec); } static int cmd_video_capture(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; struct video_format fmt = {.type = VIDEO_BUF_TYPE_OUTPUT}; struct video_buffer *buffers[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX] = {NULL}; struct video_buffer vbuf0 = {.type = VIDEO_BUF_TYPE_OUTPUT}; struct video_buffer *vbuf = &vbuf0; char *arg_device = argv[1]; char *arg_nbufs = argv[2]; uint32_t first_uptime; uint32_t prev_uptime; uint32_t this_uptime; uint32_t frmival_msec; uint32_t frmrate_fps; size_t buf_size; unsigned long num_buffers; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } ret = video_get_format(dev, &fmt); if (ret < 0) { shell_error(sh, "Failed to get the current format interval"); return ret; } num_buffers = strtoull(arg_nbufs, &arg_nbufs, 10); if (*arg_nbufs != '\0') { shell_error(sh, "Invalid integer '%s' for this type", arg_nbufs); return -EINVAL; } buf_size = MIN(fmt.pitch * fmt.height, CONFIG_VIDEO_BUFFER_POOL_SZ_MAX); shell_print(sh, "Preparing %u buffers of %u bytes each", CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, buf_size); for (int i = 0; i < ARRAY_SIZE(buffers); i++) { buffers[i] = video_buffer_alloc(buf_size, K_NO_WAIT); if (buffers[i] == NULL) { shell_error(sh, "Failed to allocate buffer %u", i); goto end; } /* Only queueing of output buffers is supported for now */ buffers[i]->type = VIDEO_BUF_TYPE_OUTPUT; ret = video_enqueue(dev, buffers[i]); if (ret < 0) { shell_error(sh, "Failed to enqueue buffer %u: %s", i, strerror(-ret)); goto end; } } shell_print(sh, "Starting the capture of %lu buffers from %s", num_buffers, dev->name); ret = video_stream_start(dev, VIDEO_BUF_TYPE_OUTPUT); if (ret < 0) { shell_error(sh, "Failed to start %s", dev->name); goto end; } first_uptime = prev_uptime = this_uptime = k_uptime_get_32(); for (unsigned long i = 0; i < num_buffers;) { ret = video_dequeue(dev, &vbuf, K_FOREVER); if (ret < 0) { shell_error(sh, "Failed to dequeue this buffer: %s", strerror(-ret)); goto end; } this_uptime = k_uptime_get_32(); frmival_msec = this_uptime - prev_uptime; frmrate_fps = (frmival_msec == 0) ? (UINT32_MAX) : (MSEC_PER_SEC / frmival_msec); prev_uptime = this_uptime; video_shell_print_buffer(sh, vbuf, &fmt, i, num_buffers, frmrate_fps, frmival_msec); /* Only increment the frame counter on the beginning of a new frame */ i += (vbuf->line_offset == 0); ret = video_enqueue(dev, vbuf); if (ret < 0) { shell_error(sh, "Failed to enqueue this buffer: %s", strerror(-ret)); goto end; } } frmival_msec = this_uptime - first_uptime; frmrate_fps = (frmival_msec == 0) ? (UINT32_MAX) : (num_buffers * MSEC_PER_SEC / frmival_msec); shell_print(sh, "Capture of %lu buffers in %u ms in total, %u FPS on average, stopping %s", num_buffers, frmival_msec, frmrate_fps, dev->name); end: video_stream_stop(dev, VIDEO_BUF_TYPE_OUTPUT); if (vbuf != NULL) { video_buffer_release(vbuf); } while (video_dequeue(dev, &vbuf, K_NO_WAIT) == 0) { video_buffer_release(vbuf); } return ret; } static void complete_video_dev(size_t idx, struct shell_static_entry *entry) { const struct device *dev; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; entry->syntax = NULL; dev = shell_device_filter(idx, device_is_video_and_ready); if (dev == NULL) { return; } entry->syntax = dev->name; } SHELL_DYNAMIC_CMD_CREATE(dsub_video_dev, complete_video_dev); /* Video frame interval handling */ static int video_shell_print_frmival(const struct shell *sh, const struct device *dev, uint32_t pixfmt, uint32_t width, uint32_t height) { struct video_format fmt = {.pixelformat = pixfmt, .width = width, .height = height}; struct video_frmival_enum fie = {.format = &fmt}; for (fie.index = 0; video_enum_frmival(dev, &fie) == 0; fie.index++) { switch (fie.type) { case VIDEO_FRMIVAL_TYPE_DISCRETE: shell_print(sh, "\t\t\tInterval: " "Discrete %u ms (%u FPS)", VIDEO_FRMIVAL_MSEC(&fie.discrete), VIDEO_FRMIVAL_FPS(&fie.discrete)); break; case VIDEO_FRMIVAL_TYPE_STEPWISE: shell_print(sh, "\t\t\tInterval: " "Stepwise: %u ms - %u ms with step %u ms (%u - %u FPS)", VIDEO_FRMIVAL_MSEC(&fie.stepwise.min), VIDEO_FRMIVAL_MSEC(&fie.stepwise.max), VIDEO_FRMIVAL_MSEC(&fie.stepwise.step), VIDEO_FRMIVAL_FPS(&fie.stepwise.max), VIDEO_FRMIVAL_FPS(&fie.stepwise.min)); break; default: shell_error(sh, "Invalid type 0x%x", fie.type); break; } } if (fie.index == 0) { shell_warn(sh, "No frame interval supported for this format for output endpoint"); } return 0; } static int video_shell_set_frmival(const struct shell *sh, const struct device *dev, size_t argc, char **argv) { struct video_frmival frmival = {0}; char *arg_rate = argv[0]; char *end_rate; unsigned long val; int ret; val = strtoul(arg_rate, &end_rate, 10); if (strcmp(end_rate, "fps") == 0) { frmival.numerator = 1; frmival.denominator = val; } else if (strcmp(end_rate, "ms") == 0) { frmival.numerator = val; frmival.denominator = MSEC_PER_SEC; } else if (strcmp(end_rate, "us") == 0) { frmival.numerator = val; frmival.denominator = USEC_PER_SEC; } else { shell_error(sh, "Expected ms, us or fps, not '%s'", arg_rate); return -EINVAL; } ret = video_set_frmival(dev, &frmival); if (ret < 0) { shell_error(sh, "Failed to set frame interval"); return ret; } shell_print(sh, "New frame interval: %u ms (%u FPS)", VIDEO_FRMIVAL_MSEC(&frmival), VIDEO_FRMIVAL_FPS(&frmival)); return 0; } static int cmd_video_frmival(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; struct video_format fmt = {.type = video_shell_buf_type}; struct video_frmival frmival = {0}; char *arg_device = argv[1]; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } ret = video_get_frmival(dev, &frmival); if (ret < 0) { shell_error(sh, "Failed to get the current frame interval"); return ret; } ret = video_get_format(dev, &fmt); if (ret < 0) { shell_error(sh, "Failed to get the current format interval"); return ret; } shell_print(sh, "Current frame interval: %u ms (%u FPS)", VIDEO_FRMIVAL_MSEC(&frmival), VIDEO_FRMIVAL_FPS(&frmival)); switch (argc) { case 2: return video_shell_print_frmival(sh, dev, fmt.pixelformat, fmt.width, fmt.height); case 3: return video_shell_set_frmival(sh, dev, argc - 2, &argv[2]); default: shell_error(sh, "Wrong parameter count"); return -EINVAL; } } static void complete_video_frmival_dev(size_t idx, struct shell_static_entry *entry) { const struct device *dev; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; entry->syntax = NULL; dev = shell_device_filter(idx, device_is_video_and_ready); if (dev == NULL) { return; } entry->syntax = dev->name; } SHELL_DYNAMIC_CMD_CREATE(dsub_video_frmival_dev, complete_video_frmival_dev); /* Video format handling */ static int video_shell_print_format_cap(const struct shell *sh, const struct device *dev, enum video_buf_type type, const struct video_format_cap *cap, size_t i) { size_t bpp = video_bits_per_pixel(cap->pixelformat); size_t size_max = cap->width_max * cap->height_max * bpp / BITS_PER_BYTE; size_t size_min = cap->width_min * cap->height_min * bpp / BITS_PER_BYTE; int ret; shell_print(sh, "\t[%u]: '%s' (%u bits per pixel)", i, VIDEO_FOURCC_TO_STR(cap->pixelformat), bpp); if (cap->width_min == cap->width_max && cap->height_min == cap->height_max) { shell_print(sh, "\t\tSize: " "Discrete: %ux%u (%u bytes per frame)", cap->width_max, cap->height_max, size_min); ret = video_shell_print_frmival(sh, dev, cap->pixelformat, cap->width_min, cap->height_min); if (ret < 0) { return ret; } } else { shell_print(sh, "\t\tSize: " "Stepwise: %ux%u - %ux%u with step %ux%u (%u - %u bytes per frame)", cap->width_min, cap->height_min, cap->width_max, cap->height_max, cap->width_step, cap->height_step, size_min, size_max); ret = video_shell_print_frmival(sh, dev, cap->pixelformat, cap->width_min, cap->height_min); if (ret < 0) { return ret; } ret = video_shell_print_frmival(sh, dev, cap->pixelformat, cap->width_max, cap->height_max); if (ret < 0) { return ret; } } return 0; } static int video_shell_print_caps(const struct shell *sh, const struct device *dev, struct video_caps *caps) { int ret; shell_print(sh, "min vbuf count: %u", caps->min_vbuf_count); shell_print(sh, "min line count: %u", caps->min_line_count); shell_print(sh, "max line count: %u", caps->max_line_count); for (size_t i = 0; caps->format_caps[i].pixelformat != 0; i++) { ret = video_shell_print_format_cap(sh, dev, caps->type, &caps->format_caps[i], i); if (ret < 0) { return ret; } } return 0; } static int video_shell_set_format(const struct shell *sh, const struct device *dev, enum video_buf_type type, size_t argc, char **argv) { struct video_format fmt = {.type = video_shell_buf_type}; char *arg_fourcc = argv[0]; char *arg_size = argv[1]; char *end_size; int ret; if (strlen(arg_fourcc) != 4) { shell_error(sh, "Invalid four character code: '%s'", arg_fourcc); return -EINVAL; } fmt.pixelformat = VIDEO_FOURCC_FROM_STR(arg_fourcc); fmt.width = strtoul(arg_size, &end_size, 10); if (*end_size != 'x' || fmt.width == 0) { shell_error(sh, "Invalid width in x parameter %s", arg_size); return -EINVAL; } end_size++; fmt.height = strtoul(end_size, &end_size, 10); if (*end_size != '\0' || fmt.height == 0) { shell_error(sh, "Invalid height in x parameter %s (%s)", arg_size, end_size); return -EINVAL; } fmt.pitch = fmt.width * video_bits_per_pixel(fmt.pixelformat) / BITS_PER_BYTE; ret = video_set_format(dev, &fmt); if (ret < 0) { shell_error(sh, "Failed to set the format to '%s' %ux%u on output endpoint", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); return ret; } shell_print(sh, "New format: '%s' %ux%u (%u bytes per frame)", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height, fmt.pitch * fmt.height); return 0; } static int cmd_video_format(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; struct video_caps caps = {0}; struct video_format fmt = {0}; enum video_buf_type type; char *arg_device = argv[1]; char *arg_in_out = argv[2]; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } ret = video_shell_parse_in_out(sh, arg_in_out, &type); if (ret < 0) { return ret; } fmt.type = caps.type = type; ret = video_get_caps(dev, &caps); if (ret < 0) { shell_error(sh, "Failed to query %s capabilities", dev->name); return -ENODEV; } ret = video_get_format(dev, &fmt); if (ret < 0) { shell_error(sh, "Failed to query %s capabilities", dev->name); return -ENODEV; } shell_print(sh, "Current format: '%s' %ux%u (%u bytes per frame)", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height, fmt.pitch * fmt.height); switch (argc) { case 3: return video_shell_print_caps(sh, dev, &caps); case 5: return video_shell_set_format(sh, dev, type, argc - 3, &argv[3]); default: shell_error(sh, "Wrong parameter count"); return -EINVAL; } } /* Video control handling */ static void video_shell_convert_ctrl_name(char const *src, char *dst, size_t dst_size) { size_t o = 0; bool add_underscore = false; for (size_t i = 0; src[i] != '\0'; i++) { if (o >= dst_size) { break; } if (isalnum(src[i])) { dst[o] = tolower(src[i]); o++; add_underscore = true; } else if (add_underscore) { dst[o] = '_'; o++; add_underscore = false; } else { /* skip */ } } do { dst[o] = '\0'; } while (o-- > 0 && !isalnum(dst[o])); } static int video_shell_get_ctrl_by_name(struct video_ctrl_query *cq, char const *name) { char ctrl_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE]; int ret; cq->id = 0; for (size_t i = 0;; i++) { cq->id |= VIDEO_CTRL_FLAG_NEXT_CTRL; ret = video_query_ctrl(cq); if (ret < 0) { return -ENOENT; } video_shell_convert_ctrl_name(name, ctrl_name, sizeof(ctrl_name)); if (strcmp(ctrl_name, name) == 0) { break; } } return 0; } static int video_shell_get_ctrl_by_num(struct video_ctrl_query *cq, int num) { int ret; cq->id = 0; for (size_t i = 0; i <= num; i++) { cq->id |= VIDEO_CTRL_FLAG_NEXT_CTRL; ret = video_query_ctrl(cq); if (ret < 0) { return -ENOENT; } } return 0; } static int video_shell_set_ctrl(const struct shell *sh, const struct device *dev, size_t argc, char **argv) { struct video_ctrl_query cq = {.dev = dev}; struct video_control ctrl; char menu_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE]; char *arg_ctrl = argv[0]; char *arg_value = argv[1]; char *end_value; size_t i; int ret; ret = video_shell_get_ctrl_by_name(&cq, arg_ctrl); if (ret < 0) { shell_error(sh, "Video control '%s' not found for this device", arg_ctrl); return ret; } ctrl.id = cq.id; switch (cq.type) { case VIDEO_CTRL_TYPE_MENU: for (i = 0; cq.menu[i] != NULL; i++) { video_shell_convert_ctrl_name(cq.menu[i], menu_name, sizeof(menu_name)); if (strcmp(menu_name, arg_value) == 0) { ctrl.val64 = i; } } if (cq.menu[i] != NULL) { shell_error(sh, "Video control value '%s' is not present in the menu", arg_value); return -EINVAL; } break; case VIDEO_CTRL_TYPE_BOOLEAN: ret = video_shell_parse_on_off(sh, arg_value, &ctrl.val); if (ret < 0) { return ret; } break; case VIDEO_CTRL_TYPE_INTEGER: ctrl.val = strtol(arg_value, &end_value, 10); if (*end_value != '\0') { shell_error(sh, "Invalid integer '%s' for this type", arg_value); return -EINVAL; } break; case VIDEO_CTRL_TYPE_INTEGER64: ctrl.val64 = strtoll(arg_value, &arg_value, 10); if (*end_value != '\0') { shell_error(sh, "Invalid integer '%s' for this type", arg_value); return -EINVAL; } break; default: CODE_UNREACHABLE; } shell_print(sh, "Setting control '%s' (0x%08x) to value '%s' (%lld)", arg_ctrl, ctrl.id, arg_value, (long long)(cq.type == VIDEO_CTRL_TYPE_INTEGER64 ? ctrl.val64 : ctrl.val)); ret = video_set_ctrl(dev, &ctrl); if (ret < 0) { shell_error(sh, "Failed to update control 0x%08x", ctrl.id); return ret; } return 0; } static int video_shell_print_ctrls(const struct shell *sh, const struct device *dev) { struct video_ctrl_query cq = {.dev = dev, .id = VIDEO_CTRL_FLAG_NEXT_CTRL}; char ctrl_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE]; while (video_query_ctrl(&cq) == 0) { /* Convert from human-friendly form to machine-friendly */ video_shell_convert_ctrl_name(cq.name, ctrl_name, sizeof(ctrl_name)); cq.name = ctrl_name; video_print_ctrl(&cq); cq.id |= VIDEO_CTRL_FLAG_NEXT_CTRL; } return 0; } static int cmd_video_ctrl(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; char *arg_device = argv[1]; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } switch (argc) { case 2: return video_shell_print_ctrls(sh, dev); case 4: return video_shell_set_ctrl(sh, dev, argc - 2, &argv[2]); default: shell_error(sh, "Wrong parameter count"); return -EINVAL; } return 0; } static void complete_video_ctrl_boolean(size_t idx, struct shell_static_entry *entry) { static const char *const syntax[] = {"on", "off"}; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; entry->syntax = NULL; if (idx >= ARRAY_SIZE(syntax)) { return; } entry->syntax = syntax[idx]; } SHELL_DYNAMIC_CMD_CREATE(dsub_video_ctrl_boolean, complete_video_ctrl_boolean); static void complete_video_ctrl_menu_name(size_t idx, struct shell_static_entry *entry, int ctrln) { const struct device *dev = video_shell_dev; struct video_ctrl_query cq = {.dev = dev}; int ret; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; entry->syntax = NULL; if (!device_is_ready(dev)) { return; } /* Check which control name was selected */ ret = video_shell_get_ctrl_by_num(&cq, ctrln); if (ret < 0) { return; } /* Then complete the menu value */ for (size_t i = 0; cq.menu[i] != NULL; i++) { if (i == idx) { video_shell_convert_ctrl_name(cq.menu[i], video_shell_menu_name, sizeof(video_shell_menu_name)); entry->syntax = video_shell_menu_name; break; } } } LISTIFY(10, VIDEO_SHELL_COMPLETE_DEFINE, (;), video_ctrl_menu_name); static const union shell_cmd_entry *dsub_video_ctrl_menu_name[] = { &dsub_video_ctrl_menu_name0, &dsub_video_ctrl_menu_name1, &dsub_video_ctrl_menu_name2, &dsub_video_ctrl_menu_name3, &dsub_video_ctrl_menu_name4, &dsub_video_ctrl_menu_name5, &dsub_video_ctrl_menu_name6, &dsub_video_ctrl_menu_name7, &dsub_video_ctrl_menu_name8, &dsub_video_ctrl_menu_name9, }; static void complete_video_ctrl_name_dev(size_t idx, struct shell_static_entry *entry, int devn) { const struct device *dev; struct video_ctrl_query *cq = &video_shell_cq; int ret; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; entry->syntax = NULL; /* Check which device was selected */ dev = video_shell_dev = video_shell_get_dev_by_num(devn); if (!device_is_ready(dev)) { return; } /* Then complete the control name */ cq->dev = dev; ret = video_shell_get_ctrl_by_num(cq, idx); if (ret < 0) { return; } video_shell_convert_ctrl_name(cq->name, video_shell_ctrl_name, sizeof(video_shell_ctrl_name)); entry->syntax = video_shell_ctrl_name; switch (cq->type) { case VIDEO_CTRL_TYPE_BOOLEAN: entry->subcmd = &dsub_video_ctrl_boolean; break; case VIDEO_CTRL_TYPE_MENU: if (idx >= ARRAY_SIZE(dsub_video_ctrl_menu_name)) { return; } entry->subcmd = dsub_video_ctrl_menu_name[idx]; break; default: entry->subcmd = NULL; break; } } LISTIFY(10, VIDEO_SHELL_COMPLETE_DEFINE, (;), video_ctrl_name_dev); static const union shell_cmd_entry *dsub_video_ctrl_name_dev[] = { &dsub_video_ctrl_name_dev0, &dsub_video_ctrl_name_dev1, &dsub_video_ctrl_name_dev2, &dsub_video_ctrl_name_dev3, &dsub_video_ctrl_name_dev4, &dsub_video_ctrl_name_dev5, &dsub_video_ctrl_name_dev6, &dsub_video_ctrl_name_dev7, &dsub_video_ctrl_name_dev8, &dsub_video_ctrl_name_dev9, }; static void complete_video_ctrl_dev(size_t idx, struct shell_static_entry *entry) { const struct device *dev; entry->syntax = NULL; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; dev = video_shell_get_dev_by_num(idx); if (!device_is_ready(dev) || idx >= ARRAY_SIZE(dsub_video_ctrl_name_dev)) { return; } entry->syntax = dev->name; entry->subcmd = dsub_video_ctrl_name_dev[idx]; } SHELL_DYNAMIC_CMD_CREATE(dsub_video_ctrl_dev, complete_video_ctrl_dev); static void complete_video_format_name(size_t idx, struct shell_static_entry *entry, enum video_buf_type type) { const struct device *dev = video_shell_dev; static char fourcc[5] = {0}; struct video_caps caps = {.type = video_shell_buf_type}; uint32_t prev_pixfmt = 0; uint32_t pixfmt = 0; int ret; entry->syntax = NULL; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; /* Fill which buffer type was given in the partial input */ video_shell_buf_type = type; /* Then complete the format name */ ret = video_get_caps(dev, &caps); if (ret < 0) { return; } for (size_t i = 0; caps.format_caps[i].pixelformat != 0; i++) { if (idx == 0) { pixfmt = caps.format_caps[i].pixelformat; break; } if (prev_pixfmt != caps.format_caps[i].pixelformat) { idx--; } prev_pixfmt = caps.format_caps[i].pixelformat; } if (pixfmt == 0) { return; } memcpy(fourcc, VIDEO_FOURCC_TO_STR(pixfmt), sizeof(fourcc)); entry->syntax = fourcc; } static void complete_video_format_in_name(size_t idx, struct shell_static_entry *entry) { complete_video_format_name(idx, entry, VIDEO_BUF_TYPE_INPUT); } static void complete_video_format_out_name(size_t idx, struct shell_static_entry *entry) { complete_video_format_name(idx, entry, VIDEO_BUF_TYPE_OUTPUT); } SHELL_DYNAMIC_CMD_CREATE(dsub_video_format_in_name, complete_video_format_in_name); SHELL_DYNAMIC_CMD_CREATE(dsub_video_format_out_name, complete_video_format_out_name); static void complete_video_format_dir_dev(size_t idx, struct shell_static_entry *entry, int devn) { const struct device *dev; entry->syntax = NULL; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; video_shell_dev = NULL; /* Check which device was selected */ dev = video_shell_dev = video_shell_get_dev_by_num(devn); if (!device_is_ready(dev)) { return; } /* Then complete the endpoint direction */ switch (idx) { case 0: entry->subcmd = &dsub_video_format_in_name; entry->syntax = "in"; break; case 1: entry->subcmd = &dsub_video_format_out_name; entry->syntax = "out"; break; default: entry->syntax = NULL; break; } } LISTIFY(10, VIDEO_SHELL_COMPLETE_DEFINE, (;), video_format_dir_dev); static const union shell_cmd_entry *dsub_video_format_dir_dev[] = { &dsub_video_format_dir_dev0, &dsub_video_format_dir_dev1, &dsub_video_format_dir_dev2, &dsub_video_format_dir_dev3, &dsub_video_format_dir_dev4, &dsub_video_format_dir_dev5, &dsub_video_format_dir_dev6, &dsub_video_format_dir_dev7, &dsub_video_format_dir_dev8, &dsub_video_format_dir_dev9, }; static void complete_video_format_dev(size_t idx, struct shell_static_entry *entry) { const struct device *dev; entry->syntax = NULL; entry->handler = NULL; entry->help = NULL; entry->subcmd = NULL; dev = video_shell_get_dev_by_num(idx); if (!device_is_ready(dev) || idx >= ARRAY_SIZE(dsub_video_format_dir_dev)) { return; } entry->syntax = dev->name; entry->subcmd = dsub_video_format_dir_dev[idx]; } SHELL_DYNAMIC_CMD_CREATE(dsub_video_format_dev, complete_video_format_dev); /* Video selection handling */ static void video_shell_print_selection(const struct shell *sh, struct video_selection *sel) { shell_print(sh, "\tselection target: %s: (%u,%u)/%ux%u", sel->target == VIDEO_SEL_TGT_CROP ? "crop" : sel->target == VIDEO_SEL_TGT_CROP_BOUND ? "crop_bound" : sel->target == VIDEO_SEL_TGT_NATIVE_SIZE ? "native_size" : sel->target == VIDEO_SEL_TGT_COMPOSE ? "compose" : "unknown", sel->rect.left, sel->rect.top, sel->rect.width, sel->rect.height); } static int video_shell_selection_parse_target(const struct shell *sh, char const *arg_target, enum video_selection_target *sel_target) { if (strcmp(arg_target, "crop") == 0) { *sel_target = VIDEO_SEL_TGT_CROP; } else if (strcmp(arg_target, "crop_bound") == 0) { *sel_target = VIDEO_SEL_TGT_CROP_BOUND; } else if (strcmp(arg_target, "native_size") == 0) { *sel_target = VIDEO_SEL_TGT_NATIVE_SIZE; } else if (strcmp(arg_target, "compose") == 0) { *sel_target = VIDEO_SEL_TGT_COMPOSE; } else if (strcmp(arg_target, "compose_bound") == 0) { *sel_target = VIDEO_SEL_TGT_COMPOSE_BOUND; } else { shell_error(sh, "Target must be crop, crop_bound, native_size, compose or compose_bound"); return -EINVAL; } return 0; } static int video_shell_selection_parse_rect(const struct shell *sh, char **args_rect, struct video_rect *rect) { char *arg_left = args_rect[0]; char *arg_top = args_rect[1]; char *arg_width_height = args_rect[2]; char *end_size; rect->left = strtoul(arg_left, &end_size, 10); if (*end_size != '\0') { shell_error(sh, "Invalid left value in rect x parameters"); return -EINVAL; } rect->top = strtoul(arg_top, &end_size, 10); if (*end_size != '\0') { shell_error(sh, "Invalid top value in rect x parameters"); return -EINVAL; } rect->width = strtoul(arg_width_height, &end_size, 10); if (*end_size != 'x' || rect->width == 0) { shell_error(sh, "Invalid width value in rect left> x parameters"); return -EINVAL; } end_size++; rect->height = strtoul(end_size, &end_size, 10); if (*end_size != '\0' || rect->height == 0) { shell_error(sh, "Invalid height value in rect left> x parameters"); return -EINVAL; } return 0; } static int cmd_video_selection(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; struct video_selection sel = {0}; char *arg_device = argv[1]; char *arg_in_out = argv[2]; char *arg_target = argv[3]; int ret; dev = device_get_binding(arg_device); ret = video_shell_check_device(sh, dev); if (ret < 0) { return ret; } ret = video_shell_parse_in_out(sh, arg_in_out, &sel.type); if (ret < 0) { return ret; } ret = video_shell_selection_parse_target(sh, arg_target, &sel.target); if (ret < 0) { return ret; } switch (argc) { case 4: ret = video_get_selection(dev, &sel); if (ret < 0) { shell_error(sh, "Failed to get %s selection", dev->name); return -ENODEV; } video_shell_print_selection(sh, &sel); break; case 7: ret = video_shell_selection_parse_rect(sh, &argv[4], &sel.rect); if (ret < 0) { return ret; } ret = video_set_selection(dev, &sel); if (ret < 0) { shell_error(sh, "Failed to set %s selection", dev->name); return -ENODEV; } video_shell_print_selection(sh, &sel); break; default: shell_error(sh, "Wrong parameter count"); return -EINVAL; } return 0; } /* Video shell commands declaration */ SHELL_STATIC_SUBCMD_SET_CREATE(sub_video_cmds, SHELL_CMD_ARG(start, &dsub_video_dev, SHELL_HELP("Start a video device and its sources", ""), cmd_video_start, 2, 0), SHELL_CMD_ARG(stop, &dsub_video_dev, SHELL_HELP("Stop a video device and its sources", ""), cmd_video_stop, 2, 0), SHELL_CMD_ARG(capture, &dsub_video_dev, SHELL_HELP("Capture a given number of buffers from a device", " "), cmd_video_capture, 3, 0), SHELL_CMD_ARG(format, &dsub_video_format_dev, SHELL_HELP("Query or set the video format of a device", " [ x]"), cmd_video_format, 3, 2), SHELL_CMD_ARG(frmival, &dsub_video_frmival_dev, SHELL_HELP("Query or set the video frame rate/interval of a device", " [fps|ms|us]"), cmd_video_frmival, 2, 1), SHELL_CMD_ARG(ctrl, &dsub_video_ctrl_dev, SHELL_HELP("Query or set video controls of a device", " [ ]"), cmd_video_ctrl, 2, 2), SHELL_CMD_ARG(selection, &dsub_video_format_dev, SHELL_HELP("Query or set the video selection of a device", " [ x]"), cmd_video_selection, 4, 3), SHELL_SUBCMD_SET_END ); SHELL_CMD_REGISTER(video, &sub_video_cmds, "Video driver commands", NULL);