drivers: video: common: Add utilities to seek frmival/caps structures
Introduce a video_get_format_index() utility to help finding a caps entry out of a given format. Introduce several utilities to seek and apply frame intervals. Signed-off-by: Josuah Demangeon <me@josuah.net>
This commit is contained in:
parent
498138ecd4
commit
46a262ffe6
6 changed files with 314 additions and 1 deletions
|
@ -1,9 +1,12 @@
|
|||
/*
|
||||
* Copyright (c) 2019, Linaro Limited
|
||||
* Copyright (c) 2024, tinyVision.ai Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/video.h>
|
||||
|
||||
|
@ -83,3 +86,81 @@ void video_buffer_release(struct video_buffer *vbuf)
|
|||
VIDEO_COMMON_FREE(block->data);
|
||||
}
|
||||
}
|
||||
|
||||
int video_format_caps_index(const struct video_format_cap *fmts, const struct video_format *fmt,
|
||||
size_t *idx)
|
||||
{
|
||||
for (int i = 0; fmts[i].pixelformat != 0; i++) {
|
||||
if (fmts[i].pixelformat == fmt->pixelformat &&
|
||||
IN_RANGE(fmt->width, fmts[i].width_min, fmts[i].width_max) &&
|
||||
IN_RANGE(fmt->height, fmts[i].height_min, fmts[i].height_max)) {
|
||||
*idx = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwise,
|
||||
const struct video_frmival *desired,
|
||||
struct video_frmival *match)
|
||||
{
|
||||
uint64_t min = stepwise->min.numerator;
|
||||
uint64_t max = stepwise->max.numerator;
|
||||
uint64_t step = stepwise->step.numerator;
|
||||
uint64_t goal = desired->numerator;
|
||||
|
||||
/* Set a common denominator to all values */
|
||||
min *= stepwise->max.denominator * stepwise->step.denominator * desired->denominator;
|
||||
max *= stepwise->min.denominator * stepwise->step.denominator * desired->denominator;
|
||||
step *= stepwise->min.denominator * stepwise->max.denominator * desired->denominator;
|
||||
goal *= stepwise->min.denominator * stepwise->max.denominator * stepwise->step.denominator;
|
||||
|
||||
/* Saturate the desired value to the min/max supported */
|
||||
goal = CLAMP(goal, min, max);
|
||||
|
||||
/* Compute a numerator and denominator */
|
||||
match->numerator = min + DIV_ROUND_CLOSEST(goal - min, step) * step;
|
||||
match->denominator = stepwise->min.denominator * stepwise->max.denominator *
|
||||
stepwise->step.denominator * desired->denominator;
|
||||
}
|
||||
|
||||
void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
|
||||
struct video_frmival_enum *match)
|
||||
{
|
||||
uint64_t best_diff_nsec = INT32_MAX;
|
||||
struct video_frmival desired = match->discrete;
|
||||
struct video_frmival_enum fie = {.format = match->format};
|
||||
|
||||
__ASSERT(match->type != VIDEO_FRMIVAL_TYPE_STEPWISE,
|
||||
"cannot find range matching the range, only a value matching the range");
|
||||
|
||||
while (video_enum_frmival(dev, ep, &fie) == 0) {
|
||||
struct video_frmival tmp = {0};
|
||||
uint64_t diff_nsec = 0, a, b;
|
||||
|
||||
switch (fie.type) {
|
||||
case VIDEO_FRMIVAL_TYPE_DISCRETE:
|
||||
tmp = fie.discrete;
|
||||
break;
|
||||
case VIDEO_FRMIVAL_TYPE_STEPWISE:
|
||||
video_closest_frmival_stepwise(&fie.stepwise, &desired, &tmp);
|
||||
break;
|
||||
default:
|
||||
__ASSERT(false, "invalid answer from the queried video device");
|
||||
}
|
||||
|
||||
a = video_frmival_nsec(&desired);
|
||||
b = video_frmival_nsec(&tmp);
|
||||
diff_nsec = a > b ? a - b : b - a;
|
||||
if (diff_nsec < best_diff_nsec) {
|
||||
best_diff_nsec = diff_nsec;
|
||||
memcpy(&match->discrete, &tmp, sizeof(tmp));
|
||||
|
||||
/* The video_enum_frmival() function will increment fie.index every time.
|
||||
* Compensate for it to get the current index, not the next index.
|
||||
*/
|
||||
match->index = fie.index - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* @brief Video Interface
|
||||
* @defgroup video_interface Video Interface
|
||||
* @since 2.1
|
||||
* @version 1.0.0
|
||||
* @version 1.1.0
|
||||
* @ingroup io_interfaces
|
||||
* @{
|
||||
*/
|
||||
|
@ -753,6 +753,63 @@ struct video_buffer *video_buffer_alloc(size_t size);
|
|||
*/
|
||||
void video_buffer_release(struct video_buffer *buf);
|
||||
|
||||
/**
|
||||
* @brief Search for a format that matches in a list of capabilities
|
||||
*
|
||||
* @param fmts The format capability list to search.
|
||||
* @param fmt The format to find in the list.
|
||||
* @param idx The pointer to a number of the first format that matches.
|
||||
*
|
||||
* @return 0 when a format is found.
|
||||
* @return -ENOENT when no matching format is found.
|
||||
*/
|
||||
int video_format_caps_index(const struct video_format_cap *fmts, const struct video_format *fmt,
|
||||
size_t *idx);
|
||||
|
||||
/**
|
||||
* @brief Compute the difference between two frame intervals
|
||||
*
|
||||
* @param frmival Frame interval to turn into microseconds.
|
||||
*
|
||||
* @return The frame interval value in microseconds.
|
||||
*/
|
||||
static inline uint64_t video_frmival_nsec(const struct video_frmival *frmival)
|
||||
{
|
||||
return (uint64_t)NSEC_PER_SEC * frmival->numerator / frmival->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the closest match to a frame interval value within a stepwise frame interval.
|
||||
*
|
||||
* @param stepwise The stepwise frame interval range to search
|
||||
* @param desired The frame interval for which find the closest match
|
||||
* @param match The resulting frame interval closest to @p desired
|
||||
*/
|
||||
void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwise,
|
||||
const struct video_frmival *desired,
|
||||
struct video_frmival *match);
|
||||
|
||||
/**
|
||||
* @brief Find the closest match to a frame interval value within a video device.
|
||||
*
|
||||
* To compute the closest match, fill @p match with the following fields:
|
||||
*
|
||||
* - @c match->format to the @ref video_format of interest.
|
||||
* - @c match->type to @ref VIDEO_FRMIVAL_TYPE_DISCRETE.
|
||||
* - @c match->discrete to the desired frame interval.
|
||||
*
|
||||
* The result will be loaded into @p match, with the following fields set:
|
||||
*
|
||||
* - @c match->discrete to the value of the closest frame interval.
|
||||
* - @c match->index to the index of the closest frame interval.
|
||||
*
|
||||
* @param dev Video device to query.
|
||||
* @param ep Video endpoint ID to query.
|
||||
* @param match Frame interval enumerator with the query, and loaded with the result.
|
||||
*/
|
||||
void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
|
||||
struct video_frmival_enum *match);
|
||||
|
||||
/* fourcc - four-character-code */
|
||||
#define video_fourcc(a, b, c, d) \
|
||||
((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
||||
|
|
7
tests/drivers/video/api/CMakeLists.txt
Normal file
7
tests/drivers/video/api/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(integration)
|
||||
|
||||
target_sources(app PRIVATE src/video_common.c)
|
5
tests/drivers/video/api/prj.conf
Normal file
5
tests/drivers/video/api/prj.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_VIDEO=y
|
||||
CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=16384
|
||||
CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=1
|
||||
CONFIG_VIDEO_LOG_LEVEL_DBG=y
|
154
tests/drivers/video/api/src/video_common.c
Normal file
154
tests/drivers/video/api/src/video_common.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2024 tinyVision.ai Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/drivers/video.h>
|
||||
|
||||
enum {
|
||||
RGB565,
|
||||
YUYV_A,
|
||||
YUYV_B,
|
||||
};
|
||||
|
||||
static const struct video_format_cap fmts[] = {
|
||||
[RGB565] = {.pixelformat = VIDEO_PIX_FMT_RGB565,
|
||||
.width_min = 1280, .width_max = 1280, .width_step = 50,
|
||||
.height_min = 720, .height_max = 720, .height_step = 50},
|
||||
[YUYV_A] = {.pixelformat = VIDEO_PIX_FMT_YUYV,
|
||||
.width_min = 100, .width_max = 1000, .width_step = 50,
|
||||
.height_min = 100, .height_max = 1000, .height_step = 50},
|
||||
[YUYV_B] = {.pixelformat = VIDEO_PIX_FMT_YUYV,
|
||||
.width_min = 1920, .width_max = 1920, .width_step = 0,
|
||||
.height_min = 1080, .height_max = 1080, .height_step = 0},
|
||||
{0},
|
||||
};
|
||||
|
||||
ZTEST(video_common, test_video_format_caps_index)
|
||||
{
|
||||
struct video_format fmt = {0};
|
||||
size_t idx;
|
||||
int ret;
|
||||
|
||||
fmt.pixelformat = VIDEO_PIX_FMT_YUYV;
|
||||
|
||||
fmt.width = 100;
|
||||
fmt.height = 100;
|
||||
fmt.pitch = 100 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_ok(ret, "expecting minimum value to match");
|
||||
zassert_equal(idx, YUYV_A);
|
||||
|
||||
fmt.width = 1000;
|
||||
fmt.height = 1000;
|
||||
fmt.pitch = 1000 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_ok(ret, "expecting maximum value to match");
|
||||
zassert_equal(idx, YUYV_A);
|
||||
|
||||
fmt.width = 1920;
|
||||
fmt.height = 1080;
|
||||
fmt.pitch = 1920 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_ok(ret, "expecting exact match to work");
|
||||
zassert_equal(idx, YUYV_B);
|
||||
|
||||
fmt.width = 1001;
|
||||
fmt.height = 1000;
|
||||
fmt.pitch = 1001 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_not_ok(ret, "expecting 1 above maximum width to mismatch");
|
||||
|
||||
fmt.width = 1000;
|
||||
fmt.height = 1001;
|
||||
fmt.pitch = 1000 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_not_ok(ret, "expecting 1 above maximum height to mismatch");
|
||||
|
||||
fmt.width = 1280;
|
||||
fmt.height = 720;
|
||||
fmt.pitch = 1280 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_not_ok(ret);
|
||||
zassert_not_ok(ret, "expecting wrong format to mismatch");
|
||||
|
||||
fmt.pixelformat = VIDEO_PIX_FMT_RGB565;
|
||||
|
||||
fmt.width = 1000;
|
||||
fmt.height = 1000;
|
||||
fmt.pitch = 1000 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_not_ok(ret, "expecting wrong format to mismatch");
|
||||
|
||||
fmt.width = 1280;
|
||||
fmt.height = 720;
|
||||
fmt.pitch = 1280 * 2;
|
||||
ret = video_format_caps_index(fmts, &fmt, &idx);
|
||||
zassert_ok(ret, "expecting exact match to work");
|
||||
zassert_equal(idx, RGB565);
|
||||
}
|
||||
|
||||
ZTEST(video_common, test_video_frmival_nsec)
|
||||
{
|
||||
zassert_equal(
|
||||
video_frmival_nsec(&(struct video_frmival){.numerator = 1, .denominator = 15}),
|
||||
66666666);
|
||||
|
||||
zassert_equal(
|
||||
video_frmival_nsec(&(struct video_frmival){.numerator = 1, .denominator = 30}),
|
||||
33333333);
|
||||
|
||||
zassert_equal(
|
||||
video_frmival_nsec(&(struct video_frmival){.numerator = 5, .denominator = 1}),
|
||||
5000000000);
|
||||
|
||||
zassert_equal(
|
||||
video_frmival_nsec(&(struct video_frmival){.numerator = 1, .denominator = 1750000}),
|
||||
571);
|
||||
}
|
||||
|
||||
ZTEST(video_common, test_video_closest_frmival_stepwise)
|
||||
{
|
||||
struct video_frmival_stepwise stepwise;
|
||||
struct video_frmival desired;
|
||||
struct video_frmival expected;
|
||||
struct video_frmival match;
|
||||
|
||||
stepwise.min.numerator = 1;
|
||||
stepwise.min.denominator = 30;
|
||||
stepwise.max.numerator = 30;
|
||||
stepwise.max.denominator = 30;
|
||||
stepwise.step.numerator = 1;
|
||||
stepwise.step.denominator = 30;
|
||||
|
||||
desired.numerator = 1;
|
||||
desired.denominator = 1;
|
||||
video_closest_frmival_stepwise(&stepwise, &desired, &match);
|
||||
zassert_equal(video_frmival_nsec(&match), video_frmival_nsec(&desired), "1 / 1");
|
||||
|
||||
desired.numerator = 3;
|
||||
desired.denominator = 30;
|
||||
video_closest_frmival_stepwise(&stepwise, &desired, &match);
|
||||
zassert_equal(video_frmival_nsec(&match), video_frmival_nsec(&desired), "3 / 30");
|
||||
|
||||
desired.numerator = 7;
|
||||
desired.denominator = 80;
|
||||
expected.numerator = 3;
|
||||
expected.denominator = 30;
|
||||
video_closest_frmival_stepwise(&stepwise, &desired, &match);
|
||||
zassert_equal(video_frmival_nsec(&match), video_frmival_nsec(&expected), "7 / 80");
|
||||
|
||||
desired.numerator = 1;
|
||||
desired.denominator = 120;
|
||||
video_closest_frmival_stepwise(&stepwise, &desired, &match);
|
||||
zassert_equal(video_frmival_nsec(&match), video_frmival_nsec(&stepwise.min), "1 / 120");
|
||||
|
||||
desired.numerator = 100;
|
||||
desired.denominator = 1;
|
||||
video_closest_frmival_stepwise(&stepwise, &desired, &match);
|
||||
zassert_equal(video_frmival_nsec(&match), video_frmival_nsec(&stepwise.max), "100 / 1");
|
||||
}
|
||||
|
||||
ZTEST_SUITE(video_common, NULL, NULL, NULL, NULL, NULL);
|
9
tests/drivers/video/api/testcase.yaml
Normal file
9
tests/drivers/video/api/testcase.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2024 tinyVision.ai Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
tests:
|
||||
drivers.video.api:
|
||||
tags:
|
||||
- drivers
|
||||
- video
|
||||
platform_allow: native_sim
|
Loading…
Add table
Add a link
Reference in a new issue