From fa54de736e97e6aa85568308c8107cb74948bf40 Mon Sep 17 00:00:00 2001 From: houhaiyan Date: Wed, 4 Jun 2025 21:59:27 +0800 Subject: [PATCH] feat(esp_h264): Added esp_h264 encoder and decoder --- examples/peripherals/.build-test-rules.yml | 7 + examples/peripherals/h264/CMakeLists.txt | 7 + examples/peripherals/h264/README.md | 153 ++++++++ examples/peripherals/h264/main/CMakeLists.txt | 2 + .../peripherals/h264/main/Kconfig.projbuild | 96 +++++ .../peripherals/h264/main/esp_h264_enc_dec.c | 233 ++++++++++++ .../peripherals/h264/main/idf_component.yml | 4 + .../peripherals/h264/main/video_pattern.c | 358 ++++++++++++++++++ .../peripherals/h264/main/video_pattern.h | 64 ++++ examples/peripherals/h264/pytest_esp_h264.py | 17 + examples/peripherals/h264/sdkconfig.defaults | 17 + .../h264/sdkconfig.defaults.esp32p4 | 7 + .../h264/sdkconfig.defaults.esp32s3 | 6 + 13 files changed, 971 insertions(+) create mode 100644 examples/peripherals/h264/CMakeLists.txt create mode 100644 examples/peripherals/h264/README.md create mode 100644 examples/peripherals/h264/main/CMakeLists.txt create mode 100644 examples/peripherals/h264/main/Kconfig.projbuild create mode 100644 examples/peripherals/h264/main/esp_h264_enc_dec.c create mode 100644 examples/peripherals/h264/main/idf_component.yml create mode 100644 examples/peripherals/h264/main/video_pattern.c create mode 100644 examples/peripherals/h264/main/video_pattern.h create mode 100644 examples/peripherals/h264/pytest_esp_h264.py create mode 100644 examples/peripherals/h264/sdkconfig.defaults create mode 100644 examples/peripherals/h264/sdkconfig.defaults.esp32p4 create mode 100644 examples/peripherals/h264/sdkconfig.defaults.esp32s3 diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 95a61b9d6c..b82a104f53 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -80,6 +80,13 @@ examples/peripherals/gpio/matrix_keyboard: enable: - if: IDF_TARGET == "esp32s2" +examples/peripherals/h264: + enable: + - if: IDF_TARGET in ["esp32p4", "esp32s3"] + reason: only supports esp32p4 and esp32s3 + depends_components: + - esp_h264 + examples/peripherals/i2c/i2c_basic: disable: - if: SOC_I2C_SUPPORTED != 1 diff --git a/examples/peripherals/h264/CMakeLists.txt b/examples/peripherals/h264/CMakeLists.txt new file mode 100644 index 0000000000..9b2493c6f4 --- /dev/null +++ b/examples/peripherals/h264/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists.txt file. + +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp_h264_example) diff --git a/examples/peripherals/h264/README.md b/examples/peripherals/h264/README.md new file mode 100644 index 0000000000..3ae5e53b53 --- /dev/null +++ b/examples/peripherals/h264/README.md @@ -0,0 +1,153 @@ +| Supported Targets | ESP32-P4 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# H.264 Encoder-Decoder Example + +## Overview + +This example demonstrates how to use H.264 hardware/software encoder and decoder with visual pattern generation: + +- Generate colorful test patterns for video processing +- Encode video frames using H.264 codec (hardware on ESP32-P4, software on ESP32-S3) +- Decode the encoded frames back to original format using software decoder +- Display visual comparison between source and decoded images + +The example supports multiple YUV formats and provides side-by-side colorized display in the console. All encoding parameters are configurable through the ESP-IDF menuconfig system. + +## Configuration + +This example provides comprehensive configuration options through `idf.py menuconfig`: + +### H.264 Encoder Type Selection +- **Hardware Encoder**: Available only on ESP32-P4, provides better performance and lower power consumption +- **Software Encoder**: Available on all targets (ESP32-S3, ESP32-P4), uses more CPU resources + +### Configurable Parameters +All parameters can be adjusted in "H.264 Example Configuration" menu: + +- **Video Width**: 64-1920 pixels (default: 320) +- **Video Height**: 64-1080 pixels (default: 240) +- **Frame Rate**: 1-60 fps (default: 30 for hardware, 15 for software) +- **Bitrate**: 64K-10M bps (default: 512K for hardware, 256K for software) +- **GOP Size**: 1-255 frames (default: 30) +- **QP Value**: 10-51 (default: 26 for hardware, 28 for software) + +### Target-Specific Defaults +- **ESP32-P4**: Optimized for hardware encoding with higher performance settings +- **ESP32-S3**: Optimized for software encoding with conservative settings + +## How to use example + +### Prerequisites Required + +This example requires: +- ESP32-P4 development board (for hardware encoding) or ESP32-S3 development board (for software encoding) +- USB cable for programming and power supply +- Terminal that supports ANSI color codes for proper visual output + +### Configure the Example + +Before building, configure the example parameters: + +```bash +idf.py menuconfig +``` + +Navigate to: `Component config` → `H.264 Example Configuration` + +1. **Select Encoder Type**: Choose between Hardware (ESP32-P4 only) or Software encoder +2. **Adjust Parameters**: Configure video resolution, frame rate, bitrate, etc. +3. **Save and Exit**: Press 'S' to save configuration + +### Build and Flash + +For ESP32-P4 (with hardware encoding support): +```bash +idf.py set-target esp32p4 +idf.py menuconfig # Configure as needed +idf.py build +idf.py -p PORT flash monitor +``` + +For ESP32-S3 (software encoding only): +```bash +idf.py set-target esp32s3 +idf.py menuconfig # Software encoder will be automatically selected +idf.py build +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```bash +I (1555) H264_ENC_DEC: H264 Example starting: 320x240 @ 30fps +I (1565) H264_ENC_DEC: Encoder: Hardware, Decoder: Software +I (1575) H264_ENC_DEC: Config: GOP=30, Bitrate=512000 bps, QP=26 +I (1585) H264_DEC.SW: tinyh264 version: 1c7f584 +I (1585) H264_ENC_DEC: H264 encode-decode loop started (320x240 @ 30fps) + +Frame 0: source image | decoded image +[Colorized bar patterns displayed side by side using ANSI colors] + +Frame 1: source image | decoded image +[Colorized bar patterns displayed side by side using ANSI colors] + +Frame 2: source image | decoded image +[Colorized bar patterns displayed side by side using ANSI colors] +... + +I (21465) H264_ENC_DEC: H264 process Completed successfully +I (21475) main_task: Returned from app_main() +``` + +*Note: The exact values shown will depend on your menuconfig settings.* + +## Video Format Support + +- **ESP_H264_RAW_FMT_I420**: Planar YUV 4:2:0 format (decoder output, software encoder input) +- **ESP_H264_RAW_FMT_O_UYY_E_VYY**: Interlaced YUV format (hardware encoder input on ESP32-P4) + +## Performance Recommendations + +### For ESP32-P4 (Hardware Encoding): +- Resolution: Up to 1920x1080 supported +- Frame Rate: 30-60 fps achievable +- Bitrate: 512K-5M bps recommended +- QP: 20-30 for optimal quality/performance balance + +### For ESP32-S3 (Software Encoding): +- Resolution: 320x240 or smaller recommended +- Frame Rate: 10-15 fps for stable performance +- Bitrate: 256K-1M bps recommended +- QP: 28-35 for better performance + +## Troubleshooting + +**Configuration Issues:** +- Use `idf.py menuconfig` to verify H.264 settings before building +- Ensure hardware encoder is only selected for ESP32-P4 target + +**Memory allocation failures:** +- Reduce resolution or frame rate in menuconfig +- Ensure sufficient SPIRAM is available +- Check ESP-IDF memory configuration + +**Encoding/decoding errors:** +- Verify the correct target is selected (ESP32-P4 for hardware) +- Check that H.264 component is properly configured in menuconfig +- Adjust bitrate settings for your resolution/frame rate combination + +**Performance Issues:** +- Lower resolution, frame rate, or bitrate for software encoding +- Use hardware encoder on ESP32-P4 for better performance +- Increase QP value to reduce computational load + +**Visual output issues:** +- Ensure your terminal supports ANSI color codes +- Try different terminal applications if colors don't display properly + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/peripherals/h264/main/CMakeLists.txt b/examples/peripherals/h264/main/CMakeLists.txt new file mode 100644 index 0000000000..03bd31cb03 --- /dev/null +++ b/examples/peripherals/h264/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "./" + INCLUDE_DIRS "./") diff --git a/examples/peripherals/h264/main/Kconfig.projbuild b/examples/peripherals/h264/main/Kconfig.projbuild new file mode 100644 index 0000000000..f5980f6bd3 --- /dev/null +++ b/examples/peripherals/h264/main/Kconfig.projbuild @@ -0,0 +1,96 @@ +menu "H.264 Example Configuration" + + choice H264_ENCODER_TYPE + prompt "H.264 Encoder Type" + default H264_ENCODER_HARDWARE if IDF_TARGET_ESP32P4 + default H264_ENCODER_SOFTWARE + help + Select the H.264 encoder type to use. + Hardware encoder is only available on ESP32P4 and provides + better performance and lower power consumption. + Software encoder is available on all targets but requires + more CPU resources. + + config H264_ENCODER_HARDWARE + bool "Hardware Encoder (ESP32P4 only)" + depends on IDF_TARGET_ESP32P4 + help + Use hardware H.264 encoder. + This option is only available on ESP32P4 which has + dedicated H.264 hardware encoding capabilities. + Provides better performance and lower power consumption + compared to software encoding. + + config H264_ENCODER_SOFTWARE + bool "Software Encoder" + help + Use software H.264 encoder using OpenH264 library. + Available on all supported targets (ESP32S3, ESP32P4) + but requires more CPU resources and power consumption + compared to hardware encoding. + endchoice + + menu "H.264 Encoder Parameters" + + config H264_ENCODER_WIDTH + int "Video Width" + range 64 1920 + default 320 + help + Video frame width in pixels. + Must be multiple of 16 for optimal performance. + Recommended values: 128, 160, 320, 640, 1280. + + config H264_ENCODER_HEIGHT + int "Video Height" + range 64 1080 + default 240 + help + Video frame height in pixels. + Must be multiple of 16 for optimal performance. + Recommended values: 96, 120, 240, 480, 720. + + config H264_ENCODER_FPS + int "Frame Rate (FPS)" + range 1 60 + default 30 if H264_ENCODER_HARDWARE + default 15 if H264_ENCODER_SOFTWARE + help + Video frame rate in frames per second. + Hardware encoder can support higher frame rates. + Software encoder performance depends on CPU capability. + + config H264_ENCODER_BITRATE + int "Bitrate (bps)" + range 64000 10000000 + default 512000 if H264_ENCODER_HARDWARE + default 256000 if H264_ENCODER_SOFTWARE + help + Video bitrate in bits per second. + Higher bitrate provides better quality but larger file size. + Hardware encoder can handle higher bitrates more efficiently. + Typical values: 256K-1M for low quality, 1M-5M for high quality. + + config H264_ENCODER_GOP_SIZE + int "GOP Size" + range 1 255 + default 30 + help + Group of Pictures size. Determines the frequency + of I-frames in the video stream. + Larger GOP size = better compression, higher latency. + Smaller GOP size = lower compression, lower latency. + + config H264_ENCODER_QP_VALUE + int "Quantization Parameter (QP)" + range 10 51 + default 26 if H264_ENCODER_HARDWARE + default 28 if H264_ENCODER_SOFTWARE + help + Quantization parameter that controls video quality. + Lower values = higher quality, larger file size. + Higher values = lower quality, smaller file size. + Hardware encoder can handle lower QP values more efficiently. + + endmenu +endmenu diff --git a/examples/peripherals/h264/main/esp_h264_enc_dec.c b/examples/peripherals/h264/main/esp_h264_enc_dec.c new file mode 100644 index 0000000000..dc461d5c5f --- /dev/null +++ b/examples/peripherals/h264/main/esp_h264_enc_dec.c @@ -0,0 +1,233 @@ +/** + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "esp_h264_alloc.h" +#include "esp_h264_dec_sw.h" +#if CONFIG_H264_ENCODER_HARDWARE +#include "esp_h264_enc_single_hw.h" +#else +#include "esp_h264_enc_single_sw.h" +#endif /* CONFIG_H264_ENCODER_HARDWARE */ +#include "video_pattern.h" +#include "esp_log.h" + +static const char *TAG = "H264_ENC_DEC"; + +#define FRAME_MAX_NUM 10 + +// Helper function to allocate aligned memory with error checking +static void *allocate_frame_buffer(size_t size, uint32_t *actual_size, const char *buffer_name) +{ + void *buffer = esp_h264_aligned_calloc(16, 1, size, actual_size, ESP_H264_MEM_SPIRAM); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate %s buffer memory (%zu bytes)", buffer_name, size); + } + return buffer; +} + +// Helper function to initialize pattern info +static void init_pattern_info(pattern_info_t *pattern, uint32_t width, uint32_t height, uint32_t format_id) +{ + pattern->res.width = width; + pattern->res.height = height; + pattern->format_id = format_id; + pattern->vertical = false; + pattern->bar_count = 16; + pattern->data_size = width * height * 3 / 2; +} + +/* + This function is used to encode and decode a single frame. + src_frame --> encoder --> enc_frame(dec_input) --> decoder --> dest_frame(out_pattern) +*/ + +#if CONFIG_H264_ENCODER_HARDWARE +esp_h264_err_t single_enc_dec_process(esp_h264_enc_cfg_hw_t enc_cfg, esp_h264_dec_cfg_sw_t dec_cfg) +#else +esp_h264_err_t single_enc_dec_process(esp_h264_enc_cfg_sw_t enc_cfg, esp_h264_dec_cfg_sw_t dec_cfg) +#endif /* CONFIG_H264_ENCODER_HARDWARE */ +{ + int frame_num = 0; + // Frame buffers - Fixed types to match decoder expectations + esp_h264_enc_in_frame_t src_frame = {0}; // Original input frame + esp_h264_enc_out_frame_t enc_frame = {0}; // Encoded frame output + esp_h264_dec_in_frame_t dec_input = {0}; // Decoder input frame (fixed type) + esp_h264_dec_out_frame_t dest_frame = {0}; // Decoded frame output (fixed type) + + // Handles and variables + esp_h264_err_t ret = ESP_H264_ERR_OK; + esp_h264_enc_handle_t enc = NULL; + esp_h264_dec_handle_t dec = NULL; + + // Pattern info structures + pattern_info_t in_pattern = {}; + pattern_info_t out_pattern = {}; + + size_t frame_size = enc_cfg.res.width * enc_cfg.res.height; + size_t pixel_bits = 12; // 12 bits per pixel for YUV420 + if (enc_cfg.pic_type == ESP_H264_RAW_FMT_YUYV) { + // Calculate frame size + pixel_bits = 16; // 16 bits per pixel for YUYV + } + frame_size *= pixel_bits; + + // Initialize pattern configurations + init_pattern_info(&in_pattern, enc_cfg.res.width, enc_cfg.res.height, enc_cfg.pic_type); + init_pattern_info(&out_pattern, enc_cfg.res.width, enc_cfg.res.height, dec_cfg.pic_type); + + // Allocate frame buffers + src_frame.raw_data.buffer = allocate_frame_buffer(frame_size, &src_frame.raw_data.len, "source frame"); + if (!src_frame.raw_data.buffer) { + goto cleanup; + } + // Because of the different bitrate, the encoded frame buffer size is different. + // It uses the same buffer size as the source frame to avoid not enough buffer error. + enc_frame.raw_data.buffer = allocate_frame_buffer(frame_size, &enc_frame.raw_data.len, "encoded frame"); + if (!enc_frame.raw_data.buffer) { + goto cleanup; + } + + // Setup decoder input frame (correct structure for decoder) + dec_input.raw_data.buffer = enc_frame.raw_data.buffer; + + // Assign pattern pixel buffers + in_pattern.pixel = src_frame.raw_data.buffer; + + // Initialize H264 encoder +#if CONFIG_H264_ENCODER_HARDWARE + ret = esp_h264_enc_hw_new(&enc_cfg, &enc); +#else + ret = esp_h264_enc_sw_new(&enc_cfg, &enc); +#endif /* CONFIG_H264_ENCODER_HARDWARE */ + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "Failed to create H264 encoder (error: %d)", ret); + goto cleanup; + } + + ret = esp_h264_enc_open(enc); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "Failed to open H264 encoder (error: %d)", ret); + goto cleanup; + } + + // Initialize H264 decoder + ret = esp_h264_dec_sw_new(&dec_cfg, &dec); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "Failed to create H264 decoder (error: %d)", ret); + goto cleanup; + } + + ret = esp_h264_dec_open(dec); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "Failed to open H264 decoder (error: %d)", ret); + goto cleanup; + } + + ESP_LOGI(TAG, "H264 encode-decode loop started (%dx%d @ %dfps)", + enc_cfg.res.width, enc_cfg.res.height, enc_cfg.fps); + + while (1) { + // Generate input pattern + gen_pattern_color_bar(&in_pattern); + // Encode frame + ret = esp_h264_enc_process(enc, &src_frame, &enc_frame); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "H264 encoding failed (error: %d)", ret); + break; + } + //update decoder input + dec_input.raw_data.len = enc_frame.length; + // Decode frame + ret = esp_h264_dec_process(dec, &dec_input, &dest_frame); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "H264 decoding failed (error: %d)", ret); + break; + } + out_pattern.pixel = dest_frame.outbuf; + // Display conversion result + draw_convert_result(&in_pattern, &out_pattern); + printf("\nFrame %d: source image | decoded image\n", frame_num); + frame_num++; + if (frame_num >= FRAME_MAX_NUM) { + break; + } + } + +cleanup: + // Cleanup encoder + esp_h264_enc_close(enc); + esp_h264_enc_del(enc); + // Cleanup decoder + esp_h264_dec_close(dec); + esp_h264_dec_del(dec); + // Free memory buffers + if (src_frame.raw_data.buffer) { + esp_h264_free(src_frame.raw_data.buffer); + } + if (enc_frame.raw_data.buffer) { + esp_h264_free(enc_frame.raw_data.buffer); + } + ESP_LOGI(TAG, "H264 process %s", (ret == ESP_H264_ERR_OK) ? "Completed successfully" : "Failed"); + return ret; +} + +void app_main(void) +{ + +#if CONFIG_H264_ENCODER_HARDWARE + esp_h264_enc_cfg_hw_t enc_cfg = { + .gop = CONFIG_H264_ENCODER_GOP_SIZE, + .fps = CONFIG_H264_ENCODER_FPS, + .res = {.width = CONFIG_H264_ENCODER_WIDTH, .height = CONFIG_H264_ENCODER_HEIGHT}, + .rc = { + .bitrate = CONFIG_H264_ENCODER_BITRATE, + .qp_min = CONFIG_H264_ENCODER_QP_VALUE, + .qp_max = CONFIG_H264_ENCODER_QP_VALUE + }, + .pic_type = ESP_H264_RAW_FMT_O_UYY_E_VYY, + }; +#else + esp_h264_enc_cfg_sw_t enc_cfg = { + .gop = CONFIG_H264_ENCODER_GOP_SIZE, + .fps = CONFIG_H264_ENCODER_FPS, + .res = {.width = CONFIG_H264_ENCODER_WIDTH, .height = CONFIG_H264_ENCODER_HEIGHT}, + .rc = { + .bitrate = CONFIG_H264_ENCODER_BITRATE, + .qp_min = CONFIG_H264_ENCODER_QP_VALUE, + .qp_max = CONFIG_H264_ENCODER_QP_VALUE + }, + .pic_type = ESP_H264_RAW_FMT_I420, + }; +#endif /* CONFIG_H264_ENCODER_HARDWARE */ + + // Always use software decoder since decoder choice was removed + esp_h264_dec_cfg_sw_t dec_cfg = { + .pic_type = ESP_H264_RAW_FMT_I420, + }; + + ESP_LOGI(TAG, "H264 Example starting: %dx%d @ %dfps", + CONFIG_H264_ENCODER_WIDTH, CONFIG_H264_ENCODER_HEIGHT, CONFIG_H264_ENCODER_FPS); + ESP_LOGI(TAG, "Encoder: %s, Decoder: Software", +#if CONFIG_H264_ENCODER_HARDWARE + "Hardware" +#else + "Software" +#endif + ); + // Fixed format specifiers to use PRIu32 for uint32_t values + ESP_LOGI(TAG, "Config: GOP=%d, Bitrate=%" PRIu32 " bps, QP=%d", + CONFIG_H264_ENCODER_GOP_SIZE, CONFIG_H264_ENCODER_BITRATE, CONFIG_H264_ENCODER_QP_VALUE); + + // Start encode-decode process + esp_h264_err_t ret = single_enc_dec_process(enc_cfg, dec_cfg); + if (ret != ESP_H264_ERR_OK) { + ESP_LOGE(TAG, "H264 example failed with error: %d", ret); + } +} diff --git a/examples/peripherals/h264/main/idf_component.yml b/examples/peripherals/h264/main/idf_component.yml new file mode 100644 index 0000000000..1a910d9301 --- /dev/null +++ b/examples/peripherals/h264/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/esp_h264: "^1.0.4" + idf: + version: ">=5.3.0" diff --git a/examples/peripherals/h264/main/video_pattern.c b/examples/peripherals/h264/main/video_pattern.c new file mode 100644 index 0000000000..c376eb9ce9 --- /dev/null +++ b/examples/peripherals/h264/main/video_pattern.c @@ -0,0 +1,358 @@ +/** + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file video_pattern.c + * @brief Video pattern generation and processing implementation + * + * This file implements functions for generating test video patterns + * and displaying conversion results for H264 codec testing. + */ + +#include "video_pattern.h" + +#define GET_RGB565_R(x) (((((x) >> 11) & 0x1F) << 3) | (((x) >> 8) & 0x07)) +#define GET_RGB565_G(x) (((((x) >> 5) & 0x3F) << 2) | (((x) >> 3) & 0x03)) +#define GET_RGB565_B(x) ((((x) & 0x1F) << 3) | (((x) & 0x1C) >> 2)) +#define SWAP_EDIAN(x) (((x) << 8) | ((x) >> 8)) +#define CLAMP(x) ((x) < 0 ? 0 : ((x) > 255 ? 255 : (x))) + +/** + * @brief YUV pixel structure + */ +typedef struct { + uint8_t y; /*!< Y (luma) component */ + uint8_t u; /*!< U (chroma) component */ + uint8_t v; /*!< V (chroma) component */ +} yuv_pixel_t; + +/** + * @brief RGB888 pixel structure + */ +typedef struct { + uint8_t r; /*!< Red component */ + uint8_t g; /*!< Green component */ + uint8_t b; /*!< Blue component */ +} rgb888_pixel_t; + +static void yuv_to_rgb(uint8_t y, uint8_t u, uint8_t v, rgb888_pixel_t *pixel) +{ + int c = y - 16; + int d = u - 128; + int e = v - 128; + int r_temp = (298 * c + 409 * e + 128) >> 8; // R = Y + 1.403 * (V-128) + int g_temp = (298 * c - 100 * d - 208 * e + 128) >> 8; // G = Y - 0.344 * (U-128) - 0.714 * (V-128) + int b_temp = (298 * c + 516 * d + 128) >> 8; // B = Y + 1.770 * (U-128) + pixel->r = CLAMP(r_temp); + pixel->g = CLAMP(g_temp); + pixel->b = CLAMP(b_temp); +} + +static void get_pixel(pattern_info_t *info, rgb888_pixel_t *pixel, int x, int y) +{ + uint8_t *data = info->pixel; + switch (info->format_id) { + case ESP_H264_RAW_FMT_YUYV: { + x = (x >> 1 << 1); + uint8_t *yuyv = data + y * info->res.width * 2 + x * 2; + yuv_to_rgb(yuyv[0], yuyv[1], yuyv[3], pixel); + break; + } + case ESP_H264_RAW_FMT_I420: { + uint8_t *py = data + y * info->res.width + x; + y >>= 1; + x >>= 1; + uint8_t *pu = data + info->res.height * info->res.width + y * info->res.width / 2 + x; + uint8_t *pv = data + info->res.height * info->res.width * 5 / 4 + y * info->res.width / 2 + x; + yuv_to_rgb(py[0], pu[0], pv[0], pixel); + break; + } + case ESP_H264_RAW_FMT_O_UYY_E_VYY: { + uint8_t *uyy = data + (y >> 1) * info->res.width * 3 + (x >> 1) * 3; + uint8_t *vyy = uyy + info->res.width * 3 / 2; + uint8_t y_pixel = (y & 1) ? vyy[1 + (x & 1)] : uyy[1 + (x & 1)]; + yuv_to_rgb(y_pixel, uyy[0], vyy[0], pixel); + break; + } + default: + break; + } +} + +esp_err_t gen_pattern_color_bar(pattern_info_t *info) +{ + uint8_t *pixel = info->pixel; + bool vertical = info->vertical; + uint8_t n = info->bar_count; + + switch (info->format_id) { + case ESP_H264_RAW_FMT_I420: { + yuv_pixel_t *color = (yuv_pixel_t *)malloc(n * sizeof(yuv_pixel_t)); + if (color == NULL) { + return ESP_FAIL; + } + for (int i = 0; i < n; i++) { + color[i].y = (uint8_t)(rand() & 0xFF); + color[i].u = (uint8_t)(rand() & 0xFF); + color[i].v = (uint8_t)(rand() & 0xFF); + } + if (vertical) { + uint32_t bar_w = (info->res.width / n) >> 1 << 1; + uint32_t last_bar_w = info->res.width - bar_w * (n - 1); + // Fill Y firstly + for (int y = 0; y < info->res.height; y++) { + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_w : bar_w); + memset(pixel, color[i].y, bytes); + pixel += bytes; + } + } + // Fill U + for (int y = 0; y < info->res.height >> 1; y++) { + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_w : bar_w) >> 1; + memset(pixel, color[i].u, bytes); + pixel += bytes; + } + } + // Fill V + for (int y = 0; y < info->res.height >> 1; y++) { + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_w : bar_w) >> 1; + memset(pixel, color[i].v, bytes); + pixel += bytes; + } + } + } else { + uint32_t bar_h = (info->res.height / n) >> 1 << 1; + uint32_t last_bar_h = info->res.height - bar_h * (n - 1); + // Fill Y firstly + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_h : bar_h) * info->res.width; + memset(pixel, color[i].y, bytes); + pixel += bytes; + } + // Fill U + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_h : bar_h) * info->res.width >> 2; + memset(pixel, color[i].u, bytes); + pixel += bytes; + } + // Fill V + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_h : bar_h) * info->res.width >> 2; + memset(pixel, color[i].v, bytes); + pixel += bytes; + } + } + free(color); + } break; + case ESP_H264_RAW_FMT_YUYV: { + yuv_pixel_t *color = (yuv_pixel_t *)malloc(n * sizeof(yuv_pixel_t)); + if (color == NULL) { + return ESP_FAIL; + } + for (int i = 0; i < n; i++) { + color[i].y = (uint8_t)(rand() & 0xFF); + color[i].u = (uint8_t)(rand() & 0xFF); + color[i].v = (uint8_t)(rand() & 0xFF); + } + if (vertical) { + uint32_t bar_w = (info->res.width / n) >> 1 << 1; + // Fill Y firstly + for (int y = 0; y < info->res.height; y++) { + int bar_filled = 0; + int i = 0; + for (int x = 0; x < (info->res.width >> 1); x++) { + *pixel++ = color[i].y; + *pixel++ = color[i].u; + *pixel++ = color[i].y; + *pixel++ = color[i].v; + bar_filled += 2; + if (bar_filled >= bar_w) { + bar_filled = 0; + if (i < n - 1) { + i++; + } + } + } + } + } else { + uint32_t bar_h = (info->res.height / n) >> 1 << 1; + uint32_t last_bar_h = info->res.height - bar_h * (n - 1); + // Fill Y firstly + for (int i = 0; i < n; i++) { + uint32_t bytes = (i == n - 1 ? last_bar_h : bar_h) * info->res.width * 3 / 2; + while (bytes > 0) { + *pixel++ = color[i].y; + *pixel++ = color[i].u; + *pixel++ = color[i].y; + *pixel++ = color[i].v; + bytes -= 3; + } + } + } + free(color); + } break; + case ESP_H264_RAW_FMT_O_UYY_E_VYY: { + yuv_pixel_t *color = (yuv_pixel_t *)malloc(n * sizeof(yuv_pixel_t)); + if (color == NULL) { + return ESP_FAIL; + } + for (int i = 0; i < n; i++) { + color[i].y = (uint8_t)(rand() & 0xFF); + color[i].u = (uint8_t)(rand() & 0xFF); + color[i].v = (uint8_t)(rand() & 0xFF); + } + if (vertical) { + uint32_t bar_w = (info->res.width / n) >> 1 << 1; + // Fill Y firstly + for (int y = 0; y < (info->res.height >> 1); y++) { + int bar_filled = 0; + int i = 0; + for (int x = 0; x < (info->res.width >> 1); x++) { + *pixel++ = color[i].u; + *pixel++ = color[i].y; + *pixel++ = color[i].y; + bar_filled += 2; + if (bar_filled >= bar_w) { + bar_filled = 0; + if (i < n - 1) { + i++; + } + } + } + bar_filled = 0; + i = 0; + for (int x = 0; x < (info->res.width >> 1); x++) { + *pixel++ = color[i].v; + *pixel++ = color[i].y; + *pixel++ = color[i].y; + bar_filled += 2; + if (bar_filled >= bar_w) { + bar_filled = 0; + if (i < n - 1) { + i++; + } + } + } + } + } else { + uint32_t bar_h = (info->res.height / n) >> 1 << 1; + uint32_t last_bar_h = info->res.height - bar_h * (n - 1); + // Fill Y firstly + for (int i = 0; i < n; i++) { + uint32_t height = (i == n - 1 ? last_bar_h : bar_h); + uint32_t width = info->res.width >> 1; + for (int y = 0; y < (height >> 1); y++) { + for (int x = 0; x < width; x++) { + *pixel++ = color[i].u; + *pixel++ = color[i].y; + *pixel++ = color[i].y; + } + for (int x = 0; x < width; x++) { + *pixel++ = color[i].v; + *pixel++ = color[i].y; + *pixel++ = color[i].y; + } + } + } + } + free(color); + } + default: + break; + } + return ESP_OK; +} + +void draw_convert_result(pattern_info_t *a, pattern_info_t *b) +{ + if (a->bar_count == 0) { + return; + } + printf("\n"); + int y = 0; + rgb888_pixel_t block_start = {}; + rgb888_pixel_t block_end = {}; + + if (a->bar_count == b->bar_count) { + int n = a->bar_count; + uint32_t bar_w = (a->res.width / n) >> 1 << 1; + uint32_t last_bar_w = a->res.width - bar_w * (n - 1); + uint32_t bar_h = a->res.height / n; + + uint32_t bar_w_b = (b->res.width / n) >> 1 << 1; + uint32_t last_bar_w_b = b->res.width - bar_w_b * (n - 1); + uint32_t bar_h_b = b->res.height / n; + int y_b = 0; + + for (int col = 0; col < n; col++) { + int x = 0; + for (int row = 0; row < n; row++) { + get_pixel(a, &block_start, x, y); + x += (row == n - 1 ? last_bar_w : bar_w); + get_pixel(a, &block_end, x - 1, y); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_start.r, block_start.g, block_start.b, ' '); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_end.r, block_end.g, block_end.b, ' '); + } + if (b->pixel) { + printf(" | "); + x = 0; + for (int row = 0; row < n; row++) { + get_pixel(b, &block_start, x, y_b); + x += (row == n - 1 ? last_bar_w_b : bar_w_b); + get_pixel(b, &block_end, x - 1, y_b); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_start.r, block_start.g, block_start.b, ' '); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_end.r, block_end.g, block_end.b, ' '); + } + y_b += bar_h_b; + } + y += bar_h; + printf("\n"); + } + } else { + // Draw image + int n = a->bar_count; + uint32_t bar_w = (a->res.width / n) >> 1 << 1; + uint32_t last_bar_w = a->res.width - bar_w * (n - 1); + uint32_t bar_h = a->res.height / n; + + printf("A:\n"); + for (int col = 0, y = 0; col < n; col++, y += bar_h) { + int x = 0; + printf("║ "); + for (int row = 0; row < n; row++) { + get_pixel(a, &block_start, x, y); + x += (row == n - 1 ? last_bar_w : bar_w); + get_pixel(a, &block_end, x - 1, y); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_start.r, block_start.g, block_start.b, ' '); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_end.r, block_end.g, block_end.b, ' '); + } + printf("\n"); + } + if (b->pixel) { + uint32_t bar_w = (b->res.width / n) >> 1 << 1; + uint32_t last_bar_w = b->res.width - bar_w * (n - 1); + uint32_t bar_h = b->res.height / n; + + printf("B:\n"); + for (int col = 0, y = 0; col < n; col++, y += bar_h) { + int x = 0; + printf("║ "); + for (int row = 0; row < n; row++) { + get_pixel(b, &block_start, x, y); + x += (row == n - 1 ? last_bar_w : bar_w); + get_pixel(b, &block_end, x - 1, y); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_start.r, block_start.g, block_start.b, ' '); + printf("\033[48;2;%d;%d;%dm%c\033[0m", block_end.r, block_end.g, block_end.b, ' '); + } + printf("\n"); + } + printf("\n"); + } + } +} diff --git a/examples/peripherals/h264/main/video_pattern.h b/examples/peripherals/h264/main/video_pattern.h new file mode 100644 index 0000000000..11ed472982 --- /dev/null +++ b/examples/peripherals/h264/main/video_pattern.h @@ -0,0 +1,64 @@ +/** + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file video_pattern.h + * @brief Video pattern generation and processing utilities for H264 encoding/decoding examples + * + * This header provides structures and functions for generating test video patterns + * and displaying conversion results for H264 codec testing. + */ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_h264_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @brief Video resolution definition + */ +typedef struct { + uint16_t width; /*!< Width of the video in pixels */ + uint16_t height; /*!< Height of the video in pixels */ +} video_resolution_t; + +/** + * @brief Pattern information structure for video pattern generation + */ +typedef struct { + esp_h264_raw_format_t format_id; /*!< Video format identifier */ + video_resolution_t res; /*!< Video resolution */ + uint8_t *pixel; /*!< Pixel data buffer */ + uint32_t data_size; /*!< Size of pixel data in bytes */ + bool vertical; /*!< Vertical orientation flag */ + uint8_t bar_count; /*!< Number of color bars */ +} pattern_info_t; + +/** + * @brief Generate color bar pattern for video testing + * @param info Pointer to pattern information structure + * @return + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t gen_pattern_color_bar(pattern_info_t *info); + +/** + * @brief Draw and display conversion result comparison + * @param a Pointer to source pattern information + * @param b Pointer to destination pattern information + */ +void draw_convert_result(pattern_info_t *a, pattern_info_t *b); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ diff --git a/examples/peripherals/h264/pytest_esp_h264.py b/examples/peripherals/h264/pytest_esp_h264.py new file mode 100644 index 0000000000..731c5ffb10 --- /dev/null +++ b/examples/peripherals/h264/pytest_esp_h264.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.octal_psram +@idf_parametrize('target', ['esp32s3'], indirect=['target']) +def test_esp_h264_esp32s3(dut: Dut) -> None: + dut.expect_exact('H264 process Completed successfully') + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_esp_h264_esp32p4(dut: Dut) -> None: + dut.expect_exact('H264 process Completed successfully') diff --git a/examples/peripherals/h264/sdkconfig.defaults b/examples/peripherals/h264/sdkconfig.defaults new file mode 100644 index 0000000000..54ed37d601 --- /dev/null +++ b/examples/peripherals/h264/sdkconfig.defaults @@ -0,0 +1,17 @@ +CONFIG_SPIRAM=y + +CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240 + +# FreeRTOS configurations +CONFIG_FREERTOS_HZ=1000 + +# H264 IRAM Configuration Notes: +# When LDGEN_CHECK_MAPPING="1" strict memory mapping check is enabled, linking errors may occur +# Cause: Prebuilt libtinyh264.a library doesn't support forced IRAM placement +# Solutions: +# Disable IRAM optimization: CONFIG_ESP_H264_DECODER_IRAM=n +CONFIG_ESP_H264_DECODER_IRAM=n + +# This case uses the terminal to print the brief diagrams before encoding and after decoding. +# Turn off the watchdog to ensure the integrity of the picture printing +CONFIG_ESP_TASK_WDT_EN=n diff --git a/examples/peripherals/h264/sdkconfig.defaults.esp32p4 b/examples/peripherals/h264/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000000..e6d77e2fa9 --- /dev/null +++ b/examples/peripherals/h264/sdkconfig.defaults.esp32p4 @@ -0,0 +1,7 @@ +# SPIRAM configurations for ESP32P4 +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y + +# CPU configuration +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_360=y diff --git a/examples/peripherals/h264/sdkconfig.defaults.esp32s3 b/examples/peripherals/h264/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000000..cedd1b8999 --- /dev/null +++ b/examples/peripherals/h264/sdkconfig.defaults.esp32s3 @@ -0,0 +1,6 @@ +# SPIRAM configurations for ESP32S3 +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y + +# CPU configuration +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y