diff --git a/drivers/video/video_sw_generator.c b/drivers/video/video_sw_generator.c index 40ba10011bea..46fceafa5116 100644 --- a/drivers/video/video_sw_generator.c +++ b/drivers/video/video_sw_generator.c @@ -6,10 +6,12 @@ #define DT_DRV_COMPAT zephyr_sw_generator -#include -#include #include +#include +#include #include +#include +#include #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; 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}, +}; 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) +{ + 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) { + 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); } diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index f5afea592542..eba4e4f8f959 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -37,10 +37,10 @@ static inline int display_setup(const struct device *const display_dev, const ui LOG_INF("- Capabilities:"); LOG_INF(" x_resolution = %u, y_resolution = %u, supported_pixel_formats = %u" - " current_pixel_format = %u, current_orientation = %u", - capabilities.x_resolution, capabilities.y_resolution, - capabilities.supported_pixel_formats, capabilities.current_pixel_format, - capabilities.current_orientation); + " current_pixel_format = %u, current_orientation = %u", + capabilities.x_resolution, capabilities.y_resolution, + capabilities.supported_pixel_formats, capabilities.current_pixel_format, + capabilities.current_orientation); /* Set display pixel format to match the one in use by the camera */ switch (pixfmt) { @@ -90,7 +90,8 @@ static inline void video_display_frame(const struct device *const display_dev, int main(void) { - struct video_buffer *buffers[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX], *vbuf; + struct video_buffer *buffers[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX]; + struct video_buffer *vbuf = &(struct video_buffer){}; struct video_format fmt; struct video_caps caps; struct video_frmival frmival; @@ -131,9 +132,9 @@ int main(void) const struct video_format_cap *fcap = &caps.format_caps[i]; /* fourcc to string */ LOG_INF(" %s width [%u; %u; %u] height [%u; %u; %u]", - VIDEO_FOURCC_TO_STR(fcap->pixelformat), - fcap->width_min, fcap->width_max, fcap->width_step, - fcap->height_min, fcap->height_max, fcap->height_step); + VIDEO_FOURCC_TO_STR(fcap->pixelformat), + fcap->width_min, fcap->width_max, fcap->width_step, + fcap->height_min, fcap->height_max, fcap->height_step); i++; } @@ -167,7 +168,7 @@ int main(void) if (!video_get_frmival(video_dev, &frmival)) { LOG_INF("- Default frame rate : %f fps", - 1.0 * frmival.denominator / frmival.numerator); + 1.0 * frmival.denominator / frmival.numerator); } LOG_INF("- Supported frame intervals for the default format:"); @@ -175,12 +176,12 @@ int main(void) fie.format = &fmt; while (video_enum_frmival(video_dev, &fie) == 0) { if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) { - LOG_INF(" %u/%u ", fie.discrete.numerator, fie.discrete.denominator); + LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator); } else { LOG_INF(" [min = %u/%u; max = %u/%u; step = %u/%u]", - fie.stepwise.min.numerator, fie.stepwise.min.denominator, - fie.stepwise.max.numerator, fie.stepwise.max.denominator, - fie.stepwise.step.numerator, fie.stepwise.step.denominator); + fie.stepwise.min.numerator, fie.stepwise.min.denominator, + fie.stepwise.max.numerator, fie.stepwise.max.denominator, + fie.stepwise.step.numerator, fie.stepwise.step.denominator); } fie.index++; } @@ -262,8 +263,8 @@ int main(void) return 0; } - LOG_DBG("Got frame %u! size: %u; timestamp %u ms", frame++, vbuf->bytesused, - vbuf->timestamp); + LOG_DBG("Got frame %u! size: %u; timestamp %u ms", + frame++, vbuf->bytesused, vbuf->timestamp); #ifdef CONFIG_TEST if (is_colorbar_ok(vbuf->buffer, fmt)) { diff --git a/samples/drivers/video/capture_to_lvgl/src/main.c b/samples/drivers/video/capture_to_lvgl/src/main.c index 81883577ad8f..c5f37b69b726 100644 --- a/samples/drivers/video/capture_to_lvgl/src/main.c +++ b/samples/drivers/video/capture_to_lvgl/src/main.c @@ -19,7 +19,8 @@ LOG_MODULE_REGISTER(main); int main(void) { - struct video_buffer *buffers[2], *vbuf; + struct video_buffer *buffers[2]; + struct video_buffer *vbuf = &(struct video_buffer){}; const struct device *display_dev; struct video_format fmt; struct video_caps caps; diff --git a/samples/drivers/video/tcpserversink/src/main.c b/samples/drivers/video/tcpserversink/src/main.c index 85d321bf8e7a..05c190013ad0 100644 --- a/samples/drivers/video/tcpserversink/src/main.c +++ b/samples/drivers/video/tcpserversink/src/main.c @@ -38,7 +38,8 @@ int main(void) { struct sockaddr_in addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); - struct video_buffer *buffers[2], *vbuf; + struct video_buffer *buffers[2]; + struct video_buffer *vbuf = &(struct video_buffer){}; int i, ret, sock, client; struct video_format fmt; struct video_caps caps;