diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 39d721d..ca7c173 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -1,8 +1,8 @@ .build_template: &build_template stage: build - image: espressif/idf:release-v5.0 tags: - build + image: ${IMAGE} variables: # Enable ccache for all build jobs. See configure_ci_environment.sh for more ccache related settings. IDF_CCACHE_ENABLE: "1" @@ -31,20 +31,13 @@ expire_in: 1 week variables: IDF_CI_BUILD: "1" + # By configuring this macro, you can append the compiled configuration file. + # For example, using "sdkconf.etc=default" specifies the default sdkconfig file. + EXAMPLE_CONFIG: "=" script: - pip install --upgrade idf-component-manager - pip install idf_build_apps - - python -m idf_build_apps build -vv - -p $EXAMPLE_DIR - -t all - --config "sdkconfig.ci.*=" - --build-dir build_@t_@w - --build-log build_log.txt - --size-file size.json - --check-warnings - --ignore-warning-str "$WARNING_STR" - --collect-size-info size_info.txt - --default-build-targets $IDF_TARGET + - python tools/build_apps.py ${EXAMPLE_DIR} --config ${EXAMPLE_CONFIG} -t esp32s3 -vv .build_matter_examples: &build_matter_examples <<: *build_template @@ -87,183 +80,97 @@ build_example_matter_switch_v5.1: IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/matter_switch -build_example_chatgpt_demo_v5.0: +build_example_chatgpt_demo: extends: - .build_examples_template - .rules:build:example_chatgpt_demo - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/chatgpt_demo -build_example_chatgpt_demo_latest: - extends: - - .build_examples_template - - .rules:build:example_chatgpt_demo - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/chatgpt_demo - -build_example_factory_demo_v5.0: +build_example_factory_demo: extends: - .build_examples_template - .rules:build:example_factory_demo - #image: espressif/idf:release-v5.0 don't support release-v5.0 now. - image: espressif/idf:release-v5.1 + parallel: + matrix: + #- IMAGE: espressif/idf:release-v5.0 don't support release-v5.0 now. + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/factory_demo -build_example_factory_demo_v5.1: - extends: - - .build_examples_template - - .rules:build:example_factory_demo - image: espressif/idf:release-v5.1 - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/factory_demo - -build_example_factory_demo_latest: - extends: - - .build_examples_template - - .rules:build:example_factory_demo - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/factory_demo - -build_example_image_display_v5.0: +build_example_image_display: extends: - .build_examples_template - .rules:build:example_image_display - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/image_display -build_example_image_display_v5.1: - extends: - - .build_examples_template - - .rules:build:example_image_display - image: espressif/idf:release-v5.1 - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/image_display - -build_example_image_display_latest: - extends: - - .build_examples_template - - .rules:build:example_image_display - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/image_display - -build_example_lv_demos_v5.0: +build_example_lv_demos: extends: - .build_examples_template - .rules:build:example_lv_demos - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/lv_demos -build_example_lv_demos_v5.1: - extends: - - .build_examples_template - - .rules:build:example_lv_demos - image: espressif/idf:release-v5.1 - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/lv_demos - -build_example_lv_demos_latest: - extends: - - .build_examples_template - - .rules:build:example_lv_demos - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/lv_demos - -build_example_mp3_demo_v5.0: +build_example_mp3_demo: extends: - .build_examples_template - .rules:build:example_mp3_demo - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/mp3_demo -build_example_mp3_demo_v5.1: +build_example_usb_camera_lcd_display: extends: - .build_examples_template - - .rules:build:example_mp3_demo - image: espressif/idf:release-v5.1 + - .rules:build:example_usb_camera_lcd_display + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/mp3_demo + EXAMPLE_DIR: examples/usb_camera_lcd_display -build_example_mp3_demo_latest: - extends: - - .build_examples_template - - .rules:build:example_mp3_demo - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/mp3_demo - -build_example_usb_headset_v5.0: +build_example_usb_headset: extends: - .build_examples_template - .rules:build:example_usb_headset - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/usb_headset -build_example_usb_headset_v5.1: - extends: - - .build_examples_template - - .rules:build:example_usb_headset - image: espressif/idf:release-v5.1 - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/usb_headset - -build_example_usb_headset_latest: - extends: - - .build_examples_template - - .rules:build:example_usb_headset - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/usb_headset - -build_example_watering_demo_v5.0: +build_example_watering_demo: extends: - .build_examples_template - .rules:build:example_watering_demo - image: espressif/idf:release-v5.0 + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:latest variables: - IDF_TARGET: esp32s3 EXAMPLE_DIR: examples/watering_demo - -build_example_watering_demo_v5.1: - extends: - - .build_examples_template - - .rules:build:example_watering_demo - image: espressif/idf:release-v5.1 - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/watering_demo - -build_example_watering_demo_latest: - extends: - - .build_examples_template - - .rules:build:example_watering_demo - image: espressif/idf:latest - variables: - IDF_TARGET: esp32s3 - EXAMPLE_DIR: examples/watering_demo \ No newline at end of file diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 1c4f26a..46f3357 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -25,6 +25,9 @@ .patterns-example_mp3_demo: &patterns-example_mp3_demo - "examples/mp3_demo/**/*" +.patterns-example_usb_camera_lcd_display: &patterns-example_usb_camera_lcd_display + - "examples/usb_camera_lcd_display/**/*" + .patterns-example_usb_headset: &patterns-example_usb_headset - "examples/usb_headset/**/*" @@ -101,6 +104,15 @@ - <<: *if-dev-push changes: *patterns-example_mp3_demo +.rules:build:example_usb_camera_lcd_display: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-dev-push + changes: *patterns-components_bsp + - <<: *if-dev-push + changes: *patterns-example_usb_camera_lcd_display + .rules:build:example_usb_headset: rules: - <<: *if-protected diff --git a/examples/usb_camera_lcd_display/CMakeLists.txt b/examples/usb_camera_lcd_display/CMakeLists.txt new file mode 100644 index 0000000..d908c72 --- /dev/null +++ b/examples/usb_camera_lcd_display/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(usb_camera_lcd_display) diff --git a/examples/usb_camera_lcd_display/README.md b/examples/usb_camera_lcd_display/README.md new file mode 100644 index 0000000..36497b2 --- /dev/null +++ b/examples/usb_camera_lcd_display/README.md @@ -0,0 +1,54 @@ +## USB Stream LCD Display Example + +| Board | Support Status | +| ----------------- | -------------- | +| ESP32-S3-BOX | NO | +| ESP32-S3-BOX-Lite | NO | +| ESP32-S3-BOX-3 | YES | + +This routine demonstrates how to use the [usb_stream](https://components.espressif.com/components/espressif/usb_stream) component to acquire a USB camera image and display it adaptively on the LCD screen. + +* Pressing the boot button can switch the display resolution. +* For better performance, please use ESP-IDF release/v5.0 or above versions. +* Currently only images with a resolution smaller than the screen resolution are displayed +* When the image width is equal to the screen width, the refresh rate is at its highest. + +## Hardware + +* An ESP32-S3-BOX-3 development board with 320*240 LCD. +* A [USB camera](https://docs.espressif.com/projects/espressif-esp-iot-solution/zh_CN/latest/usb/usb_stream.html#id1) (Can be connected to USB port or Expansion Connector). +* An USB Type-C cable for Power supply and programming (Please connect to UART port instead of USB port) + +Note: + Error message with `A fatal error occurred: Could not open /dev/ttyACM0, the port doesn't exist`: Please first make sure development board connected, then make board into "Download Boot" mode to upload by following steps: + 1. keep pressing "BOOT(SW2)" button + 2. short press "RST(SW1)" button + 3. release "BOOT(SW2)". + 4. upload program and reset + +## PSRAM 120M DDR + +The PSRAM 120M DDR feature is intended to achieve the best performance of RGB LCD. It is only available with ESP-IDF **release/v5.1** and above. It can be used by enabling the `IDF_EXPERIMENTAL_FEATURES`, `SPIRAM_SPEED_120M`, `SPIRAM_MODE_OCT` options. see [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/flash_psram_config.html#all-supported-modes-and-speeds) for more details. + +**Note: The PSRAM 120 MHz DDR is an experimental feature and it has temperature risks as below.** + * Cannot guarantee normal functioning with a temperature higher than 65 degrees Celsius. + * Temperature changes can also cause the crash of accessing to PSRAM/Flash, see [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/flash_psram_config.html#all-supported-modes-and-speeds) for more details. + +### How To Use + +Note: + +1. First delete existing `build`, `sdkconfig` , `sdkconfig.old` +``` +rm -rf build sdkconfig sdkconfig.old +``` + +2. Use the command line to enable the relevant configuration +``` +idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.psram_octal_120m" reconfigure +``` + +3. Compile and burn +``` +idf.py build flash monitor +``` \ No newline at end of file diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/CMakeLists.txt b/examples/usb_camera_lcd_display/components/esp_jpeg/CMakeLists.txt new file mode 100644 index 0000000..48c1354 --- /dev/null +++ b/examples/usb_camera_lcd_display/components/esp_jpeg/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "src/audio_malloc.c" + INCLUDE_DIRS "include" +) + +target_link_libraries(${COMPONENT_LIB} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/libs/${CONFIG_IDF_TARGET}/libesp_codec.a") +target_link_libraries(${COMPONENT_LIB} INTERFACE "-u audio_free -u audio_calloc_inner -u audio_calloc") \ No newline at end of file diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_common.h b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_common.h new file mode 100644 index 0000000..a0c975f --- /dev/null +++ b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_common.h @@ -0,0 +1,120 @@ +// Copyright 2022 Espressif Systems (Shanghai) CO., LTD +// All rights reserved. + +#pragma once + +#include "stdint.h" + +/* Error code */ +typedef enum { + JPEG_ERR_OK = 0, /* Succeeded */ + JPEG_ERR_FAIL = -1, /* Device error or wrong termination of input stream */ + JPEG_ERR_MEM = -2, /* Insufficient memory pool for the image */ + JPEG_ERR_NO_MORE_DATA = -3, /* Input data is not enough */ + JPEG_ERR_PAR = -4, /* Parameter error */ + JPEG_ERR_FMT1 = -5, /* Data format error (may be damaged data) */ + JPEG_ERR_FMT2 = -6, /* Right format but not supported */ + JPEG_ERR_FMT3 = -7 /* Not supported JPEG standard */ +} jpeg_error_t; + +/* Jpeg raw data type. + * raw data ---> encoder ---> encoded data ---> transmit ---> decoder ---> raw data + */ +typedef enum { + JPEG_RAW_TYPE_GRAY = 0, /*!< Grayscale. encoder supported. decoder un-supported.*/ + JPEG_RAW_TYPE_RGB888 = 1, /*!< RGB888. encoder supported. decoder supported. */ + JPEG_RAW_TYPE_RGBA = 2, /*!< RGBA. encoder supported. decoder un-supported.*/ + JPEG_RAW_TYPE_YCbYCr = 3, /*!< Y422. encoder supported. decoder un-supported.*/ + JPEG_RAW_TYPE_YCbY2YCrY2 = 4, /*!< Y420. encoder supported. decoder un-supported.*/ + JPEG_RAW_TYPE_RGB565_BE = 5, /*!< RGB565. The data is big end. encoder un-supported. decoder supported. */ + JPEG_RAW_TYPE_RGB565_LE = 6, /*!< RGB565. The data is little end. encoder un-supported. decoder supported.*/ +} jpeg_raw_type_t; + +/* JPEG chroma subsampling factors */ +typedef enum { + JPEG_SUB_SAMPLE_Y = 0, /*!< Grayscale */ + JPEG_SUB_SAMPLE_YUV444 = 1, /*!< data order:YUV 1x1x1 3 pixel per MCU Y444*/ + JPEG_SUB_SAMPLE_YUV422 = 2, /*!< data order:YUYV 2x1x1 2 pixel per MCU Y422*/ + JPEG_SUB_SAMPLE_YUV420 = 3 /*!< data order:YUY2YVY2 4x1x1 1.25 pixel per MCU Y420)*/ +} jpeg_subsampling_t; + +typedef enum { + JPEG_ROTATE_0D = 0, /*!< Source image rotates clockwise 0 degree before encoding */ + JPEG_ROTATE_90D = 1, /*!< Source image rotates clockwise 90 degree before encoding */ + JPEG_ROTATE_180D = 2, /*!< Source image rotates clockwise 180 degree before encoding */ + JPEG_ROTATE_270D = 3, /*!< Source image rotates clockwise 270 degree before encoding */ +} jpeg_rotate_t; + +/** + * @brief Convert YUV to RGB + * + * @param[in] sub_sample chroma subsampling factors. Grayscale is un-supported. The others are supported. + * @param[in] raw_type raw data type. RGB888 and RGB565 are supported. The others are un-supported. + * @param[in] yuv_image input YUV image data. The buffer must be aligned 16 byte. + * @param[in] width image width + * @param[in] height image height + * @param[out] rgb_image output RGB image data + * + * @return jpeg_error_t + * - JPEG_ERR_OK: on success + * - Others: error occurs + */ +jpeg_error_t jpeg_yuv2rgb(jpeg_subsampling_t sub_sample, jpeg_raw_type_t raw_type, uint8_t *yuv_image, int width, int height, uint8_t *rgb_image); + +/** + * @brief Allocated buffer. And the buffer address will be aligned. + * + * @param[in] size Allocate buffer size. + * @param[in] aligned Aligned byte + * + * @return positive: Allocate buffer address + * NULL: failed + */ +void *jpeg_malloc_align(int size, int aligned); + +/** + * @brief Free buffer. The buffer address come from `jpeg_malloc_align` + * + * @param[in] data The buffer address. + * + */ +void jpeg_free_align(void *data); + +/** + * Example usage: + * @code{c} + * + * void* yu2_rgb_demo() + * { + * // allocate memory + * int width = 320; + * int height = 240; + * int yuv_pixel = 3; //JPEG_SUB_SAMPLE_YUV444:3 JPEG_SUB_SAMPLE_YUV422:2 JPEG_SUB_SAMPLE_YUV420:1.5 + * int yuv_image_size = width * height * yuv_pixel; + * uint8_t* yuv_image = (uint8_t*)calloc(1, yuv_image_size); + * if(yuv_image == NULL) { + * return; + * } + * int rgb_pixel = 3; // JPEG_RAW_TYPE_RGB888:3 JPEG_RAW_TYPE_RGB565_LE/JPEG_RAW_TYPE_RGB565_BE:2 + * int rgb_image_size = width * height * rgb_pixel; + * uint8_t* rgb_image = (uint8_t*)jpeg_malloc_align(rgb_image_size, 16); + * if(rgb_image == NULL) { + * return; + * } + * // read yuv data + * memset(yuv_image, 128, yuv_image_size) + * for (int i= 0; i< yuv_image_size; i+=3) { + * yuv_image[i] = i / 3; + * } + * // convert + * jpeg_error_t ret = jpeg_yuv2rgb(JPEG_SUB_SAMPLE_YUV444, JPEG_RAW_TYPE_RGB888, yuv_image, width, height, rgb_image); + * // print rgb data + * for (int i= 0; i< rgb_image_size; i++) { + * printf("%x ", rgb_image[i]&0xff); + * } + * jpeg_free_align(rgb_image); + * free(yuv_image); + * } + * + * @endcode + */ \ No newline at end of file diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_dec.h b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_dec.h new file mode 100644 index 0000000..36b25ae --- /dev/null +++ b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_dec.h @@ -0,0 +1,195 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO., LTD +// All rights reserved. + +#ifndef ESP_JPEG_DEC_H +#define ESP_JPEG_DEC_H + +/** + * @file esp_jpeg_dec.h + * @brief Create an JPEG decoder. + * Currently, support functions as follows: + * - Support variety of width and height to decoder + * - Support RGB888 RGB565(big end) RGB565(little end) raw data to output + * - Support 0, 90 180 270 degree clockwise rotation, under width and height are multiply of 8. + * The encoder do ASM optimization in ESP32S3. The encoder frame rate performs better than the others chips. + * @version 1.0.0 + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_jpeg_common.h" + +#define DEFAULT_JPEG_DEC_CONFIG() { \ + .output_type = JPEG_RAW_TYPE_RGB565_LE, \ + .rotate = JPEG_ROTATE_0D, \ +} + +#define JPEG_DEC_MAX_MARKER_CHECK_LEN (1024) + +typedef void* jpeg_dec_handle_t; + +/* Jpeg dec user need to config */ +typedef struct { + jpeg_raw_type_t output_type; /*!< jpeg_dec_out_type 1:rgb888 0:rgb565 */ + jpeg_rotate_t rotate; /*!< Supports 0, 90 180 270 degree clockwise rotation. + Under width % 8 == 0. height % 8 = 0 conditions, rotation enabled. Otherwise unsupported */ +} jpeg_dec_config_t; + +/* Jpeg dec out info */ +typedef struct { + int width; /* Number of pixels in the horizontal direction */ + int height; /* Number of pixels in the vertical direction */ + int component_num; /* Number of color component*/ + uint8_t component_id[3]; /* ID of color component*/ + uint8_t x_factory[3]; /* Size factory in the x direction*/ + uint8_t y_factory[3]; /* Size factory in the y direction*/ + uint8_t huffbits[2][2][16]; /* Huffman bit distribution tables [id][dcac] */ + uint16_t huffdata[2][2][256]; /* Huffman decoded data tables [id][dcac] */ + uint8_t qtid[3]; /* Quantization table ID of each component */ + int16_t qt_tbl[4][64]; /* De-quantizer tables [id] */ +} jpeg_dec_header_info_t; + +/* Jpeg dec io control */ +typedef struct { + unsigned char *inbuf; /* The input buffer pointer */ + int inbuf_len; /* The number of the input buffer */ + int inbuf_remain; /* Not used number of the in buffer */ + unsigned char *outbuf; /* The decoded data is placed.The buffer must be aligned 16 byte. */ +} jpeg_dec_io_t; + +/** + * @brief Create a Jpeg decode handle, set user config info to decode handle + * + * @param[in] config The configuration information + * + * @return other values: The JPEG decoder handle + * NULL: failed + */ +jpeg_dec_handle_t *jpeg_dec_open(jpeg_dec_config_t *config); + +/** + * @brief Parse picture data header, and out put info to user + * + * @param[in] jpeg_dec jpeg decoder handle + * + * @param[in] io struct of jpeg_dec_io_t + * + * @param[out] out_info output info struct to user + * + * @return jpeg_error_t + * - JPEG_ERR_OK: on success + * - Others: error occurs + */ +jpeg_error_t jpeg_dec_parse_header(jpeg_dec_handle_t *jpeg_dec, jpeg_dec_io_t *io, jpeg_dec_header_info_t *out_info); + +/** + * @brief Decode one Jpeg picture + * + * @param[in] jpeg_dec jpeg decoder handle + * + * @param[in] io struct of jpeg_dec_io_t + * + * @return jpeg_error_t + * - JPEG_ERR_OK: on success + * - Others: error occurs + */ +jpeg_error_t jpeg_dec_process(jpeg_dec_handle_t *jpeg_dec, jpeg_dec_io_t *io); + +/** + * @brief Deinitialize Jpeg decode handle + * + * @param[in] jpeg_dec jpeg decoder handle + * + * @return jpeg_error_t + * - JPEG_ERR_OK: on success + * - Others: error occurs + */ +jpeg_error_t jpeg_dec_close(jpeg_dec_handle_t *jpeg_dec); + +/** + * Example usage: + * @code{c} + * + * // Function for decode one jpeg picture + * // input_buf input picture data + * // len input picture data length + * int esp_jpeg_decoder_one_picture(unsigned char *input_buf, int len, unsigned char **output_buf) + * { + * // Generate default configuration + * jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG(); + * + * // Empty handle to jpeg_decoder + * jpeg_dec_handle_t jpeg_dec = NULL; + * + * // Create jpeg_dec + * jpeg_dec = jpeg_dec_open(&config); + * + * // Create io_callback handle + * jpeg_dec_io_t *jpeg_io = calloc(1, sizeof(jpeg_dec_io_t)); + * if (jpeg_io == NULL) { + * return ESP_FAIL; + * } + * + * // Create out_info handle + * jpeg_dec_header_info_t *out_info = calloc(1, sizeof(jpeg_dec_header_info_t)); + * if (out_info == NULL) { + * return ESP_FAIL; + * } + * + * // Set input buffer and buffer len to io_callback + * jpeg_io->inbuf = input_buf; + * jpeg_io->inbuf_len = len; + * + * int ret = 0; + * // Parse jpeg picture header and get picture for user and decoder + * ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info); + * if (ret < 0) { + * return ret; + * } + * + * // Calloc out_put data buffer and update inbuf ptr and inbuf_len + * int outbuf_len; + * if (config.output_type == JPEG_RAW_TYPE_RGB565_LE + * || config.output_type == JPEG_RAW_TYPE_RGB565_BE) { + * outbuf_len = out_info->width * out_info->height * 2; + * } else if (config.output_type == JPEG_RAW_TYPE_RGB888) { + * outbuf_len = out_info->width * out_info->height * 3; + * } else { + * return ESP_FAIL; + * } + * unsigned char *out_buf = jpeg_malloc_align(outbuf_len, 16); + * if (out_buf == NULL) { + * return ESP_FAIL; + * } + * jpeg_io->outbuf = out_buf; + * *output_buf = out_buf; + * int inbuf_consumed = jpeg_io->inbuf_len - jpeg_io->inbuf_remain; + * jpeg_io->inbuf = input_buf + inbuf_consumed; + * jpeg_io->inbuf_len = jpeg_io->inbuf_remain; + * + * // Start decode jpeg raw data + * ret = jpeg_dec_process(jpeg_dec, jpeg_io); + * if (ret < 0) { + * return ret; + * } + * + * // Decoder deinitialize + * jpeg_free_align(out_buf); + * jpeg_dec_close(jpeg_dec); + * free(out_info); + * free(jpeg_io); + * return ESP_OK; + * } + * + * @endcode + * + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_enc.h b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_enc.h new file mode 100644 index 0000000..77d1647 --- /dev/null +++ b/examples/usb_camera_lcd_display/components/esp_jpeg/include/esp_jpeg_enc.h @@ -0,0 +1,244 @@ +// Copyright 2022 Espressif Systems (Shanghai) CO., LTD +// All rights reserved. + +#pragma once + +/** + * @file esp_jpeg_enc.h + * @brief Create an JPEG encoder. + * Currently, support functions as follows: + * - Support variety of width and height to encoder + * - Support RGB888 RGBA YCbYCr YCbY2YCrY2 GRAY raw data to encode + * - Support sub-sample(YUV444 YUV422 Yuv420) + * - Support quality(1-100) + * - Support 0, 90 180 270 degree clockwise rotation, under src_type = JPEG_RAW_TYPE_YCbYCr, + * subsampling = JPEG_SUB_SAMPLE_YUV420, width and height are multiply of 16 and + * src_type = JPEG_RAW_TYPE_YCbYCr, subsampling = JPEG_SUB_SAMPLE_Y, width and height are multiply of 8. + * - Support dual-task + * - Support two mode encoder, respectively block encoder and one image encoder + * The encoder does ASM optimization in ESP32S3. The encoder frame rate performs better than the others chips. + * Under src_type = JPEG_RAW_TYPE_YCbYCr, subsampling = JPEG_SUB_SAMPLE_YUV420, width % 16 == 0. height % 16 = 0 conditions, + * memory consumption is about 9K.Others, with wider the image and larger `subsampling`, the greater memory consumption will be. + * + * @version 1.0.0 + * + */ + +#include "stdbool.h" +#include "stdint.h" +#include "esp_jpeg_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEFAULT_JPEG_ENC_CONFIG() \ + { \ + .width = 320, \ + .height = 240, \ + .src_type = JPEG_RAW_TYPE_YCbYCr, \ + .subsampling = JPEG_SUB_SAMPLE_YUV420, \ + .quality = 40, \ + .task_enable = false, \ + .hfm_task_priority = 13, \ + .hfm_task_core = 1, \ + .rotate = JPEG_ROTATE_0D, \ + } + +/* JPEG configure information*/ +typedef struct jpeg_info { + int width; /*!< Image width */ + int height; /*!< Image height */ + jpeg_raw_type_t src_type; /*!< Input image type */ + jpeg_subsampling_t subsampling; /*!< JPEG chroma subsampling factors. If `src_type` is JPEG_RAW_TYPE_YCbY2YCrY2, this must be `JPEG_SUB_SAMPLE_Y` or `JPEG_SUB_SAMPLE_YUV420`. Others is un-support */ + uint8_t quality; /*!< Quality: 1-100, higher is better. Typical values are around 40 - 100. */ + bool task_enable; /*!< True: `jpeg_enc_open` would create task to finish part of encoding work. false: no task help the encoder encode */ + uint8_t hfm_task_priority; /*!< Task priority.If task_enable is true, this must be set */ + uint8_t hfm_task_core; /*!< Task core.If task_enable is true, this must be set */ + jpeg_rotate_t rotate; /*!< Supports 0, 90 180 270 degree clockwise rotation. + Under src_type = JPEG_RAW_TYPE_YCbYCr, subsampling = JPEG_SUB_SAMPLE_YUV420, width % 16 == 0. height % 16 = 0 conditions, rotation enabled. + Otherwise unsupported */ +} jpeg_enc_info_t; + +/** + * @brief Create an JPEG handle to encode + * + * @param info The configuration information + * + * @return >0: The JPEG encoder handle + * NULL: refer to `jpeg_error_t` + */ +void *jpeg_enc_open(jpeg_enc_info_t *info); + +/** + * @brief Encode one image + * + * @param handle The JPEG encoder handle. It gained from `jpeg_enc_open` + * @param in_buf The input buffer, It needs a completed image. The buffer must be aligned 16 byte. + * @param inbuf_size The size of `in_buf`. The value must be size of a completed image. + * @param out_buf The output buffer, it saves a completed JPEG image. The size must be gather than 700 bytes. + * @param outbuf_size The size of output buffer + * @param out_size The size of JPEG image + * + * @return `JPEG_ERR_OK`: It has finished to encode one image. + * other values refer to `jpeg_error_t` + */ +jpeg_error_t jpeg_enc_process(const void *handle, const uint8_t *in_buf, int inbuf_size, uint8_t *out_buf, int outbuf_size, int *out_size); + +/** + * @brief Get block size. Block size is minimum process unit. + * + * @param handle The JPEG encoder handle. It gained from `jpeg_enc_open` + * + * @return positive value block size + */ +int jpeg_enc_get_block_size(const void *handle); + +/** + * @brief Encode block size image. Get block size from `jpeg_enc_get_block_size` + * + * @param handle The JPEG encoder handle. It gained from `jpeg_enc_open` + * @param in_buf The input buffer, It needs a completed image. + * @param inbuf_size The size of `in_buf`. Get block size from `jpeg_enc_get_block_size` + * @param out_buf The output buffer, it saves a completed JPEG image. The size must be gather than 700 bytes. + * @param outbuf_size The size of output buffer + * @param out_size The size of JPEG image + * + * @return block size: It has finished to encode current block size image. + * `JPEG_ERR_OK`: It has finished to encode one image. + * `JPEG_ERR_FAIL`: Encoder failed + */ +int jpeg_enc_process_with_block(const void *handle, const uint8_t *in_buf, int inbuf_size, uint8_t *out_buf, int outbuf_size, int *out_size); + +/** + * @brief Deinit JPEG handle + * + * @param handle The JPEG encoder handle. It gained from `jpeg_enc_open` + * + * @return `JPEG_ERR_OK` It has finished to deinit. + */ +jpeg_error_t jpeg_enc_close(void *handle); + +/** + * @brief Reset quality. + * + * @param handle The JPEG encoder handle. It gained from `jpeg_enc_open` + * @param q Quality: 1-100, higher is better. Typical values are around 40 - 100. + * + * @return JPEG_ERR_Ok succeed + * others failed + */ +jpeg_error_t jpeg_enc_set_quality(const void *handle, uint8_t q); + +/** + * Example usage: + * demo1: It is to encode one image using `jpeg_enc_process` + * @code{c} + * + * int esp_jpeg_enc_demo1 () { + * /// configure encoder + * jpeg_enc_info_t info = DEFAULT_JPEG_ENC_CONFIG(); + * + * /// allocate input buffer to fill original image stream. + * int in_len = info.width *info.height * 2; + * char *inbuf = jpeg_malloc_align(in_len); + * if(inbuf == NULL) { + * goto exit; + * } + * /// allocate output buffer to fill encoded image stream. + * int out_len = info.width * info.height * 2; + * char *outbuf = malloc(out_len); + * if (inbuf == NULL) { + * goto exit; + * } + * int out_size = 0; + * void* el = jpeg_enc_open(&info); + * if (el == NULL) { + * goto exit; + * } + * char in_name[100] = "test.yuv"; + * char out_name[100] = "test.jpg"; + * FILE *in = fopen(in_name, "rb"); + * if (el == NULL){ + * goto exit; + * } + * FILE *out = fopen(out_name, "wb"); + * if (el == NULL) { + * fclose(in); + * goto exit; + * } + * int ret = fread(inbuf, 1, in_len, in); + * if(ret <= 0) { + * fclose(in); + * fclose(out); + * goto exit; + * } + * jpeg_enc_process(el, inbuf, in_len, outbuf, out_len, &out_size); + * ret = fwrite(outbuf, 1, out_size, out); + * if (ret <= 0){ + * fclose(in); + * fclose(out); + * goto exit; + * } + * fclose(in); + * fclose(out); + * exit: + * jpeg_enc_close(el); + * free(inbuf); + * free(outbuf); + * } + + * demo2: It is to encode one image using `jpeg_enc_process_with_block` + * int esp_jpeg_enc_demo2 () { + * jpeg_enc_info_t info = DEFAULT_JPEG_ENC_CONFIG(); + * int in_len = jpeg_enc_get_block_size(el); + * char *inbuf = jpeg_malloc_align(in_len); + * if(inbuf == NULL) { + * goto exit; + * } + * int num_times = info.width * info.height * 3 / in_len; + * int out_len = info.width * info.height * 2; + * char *outbuf = malloc(out_len); + * if (inbuf == NULL){ + * goto exit; + * } + * int out_size = 0; + * void* el = jpeg_enc_open(&info); + * if (el == NULL){ + * goto exit; + * } + * + * char in_name[100] = "test.yuv"; + * char out_name[100] = "test.jpg"; + * FILE *in = fopen(in_name, "rb") if (el == NULL) + * { + * goto exit; + * } + * FILE *out = fopen(out_name, "wb") if (el == NULL) + * { + * fclose(in); + * goto exit; + * } + * for (size_t j = 0; j < num_times; j++) { + * int ret = fread(inbuf, 1, in_len, in); + * if(ret <= 0) { + * ret = fwrite(outbuf, 1, out_size, out); + * fclose(in); + * fclose(out); + * goto exit; + * } + * jpeg_enc_process_with_block(el, inbuf, in_len, outbuf, out_len, &out_size); + * } + * fclose(in); + * fclose(out); + * exit: + * jpeg_enc_close(el); + * free(inbuf); + * free(outbuf); + * } + * @endcode + */ + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/libs/esp32s3/libesp_codec.a b/examples/usb_camera_lcd_display/components/esp_jpeg/libs/esp32s3/libesp_codec.a new file mode 100644 index 0000000..cdaa82f Binary files /dev/null and b/examples/usb_camera_lcd_display/components/esp_jpeg/libs/esp32s3/libesp_codec.a differ diff --git a/examples/usb_camera_lcd_display/components/esp_jpeg/src/audio_malloc.c b/examples/usb_camera_lcd_display/components/esp_jpeg/src/audio_malloc.c new file mode 100644 index 0000000..8742732 --- /dev/null +++ b/examples/usb_camera_lcd_display/components/esp_jpeg/src/audio_malloc.c @@ -0,0 +1,40 @@ +#include +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "esp_log.h" + +// #define audio_malloc malloc +// #define audio_free free +// #define audio_strdup strdup +// #define audio_calloc calloc +// #define audio_realloc realloc + +void *audio_calloc_inner(size_t n, size_t size) +{ + void *data = NULL; +#if CONFIG_SPIRAM_BOOT_INIT + data = heap_caps_calloc_prefer(n, size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM); +#else + data = heap_caps_calloc(n, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); +#endif + +#ifdef ENABLE_AUDIO_MEM_TRACE + ESP_LOGI("AUIDO_MEM", "calloc_inner:%p, size:%d, called:0x%08x", data, size, (intptr_t)__builtin_return_address(0) - 2); +#endif + return data; +} + +void *audio_calloc(size_t n, size_t size) +{ + return calloc(n, size); +} + +// void* realloc(void* ptr, size_t size) +// { +// return heap_caps_realloc_default(ptr, size); +// } + +void audio_free(void *ptr) +{ + free(ptr); +} diff --git a/examples/usb_camera_lcd_display/main/CMakeLists.txt b/examples/usb_camera_lcd_display/main/CMakeLists.txt new file mode 100644 index 0000000..cf0acf3 --- /dev/null +++ b/examples/usb_camera_lcd_display/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS main.c) \ No newline at end of file diff --git a/examples/usb_camera_lcd_display/main/idf_component.yml b/examples/usb_camera_lcd_display/main/idf_component.yml new file mode 100644 index 0000000..57ffa6b --- /dev/null +++ b/examples/usb_camera_lcd_display/main/idf_component.yml @@ -0,0 +1,5 @@ +dependencies: + espressif/esp-box-3: "^1.0.0" + idf: ">=5.0" + usb_stream: + version: "~1.1.0" \ No newline at end of file diff --git a/examples/usb_camera_lcd_display/main/main.c b/examples/usb_camera_lcd_display/main/main.c new file mode 100644 index 0000000..766150d --- /dev/null +++ b/examples/usb_camera_lcd_display/main/main.c @@ -0,0 +1,449 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "bsp/esp-bsp.h" +#include "bsp/display.h" +#include "esp_log.h" +#include "esp_jpeg_dec.h" +#include "esp_timer.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "usb_stream.h" +#include "iot_button.h" + +static const char *TAG = "uvc_camera_lcd_demo"; +/****************** configure the example working mode *******************************/ + +#define DEMO_UVC_XFER_BUFFER_SIZE (35 * 1024) // Double buffer +#define DEMO_KEY_RESOLUTION "resolution" // The key of the resolution stored in nvs +#define DEMO_SWITCH_BUTTON_IO 0 // The button to switch resolution +#define DEMO_MAX_H 680 // The max width of the camera +#define DEMO_MAX_V 480 // The max height of the camera + +#define BIT0_FRAME_START (0x01 << 0) +static EventGroupHandle_t s_evt_handle; + +typedef struct +{ + uint16_t width; + uint16_t height; +} camera_frame_size_t; + +typedef struct +{ + camera_frame_size_t camera_frame_size; + uvc_frame_size_t *camera_frame_list; + size_t camera_frame_list_num; + size_t camera_currect_frame_index; +} camera_resolution_info_t; + +static camera_resolution_info_t camera_resolution_info = {0}; +static uint8_t *jpg_frame_buf = NULL; +static uint8_t *xfer_buffer_a = NULL; +static uint8_t *xfer_buffer_b = NULL; +static uint8_t *frame_buffer = NULL; +static uint16_t current_width = 0; +static uint16_t current_height = 0; +static lv_obj_t *camera_canvas = NULL; +static lv_obj_t *label = NULL; + +static jpeg_error_t esp_jpeg_decoder_one_picture(uint8_t *input_buf, int len, uint8_t *output_buf) +{ + esp_err_t ret = ESP_OK; + // Generate default configuration + jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG(); + config.output_type = JPEG_RAW_TYPE_RGB565_BE; + // Empty handle to jpeg_decoder + jpeg_dec_handle_t jpeg_dec = NULL; + + // Create jpeg_dec + jpeg_dec = jpeg_dec_open(&config); + + // Create io_callback handle + jpeg_dec_io_t *jpeg_io = calloc(1, sizeof(jpeg_dec_io_t)); + if (jpeg_io == NULL) + { + return ESP_FAIL; + } + + // Create out_info handle + jpeg_dec_header_info_t *out_info = calloc(1, sizeof(jpeg_dec_header_info_t)); + if (out_info == NULL) + { + return ESP_FAIL; + } + // Set input buffer and buffer len to io_callback + jpeg_io->inbuf = input_buf; + jpeg_io->inbuf_len = len; + + // Parse jpeg picture header and get picture for user and decoder + ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info); + if (ret < 0) + { + goto _exit; + } + + jpeg_io->outbuf = output_buf; + int inbuf_consumed = jpeg_io->inbuf_len - jpeg_io->inbuf_remain; + jpeg_io->inbuf = input_buf + inbuf_consumed; + jpeg_io->inbuf_len = jpeg_io->inbuf_remain; + + // Start decode jpeg raw data + ret = jpeg_dec_process(jpeg_dec, jpeg_io); + if (ret < 0) + { + goto _exit; + } + +_exit: + // Decoder deinitialize + jpeg_dec_close(jpeg_dec); + free(out_info); + free(jpeg_io); + return ret; +} + +static void adaptive_jpg_frame_buffer(size_t length) +{ + if (jpg_frame_buf != NULL) + { + free(jpg_frame_buf); + } + + jpg_frame_buf = (uint8_t *)heap_caps_aligned_alloc(16, length, MALLOC_CAP_SPIRAM); + assert(jpg_frame_buf != NULL); +} + +static void _camera_display(uint8_t *lcd_buffer) +{ + bsp_display_lock(0); + lv_canvas_set_buffer(camera_canvas, lcd_buffer, current_width, current_height, LV_IMG_CF_TRUE_COLOR); + lv_label_set_text_fmt(label, "#FF0000 %d*%d#", current_width, current_height); + bsp_display_unlock(); +} + +static void camera_frame_cb(uvc_frame_t *frame, void *ptr) +{ + ESP_LOGI(TAG, "uvc callback! frame_format = %d, seq = %" PRIu32 ", width = %" PRIu32 ", height = %" PRIu32 ", length = %u, ptr = %d", + frame->frame_format, frame->sequence, frame->width, frame->height, frame->data_bytes, (int)ptr); + if (current_width != frame->width || current_height != frame->height) + { + current_width = frame->width; + current_height = frame->height; + adaptive_jpg_frame_buffer(current_width * current_height * 2); + } + + esp_jpeg_decoder_one_picture((uint8_t *)frame->data, frame->data_bytes, jpg_frame_buf); + _camera_display(jpg_frame_buf); + vTaskDelay(pdMS_TO_TICKS(1)); +} + + +static esp_err_t _display_init(void) +{ + bsp_display_start(); + bsp_display_backlight_on(); // Set display brightness to 100% + bsp_display_lock(0); + camera_canvas = lv_canvas_create(lv_scr_act()); + assert(camera_canvas); + lv_obj_center(camera_canvas); + label = lv_label_create(lv_scr_act()); + lv_label_set_recolor(label, true); + lv_obj_set_pos(label, 0, 0); + lv_label_set_text(label, "Insert a camera, press boot for resolution."); + bsp_display_unlock(); + return ESP_OK; +} + +static void _get_value_from_nvs(char *key, void *value, size_t *size) +{ + nvs_handle_t my_handle; + esp_err_t err = nvs_open("memory", NVS_READWRITE, &my_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err)); + } + else + { + err = nvs_get_blob(my_handle, key, value, size); + switch (err) + { + case ESP_OK: + break; + case ESP_ERR_NVS_NOT_FOUND: + ESP_LOGI(TAG, "%s is not initialized yet!", key); + break; + default: + ESP_LOGE(TAG, "Error (%s) reading!\n", esp_err_to_name(err)); + } + + nvs_close(my_handle); + } +} + +static esp_err_t _set_value_to_nvs(char *key, void *value, size_t size) +{ + nvs_handle_t my_handle; + esp_err_t err = nvs_open("memory", NVS_READWRITE, &my_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err)); + return ESP_FAIL; + } + else + { + err = nvs_set_blob(my_handle, key, value, size); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "NVS set failed %s", esp_err_to_name(err)); + } + + err = nvs_commit(my_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "NVS commit failed"); + } + + nvs_close(my_handle); + } + + return err; +} + +static esp_err_t _usb_stream_init(void) +{ + uvc_config_t uvc_config = { + .frame_interval = FRAME_INTERVAL_FPS_30, + .xfer_buffer_size = DEMO_UVC_XFER_BUFFER_SIZE, + .xfer_buffer_a = xfer_buffer_a, + .xfer_buffer_b = xfer_buffer_b, + .frame_buffer_size = DEMO_UVC_XFER_BUFFER_SIZE, + .frame_buffer = frame_buffer, + .frame_cb = &camera_frame_cb, + .frame_cb_arg = NULL, + .frame_width = FRAME_RESOLUTION_ANY, + .frame_height = FRAME_RESOLUTION_ANY, + .flags = FLAG_UVC_SUSPEND_AFTER_START, + }; + + esp_err_t ret = uvc_streaming_config(&uvc_config); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "uvc streaming config failed"); + } + return ret; +} + +static size_t _find_current_resolution(camera_frame_size_t *camera_frame_size) +{ + if (camera_resolution_info.camera_frame_list == NULL) + { + return -1; + } + + size_t i = 0; + while (i < camera_resolution_info.camera_frame_list_num) + { + if (camera_frame_size->width >= camera_resolution_info.camera_frame_list[i].width && camera_frame_size->height >= camera_resolution_info.camera_frame_list[i].height) + { + /* Find next resolution + If current resolution is the min resolution, switch to the max resolution*/ + camera_frame_size->width = camera_resolution_info.camera_frame_list[i].width; + camera_frame_size->height = camera_resolution_info.camera_frame_list[i].height; + break; + } + else if (i == camera_resolution_info.camera_frame_list_num - 1) + { + camera_frame_size->width = camera_resolution_info.camera_frame_list[i].width; + camera_frame_size->height = camera_resolution_info.camera_frame_list[i].height; + break; + } + i++; + } + ESP_LOGI(TAG, "Current resolution is %dx%d", camera_frame_size->width, camera_frame_size->height); + return i; +} + +static void switch_button_press_down_cb(void *arg, void *data) +{ + if (camera_resolution_info.camera_frame_list == NULL || xEventGroupWaitBits(s_evt_handle, BIT0_FRAME_START, false, false, pdMS_TO_TICKS(10)) != pdTRUE) + { + return; + } + + ESP_LOGI(TAG, "old resolution is %d*%d", camera_resolution_info.camera_frame_size.width, camera_resolution_info.camera_frame_size.height); + if (++camera_resolution_info.camera_currect_frame_index >= camera_resolution_info.camera_frame_list_num) + { + camera_resolution_info.camera_currect_frame_index = 0; + } + camera_resolution_info.camera_frame_size.width = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width; + camera_resolution_info.camera_frame_size.height = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height; + ESP_LOGI(TAG, "currect resolution is %d*%d", camera_resolution_info.camera_frame_size.width, camera_resolution_info.camera_frame_size.height); + + /* Save the new camera resolution to nsv */ + usb_streaming_control(STREAM_UVC, CTRL_SUSPEND, NULL); + ESP_ERROR_CHECK(uvc_frame_size_reset(camera_resolution_info.camera_frame_size.width, + camera_resolution_info.camera_frame_size.height, + FPS2INTERVAL(30))); + ESP_ERROR_CHECK(_set_value_to_nvs(DEMO_KEY_RESOLUTION, &camera_resolution_info.camera_frame_size, sizeof(camera_frame_size_t))); + usb_streaming_control(STREAM_UVC, CTRL_RESUME, NULL); +} + +static esp_err_t _switch_button_init(void) +{ + button_config_t button_config = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = DEMO_SWITCH_BUTTON_IO, + .active_level = 0, + }, + }; + + button_handle_t button_handle = iot_button_create(&button_config); + assert(button_handle != NULL); + esp_err_t ret = iot_button_register_cb(button_handle, BUTTON_PRESS_DOWN, switch_button_press_down_cb, NULL); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "button register callback fail"); + } + return ret; +} + +static void _stream_state_changed_cb(usb_stream_state_t event, void *arg) +{ + switch (event) + { + case STREAM_CONNECTED: + { + /* Get camera resolution in nvs*/ + size_t size = sizeof(camera_frame_size_t); + _get_value_from_nvs(DEMO_KEY_RESOLUTION, &camera_resolution_info.camera_frame_size, &size); + size_t frame_index = 0; + uvc_frame_size_list_get(NULL, &camera_resolution_info.camera_frame_list_num, NULL); + if (camera_resolution_info.camera_frame_list_num) + { + ESP_LOGI(TAG, "UVC: get frame list size = %u, current = %u", camera_resolution_info.camera_frame_list_num, frame_index); + uvc_frame_size_t *_frame_list = (uvc_frame_size_t *)malloc(camera_resolution_info.camera_frame_list_num * sizeof(uvc_frame_size_t)); + + camera_resolution_info.camera_frame_list = (uvc_frame_size_t *)realloc(camera_resolution_info.camera_frame_list, camera_resolution_info.camera_frame_list_num * sizeof(uvc_frame_size_t)); + if (NULL == camera_resolution_info.camera_frame_list) + { + ESP_LOGE(TAG, "camera_resolution_info.camera_frame_list"); + } + uvc_frame_size_list_get(_frame_list, NULL, NULL); + for (size_t i = 0; i < camera_resolution_info.camera_frame_list_num; i++) + { + if (_frame_list[i].width <= DEMO_MAX_H && _frame_list[i].height <= DEMO_MAX_V) + { + camera_resolution_info.camera_frame_list[frame_index++] = _frame_list[i]; + ESP_LOGI(TAG, "\tpick frame[%u] = %ux%u", i, _frame_list[i].width, _frame_list[i].height); + } + else + { + ESP_LOGI(TAG, "\tdrop frame[%u] = %ux%u", i, _frame_list[i].width, _frame_list[i].height); + } + } + camera_resolution_info.camera_frame_list_num = frame_index; + if (camera_resolution_info.camera_frame_size.width != 0 && camera_resolution_info.camera_frame_size.height != 0) + { + camera_resolution_info.camera_currect_frame_index = _find_current_resolution(&camera_resolution_info.camera_frame_size); + } + else + { + camera_resolution_info.camera_currect_frame_index = 0; + } + + if (-1 == camera_resolution_info.camera_currect_frame_index) + { + ESP_LOGE(TAG, "fine current resolution fail"); + break; + } + ESP_ERROR_CHECK(uvc_frame_size_reset(camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width, + camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height, FPS2INTERVAL(30))); + camera_frame_size_t camera_frame_size = { + .width = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].width, + .height = camera_resolution_info.camera_frame_list[camera_resolution_info.camera_currect_frame_index].height, + }; + ESP_ERROR_CHECK(_set_value_to_nvs(DEMO_KEY_RESOLUTION, &camera_frame_size, sizeof(camera_frame_size_t))); + + if (_frame_list != NULL) + { + free(_frame_list); + } + /* Wait USB Camera connected */ + usb_streaming_control(STREAM_UVC, CTRL_RESUME, NULL); + xEventGroupSetBits(s_evt_handle, BIT0_FRAME_START); + } + else + { + ESP_LOGW(TAG, "UVC: get frame list size = %u", camera_resolution_info.camera_frame_list_num); + } + ESP_LOGI(TAG, "Device connected"); + break; + } + case STREAM_DISCONNECTED: + xEventGroupClearBits(s_evt_handle, BIT0_FRAME_START); + ESP_LOGI(TAG, "Device disconnected"); + break; + default: + ESP_LOGE(TAG, "Unknown event"); + break; + } +} + +void app_main(void) +{ + esp_log_level_set("*", ESP_LOG_INFO); + /* Initialize NVS */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + /* NVS partition was truncated and needs to be erased */ + ESP_ERROR_CHECK(nvs_flash_erase()); + /* Retry nvs_flash_init */ + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /* Create event group */ + s_evt_handle = xEventGroupCreate(); + if (s_evt_handle == NULL) + { + ESP_LOGE(TAG, "line-%u event group create failed", __LINE__); + assert(0); + } + + /* malloc double buffer for usb payload, xfer_buffer_size >= frame_buffer_size*/ + // xfer_buffer_a = (uint8_t *)heap_caps_calloc(1, DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + xfer_buffer_a = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + assert(xfer_buffer_a != NULL); + xfer_buffer_b = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + assert(xfer_buffer_b != NULL); + + /* malloc frame buffer for a jpeg frame*/ + frame_buffer = (uint8_t *)heap_caps_malloc(DEMO_UVC_XFER_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + assert(frame_buffer != NULL); + + /* Initialize the screen */ + ESP_ERROR_CHECK(_display_init()); + + /* Initialize the button to switch resolution */ + ESP_ERROR_CHECK(_switch_button_init()); + + /* Initialize the screen according to the resolution stored in nvs */ + ESP_ERROR_CHECK(_usb_stream_init()); + + /* Register a callback to control uvc frame size */ + ESP_ERROR_CHECK(usb_streaming_state_register(&_stream_state_changed_cb, NULL)); + + /* Start stream with pre-configs, usb stream driver will create multi-tasks internal + to handle usb data from different pipes, and user's callback will be called after new frame ready. */ + ESP_ERROR_CHECK(usb_streaming_start()); + ESP_ERROR_CHECK(usb_streaming_connect_wait(portMAX_DELAY)); +} diff --git a/examples/usb_camera_lcd_display/sdkconfig.defaults b/examples/usb_camera_lcd_display/sdkconfig.defaults new file mode 100644 index 0000000..13e5563 --- /dev/null +++ b/examples/usb_camera_lcd_display/sdkconfig.defaults @@ -0,0 +1,51 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_IDF_TARGET_ESP32S3=y + +# +# ESP System Settings +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y + +# +# FreeRTOS +# + +# +# Kernel +# +CONFIG_FREERTOS_HZ=1000 + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_PERF=y + +# +# ESP PSRAM +# +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + +# +# SPI RAM config +# +CONFIG_SPIRAM_MODE_OCT=y + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + +# +# LCD +# +CONFIG_BSP_LCD_RGB_BUFFER_NUMS=2 + +CONFIG_BSP_LCD_DRAW_BUF_HEIGHT=150 +CONFIG_BSP_LCD_DRAW_BUF_DOUBLE=y +CONFIG_LV_COLOR_16_SWAP=y diff --git a/examples/usb_camera_lcd_display/sdkconfig.psram_octal_120m b/examples/usb_camera_lcd_display/sdkconfig.psram_octal_120m new file mode 100644 index 0000000..17ad577 --- /dev/null +++ b/examples/usb_camera_lcd_display/sdkconfig.psram_octal_120m @@ -0,0 +1,16 @@ +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y + +# +# SPI RAM config +# +CONFIG_SPIRAM_SPEED_120M=y + +# +# Cache config +# +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y diff --git a/tools/build_apps.py b/tools/build_apps.py new file mode 100644 index 0000000..5314898 --- /dev/null +++ b/tools/build_apps.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +""" +This file is used in CI generate binary files for different kinds of apps +""" + +import argparse +import sys +import os +import re +from pathlib import Path +from typing import List + +from idf_build_apps import LOGGER, App, build_apps, find_apps, setup_logging + +PROJECT_ROOT = Path(__file__).parent.parent.absolute() +APPS_BUILD_PER_JOB = 30 +IGNORE_WARNINGS = [ + +] + +def _get_idf_version(): + if os.environ.get('IDF_VERSION'): + return os.environ.get('IDF_VERSION') + version_path = os.path.join(os.environ['IDF_PATH'], 'tools/cmake/version.cmake') + regex = re.compile(r'^\s*set\s*\(\s*IDF_VERSION_([A-Z]{5})\s+(\d+)') + ver = {} + with open(version_path) as f: + for line in f: + m = regex.match(line) + if m: + ver[m.group(1)] = m.group(2) + return '{}.{}'.format(int(ver['MAJOR']), int(ver['MINOR'])) + +def get_cmake_apps( + paths, + target, + config_rules_str, + default_build_targets, +): # type: (List[str], str, List[str]) -> List[App] + idf_ver = _get_idf_version() + apps = find_apps( + paths, + recursive=True, + target=target, + build_dir=f'{idf_ver}/build_@t_@w', + config_rules_str=config_rules_str, + build_log_path='build_log.txt', + size_json_path='size.json', + check_warnings=True, + preserve=True, + default_build_targets=default_build_targets, + manifest_files=[ + ], + ) + return apps + + +def main(args): # type: (argparse.Namespace) -> None + default_build_targets = args.default_build_targets.split(',') if args.default_build_targets else None + apps = get_cmake_apps(args.paths, args.target, args.config, default_build_targets) + if args.exclude_apps: + apps_to_build = [app for app in apps if app.name not in args.exclude_apps] + else: + apps_to_build = apps[:] + + LOGGER.info('Found %d apps after filtering', len(apps_to_build)) + LOGGER.info( + 'Suggest setting the parallel count to %d for this build job', + len(apps_to_build) // APPS_BUILD_PER_JOB + 1, + ) + + ret_code = build_apps( + apps_to_build, + parallel_count=args.parallel_count, + parallel_index=args.parallel_index, + dry_run=False, + collect_size_info=args.collect_size_info, + keep_going=True, + ignore_warning_strs=IGNORE_WARNINGS, + copy_sdkconfig=True, + ) + + sys.exit(ret_code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Build all the apps for different test types. Will auto remove those non-test apps binaries', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument('paths', nargs='*', help='Paths to the apps to build.') + parser.add_argument( + '-t', '--target', + default='all', + help='Build apps for given target. could pass "all" to get apps for all targets', + ) + parser.add_argument( + '--config', + default=['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'], + action='append', + help='Adds configurations (sdkconfig file names) to build. This can either be ' + 'FILENAME[=NAME] or FILEPATTERN. FILENAME is the name of the sdkconfig file, ' + 'relative to the project directory, to be used. Optional NAME can be specified, ' + 'which can be used as a name of this configuration. FILEPATTERN is the name of ' + 'the sdkconfig file, relative to the project directory, with at most one wildcard. ' + 'The part captured by the wildcard is used as the name of the configuration.', + ) + parser.add_argument( + '--parallel-count', default=1, type=int, help='Number of parallel build jobs.' + ) + parser.add_argument( + '--parallel-index', + default=1, + type=int, + help='Index (1-based) of the job, out of the number specified by --parallel-count.', + ) + parser.add_argument( + '--collect-size-info', + type=argparse.FileType('w'), + help='If specified, the test case name and size info json will be written to this file', + ) + parser.add_argument( + '--exclude-apps', + nargs='*', + help='Exclude build apps', + ) + parser.add_argument( + '--default-build-targets', + default=None, + help='default build targets used in manifest files', + ) + parser.add_argument( + '-v', '--verbose', + action='count', default=0, + help='Show verbose log message', + ) + + arguments = parser.parse_args() + if not arguments.paths: + arguments.paths = [PROJECT_ROOT] + setup_logging(verbose=arguments.verbose) # Info + main(arguments)