Skip to content

Commit 638fa35

Browse files
committed
drivers: video: sw_generator: add hue, saturation, brightness controls
This switches from the 8-bars to the classic SMPTE color bars pattern and express the colors in HSV format, allowing to control the hue, saturation and value (brightness) of the colors. The runtime overhead is the same as the color computation is only performed This leverages the newly introduced fixed point airthmetic library. This also introduces an YUV output format, and enable the test pattern generator to be used on the devicetree. Signed-off-by: Josuah Demangeon <me@josuah.net>
1 parent d65fbf3 commit 638fa35

File tree

3 files changed

+210
-46
lines changed

3 files changed

+210
-46
lines changed

drivers/video/Kconfig.sw_generator

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# MT9m114
1+
# Software-based pattern generator
22

33
# Copyright (c) 2016 Linaro Limited
44
# SPDX-License-Identifier: Apache-2.0

drivers/video/video_sw_generator.c

Lines changed: 198 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2019, Linaro Limited
3+
* Copyright (c) 2024, tinyVision.ai Inc.
34
*
45
* SPDX-License-Identifier: Apache-2.0
56
*/
@@ -9,6 +10,11 @@
910
#include <zephyr/kernel.h>
1011
#include <zephyr/drivers/video.h>
1112
#include <zephyr/logging/log.h>
13+
#include <zephyr/dsp/print_format.h>
14+
#include <zephyr/dsp/types.h>
15+
#include <zephyr/dsp/macros.h>
16+
17+
#include <video_common.h>
1218

1319
LOG_MODULE_REGISTER(video_sw_generator, CONFIG_VIDEO_LOG_LEVEL);
1420

@@ -22,6 +28,15 @@ LOG_MODULE_REGISTER(video_sw_generator, CONFIG_VIDEO_LOG_LEVEL);
2228
* format. 60 fps is therefore chosen as a common value in practice.
2329
*/
2430
#define MAX_FRAME_RATE 60
31+
#define HUE(a) Q15f((double)(a) / 360.)
32+
#define SMPTE_NUM 7
33+
34+
static const q15_t smpte_colorbar_hsv[3][SMPTE_NUM] = {
35+
/* white, yellow, cyan, green, magenta, red, blue */
36+
{HUE(0), HUE(60), HUE(180), HUE(120), HUE(300), HUE(0), HUE(240)},
37+
{Q15f(0.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.)},
38+
{Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.), Q15f(1.)},
39+
};
2540

2641
struct video_sw_generator_data {
2742
const struct device *dev;
@@ -31,30 +46,27 @@ struct video_sw_generator_data {
3146
struct k_work_delayable buf_work;
3247
struct k_work_sync work_sync;
3348
int pattern;
49+
uint8_t colorbar_rgb565[SMPTE_NUM][2];
50+
uint8_t colorbar_xrgb32[SMPTE_NUM][4];
51+
uint8_t colorbar_yuyv[SMPTE_NUM][4];
52+
q7_t ctrl_hsv_q7[3];
3453
bool ctrl_hflip;
35-
bool ctrl_vflip;
3654
struct k_poll_signal *signal;
3755
uint32_t frame_rate;
3856
};
3957

40-
static const struct video_format_cap fmts[] = {{
41-
.pixelformat = VIDEO_PIX_FMT_RGB565,
42-
.width_min = 64,
43-
.width_max = 1920,
44-
.height_min = 64,
45-
.height_max = 1080,
46-
.width_step = 1,
47-
.height_step = 1,
48-
}, {
49-
.pixelformat = VIDEO_PIX_FMT_XRGB32,
50-
.width_min = 64,
51-
.width_max = 1920,
52-
.height_min = 64,
53-
.height_max = 1080,
54-
.width_step = 1,
55-
.height_step = 1,
56-
},
57-
{0}};
58+
#define VIDEO_SW_GENERATOR_FORMAT_CAP(fourcc) \
59+
{ \
60+
.pixelformat = (fourcc), .width_min = 64, .width_max = 1920, .height_min = 1, \
61+
.height_max = 1080, .width_step = 1, .height_step = 1, \
62+
}
63+
64+
static const struct video_format_cap fmts[] = {
65+
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_RGB565),
66+
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_XRGB32),
67+
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_YUYV),
68+
{0},
69+
};
5870

