-
Notifications
You must be signed in to change notification settings - Fork 7.6k
drivers: video: sw_generator: add more formats and try to simplify #85968
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
kartben
merged 12 commits into
zephyrproject-rtos:main
from
josuah:pr-video-sw-generator
May 21, 2025
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7557dd5
drivers: video: sw_generator: preserve full prefix for internal funct…
josuah 7eea58d
drivers: video: sw_generator: refactor: flatten arrays
josuah 606ecd7
drivers: video: sw_generator: use video_format_caps_index()
josuah f80515e
drivers: video: sw_generator: fix video_sw_generator_enum_frmival()
josuah a481c8c
drivers: video: sw_generator: modify video_sw_generator_fill_colorbar()
josuah b5ee6ee
drivers: video: sw_generator: add support for bayer and YUV formats
josuah 43f9e32
drivers: video: sw_generator: introduce the RGB24 test pattern
josuah 0391d8b
drivers: video: sw_generator: make k_work_sync a local variable
josuah e9ce587
drivers: video: sw_generator: fix div by zero on very low framerate
josuah dabaae9
drivers: video: sw_generator: sort header alphabetically
josuah 55eb71f
samples: drivers: video: set an initial video buffer
josuah 0850ee8
samples: drivers: video: capture: logging improvements
josuah File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,12 @@ | |
|
||
#define DT_DRV_COMPAT zephyr_sw_generator | ||
|
||
#include <zephyr/kernel.h> | ||
#include <zephyr/drivers/video.h> | ||
#include <zephyr/drivers/video-controls.h> | ||
#include <zephyr/drivers/video.h> | ||
#include <zephyr/kernel.h> | ||
#include <zephyr/logging/log.h> | ||
#include <zephyr/sys/byteorder.h> | ||
#include <zephyr/sys/util.h> | ||
|
||
#include "video_ctrls.h" | ||
#include "video_device.h" | ||
|
@@ -26,6 +28,7 @@ LOG_MODULE_REGISTER(video_sw_generator, CONFIG_VIDEO_LOG_LEVEL); | |
* format. 60 fps is therefore chosen as a common value in practice. | ||
*/ | ||
#define MAX_FRAME_RATE 60 | ||
#define MIN_FRAME_RATE 1 | ||
|
||
struct sw_ctrls { | ||
struct video_ctrl hflip; | ||
|
@@ -37,52 +40,48 @@ struct video_sw_generator_data { | |
struct video_format fmt; | ||
struct k_fifo fifo_in; | ||
struct k_fifo fifo_out; | ||
struct k_work_delayable buf_work; | ||
struct k_work_sync work_sync; | ||
struct k_work_delayable work; | ||
ngphibang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int pattern; | ||
struct k_poll_signal *sig; | ||
uint32_t frame_rate; | ||
}; | ||
|
||
static const struct video_format_cap fmts[] = {{ | ||
.pixelformat = VIDEO_PIX_FMT_RGB565, | ||
.width_min = 64, | ||
.width_max = 1920, | ||
.height_min = 64, | ||
.height_max = 1080, | ||
.width_step = 1, | ||
.height_step = 1, | ||
}, { | ||
.pixelformat = VIDEO_PIX_FMT_XRGB32, | ||
.width_min = 64, | ||
.width_max = 1920, | ||
.height_min = 64, | ||
.height_max = 1080, | ||
.width_step = 1, | ||
.height_step = 1, | ||
}, | ||
{0}}; | ||
#define VIDEO_SW_GENERATOR_FORMAT_CAP(pixfmt) \ | ||
{ \ | ||
.pixelformat = pixfmt, \ | ||
.width_min = 64, \ | ||
.width_max = 1920, \ | ||
.height_min = 64, \ | ||
.height_max = 1080, \ | ||
.width_step = 1, \ | ||
.height_step = 1, \ | ||
} | ||
|
||
static const struct video_format_cap fmts[] = { | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_RGB24), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_YUYV), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_XRGB32), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SRGGB8), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SGRBG8), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SBGGR8), | ||
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SGBRG8), | ||
{0}, | ||
ngphibang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
static int video_sw_generator_set_fmt(const struct device *dev, struct video_format *fmt) | ||
{ | ||
struct video_sw_generator_data *data = dev->data; | ||
int i = 0; | ||
|
||
for (i = 0; i < ARRAY_SIZE(fmts); ++i) { | ||
if (fmt->pixelformat == fmts[i].pixelformat && fmt->width >= fmts[i].width_min && | ||
fmt->width <= fmts[i].width_max && fmt->height >= fmts[i].height_min && | ||
fmt->height <= fmts[i].height_max) { | ||
break; | ||
} | ||
} | ||
size_t idx; | ||
int ret; | ||
|
||
if (i == ARRAY_SIZE(fmts)) { | ||
ret = video_format_caps_index(fmts, fmt, &idx); | ||
if (ret < 0) { | ||
LOG_ERR("Unsupported pixel format or resolution"); | ||
return -ENOTSUP; | ||
return ret; | ||
} | ||
|
||
data->fmt = *fmt; | ||
|
||
return 0; | ||
} | ||
|
||
|
@@ -99,56 +98,201 @@ static int video_sw_generator_set_stream(const struct device *dev, bool enable, | |
enum video_buf_type type) | ||
{ | ||
struct video_sw_generator_data *data = dev->data; | ||
struct k_work_sync work_sync = {0}; | ||
|
||
if (enable) { | ||
k_work_schedule(&data->buf_work, K_MSEC(1000 / data->frame_rate)); | ||
k_work_schedule(&data->work, K_MSEC(1000 / data->frame_rate)); | ||
} else { | ||
k_work_cancel_delayable_sync(&data->buf_work, &data->work_sync); | ||
k_work_cancel_delayable_sync(&data->work, &work_sync); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
/* Black, Blue, Red, Purple, Green, Aqua, Yellow, White */ | ||
uint16_t rgb565_colorbar_value[] = {0x0000, 0x001F, 0xF800, 0xF81F, 0x07E0, 0x07FF, 0xFFE0, 0xFFFF}; | ||
static const uint8_t pattern_rggb_idx[] = {0, 1, 1, 2}; | ||
static const uint8_t pattern_bggr_idx[] = {2, 1, 1, 0}; | ||
static const uint8_t pattern_gbrg_idx[] = {1, 2, 0, 1}; | ||
static const uint8_t pattern_grbg_idx[] = {1, 0, 2, 1}; | ||
|
||
/* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */ | ||
|
||
static const uint16_t pattern_8bars_yuv_bt709[8][3] = { | ||
{0xFE, 0x80, 0x7F}, {0xEC, 0x00, 0x8B}, {0xC8, 0x9D, 0x00}, {0xB6, 0x1D, 0x0C}, | ||
{0x48, 0xE2, 0xF3}, {0x36, 0x62, 0xFF}, {0x12, 0xFF, 0x74}, {0x00, 0x80, 0x80}, | ||
}; | ||
|
||
uint32_t xrgb32_colorbar_value[] = {0xFF000000, 0xFF0000FF, 0xFFFF0000, 0xFFFF00FF, | ||
0xFF00FF00, 0xFF00FFFF, 0xFFFFFF00, 0xFFFFFFFF}; | ||
static const uint16_t pattern_8bars_rgb[8][3] = { | ||
{0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0x00}, {0x00, 0xFF, 0xFF}, {0x00, 0xFF, 0x00}, | ||
{0xFF, 0x00, 0xFF}, {0xFF, 0x00, 0x00}, {0x00, 0x00, 0xFF}, {0x00, 0x00, 0x00}, | ||
}; | ||
|
||
static void __fill_buffer_colorbar(struct video_sw_generator_data *data, struct video_buffer *vbuf) | ||
static inline int video_sw_generator_get_color_idx(uint16_t w, uint16_t width, bool hflip) | ||
{ | ||
int bw = data->fmt.width / 8; | ||
int h, w, i = 0; | ||
|
||
for (h = 0; h < data->fmt.height; h++) { | ||
for (w = 0; w < data->fmt.width; w++) { | ||
int color_idx = data->ctrls.hflip.val ? 7 - w / bw : w / bw; | ||
if (data->fmt.pixelformat == VIDEO_PIX_FMT_RGB565) { | ||
uint16_t *pixel = (uint16_t *)&vbuf->buffer[i]; | ||
*pixel = rgb565_colorbar_value[color_idx]; | ||
i += 2; | ||
} else if (data->fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) { | ||
uint32_t *pixel = (uint32_t *)&vbuf->buffer[i]; | ||
*pixel = xrgb32_colorbar_value[color_idx]; | ||
i += 4; | ||
} | ||
/* If hflip is on, start from the right instead */ | ||
w = (hflip) ? (width - w - 1) : (w); | ||
|
||
/* Downscale from w/width to #/8 */ | ||
return 8 * w / width; | ||
} | ||
|
||
static uint16_t video_sw_generator_fill_yuyv(uint8_t *buffer, uint16_t width, bool hflip) | ||
{ | ||
josuah marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (width % 2 != 0) { | ||
LOG_ERR("YUYV pixels always go by pairs"); | ||
return 0; | ||
} | ||
|
||
for (size_t w = 0; w < width; w += 2) { | ||
int color_idx = video_sw_generator_get_color_idx(w, width, hflip); | ||
|
||
buffer[w * 2 + 0] = pattern_8bars_yuv_bt709[color_idx][0]; | ||
buffer[w * 2 + 1] = pattern_8bars_yuv_bt709[color_idx][1]; | ||
buffer[w * 2 + 2] = pattern_8bars_yuv_bt709[color_idx][0]; | ||
buffer[w * 2 + 3] = pattern_8bars_yuv_bt709[color_idx][2]; | ||
} | ||
return 1; | ||
} | ||
|
||
static uint16_t video_sw_generator_fill_xrgb32(uint8_t *buffer, uint16_t width, bool hflip) | ||
{ | ||
for (size_t w = 0; w < width; w++) { | ||
int color_idx = video_sw_generator_get_color_idx(w, width, hflip); | ||
|
||
buffer[w * 4 + 0] = 0xff; | ||
buffer[w * 4 + 1] = pattern_8bars_rgb[color_idx][0]; | ||
buffer[w * 4 + 2] = pattern_8bars_rgb[color_idx][1]; | ||
buffer[w * 4 + 3] = pattern_8bars_rgb[color_idx][2]; | ||
} | ||
return 1; | ||
} | ||
|
||
static uint16_t video_sw_generator_fill_rgb24(uint8_t *buffer, uint16_t width, bool hflip) | ||
{ | ||
for (size_t w = 0; w < width; w++) { | ||
int color_idx = video_sw_generator_get_color_idx(w, width, hflip); | ||
|
||
buffer[w * 3 + 0] = pattern_8bars_rgb[color_idx][0]; | ||
buffer[w * 3 + 1] = pattern_8bars_rgb[color_idx][1]; | ||
buffer[w * 3 + 2] = pattern_8bars_rgb[color_idx][2]; | ||
} | ||
return 1; | ||
} | ||
|
||
static uint16_t video_sw_generator_fill_rgb565(uint8_t *buffer, uint16_t width, bool hflip) | ||
{ | ||
for (size_t w = 0; w < width; w++) { | ||
int color_idx = video_sw_generator_get_color_idx(w, width, hflip); | ||
uint8_t r = pattern_8bars_rgb[color_idx][0] >> (8 - 5); | ||
uint8_t g = pattern_8bars_rgb[color_idx][1] >> (8 - 6); | ||
uint8_t b = pattern_8bars_rgb[color_idx][2] >> (8 - 5); | ||
|
||
((uint16_t *)buffer)[w] = sys_cpu_to_le16((r << 11) | (g << 6) | (b << 0)); | ||
} | ||
return 1; | ||
} | ||
|
||
static uint16_t video_sw_generator_fill_bayer8(uint8_t *buffer, uint16_t width, bool hflip, | ||
const uint8_t *bayer_idx) | ||
{ | ||
uint8_t *row0 = buffer + 0; | ||
uint8_t *row1 = buffer + width; | ||
|
||
if (width % 2 != 0) { | ||
LOG_ERR("Bayer pixels always go by pairs (vertically and horizontally)"); | ||
return 0; | ||
} | ||
|
||
for (size_t w = 0; w < width; w += 2) { | ||
int color_idx = video_sw_generator_get_color_idx(w, width, hflip); | ||
|
||
row0[w + 0] = pattern_8bars_rgb[color_idx][bayer_idx[0]]; | ||
row0[w + 1] = pattern_8bars_rgb[color_idx][bayer_idx[1]]; | ||
row1[w + 0] = pattern_8bars_rgb[color_idx][bayer_idx[2]]; | ||
row1[w + 1] = pattern_8bars_rgb[color_idx][bayer_idx[3]]; | ||
} | ||
return 2; | ||
} | ||
|
||
static int video_sw_generator_fill(const struct device *const dev, struct video_buffer *vbuf) | ||
{ | ||
struct video_sw_generator_data *data = dev->data; | ||
struct video_format *fmt = &data->fmt; | ||
size_t pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; | ||
bool hflip = data->ctrls.hflip.val; | ||
uint16_t lines = 0; | ||
|
||
if (vbuf->size < pitch * 2) { | ||
LOG_ERR("At least 2 lines needed for bayer formats support"); | ||
return -EINVAL; | ||
} | ||
|
||
/* Fill the first row of the emulated framebuffer */ | ||
switch (data->fmt.pixelformat) { | ||
case VIDEO_PIX_FMT_YUYV: | ||
lines = video_sw_generator_fill_yuyv(vbuf->buffer, fmt->width, hflip); | ||
break; | ||
case VIDEO_PIX_FMT_XRGB32: | ||
lines = video_sw_generator_fill_xrgb32(vbuf->buffer, fmt->width, hflip); | ||
break; | ||
case VIDEO_PIX_FMT_RGB24: | ||
lines = video_sw_generator_fill_rgb24(vbuf->buffer, fmt->width, hflip); | ||
break; | ||
case VIDEO_PIX_FMT_RGB565: | ||
lines = video_sw_generator_fill_rgb565(vbuf->buffer, fmt->width, hflip); | ||
break; | ||
case VIDEO_PIX_FMT_SRGGB8: | ||
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip, | ||
pattern_rggb_idx); | ||
break; | ||
case VIDEO_PIX_FMT_SGBRG8: | ||
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip, | ||
pattern_gbrg_idx); | ||
break; | ||
case VIDEO_PIX_FMT_SBGGR8: | ||
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip, | ||
pattern_bggr_idx); | ||
break; | ||
case VIDEO_PIX_FMT_SGRBG8: | ||
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip, | ||
pattern_grbg_idx); | ||
break; | ||
default: | ||
CODE_UNREACHABLE; | ||
break; | ||
} | ||
|
||
if (lines == 0) { | ||
return -EINVAL; | ||
} | ||
|
||
/* How much was filled in so far */ | ||
vbuf->bytesused = data->fmt.pitch * lines; | ||
|
||
/* Duplicate the first line(s) all over the buffer */ | ||
for (int h = lines; h < data->fmt.height; h += lines) { | ||
if (vbuf->size < vbuf->bytesused + pitch * lines) { | ||
LOG_WRN("Generation stopped early: buffer too small"); | ||
break; | ||
} | ||
memcpy(vbuf->buffer + h * pitch, vbuf->buffer, pitch * lines); | ||
vbuf->bytesused += pitch * lines; | ||
} | ||
|
||
vbuf->timestamp = k_uptime_get_32(); | ||
vbuf->bytesused = i; | ||
vbuf->line_offset = 0; | ||
|
||
return 0; | ||
} | ||
|
||
static void __buffer_work(struct k_work *work) | ||
static void video_sw_generator_worker(struct k_work *work) | ||
{ | ||
struct k_work_delayable *dwork = k_work_delayable_from_work(work); | ||
struct video_sw_generator_data *data; | ||
struct video_buffer *vbuf; | ||
|
||
data = CONTAINER_OF(dwork, struct video_sw_generator_data, buf_work); | ||
data = CONTAINER_OF(dwork, struct video_sw_generator_data, work); | ||
|
||
k_work_reschedule(&data->buf_work, K_MSEC(1000 / data->frame_rate)); | ||
k_work_reschedule(&data->work, K_MSEC(1000 / data->frame_rate)); | ||
|
||
vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT); | ||
if (vbuf == NULL) { | ||
|
@@ -157,7 +301,7 @@ static void __buffer_work(struct k_work *work) | |
|
||
switch (data->pattern) { | ||
case VIDEO_PATTERN_COLOR_BAR: | ||
__fill_buffer_colorbar(data, vbuf); | ||
video_sw_generator_fill(data->dev, vbuf); | ||
break; | ||
} | ||
|
||
|
@@ -244,13 +388,12 @@ static int video_sw_generator_set_frmival(const struct device *dev, struct video | |
{ | ||
struct video_sw_generator_data *data = dev->data; | ||
|
||
if (frmival->denominator && frmival->numerator) { | ||
data->frame_rate = MIN(DIV_ROUND_CLOSEST(frmival->denominator, frmival->numerator), | ||
MAX_FRAME_RATE); | ||
} else { | ||
if (frmival->denominator == 0 || frmival->numerator == 0) { | ||
return -EINVAL; | ||
} | ||
|
||
data->frame_rate = CLAMP(DIV_ROUND_CLOSEST(frmival->denominator, frmival->numerator), | ||
MIN_FRAME_RATE, MAX_FRAME_RATE); | ||
frmival->numerator = 1; | ||
frmival->denominator = data->frame_rate; | ||
|
||
|
@@ -269,17 +412,17 @@ static int video_sw_generator_get_frmival(const struct device *dev, struct video | |
|
||
static int video_sw_generator_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) | ||
{ | ||
int i = 0; | ||
size_t idx; | ||
int ret; | ||
|
||
while (fmts[i].pixelformat && (fmts[i].pixelformat != fie->format->pixelformat)) { | ||
i++; | ||
if (fie->index >= 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very minor point, should match this line with the commit log which mentions fie.index > 1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
return -ERANGE; | ||
} | ||
|
||
if ((i == ARRAY_SIZE(fmts)) || (fie->format->width > fmts[i].width_max) || | ||
(fie->format->width < fmts[i].width_min) || | ||
(fie->format->height > fmts[i].height_max) || | ||
(fie->format->height < fmts[i].height_min)) { | ||
return -EINVAL; | ||
ret = video_format_caps_index(fmts, fie->format, &idx); | ||
if (ret < 0) { | ||
LOG_ERR("Unsupported pixel format or resolution"); | ||
return ret; | ||
} | ||
|
||
fie->type = VIDEO_FRMIVAL_TYPE_STEPWISE; | ||
|
@@ -333,7 +476,7 @@ static int video_sw_generator_init(const struct device *dev) | |
data->dev = dev; | ||
k_fifo_init(&data->fifo_in); | ||
k_fifo_init(&data->fifo_out); | ||
k_work_init_delayable(&data->buf_work, __buffer_work); | ||
k_work_init_delayable(&data->work, video_sw_generator_worker); | ||
|
||
return video_sw_generator_init_controls(dev); | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.