blob: 88f4c4a2cb2033eb4fe55ba1f048478e3d0c19b5 [file] [log] [blame]
/*
* Copyright The Zephyr Project Contributors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/drivers/video.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/byteorder.h>
#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 <n>ms, <n>us or <n>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 <width>x<height> 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 <width>x<height> 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 <left> <top> <width>x<height> parameters");
return -EINVAL;
}
rect->top = strtoul(arg_top, &end_size, 10);
if (*end_size != '\0') {
shell_error(sh,
"Invalid top value in rect <left> <top> <width>x<height> 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> <top> <width>x<height> 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> <top> <width>x<height> 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", "<device>"),
cmd_video_start, 2, 0),
SHELL_CMD_ARG(stop, &dsub_video_dev,
SHELL_HELP("Stop a video device and its sources", "<device>"),
cmd_video_stop, 2, 0),
SHELL_CMD_ARG(capture, &dsub_video_dev,
SHELL_HELP("Capture a given number of buffers from a device",
"<device> <num-buffers>"),
cmd_video_capture, 3, 0),
SHELL_CMD_ARG(format, &dsub_video_format_dev,
SHELL_HELP("Query or set the video format of a device",
"<device> <dir> [<fourcc> <width>x<height>]"),
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",
"<device> [<n>fps|<n>ms|<n>us]"),
cmd_video_frmival, 2, 1),
SHELL_CMD_ARG(ctrl, &dsub_video_ctrl_dev,
SHELL_HELP("Query or set video controls of a device",
"<device> [<ctrl> <value>]"),
cmd_video_ctrl, 2, 2),
SHELL_CMD_ARG(selection, &dsub_video_format_dev,
SHELL_HELP("Query or set the video selection of a device",
"<device> <dir> <target> [<left> <top> <width>x<height>]"),
cmd_video_selection, 4, 3),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(video, &sub_video_cmds, "Video driver commands", NULL);