11/*
22 * Copyright (c) 2019, Linaro Limited
3+ * Copyright (c) 2024, tinyVision.ai Inc.
34 *
45 * SPDX-License-Identifier: Apache-2.0
56 */
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
1319LOG_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
2641struct 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
5971static 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
125240static 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-
361502static 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 )
0 commit comments