5971
static int video_sw_generator_set_fmt(const struct device *dev, enum video_endpoint_id ep,
6072
struct video_format *fmt)
@@ -116,28 +128,145 @@ static int video_sw_generator_stream_stop(const struct device *dev)
116128
return 0;
117129
}
118130

119-
/* Black, Blue, Red, Purple, Green, Aqua, Yellow, White */
120-
uint16_t rgb565_colorbar_value[] = {0x0000, 0x001F, 0xF800, 0xF81F, 0x07E0, 0x07FF, 0xFFE0, 0xFFFF};
131+
static void hsv_to_rgb(q15_t h, q15_t s, q15_t v, q15_t *r, q15_t *g, q15_t *b)
132+
{
133+
q15_t chroma = MULq15(s, v);
134+
q15_t x;
135+
136+
if (h < Q15f(1. / 6.)) {
137+
x = SUBq15(h, Q15f(0. / 6.)) * 6;
138+
*r = chroma;
139+
*g = MULq15(chroma, x);
140+
*b = Q15f(0.);
141+
} else if (h < Q15f(2. / 6.)) {
142+
x = SUBq15(h, Q15f(1. / 6.)) * 6;
143+
*r = MULq15(chroma, SUBq15(Q15f(1.), x));
144+
*g = chroma;
145+
*b = Q15f(0.);
146+
} else if (h < Q15f(3. / 6.)) {
147+
x = SUBq15(h, Q15f(2. / 6.)) * 6;
148+
*r = Q15f(0.);
149+
*g = chroma;
150+
*b = MULq15(chroma, x);
151+
} else if (h < Q15f(4. / 6.)) {
152+
x = SUBq15(h, Q15f(3. / 6.)) * 6;
153+
*r = Q15f(0.);
154+
*g = MULq15(chroma, SUBq15(Q15f(1), x));
155+
*b = chroma;
156+
} else if (h < Q15f(5. / 6.)) {
157+
x = SUBq15(h, Q15f(4. / 6.)) * 6;
158+
*r = MULq15(chroma, x);
159+
*g = Q15f(0.);
160+
*b = chroma;
161+
} else {
162+
x = SUBq15(h, Q15f(5. / 6.)) * 6;
163+
*r = chroma;
164+
*g = Q15f(0.);
165+
*b = MULq15(chroma, SUBq15(Q15f(1), x));
166+
}
167+
168+
*r = ADDq15(SUBq15(v, chroma), *r);
169+
*g = ADDq15(SUBq15(v, chroma), *g);
170+
*b = ADDq15(SUBq15(v, chroma), *b);
171+
}
172+
173+
#define BT709_WR 0.2126
174+
#define BT709_WG 0.7152
175+
#define BT709_WB 0.0722
176+
#define BT709_UMAX 0.436
177+
#define BT709_VMAX 0.615
178+
179+
static void rgb_to_yuv(q15_t r, q15_t g, q15_t b, q15_t *y, q15_t *u, q15_t *v)
180+
{
181+
q15_t ux = Q15f(BT709_UMAX / (1. - BT709_WB));
182+
q15_t vx = Q15f(BT709_VMAX / (1. - BT709_WR));
183+
184+
/* Using BT.709 coefficients */
185+
*y = 0;
186+
*y = ADDq15(*y, MULq15(Q15f(BT709_WR), r));
187+
*y = ADDq15(*y, MULq15(Q15f(BT709_WG), g));
188+
*y = ADDq15(*y, MULq15(Q15f(BT709_WB), b));
189+
*u = MULq15(ux, SUBq15(b, *y));
190+
*v = MULq15(vx, SUBq15(r, *y));
191+
}
192+
193+
static void hsv_adjust(q15_t *hue, q15_t *sat, q15_t *val, q15_t h, q15_t s, q15_t v)
194+
{
195+
*hue = MODq15((q31_t)MAXq15 + (q31_t)*hue + (q31_t)h, MAXq15);
196+
*sat = CLAMP((q31_t)*sat + (q31_t)s, 0, MAXq15);
197+
*val = CLAMP((q31_t)*val + (q31_t)v, 0, MAXq15);
198+
}
199+
200+
static void init_colors(const struct device *dev)
201+
{
202+
struct video_sw_generator_data *data = dev->data;
121203

122-
uint32_t xrgb32_colorbar_value[] = {0xFF000000, 0xFF0000FF, 0xFFFF0000, 0xFFFF00FF,
123-
0xFF00FF00, 0xFF00FFFF, 0xFFFFFF00, 0xFFFFFFFF};
204+
for (int i = 0; i < SMPTE_NUM; i++) {
205+
q15_t r, g, b;
206+
q15_t y, u, v;
207+
q15_t hue = smpte_colorbar_hsv[0][i];
208+
q15_t sat = smpte_colorbar_hsv[1][i];
209+
q15_t val = smpte_colorbar_hsv[2][i];
210+
uint16_t u16;
211+
212+
hsv_adjust(&hue, &sat, &val, Q15q7(data->ctrl_hsv_q7[0]),
213+
Q15q7(data->ctrl_hsv_q7[1]), Q15q7(data->ctrl_hsv_q7[2]));
214+
hsv_to_rgb(hue, sat, val, &r, &g, &b);
215+
rgb_to_yuv(r, g, b, &y, &u, &v);
216+
217+
LOG_DBG("H%1"PRIq(3)" S%1"PRIq(3)" V%1"PRIq(3)", "
218+
"R%1"PRIq(3)" G%1"PRIq(3)" B%1"PRIq(3)", "
219+
"Y%1"PRIq(3)" U%1"PRIq(3)" V%1"PRIq(3),
220+
PRIq_arg(hue, 3, 0), PRIq_arg(sat, 3, 0), PRIq_arg(val, 3, 0),
221+
PRIq_arg(r, 3, 0), PRIq_arg(g, 3, 0), PRIq_arg(b, 3, 0),
222+
PRIq_arg(y, 3, 0), PRIq_arg(u, 3, 0), PRIq_arg(v, 3, 0));
223+
224+
u16 = BITSq15(r, 5) | (BITSq15(g, 6) << 5) | (BITSq15(b, 5) << 11);
225+
data->colorbar_rgb565[i][0] = u16 >> 0;
226+
data->colorbar_rgb565[i][1] = u16 >> 8;
227+
228+
data->colorbar_xrgb32[i][0] = 0x00;
229+
data->colorbar_xrgb32[i][1] = BITSq15(r, 8);
230+
data->colorbar_xrgb32[i][2] = BITSq15(g, 8);
231+
data->colorbar_xrgb32[i][3] = BITSq15(g, 8);
232+
233+
data->colorbar_yuyv[i][0] = BITSq15(y, 8);
234+
data->colorbar_yuyv[i][1] = BITSq15(ADDq15(Q15f(0.5), u), 8);
235+
data->colorbar_yuyv[i][2] = BITSq15(y, 8);
236+
data->colorbar_yuyv[i][3] = BITSq15(ADDq15(Q15f(0.5), v), 8);
237+
}
238+
}
124239

