When enumerating controls with VIDEO_CTRL_FLAG_NEXT_CTRL, if child devices have controls with IDs lower or equal to the ones in the parent devices, those controls will be accidentally skipped. Fix this by resetting the query's ID and tracking of the queried device in the query when moving to the next device in the pipeline. Signed-off-by: Phi Bang Nguyen <phibang.nguyen@nxp.com> Signed-off-by: Josuah Demangeon <me@josuah.net>
1074 lines
28 KiB
C
1074 lines
28 KiB
C
/*
|
|
* 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 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_SUBCMD_SET_END
|
|
);
|
|
|
|
SHELL_CMD_REGISTER(video, &sub_video_cmds, "Video driver commands", NULL);
|