Merge branch 'feat/add_h264_encoder_decoder_demo' into 'master'

feat(esp_h264): Add esp_h264 enocder and decoder example

Closes AUD-6406

See merge request espressif/esp-idf!39676
This commit is contained in:
morris
2025-06-12 11:03:22 +08:00
13 changed files with 971 additions and 0 deletions

View File

@ -87,6 +87,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

View File

@ -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)

View File

@ -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.)

View File

@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "./"
INCLUDE_DIRS "./")

View File

@ -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

View File

@ -0,0 +1,233 @@
/**
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#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);
}
}

View File

@ -0,0 +1,4 @@
dependencies:
espressif/esp_h264: "^1.0.4"
idf:
version: ">=5.3.0"

View File

@ -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");
}
}
}

View File

@ -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 <string.h>
#include <stdio.h>
#include <stdint.h>
#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 */

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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