Skip to content

drivers: video: Add the video shell #88566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 20, 2025

Conversation

josuah
Copy link
Collaborator

@josuah josuah commented Apr 14, 2025

Dependencies:

Replaces:

Now that we have a nice shell control querying API, it is possible to tab-complete in-depth every control value.

video - Video driver commands
Subcommands:
  start    : Start a video device and its sources
             Usage: video start <device>
  stop     : Stop a video device and its sources
             Usage: video stop <device>
  capture  : Capture a given number of frames from a device
             Usage: video capture <device> <num-frames>
  format   : Query or set the video format of a device
             Usage: video format <device> <ep> [<fourcc> <width>x<height>]
  frmival  : Query or set the video frame rate/interval of a device
             Usage: video frmival <device> <ep> [<n>fps|<n>ms|<n>us]
  ctrl     : Query or set video controls of a device
             Usage: video ctrl <device> [<ctrl> <value>]

To test on native_sim with VIDEO_SW_GENERATOR:

west build -t run -b native_sim/native/64 samples/drivers/video/capture -- -DCONFIG_VIDEO_SHELL=y

On another terminal, using your favorite serial console client, and likely COM# on Windows instead of /dev/pts/3:

picocom -b 115200 /dev/pts/3

@josuah
Copy link
Collaborator Author

josuah commented Apr 14, 2025

Because of the limitation of the shell API, a hack was used using LISTIFY() to generate completion functions that have an extra argument (i.e. the device number), so that the tab completion results are aware of the beginning of the line.

This is not expected to take more RAM, only more ROM/Flash.

@josuah josuah force-pushed the pr-video-shell branch 3 times, most recently from 11760ae to d34be47 Compare April 14, 2025 01:28
@josuah josuah closed this May 10, 2025
@josuah josuah reopened this May 10, 2025
@josuah josuah force-pushed the pr-video-shell branch 2 times, most recently from b064217 to de4e547 Compare May 11, 2025 12:30
@josuah
Copy link
Collaborator Author

josuah commented May 11, 2025

Force-push:

  • Deploy a fix for CI's ERROR - error: XDG_RUNTIME_DIR not set in the environment. error

Comment on lines 7 to 14
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include <zephyr/device.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/shell/shell.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: better in alphabetic order

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I keep forgetting...

