feat(jpeg): simplify encode example and add pytest coverage

Embed a 720p BGR raw asset and stream the encoded JPEG over UART so
this example no longer depends on SD card setup.

Add pytest coverage that reconstructs the JPEG from base64 output and
compares it against a checked-in golden image.
This commit is contained in:
morris
2026-06-15 17:57:35 +08:00
parent 3a77af68b2
commit e2f23ea0bd
16 changed files with 723 additions and 345 deletions

View File

@@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(jpeg_encode)
project(jpeg_encode_example)

View File

@@ -5,25 +5,23 @@
## Overview
This example demonstrates how to use the JPEG hardware [encoder](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/jpeg.html) to encode a 1080p picture:
This example demonstrates how to use the JPEG hardware [encoder](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/jpeg.html) to encode a 720p raw image.
This example makes use of the hardware-based JPEG encoder. If you have multiple pictures that need to be decoded, such as *.rgb -> *.jpg, you can use this example to accelerate encoding.
The example performs:
## How to use example
- Embedding `main/assets/esp720p.rgb` into the final firmware image
- Letting the JPEG encoder read one 1280x720 `bgr24` frame directly from flash
- Encoding the frame into JPEG with the hardware encoder
- Base64-encoding the resulting JPEG bitstream and printing it with machine-parseable markers
- Letting pytest rebuild `jpeg_encode_result.jpeg` and compare it against `golden_output.jpeg`
### Hardware Required
## Hardware Required
* An Espressif development board based on a chip listed in supported targets
* A USB cable for power supply and serial communication
* Computer with ESP-IDF installed and configured
* The raw picture is the only source that you need to prepare (We have an [esp1080p.rgb](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/jpeg/jpeg_encode/resources/esp1080.rgb) in resources folder, you can also get it from [jpeg_decode](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/jpeg/jpeg_decode) example).
* ffmpeg can also be used to produce rgb picture. For example `ffmpeg -i input.jpg -pix_fmt rgb24 output.rgb`
Any board based on a supported target can be used, provided it has enough flash to hold the embedded 720p raw asset and the application image. The example defaults are configured for a 4 MB flash layout and PSRAM-enabled builds.
### Build and Flash
Before you start build and flash this example, please put the image `esp1080.rgb` in your sdcard.
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
@@ -31,27 +29,37 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
## Example Output
```bash
I (1114) jpeg.example: Initializing SD card
I (1114) gpio: GPIO[43]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1124) gpio: GPIO[44]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1134) gpio: GPIO[39]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1144) gpio: GPIO[40]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1154) gpio: GPIO[41]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1164) gpio: GPIO[42]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1414) gpio: GPIO[42]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
Name: SD64G
Type: SDHC/SDXC
Speed: 40.00 MHz (limit: 40.00 MHz)
Size: 60906MB
CSD: ver=2, sector_size=512, capacity=124735488 read_bl_len=9
SSR: bus_width=4
I (1434) jpeg.example: infile_1080p:/sdcard/esp1080.rgb
I (5174) jpeg.example: outfile:/sdcard/outjpg.jpg
I (5284) jpeg.example: Card unmounted
I (5284) main_task: Returned from app_main()
```text
Loading embedded BGR24 image from flash...
Embedded raw image size: 2764800 bytes
Encoding BGR24(raw) -> JPEG...
Encoded JPEG size: 30795 bytes
JPEG_META width=1280 height=720 format=JPEG encoding=base64 size=30795
JPEG_BASE64_BEGIN
JPEG_BASE64 ...
JPEG_BASE64 ...
JPEG_BASE64_END
JPEG encode demo done.
```
## Pytest Visual Check
The accompanying `pytest_jpeg_encode.py` script captures the `JPEG_META` and `JPEG_BASE64` output, reconstructs the encoded JPEG, and saves it as:
- `dut.logdir/jpeg_encode_result.jpeg`
It also compares the generated JPEG with `golden_output.jpeg`. This turns the example into both a functional regression test and a host-side artifact generator that makes the encoded result easy to inspect.
## Replacing The Embedded RGB Asset
If you want to regenerate a compatible raw frame from another input image, one simple workflow is:
```bash
ffmpeg -y -i input.jpg -vf scale=1280:720 -pix_fmt bgr24 -f rawvideo main/assets/esp720p.rgb
```
After replacing the raw asset, rebuild and flash the example. The firmware will emit the encoded JPEG as base64, and pytest will save the reconstructed JPEG artifact automatically. If the new image is intended to become the expected output, update `golden_output.jpeg` as well.
## Troubleshooting
(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.)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,3 +1,5 @@
idf_component_register(SRCS "jpeg_encode_main.c"
PRIV_REQUIRES fatfs esp_driver_jpeg
INCLUDE_DIRS ".")
idf_component_register(SRCS "jpeg_encode_example_main.c"
PRIV_REQUIRES esp_driver_jpeg mbedtls
INCLUDE_DIRS ".")
target_add_binary_data(${COMPONENT_LIB} "${CMAKE_CURRENT_LIST_DIR}/assets/esp720p.rgb" BINARY RENAME_TO "esp720p_rgb")

View File

@@ -1,18 +0,0 @@
menu "JPEG Encode Example menu"
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
bool "Format the card if mount failed"
default n
help
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
the mount has failed.
config EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
depends on SOC_SDMMC_IO_POWER_EXTERNAL
bool "SDMMC IO power supply comes from internal LDO (READ HELP!)"
default y
help
Please read the schematic first and check if the SDMMC VDD is connected to any internal LDO output.
If the SDMMC is powered by an external supplier, unselect me
endmenu

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "mbedtls/base64.h"
#include "esp_check.h"
#include "driver/jpeg_encode.h"
#define EXAMPLE_WIDTH 1280
#define EXAMPLE_HEIGHT 720
#define EXAMPLE_RGB_FRAME_SIZE (EXAMPLE_WIDTH * EXAMPLE_HEIGHT * 3)
#define EXAMPLE_JPEG_BUFFER_SIZE (EXAMPLE_RGB_FRAME_SIZE / 10) /* Estimate output size with an approximately 10:1 JPEG compression ratio for this demo image. */
#define EXAMPLE_BASE64_CHUNK_LEN 96
#define EXAMPLE_JPEG_QUALITY 80
extern const uint8_t esp720p_rgb_start[] asm("_binary_esp720p_rgb_start");
extern const uint8_t esp720p_rgb_end[] asm("_binary_esp720p_rgb_end");
static void print_base64_payload(const unsigned char *encoded, size_t encoded_len)
{
/* Split the printable payload into short lines so it is easy to read in
* the serial monitor and robust for pytest to parse back into a JPEG. */
printf("JPEG_BASE64_BEGIN\n");
for (size_t offset = 0; offset < encoded_len; offset += EXAMPLE_BASE64_CHUNK_LEN) {
size_t chunk_len = encoded_len - offset;
if (chunk_len > EXAMPLE_BASE64_CHUNK_LEN) {
chunk_len = EXAMPLE_BASE64_CHUNK_LEN;
}
printf("JPEG_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]);
}
printf("JPEG_BASE64_END\n");
}
void app_main(void)
{
/* EMBED_FILES turns the raw asset into linker symbols, so the example can
* read the picture directly from flash without mounting a filesystem. */
const size_t embedded_size = esp720p_rgb_end - esp720p_rgb_start;
uint32_t jpeg_size = 0;
jpeg_encoder_handle_t jpeg_handle = NULL;
printf("Loading embedded BGR24 image from flash...\n");
printf("Embedded raw image size: %zu bytes\n", embedded_size);
assert(embedded_size == EXAMPLE_RGB_FRAME_SIZE);
/* Despite the enum name, the current driver maps
* JPEG_ENCODE_IN_FORMAT_RGB888 to a BGR24-style byte layout.
* Keep the embedded raw asset in bgr24 order or red/blue will swap. */
jpeg_encode_cfg_t enc_config = {
.src_type = JPEG_ENCODE_IN_FORMAT_RGB888,
.sub_sample = JPEG_DOWN_SAMPLING_YUV422,
.image_quality = EXAMPLE_JPEG_QUALITY,
.width = EXAMPLE_WIDTH,
.height = EXAMPLE_HEIGHT,
};
size_t result_buffer_size = 0;
/* The output JPEG is compressed, so the example does not need to reserve
* a full raw-frame worth of space for the bitstream. This 10:1 estimate
* is intentionally conservative for the bundled demo image and quality
* setting; real applications should size this buffer for their own worst
* case and handle "buffer too small" errors if needed. */
jpeg_encode_memory_alloc_cfg_t mem_cfg = {
.buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER,
};
uint8_t *jpeg_buf = (uint8_t *)jpeg_alloc_encoder_mem(EXAMPLE_JPEG_BUFFER_SIZE, &mem_cfg, &result_buffer_size);
assert(jpeg_buf != NULL);
/* Create the encoder instance once, then feed it one full-frame request. */
jpeg_encode_engine_cfg_t encode_eng_cfg = {
.timeout_ms = 200,
};
ESP_ERROR_CHECK(jpeg_new_encoder_engine(&encode_eng_cfg, &jpeg_handle));
printf("JPEG encoder will read the embedded raw buffer directly from flash.\n");
printf("Encoding BGR24(raw) -> JPEG...\n");
ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, esp720p_rgb_start, EXAMPLE_RGB_FRAME_SIZE,
jpeg_buf, result_buffer_size, &jpeg_size));
printf("Encoded JPEG size: %" PRIu32 " bytes\n", jpeg_size);
size_t encoded_len = 0;
/* First call asks mbedTLS how big the base64 buffer must be, then the
* second call performs the actual binary-to-text conversion. */
int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, jpeg_buf, jpeg_size);
ESP_ERROR_CHECK((ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) ? ESP_OK : ESP_FAIL);
unsigned char *encoded = calloc(encoded_len + 1, 1);
assert(encoded != NULL);
ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &encoded_len, jpeg_buf, jpeg_size) == 0 ? ESP_OK : ESP_FAIL);
/* JPEG_META plus the chunked JPEG_BASE64 lines form a tiny text protocol
* that pytest understands and can reconstruct into a host-side .jpeg. */
printf("JPEG_META width=%u height=%u format=JPEG encoding=base64 size=%" PRIu32 "\n",
EXAMPLE_WIDTH, EXAMPLE_HEIGHT, jpeg_size);
print_base64_payload(encoded, encoded_len);
printf("JPEG encode demo done.\n");
ESP_ERROR_CHECK(jpeg_del_encoder_engine(jpeg_handle));
free(encoded);
free(jpeg_buf);
}

View File

@@ -1,153 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_heap_caps.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "driver/jpeg_encode.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
static const char *TAG = "jpeg.example";
static sdmmc_card_t *s_card;
#define MOUNT_POINT "/sdcard"
const static char s_infile_1080p[] = "/sdcard/esp1080.rgb";
const static char s_outfile_1080p[] = "/sdcard/outjpg.jpg";
static esp_err_t sdcard_init(void)
{
esp_err_t ret = ESP_OK;
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
#if CONFIG_EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
sd_pwr_ctrl_ldo_config_t ldo_config = {
.ldo_chan_id = 4, // `LDO_VO4` is used as the SDMMC IO power
};
sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;
ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to new an on-chip ldo power control driver");
return ret;
}
host.pwr_ctrl_handle = pwr_ctrl_handle;
#endif
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 4;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &s_card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return ret;
}
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, s_card);
return ret;
}
static void sdcard_deinit(void)
{
const char mount_point[] = MOUNT_POINT;
esp_vfs_fat_sdcard_unmount(mount_point, s_card);
#if SOC_SDMMC_IO_POWER_EXTERNAL
esp_err_t ret = sd_pwr_ctrl_del_on_chip_ldo(s_card->host.pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to delete on-chip ldo power control driver");
return;
}
#endif
}
void app_main(void)
{
ESP_ERROR_CHECK(sdcard_init());
uint32_t raw_size_1080p;
uint32_t jpg_size_1080p;
jpeg_encoder_handle_t jpeg_handle;
FILE *file_raw_1080p = fopen(s_infile_1080p, "rb");
ESP_LOGI(TAG, "s_infile_1080p:%s", s_infile_1080p);
if (file_raw_1080p == NULL) {
ESP_LOGE(TAG, "fopen file_raw_1080p error");
return;
}
jpeg_encode_engine_cfg_t encode_eng_cfg = {
.timeout_ms = 70,
};
jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
};
jpeg_encode_memory_alloc_cfg_t tx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
};
ESP_ERROR_CHECK(jpeg_new_encoder_engine(&encode_eng_cfg, &jpeg_handle));
// Read 1080p raw picture
fseek(file_raw_1080p, 0, SEEK_END);
raw_size_1080p = ftell(file_raw_1080p);
fseek(file_raw_1080p, 0, SEEK_SET);
size_t tx_buffer_size = 0;
uint8_t *raw_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p, &tx_mem_cfg, &tx_buffer_size);
assert(raw_buf_1080p != NULL);
fread(raw_buf_1080p, 1, raw_size_1080p, file_raw_1080p);
fclose(file_raw_1080p);
size_t rx_buffer_size = 0;
uint8_t *jpg_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p / 10, &rx_mem_cfg, &rx_buffer_size); // Assume that compression ratio of 10 to 1
assert(jpg_buf_1080p != NULL);
jpeg_encode_cfg_t enc_config = {
.src_type = JPEG_ENCODE_IN_FORMAT_RGB888,
.sub_sample = JPEG_DOWN_SAMPLING_YUV422,
.image_quality = 80,
.width = 1920,
.height = 1080,
};
ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, raw_buf_1080p, raw_size_1080p, jpg_buf_1080p, rx_buffer_size, &jpg_size_1080p));
FILE *file_jpg_1080p = fopen(s_outfile_1080p, "wb");
ESP_LOGI(TAG, "outfile:%s", s_outfile_1080p);
if (file_jpg_1080p == NULL) {
ESP_LOGE(TAG, "fopen file_jpg_1080p error");
return;
}
fwrite(jpg_buf_1080p, 1, jpg_size_1080p, file_jpg_1080p);
fclose(file_jpg_1080p);
sdcard_deinit();
ESP_LOGI(TAG, "Card unmounted");
}

View File

@@ -0,0 +1,4 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 0x380000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x6000
3 phy_init data phy 0x1000
4 factory app factory 0x380000

View File

@@ -0,0 +1,125 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import base64
import hashlib
import logging
import re
from dataclasses import dataclass
from pathlib import Path
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
JPEG_META_PATTERN = r'JPEG_META width=(\d+) height=(\d+) format=(\w+) encoding=(\w+) size=(\d+)'
JPEG_META_RE = re.compile(rf'^{JPEG_META_PATTERN}$')
JPEG_CHUNK_RE = re.compile(r'^JPEG_BASE64 ([A-Za-z0-9+/=]+)$')
JPEG_OUTPUT_NAME = 'jpeg_encode_result.jpeg'
GOLDEN_IMAGE_NAME = 'golden_output.jpeg'
GOLDEN_IMAGE_PATH = Path(__file__).with_name(GOLDEN_IMAGE_NAME)
EXPECTED_FORMAT = 'JPEG'
EXPECTED_ENCODING = 'base64'
EXPECTED_WIDTH = 1280
EXPECTED_HEIGHT = 720
@dataclass(frozen=True, slots=True)
class JpegMetadata:
width: int
height: int
image_format: str
encoding: str
size: int
def parse_jpeg_metadata(meta_line: str) -> JpegMetadata:
match = JPEG_META_RE.match(meta_line)
if not match:
raise ValueError(f'Invalid JPEG metadata line: {meta_line}')
return JpegMetadata(
width=int(match.group(1)),
height=int(match.group(2)),
image_format=match.group(3),
encoding=match.group(4),
size=int(match.group(5)),
)
def collect_base64_payload(dut: Dut) -> list[str]:
payload_chunks: list[str] = []
while True:
match = dut.expect(r'(JPEG_BASE64_END|JPEG_BASE64 [A-Za-z0-9+/=]+\r?\n)')
line = match.group(1).decode('utf-8').strip()
if line == 'JPEG_BASE64_END':
return payload_chunks
chunk_match = JPEG_CHUNK_RE.match(line)
assert chunk_match is not None
payload_chunks.append(chunk_match.group(1))
def decode_jpeg_base64_payload(metadata: JpegMetadata, payload_lines: list[str]) -> bytes:
if metadata.image_format != EXPECTED_FORMAT:
raise ValueError(f'Unsupported image format: {metadata.image_format}')
if metadata.encoding != EXPECTED_ENCODING:
raise ValueError(f'Unsupported payload encoding: {metadata.encoding}')
jpeg_bytes = base64.b64decode(''.join(payload_lines), validate=True)
if len(jpeg_bytes) != metadata.size:
raise ValueError(f'Expected {metadata.size} JPEG bytes, got {len(jpeg_bytes)}')
return jpeg_bytes
def save_jpeg_artifact(jpeg_bytes: bytes, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
output_path.write_bytes(jpeg_bytes)
except OSError:
logging.exception('Failed to save JPEG artifact to %s', output_path)
return
logging.info('Saved JPEG artifact to %s', output_path)
def _sha256_digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def assert_jpeg_matches_golden(result_bytes: bytes, golden_path: Path) -> None:
assert golden_path.is_file(), f'Golden JPEG not found: {golden_path}'
golden_bytes = golden_path.read_bytes()
result_digest = _sha256_digest(result_bytes)
golden_digest = _sha256_digest(golden_bytes)
assert result_digest == golden_digest, (
f'Generated JPEG does not match golden file: {golden_path.name} '
f'(result sha256={result_digest}, golden sha256={golden_digest})'
)
@pytest.mark.generic
@idf_parametrize('target', soc_filtered_targets('SOC_JPEG_ENCODE_SUPPORTED == 1'), indirect=['target'])
def test_jpeg_encode_example(dut: Dut) -> None:
dut.expect_exact('Loading embedded BGR24 image from flash...')
dut.expect(r'Embedded raw image size: \d+ bytes')
dut.expect_exact('JPEG encoder will read the embedded raw buffer directly from flash.')
dut.expect_exact('Encoding BGR24(raw) -> JPEG...')
dut.expect(r'Encoded JPEG size: \d+ bytes')
metadata = parse_jpeg_metadata(dut.expect(JPEG_META_PATTERN).group(0).decode('utf-8'))
assert metadata.width == EXPECTED_WIDTH
assert metadata.height == EXPECTED_HEIGHT
dut.expect_exact('JPEG_BASE64_BEGIN')
payload_lines = collect_base64_payload(dut)
jpeg_bytes = decode_jpeg_base64_payload(metadata, payload_lines)
output_path = Path(dut.logdir) / JPEG_OUTPUT_NAME
save_jpeg_artifact(jpeg_bytes, output_path)
assert_jpeg_matches_golden(jpeg_bytes, GOLDEN_IMAGE_PATH)
dut.expect_exact('JPEG encode demo done.')

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
CONFIG_SPIRAM=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"

View File

@@ -1,6 +0,0 @@
# SPIRAM configurations
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_200M=y