|
| 1 | +/* |
| 2 | + * Copyright (c) 2024 Charles Dias <[email protected]> |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <zephyr/kernel.h> |
| 8 | +#include <zephyr/device.h> |
| 9 | +#include <zephyr/drivers/display.h> |
| 10 | +#include <zephyr/drivers/video.h> |
| 11 | +#include <lvgl.h> |
| 12 | + |
| 13 | +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL |
| 14 | +#include <zephyr/logging/log.h> |
| 15 | +LOG_MODULE_REGISTER(main); |
| 16 | + |
| 17 | +#define VIDEO_DEV_SW "VIDEO_SW_GENERATOR" |
| 18 | + |
| 19 | +int main(void) |
| 20 | +{ |
| 21 | + struct video_buffer *buffers[2], *vbuf; |
| 22 | + const struct device *display_dev; |
| 23 | + struct video_format fmt; |
| 24 | + struct video_caps caps; |
| 25 | + const struct device *video_dev; |
| 26 | + size_t bsize; |
| 27 | + int i = 0; |
| 28 | + |
| 29 | + display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); |
| 30 | + if (!device_is_ready(display_dev)) { |
| 31 | + LOG_ERR("Device not ready, aborting test"); |
| 32 | + return 0; |
| 33 | + } |
| 34 | + |
| 35 | +#if DT_HAS_CHOSEN(zephyr_camera) |
| 36 | + video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); |
| 37 | + if (!device_is_ready(video_dev)) { |
| 38 | + LOG_ERR("%s device is not ready", video_dev->name); |
| 39 | + return 0; |
| 40 | + } |
| 41 | +#else |
| 42 | + video_dev = device_get_binding(VIDEO_DEV_SW); |
| 43 | + if (video_dev == NULL) { |
| 44 | + LOG_ERR("%s device not found", VIDEO_DEV_SW); |
| 45 | + return 0; |
| 46 | + } |
| 47 | +#endif |
| 48 | + |
| 49 | + LOG_INF("- Device name: %s", video_dev->name); |
| 50 | + |
| 51 | + /* Get capabilities */ |
| 52 | + if (video_get_caps(video_dev, VIDEO_EP_OUT, &caps)) { |
| 53 | + LOG_ERR("Unable to retrieve video capabilities"); |
| 54 | + return 0; |
| 55 | + } |
| 56 | + |
| 57 | + LOG_INF("- Capabilities:"); |
| 58 | + while (caps.format_caps[i].pixelformat) { |
| 59 | + const struct video_format_cap *fcap = &caps.format_caps[i]; |
| 60 | + /* four %c to string */ |
| 61 | + LOG_INF(" %c%c%c%c width [%u; %u; %u] height [%u; %u; %u]", |
| 62 | + (char)fcap->pixelformat, (char)(fcap->pixelformat >> 8), |
| 63 | + (char)(fcap->pixelformat >> 16), (char)(fcap->pixelformat >> 24), |
| 64 | + fcap->width_min, fcap->width_max, fcap->width_step, fcap->height_min, |
| 65 | + fcap->height_max, fcap->height_step); |
| 66 | + i++; |
| 67 | + } |
| 68 | + |
| 69 | + /* Get default/native format */ |
| 70 | + if (video_get_format(video_dev, VIDEO_EP_OUT, &fmt)) { |
| 71 | + LOG_ERR("Unable to retrieve video format"); |
| 72 | + return 0; |
| 73 | + } |
| 74 | + |
| 75 | + /* Set format */ |
| 76 | + fmt.width = CONFIG_VIDEO_WIDTH; |
| 77 | + fmt.height = CONFIG_VIDEO_HEIGHT; |
| 78 | + fmt.pitch = fmt.width * 2; |
| 79 | + fmt.pixelformat = VIDEO_PIX_FMT_RGB565; |
| 80 | + |
| 81 | + if (video_set_format(video_dev, VIDEO_EP_OUT, &fmt)) { |
| 82 | + LOG_ERR("Unable to set up video format"); |
| 83 | + return 0; |
| 84 | + } |
| 85 | + |
| 86 | + LOG_INF("- Format: %c%c%c%c %ux%u %u", (char)fmt.pixelformat, (char)(fmt.pixelformat >> 8), |
| 87 | + (char)(fmt.pixelformat >> 16), (char)(fmt.pixelformat >> 24), fmt.width, fmt.height, |
| 88 | + fmt.pitch); |
| 89 | + |
| 90 | + /* Size to allocate for each buffer */ |
| 91 | + bsize = fmt.pitch * fmt.height; |
| 92 | + |
| 93 | + /* Alloc video buffers and enqueue for capture */ |
| 94 | + for (i = 0; i < ARRAY_SIZE(buffers); i++) { |
| 95 | + buffers[i] = video_buffer_alloc(bsize); |
| 96 | + if (buffers[i] == NULL) { |
| 97 | + LOG_ERR("Unable to alloc video buffer"); |
| 98 | + return 0; |
| 99 | + } |
| 100 | + |
| 101 | + video_enqueue(video_dev, VIDEO_EP_OUT, buffers[i]); |
| 102 | + } |
| 103 | + |
| 104 | +#ifdef CONFIG_VIDEO_HFLIP |
| 105 | + /* Video flip image horizontally */ |
| 106 | + if (video_set_ctrl(video_dev, VIDEO_CID_HFLIP, (void *)1)) { |
| 107 | + LOG_ERR("Unable to set video control (HFLIP)"); |
| 108 | + return 0; |
| 109 | + } |
| 110 | +#endif |
| 111 | + |
| 112 | +#ifdef CONFIG_VIDEO_VFLIP |
| 113 | + /* Video flip image vertically */ |
| 114 | + if (video_set_ctrl(video_dev, VIDEO_CID_VFLIP, (void *)1)) { |
| 115 | + LOG_ERR("Unable to set video control (VFLIP)"); |
| 116 | + return 0; |
| 117 | + } |
| 118 | +#endif |
| 119 | + |
| 120 | + /* Start video capture */ |
| 121 | + if (video_stream_start(video_dev)) { |
| 122 | + LOG_ERR("Unable to start capture (interface)"); |
| 123 | + return 0; |
| 124 | + } |
| 125 | + |
| 126 | + display_blanking_off(display_dev); |
| 127 | + |
| 128 | + const lv_img_dsc_t video_img = { |
| 129 | + .header.always_zero = 0, |
| 130 | + .header.w = CONFIG_VIDEO_WIDTH, |
| 131 | + .header.h = CONFIG_VIDEO_HEIGHT, |
| 132 | + .data_size = CONFIG_VIDEO_WIDTH * CONFIG_VIDEO_HEIGHT * sizeof(lv_color_t), |
| 133 | + .header.cf = LV_IMG_CF_TRUE_COLOR, |
| 134 | + .data = (const uint8_t *)buffers[0]->buffer, |
| 135 | + }; |
| 136 | + |
| 137 | + lv_obj_t *screen = lv_img_create(lv_scr_act()); |
| 138 | + |
| 139 | + LOG_INF("- Capture started"); |
| 140 | + |
| 141 | + /* Grab video frames */ |
| 142 | + while (1) { |
| 143 | + int err; |
| 144 | + |
| 145 | + err = video_dequeue(video_dev, VIDEO_EP_OUT, &vbuf, K_FOREVER); |
| 146 | + if (err) { |
| 147 | + LOG_ERR("Unable to dequeue video buf"); |
| 148 | + return 0; |
| 149 | + } |
| 150 | + |
| 151 | + lv_img_set_src(screen, &video_img); |
| 152 | + lv_obj_align(screen, LV_ALIGN_BOTTOM_LEFT, 0, 0); |
| 153 | + |
| 154 | + lv_task_handler(); |
| 155 | + |
| 156 | + err = video_enqueue(video_dev, VIDEO_EP_OUT, vbuf); |
| 157 | + if (err) { |
| 158 | + LOG_ERR("Unable to requeue video buf"); |
| 159 | + return 0; |
| 160 | + } |
| 161 | + } |
| 162 | +} |
0 commit comments