125240
static void __fill_buffer_colorbar(struct video_sw_generator_data *data, struct video_buffer *vbuf)
126241
{
127-
int bw = data->fmt.width / 8;
128242
int h, w, i = 0;
129243

130244
for (h = 0; h < data->fmt.height; h++) {
131-
for (w = 0; w < data->fmt.width; w++) {
132-
int color_idx = data->ctrl_vflip ? 7 - w / bw : w / bw;
133-
if (data->fmt.pixelformat == VIDEO_PIX_FMT_RGB565) {
134-
uint16_t *pixel = (uint16_t *)&vbuf->buffer[i];
135-
*pixel = rgb565_colorbar_value[color_idx];
245+
for (w = 0; w < data->fmt.width;) {
246+
int color_idx = w * SMPTE_NUM / data->fmt.width;
247+
248+
if (data->ctrl_hflip) {
249+
color_idx = SMPTE_NUM - 1 - color_idx;
250+
}
251+
252+
switch (data->fmt.pixelformat) {
253+
case VIDEO_PIX_FMT_RGB565:
254+
memcpy(&vbuf->buffer[i], data->colorbar_rgb565[color_idx], 2);
136255
i += 2;
137-
} else if (data->fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) {
138-
uint32_t *pixel = (uint32_t *)&vbuf->buffer[i];
139-
*pixel = xrgb32_colorbar_value[color_idx];
256+
w += 1;
257+
break;
258+
case VIDEO_PIX_FMT_XRGB32:
259+
memcpy(&vbuf->buffer[i], data->colorbar_xrgb32[color_idx], 4);
260+
i += 4;
261+
w += 1;
262+
break;
263+
case VIDEO_PIX_FMT_YUYV:
264+
memcpy(&vbuf->buffer[i], data->colorbar_yuyv[color_idx], 4);
140265
i += 4;
266+
w += 2;
267+
break;
268+
default:
269+
__ASSERT_NO_MSG(false);
141270
}
142271
}
143272
}
@@ -259,10 +388,30 @@ static inline int video_sw_generator_set_ctrl(const struct device *dev, unsigned
259388
void *value)
260389
{
261390
struct video_sw_generator_data *data = dev->data;
391+
uint32_t u32 = (uint32_t)value;
392+
int ret;
393+
394+
ret = video_check_range_u32(dev, cid, u32);
395+
if (ret < 0) {
396+
LOG_ERR("value %u not in range", u32);
397+
return ret;
398+
}
262399

263400
switch (cid) {
264-
case VIDEO_CID_VFLIP:
265-
data->ctrl_vflip = (bool)value;
401+
case VIDEO_CID_HFLIP:
402+
data->ctrl_hflip = (bool)value;
403+
break;
404+
case VIDEO_CID_HUE:
405+
data->ctrl_hsv_q7[0] = u32 - MINq7;
406+
init_colors(dev);
407+
break;
408+
case VIDEO_CID_SATURATION:
409+
data->ctrl_hsv_q7[1] = u32 - MINq7;
410+
init_colors(dev);
411+
break;
412+
case VIDEO_CID_BRIGHTNESS:
413+
data->ctrl_hsv_q7[2] = u32 - MINq7;
414+
init_colors(dev);
266415
break;
267416
default:
268417
return -ENOTSUP;
@@ -350,18 +499,12 @@ static const struct video_driver_api video_sw_generator_driver_api = {
350499
#endif
351500
};
352501

353-
static struct video_sw_generator_data video_sw_generator_data_0 = {
354-
.fmt.width = 320,
355-
.fmt.height = 160,
356-
.fmt.pitch = 320 * 2,
357-
.fmt.pixelformat = VIDEO_PIX_FMT_RGB565,
358-
.frame_rate = DEFAULT_FRAME_RATE,
359-
};
360-
361502
static int video_sw_generator_init(const struct device *dev)
362503
{
363504
struct video_sw_generator_data *data = dev->data;
364505

506+
init_colors(dev);
507+
365508
data->dev = dev;
366509
k_fifo_init(&data->fifo_in);
367510
k_fifo_init(&data->fifo_out);
@@ -370,6 +513,16 @@ static int video_sw_generator_init(const struct device *dev)
370513
return 0;
371514
}
372515

373-
DEVICE_DEFINE(video_sw_generator, "VIDEO_SW_GENERATOR", &video_sw_generator_init, NULL,
374-
&video_sw_generator_data_0, NULL, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY,
375-
&video_sw_generator_driver_api);
516+
#define VIDEO_SW_GENERATOR_DEFINE(inst) \
517+
static struct video_sw_generator_data video_sw_generator_data_##inst = { \
518+
.fmt.width = 320, \
519+
.fmt.height = 160, \
520+
.fmt.pitch = 320 * 2, \
521+
.fmt.pixelformat = VIDEO_PIX_FMT_RGB565, \
522+
}; \
523+
\
524+
DEVICE_DT_INST_DEFINE(inst, &video_sw_generator_init, NULL, \
525+
&video_sw_generator_data_##inst, NULL, POST_KERNEL, \
526+
CONFIG_VIDEO_INIT_PRIORITY, &video_sw_generator_driver_api);
527+
528+
DT_INST_FOREACH_STATUS_OKAY(VIDEO_SW_GENERATOR_DEFINE)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright 2024 tinyVision.ai Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
compatible: "zephyr,sw-generator"
5+
6+
description: |
7+
Software-based video pattern generator for testing purpose.
8+
This generates an image pattern in the RAM buffer passed to it,
9+
at the specified dimension.
10+
11+
include: base.yaml

0 commit comments

Comments
 (0)