Comment on lines +202 to +214
if (IS_ENABLED(CONFIG_TEST)) {
ctrl.id = VIDEO_CID_TEST_PATTERN;
video_set_ctrl(video_dev, &ctrl);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to the video shell change. While it is better to use if instead of #ifdef here, this should be in a separate commit so that people will not be confused when reading commit history.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, moved and also converted the other in the process.

Comment on lines 208 to 211
if (IS_ENABLED(CONFIG_VIDEO_SHELL)) {
LOG_INF("Letting the user control the device with the video shell");
return 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return as soon as possible, right at the beginning of the main function.

  • By doing this here, the sample already reserved some video buffers for nothing:

struct video_buffer *buffers[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX], *vbuf;

Users can be very fast (or if something is stuck in the sample) doing something already in the shell before this point, so we waste a significant amount of memory and not enough memory for the shell.

  • In fact, the idea is we can use video shell not only on the video device in the sample but also for other video devices (e.g. sw generator). And all the logs information in the sample up to this point (format, framerate, controls) are not needed as user can get them with the shell commands ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, the video shell already offers a way to print all of the early main content, and the main loop as well.

I moved the if (IS_ENABLED(CONFIG_VIDEO_SHELL)) all the way up. Easy to see that nothing is required for the shell to work that way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, all the sample main loop does is to just log the buffer fields and stays at that:

                LOG_DBG("Got frame %u! size: %u; timestamp %u ms", frame++, vbuf->bytesused,
                       vbuf->timestamp);

And the video shell actually does not do it. Should I add buffer timestamps to the shell capture command?
The basic logging of the shell sample can still be useful for troubleshooting when "debug" logs are enabled.

https://github.com/zephyrproject-rtos/zephyr/compare/e992d709d11230d26ea32e5844d289441eda2eef..3e707557fb8eb7486310b024f5c663862d9ce3d5

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think

@josuah josuah force-pushed the pr-video-shell branch 2 times, most recently from e992d70 to 1f5c5b0 Compare May 11, 2025 19:51
@josuah josuah marked this pull request as draft May 11, 2025 21:07
@josuah josuah marked this pull request as ready for review May 11, 2025 22:13
Copy link
Collaborator

@ngphibang ngphibang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VIDEO_EP_ is dropped but I see that the PR still uses it and passes CI (?). That means there is no built-only test cover the video shell. I don't know if we could add a built-only test for it ?

PS : It seems we just need to re-trigger the CI

struct video_format fmt = {0};
struct video_buffer *buffers[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX] = {NULL};
struct video_buffer *vbuf = NULL;
char *arg2 = argv[2];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we make use of a local variable here for argv[2], is it better to use a more descriptive name to know what it is ?

enum video_endpoint_id ep, size_t argc, char **argv)
{
struct video_frmival frmival = {0};
char *arg0 = argv[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

enum video_endpoint_id ep, size_t argc, char **argv)
{
struct video_format fmt = {0};
char *arg1 = argv[1];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

struct video_ctrl_query cq = {0};
struct video_control ctrl;
char menu_name[CONFIG_VIDEO_SHELL_CTRL_NAME_SIZE];
char *arg1 = argv[1];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

argv[0], ctrl.id, argv[1], ctrl.val64);

if (cq.type != VIDEO_CTRL_TYPE_INTEGER64) {
ctrl.val = ctrl.val64;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed as val and val64 are in the same union so they point to the same memory address ?

Copy link
Collaborator Author

@josuah josuah May 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no problem with removing it, but is that also a no-op for big-endian architectures?

[EDIT: newer approach avoids the problem altogether]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see now the concern for BE / LE. In fact, it's related to the implementation earlier, if val and val64 are used "normally", this is not a concern (?). It does not matter to me, no need to change if you prefer to do it this way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went for something with 1 more line of code but more straightforward.
In hope that makes sense.
Thank you for the feedback.

Comment on lines 677 to 686
case VIDEO_CTRL_TYPE_BOOLEAN:
if (strcmp(argv[1], "on") == 0) {
ctrl.val64 = true;
} else if (strcmp(argv[1], "off") == 0) {
ctrl.val64 = false;
} else {
shell_error(sh, "Video control value must be 'on' or 'off', not '%s'",
argv[1]);
return -EINVAL;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it simpler to just use 1 and 0 interger instead of "on" and "off" string as v4l2-ctl ? Query-ctrl command can show the min, max, def, cur values so users will not confuse.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having "on" and "off" permitted the user to discover the type is bool with just 'tab'.
I can offer completion candidates for only 0 and 1 then?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so it's ok as currently then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added "0" and "1" as possible input values even though not tab completed. I can also add them as tab completion candidates, very easy.

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, argv[1]) == 0) {
ctrl.val64 = i;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type menu value is integer, so it should be ctrl.val (though they can be used interchangeably) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did set everything to ctrl.val64 so that strtoull() could be used for every type:

		ctrl.val64 = strtoull(arg1, &arg1, 10);

But that is confusing, so let's get back to something more readable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not confusing, I see the intention, just a bit unusual where I see ctrl.val = ctrl.val64 as commented above. Keep the implementation as is if you see it's more convenient.

Comment on lines 36 to 37
/* Current video endpoint, used for tab completion */
static enum video_endpoint_id video_shell_ep = VIDEO_EP_NONE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VIDEO_EP_ is dropped so this need to be rebased. Idem for others.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hurray!

@josuah josuah force-pushed the pr-video-shell branch 5 times, most recently from 8582c26 to 0d41a44 Compare May 18, 2025 15:38

num_buffers = strtoull(arg2, &arg2, 10);
if (*arg2 != '\0') {
shell_error(sh, "Invalid integer '%s' for this type", argv[1]);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have been argv[2].

argv[0], ctrl.id, argv[1], ctrl.val64);

if (cq.type != VIDEO_CTRL_TYPE_INTEGER64) {
ctrl.val = ctrl.val64;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went for something with 1 more line of code but more straightforward.
In hope that makes sense.
Thank you for the feedback.

Comment on lines 677 to 686
case VIDEO_CTRL_TYPE_BOOLEAN:
if (strcmp(argv[1], "on") == 0) {
ctrl.val64 = true;
} else if (strcmp(argv[1], "off") == 0) {
ctrl.val64 = false;
} else {
shell_error(sh, "Video control value must be 'on' or 'off', not '%s'",
argv[1]);
return -EINVAL;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added "0" and "1" as possible input values even though not tab completed. I can also add them as tab completion candidates, very easy.

@josuah
Copy link
Collaborator Author

josuah commented May 18, 2025

Force-push:

  • Address silent SCA warnings
  • Set vbuf->type before dequeuing
  • strtoull() -> strtoll()
  • Move for a more straightforward way to assing ctrl.val/ctrl.val64
  • Drop VIDEO_EP_###
  • Allow "0" or "1" to be used instead of "on" or "off"

Tested again each command against the sw_generator using the patch soup:

git remote add josuah https://github.com/josuah/zephyr
git fetch josuah
git cherry-pick josuah/pr-video-sw-generator~13..josuah/pr-video-sw-generator
west build -b native_sim/native/64 samples/drivers/video/capture -- -DCONFIG_VIDEO_SHELL=y
west flash

Then on another terminal, replacing /dev/pts/0 by what is printed by the sample:

picocom /dev/pts/0

Copy link

josuah added 3 commits May 18, 2025 17:38
Introduce the video shell and implement some video shell commands.
Make use of the various querying API to implement tab-completion, and
validiate the data, as well as convert string names into integers.
Commands provided: frmival, format, ctrl, start, stop, capture

Signed-off-by: Josuah Demangeon <me@josuah.net>
Modify the capture sample to only run the video shell when the user
enables it with -DCONFIG_VIDEO_SHELL=y, as an alternative way to trigger
captures.

Signed-off-by: Josuah Demangeon <me@josuah.net>
Convert #ifdef CONFIG_TEST into if (IS_ENABLED(CONFIG_TEST)) to improve
readability.

Signed-off-by: Josuah Demangeon <me@josuah.net>
@josuah josuah requested a review from kartben May 19, 2025 12:29
@kartben kartben merged commit 8248854 into zephyrproject-rtos:main May 20, 2025
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Build System area: Drivers area: Samples Samples area: Shell Shell subsystem area: Video Video subsystem platform: ESP32 Espressif ESP32 platform: NXP Drivers NXP Semiconductors, drivers platform: STM32 ST Micro STM32
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants