Merge branch 'isp_dma_one_frame_v6.0' into 'release/v6.0'

feat(isp): support isp dma input and add example (v6.0)

See merge request espressif/esp-idf!50755
This commit is contained in:
morris
2026-07-16 17:17:25 +08:00
20 changed files with 1358 additions and 19 deletions

View File

@@ -6,10 +6,16 @@ set(public_include "include")
set(priv_requires "esp_driver_gpio")
set(requires "esp_hal_cam")
if(${target} STREQUAL "linux")
set(requires "")
else()
set(requires "esp_hal_cam" "esp_mm" "esp_driver_dma")
endif()
if(CONFIG_SOC_ISP_SUPPORTED)
list(APPEND srcs "src/isp_core.c"
"src/isp_dma.c"
"src/isp_af.c"
"src/isp_ccm.c"
"src/isp_awb.c"

View File

@@ -32,6 +32,7 @@ typedef struct {
uint32_t h_res; ///< Input horizontal resolution, i.e. the number of pixels in a line
uint32_t v_res; ///< Input vertical resolution, i.e. the number of lines in a frame
color_raw_element_order_t bayer_order; ///< Bayer order
uint32_t dma_burst_size; ///< DMA output burst length in units of 64-bit beats. Set to 0 to use default value 16
int intr_priority; ///< The interrupt priority, range 0~3, if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3)
struct {
uint32_t bypass_isp : 1; ///< Bypass ISP pipelines

View File

@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "esp_err.h"
#include "driver/isp_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Process one ISP DMA frame: feed the input buffer through the ISP and wait for completion
*
* @note Input buffer content should be ready before calling this function. If the buffers are in
* cacheable memory, the caller should synchronize them around DMA access. Buffer sizes are
* derived from the ISP processor resolution and pixel formats. Both buffers must be 8-byte
* aligned. This function blocks until both input and output DMA channels finish.
*
* @param[in] proc Processor handle
* @param[in] output_buffer Destination buffer for ISP output (8-byte aligned)
* @param[in] input_buffer Source input buffer for RAW frame data (8-byte aligned)
* @param[in] timeout_ms Timeout in milliseconds for waiting transfer completion
*
* @return
* - ESP_OK On success
* - ESP_ERR_INVALID_ARG Invalid argument
* - ESP_ERR_TIMEOUT Wait timeout
*/
esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer, const void *input_buffer, uint32_t timeout_ms);
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -58,6 +58,8 @@ typedef enum {
/*---------------------------------------------------------------
Driver Context
---------------------------------------------------------------*/
typedef struct esp_isp_dma_frame_ctx_t esp_isp_dma_frame_ctx_t;
typedef struct isp_processor_t {
int proc_id;
isp_clk_src_t clk_src;
@@ -70,10 +72,14 @@ typedef struct isp_processor_t {
DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(spinlock);
isp_color_t in_color_format;
isp_color_t out_color_format;
isp_input_data_source_t input_data_source;
uint32_t h_res;
uint32_t v_res;
color_raw_element_order_t bayer_order;
bool bypass_isp;
uint32_t dma_in_burst_len; // ISP DMA input burst length (64-bit beats), kept in sync with the input GDMA dst MSIZE
uint32_t dma_out_burst_len; // ISP DMA output burst length (64-bit beats)
esp_isp_dma_frame_ctx_t *dma_frame_ctx;
/* sub module contexts */
isp_af_ctlr_t af_ctlr[ISP_LL_AF_CTLR_NUMS];
isp_awb_ctlr_t awb_ctlr;
@@ -131,6 +137,45 @@ bool esp_isp_awb_isr(isp_proc_handle_t proc, uint32_t awb_events);
bool esp_isp_sharpen_isr(isp_proc_handle_t proc, uint32_t sharp_events);
bool esp_isp_hist_isr(isp_proc_handle_t proc, uint32_t hist_events);
/*---------------------------------------------------------------
DMA INPUT
---------------------------------------------------------------*/
/**
* @brief Configure the ISP DMA input path (frame size, burst length, data type)
*
* @note Internal helper invoked while creating a processor whose input source is DWGDMA.
*
* @param[in] proc Processor handle
*
* @return
* - ESP_OK On success
* - ESP_ERR_INVALID_ARG Invalid input color format or frame size
*/
esp_err_t isp_dma_configure_input(isp_proc_handle_t proc);
/**
* @brief Create the ISP DMA frame context
*
* @note Internal helper invoked while creating a processor whose input source is DWGDMA.
*
* @param[in] proc Processor handle
*
* @return
* - ESP_OK On success
* - ESP_ERR_INVALID_ARG Invalid processor, burst length, or frame size
* - ESP_ERR_INVALID_STATE Processor input source is not DWGDMA or the context already exists
* - ESP_ERR_NO_MEM Failed to allocate the context or synchronization objects
* - Other errors returned by the underlying DMA driver
*/
esp_err_t isp_dma_new_frame_ctx(isp_proc_handle_t proc);
/**
* @brief Delete the ISP DMA frame context
*
* @param[in] proc Processor handle
*/
void isp_dma_del_frame_ctx(isp_proc_handle_t proc);
#ifdef __cplusplus
}
#endif

View File

@@ -18,6 +18,7 @@
#include "esp_private/mipi_csi_share_hw_ctrl.h"
#include "hal/hal_utils.h"
#include "hal/color_hal.h"
#include "hal/mipi_csi_brg_ll.h"
#include "soc/mipi_csi_bridge_struct.h"
#include "hal/isp_periph.h"
#include "soc/soc_caps.h"
@@ -79,7 +80,17 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(proc_config && ret_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(proc_config->h_res <= ISP_LL_HSIZE_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid h_res");
ESP_RETURN_ON_FALSE(proc_config->input_data_source != ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_NOT_SUPPORTED, TAG, "input source not supported yet");
if (proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA) {
ESP_RETURN_ON_FALSE((proc_config->input_data_color_type == ISP_COLOR_RAW8) ||
(proc_config->input_data_color_type == ISP_COLOR_RAW10) ||
(proc_config->input_data_color_type == ISP_COLOR_RAW12),
ESP_ERR_INVALID_ARG, TAG, "dma input only supports RAW8/RAW10/RAW12");
uint32_t in_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(proc_config->input_data_color_type);
uint64_t frame_bits = (uint64_t)proc_config->h_res * proc_config->v_res * in_bits_per_pixel;
ESP_RETURN_ON_FALSE((frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "frame bits should be 64-bit aligned");
ESP_RETURN_ON_FALSE((frame_bits / 64) <= ((1UL << 22) - 1), ESP_ERR_INVALID_ARG, TAG, "frame size exceeds hardware limit");
ESP_RETURN_ON_FALSE(proc_config->dma_burst_size <= 16, ESP_ERR_INVALID_ARG, TAG, "dma burst size out of range");
}
if (proc_config->flags.bypass_isp) {
ESP_RETURN_ON_FALSE(proc_config->input_data_color_type == proc_config->output_data_color_type, ESP_ERR_INVALID_ARG, TAG, "isp is bypassed, input and output data color type should be same");
}
@@ -173,7 +184,9 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
isp_ll_yuv_set_range(proc->hal.hw, proc_config->yuv_range);
}
if ((out_color_format == ISP_COLOR_RGB888 || out_color_format == ISP_COLOR_RGB565) && proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DVP) {
if ((out_color_format == ISP_COLOR_RGB888 || out_color_format == ISP_COLOR_RGB565) &&
(proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DVP ||
proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA)) {
isp_ll_color_enable(proc->hal.hw, true); // workaround for DIG-474
}
if (proc_config->flags.byte_swap_en) {
@@ -184,16 +197,23 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
proc->in_color_format = in_color_format;
proc->out_color_format = out_color_format;
proc->input_data_source = proc_config->input_data_source;
proc->h_res = proc_config->h_res;
proc->v_res = proc_config->v_res;
proc->bayer_order = proc_config->bayer_order;
proc->bypass_isp = proc_config->flags.bypass_isp;
proc->dma_out_burst_len = proc_config->dma_burst_size;
if (proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA) {
ESP_GOTO_ON_ERROR(isp_dma_configure_input(proc), err, TAG, "configure dma input failed");
ESP_GOTO_ON_ERROR(isp_dma_new_frame_ctx(proc), err, TAG, "create dma frame context failed");
}
*ret_proc = proc;
return ESP_OK;
err:
isp_dma_del_frame_ctx(proc);
if (proc->intr_hdl) {
esp_isp_deregister_isr(proc, ISP_SUBMODULE_GENERAL);
}
@@ -207,6 +227,8 @@ esp_err_t esp_isp_del_processor(isp_proc_handle_t proc)
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE(atomic_load(&proc->isp_fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "processor isn't in init state");
isp_dma_del_frame_ctx(proc);
//declaim first, then do free
ESP_RETURN_ON_ERROR(s_isp_declaim_processor(proc), TAG, "declaim processor fail");
#if SOC_ISP_SHARE_CSI_BRG
@@ -338,20 +360,26 @@ static void IRAM_ATTR s_isp_isr_dispatcher(void *arg)
do_dispatch = false;
}
if ((error_events & ISP_LL_EVENT_DATA_TYPE_ERR) || (error_events & ISP_LL_EVENT_DATA_TYPE_SETTING_ERR)) {
ESP_EARLY_LOGE(TAG, "data type error");
}
if ((error_events & ISP_LL_EVENT_ASYNC_FIFO_OVF) || (error_events & ISP_LL_EVENT_BUF_FULL)) {
ESP_EARLY_LOGE(TAG, "fifo overflow");
}
if ((error_events & ISP_LL_EVENT_HVNUM_SETTING_ERR) || (error_events & ISP_LL_EVENT_MIPI_HNUM_UNMATCH)) {
ESP_EARLY_LOGE(TAG, "hnum / vnum setting error");
}
if (error_events & ISP_LL_EVENT_GAMMA_XCOORD_ERR) {
ESP_EARLY_LOGE(TAG, "gamma xcoord error");
}
if (error_events & ISP_LL_EVENT_CROP_ERR) {
ESP_EARLY_LOGE(TAG, "crop error");
if (error_events) {
if ((error_events & ISP_LL_EVENT_DATA_TYPE_ERR) || (error_events & ISP_LL_EVENT_DATA_TYPE_SETTING_ERR)) {
ESP_EARLY_LOGE(TAG, "data type error");
}
if ((error_events & ISP_LL_EVENT_ASYNC_FIFO_OVF)) {
ESP_EARLY_LOGE(TAG, "fifo overflow");
}
if ((error_events & ISP_LL_EVENT_BUF_FULL)) {
//This error does not affect the operation of the ISP
ESP_EARLY_LOGD(TAG, "buffer full");
}
if ((error_events & ISP_LL_EVENT_HVNUM_SETTING_ERR) || (error_events & ISP_LL_EVENT_MIPI_HNUM_UNMATCH)) {
ESP_EARLY_LOGE(TAG, "hnum / vnum setting error");
}
if (error_events & ISP_LL_EVENT_GAMMA_XCOORD_ERR) {
ESP_EARLY_LOGE(TAG, "gamma xcoord error");
}
if (error_events & ISP_LL_EVENT_CROP_ERR) {
ESP_EARLY_LOGE(TAG, "crop error");
}
}
if (need_yield) {

View File

@@ -0,0 +1,265 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <esp_types.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/isp_dma.h"
#include "esp_private/dw_gdma.h"
#include "esp_private/isp_private.h"
#include "hal/color_hal.h"
#include "hal/isp_ll.h"
#include "hal/mipi_csi_brg_ll.h"
#define ISP_DMA_IN_BURST_LEN_DFT 8
#define ISP_DMA_OUT_BURST_LEN_DFT 16
struct esp_isp_dma_frame_ctx_t {
isp_proc_handle_t proc;
csi_brg_dev_t *csi_brg_hw;
dw_gdma_channel_handle_t dma_in_chan;
dw_gdma_channel_handle_t dma_out_chan;
SemaphoreHandle_t in_done_sem;
SemaphoreHandle_t out_done_sem;
dw_gdma_block_transfer_config_t dma_out_trans;
dw_gdma_block_transfer_config_t dma_in_trans;
uint32_t input_frame_size_64bit;
uint32_t output_frame_size_64bit;
};
static const char *TAG = "ISP_DMA";
static bool IRAM_ATTR s_isp_dma_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
{
(void)chan;
(void)event_data;
BaseType_t high_task_woken = pdFALSE;
xSemaphoreGiveFromISR(*(SemaphoreHandle_t *)user_data, &high_task_woken);
return high_task_woken == pdTRUE;
}
static dw_gdma_burst_items_t s_isp_dma_burst_len_to_items(uint32_t burst_len)
{
switch (burst_len) {
case 1: return DW_GDMA_BURST_ITEMS_1;
case 4: return DW_GDMA_BURST_ITEMS_4;
case 8: return DW_GDMA_BURST_ITEMS_8;
default: return DW_GDMA_BURST_ITEMS_8;
}
}
static void s_isp_dma_frame_ctx_destroy(struct esp_isp_dma_frame_ctx_t *ctx)
{
if (!ctx) {
return;
}
if (ctx->csi_brg_hw) {
mipi_csi_brg_ll_enable(ctx->csi_brg_hw, false);
}
if (ctx->dma_in_chan) {
dw_gdma_channel_enable_ctrl(ctx->dma_in_chan, false);
dw_gdma_del_channel(ctx->dma_in_chan);
}
if (ctx->dma_out_chan) {
dw_gdma_channel_enable_ctrl(ctx->dma_out_chan, false);
dw_gdma_del_channel(ctx->dma_out_chan);
}
if (ctx->in_done_sem) {
vSemaphoreDelete(ctx->in_done_sem);
}
if (ctx->out_done_sem) {
vSemaphoreDelete(ctx->out_done_sem);
}
free(ctx);
}
esp_err_t isp_dma_configure_input(isp_proc_handle_t proc)
{
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_INVALID_ARG, TAG, "processor input source is not dwgdma");
bool valid_format = isp_ll_dma_set_data_type(proc->hal.hw, proc->in_color_format);
ESP_RETURN_ON_FALSE(valid_format, ESP_ERR_INVALID_ARG, TAG, "dma input only supports RAW8/RAW10/RAW12");
uint32_t in_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(proc->in_color_format);
uint64_t frame_bits = (uint64_t)proc->h_res * proc->v_res * in_bits_per_pixel;
ESP_RETURN_ON_FALSE((frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "frame bits should be 64-bit aligned");
uint64_t frame_size_in_64bit = frame_bits / 64;
ESP_RETURN_ON_FALSE(frame_size_in_64bit <= ((1UL << 22) - 1), ESP_ERR_INVALID_ARG, TAG, "frame size exceeds hardware limit");
/*
* The input burst length is fixed at ISP_DMA_IN_BURST_LEN_DFT: it must be representable as a
* DW-GDMA MSIZE (1/4/8) and not exceed the ISP input async FIFO capacity (8 x 64-bit). It also
* has to equal the input GDMA dst MSIZE (derived from proc->dma_in_burst_len in
* isp_dma_new_frame_ctx), otherwise the async FIFO overflows.
*/
isp_ll_dma_set_frame_size(proc->hal.hw, (uint32_t)frame_size_in_64bit);
isp_ll_dma_set_burst_len(proc->hal.hw, ISP_DMA_IN_BURST_LEN_DFT);
isp_ll_dma_apply_config(proc->hal.hw);
proc->dma_in_burst_len = ISP_DMA_IN_BURST_LEN_DFT;
return ESP_OK;
}
esp_err_t isp_dma_new_frame_ctx(isp_proc_handle_t proc)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_INVALID_STATE, TAG, "processor input source is not dwgdma");
ESP_RETURN_ON_FALSE(!proc->dma_frame_ctx, ESP_ERR_INVALID_STATE, TAG, "dma frame context already exists");
uint32_t output_burst_len = proc->dma_out_burst_len ? proc->dma_out_burst_len : ISP_DMA_OUT_BURST_LEN_DFT;
ESP_RETURN_ON_FALSE(output_burst_len > 0 && output_burst_len <= 16, ESP_ERR_INVALID_ARG, TAG, "output burst len out of range");
uint32_t in_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(proc->in_color_format);
uint32_t out_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(proc->out_color_format);
uint64_t input_frame_bits = (uint64_t)proc->h_res * proc->v_res * in_bits_per_pixel;
uint64_t output_frame_bits = (uint64_t)proc->h_res * proc->v_res * out_bits_per_pixel;
ESP_RETURN_ON_FALSE((input_frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "input frame bits should be 64-bit aligned");
ESP_RETURN_ON_FALSE((output_frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "output frame bits should be 64-bit aligned");
struct esp_isp_dma_frame_ctx_t *ctx = heap_caps_calloc(1, sizeof(struct esp_isp_dma_frame_ctx_t), ISP_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for dma frame context");
ctx->proc = proc;
ctx->input_frame_size_64bit = input_frame_bits / 64;
ctx->output_frame_size_64bit = output_frame_bits / 64;
csi_brg_dev_t *csi_brg_hw = MIPI_CSI_BRG_LL_GET_HW(proc->csi_brg_id);
ctx->csi_brg_hw = csi_brg_hw;
mipi_csi_brg_ll_set_intput_data_h_pixel_num(csi_brg_hw, proc->h_res);
mipi_csi_brg_ll_set_intput_data_v_row_num(csi_brg_hw, proc->v_res);
mipi_csi_brg_ll_set_burst_len(csi_brg_hw, 512);
mipi_csi_brg_ll_enable(csi_brg_hw, true);
ctx->out_done_sem = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(ctx->out_done_sem, ESP_ERR_NO_MEM, err, TAG, "no mem for output semaphore");
ctx->in_done_sem = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(ctx->in_done_sem, ESP_ERR_NO_MEM, err, TAG, "no mem for input semaphore");
dw_gdma_channel_alloc_config_t dma_out_alloc = {
.src = {
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
.role = DW_GDMA_ROLE_PERIPH_CSI,
.handshake_type = DW_GDMA_HANDSHAKE_HW,
.num_outstanding_requests = 5,
.status_fetch_addr = MIPI_CSI_BRG_MEM_BASE,
},
.dst = {
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
.role = DW_GDMA_ROLE_MEM,
.handshake_type = DW_GDMA_HANDSHAKE_HW,
.num_outstanding_requests = 5,
},
.flow_controller = DW_GDMA_FLOW_CTRL_SRC,
.chan_priority = 1,
};
ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_out_alloc, &ctx->dma_out_chan), err, TAG, "create output dma channel failed");
dw_gdma_channel_alloc_config_t dma_in_alloc = {
.src = {
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
.role = DW_GDMA_ROLE_MEM,
.handshake_type = DW_GDMA_HANDSHAKE_SW,
.num_outstanding_requests = 2,
},
.dst = {
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
.role = DW_GDMA_ROLE_PERIPH_ISP,
.handshake_type = DW_GDMA_HANDSHAKE_HW,
.num_outstanding_requests = 1,
},
.flow_controller = DW_GDMA_FLOW_CTRL_DST,
.chan_priority = 1,
};
ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_in_alloc, &ctx->dma_in_chan), err, TAG, "create input dma channel failed");
ctx->dma_out_trans = (dw_gdma_block_transfer_config_t) {
.src = {
.addr = MIPI_CSI_BRG_MEM_BASE,
.width = DW_GDMA_TRANS_WIDTH_64,
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
.burst_items = DW_GDMA_BURST_ITEMS_512,
.burst_len = output_burst_len,
},
.dst = {
.width = DW_GDMA_TRANS_WIDTH_64,
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
.burst_items = DW_GDMA_BURST_ITEMS_512,
.burst_len = output_burst_len,
},
.size = ctx->output_frame_size_64bit,
};
ctx->dma_in_trans = (dw_gdma_block_transfer_config_t) {
.src = {
.width = DW_GDMA_TRANS_WIDTH_64,
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
.burst_items = DW_GDMA_BURST_ITEMS_32,
},
.dst = {
.addr = MIPI_CSI_BRG_MEM_BASE,
.width = DW_GDMA_TRANS_WIDTH_64,
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
.burst_items = s_isp_dma_burst_len_to_items(proc->dma_in_burst_len),
},
.size = ctx->input_frame_size_64bit,
};
dw_gdma_event_callbacks_t dma_cbs = {
.on_full_trans_done = s_isp_dma_done_cb,
};
ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(ctx->dma_out_chan, &dma_cbs, &ctx->out_done_sem), err, TAG, "register output dma callback failed");
ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(ctx->dma_in_chan, &dma_cbs, &ctx->in_done_sem), err, TAG, "register input dma callback failed");
proc->dma_frame_ctx = ctx;
return ESP_OK;
err:
s_isp_dma_frame_ctx_destroy(ctx);
return ret;
}
void isp_dma_del_frame_ctx(isp_proc_handle_t proc)
{
if (!proc || !proc->dma_frame_ctx) {
return;
}
s_isp_dma_frame_ctx_destroy(proc->dma_frame_ctx);
proc->dma_frame_ctx = NULL;
}
esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer, const void *input_buffer, uint32_t timeout_ms)
{
ESP_RETURN_ON_FALSE(proc && proc->dma_frame_ctx && output_buffer && input_buffer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE((((uintptr_t)output_buffer) % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "output buffer not 8-byte aligned");
ESP_RETURN_ON_FALSE((((uintptr_t)input_buffer) % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "input buffer not 8-byte aligned");
esp_isp_dma_frame_ctx_t *ctx = proc->dma_frame_ctx;
ctx->dma_out_trans.dst.addr = (uint32_t)output_buffer;
ctx->dma_in_trans.src.addr = (uint32_t)input_buffer;
ESP_RETURN_ON_ERROR(dw_gdma_channel_config_transfer(ctx->dma_out_chan, &ctx->dma_out_trans), TAG, "configure output transfer failed");
ESP_RETURN_ON_ERROR(dw_gdma_channel_config_transfer(ctx->dma_in_chan, &ctx->dma_in_trans), TAG, "configure input transfer failed");
dw_gdma_channel_enable_ctrl(ctx->dma_out_chan, true);
dw_gdma_channel_enable_ctrl(ctx->dma_in_chan, true);
isp_ll_dma_trigger_frame(ctx->proc->hal.hw);
if (xSemaphoreTake(ctx->in_done_sem, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
ESP_LOGE(TAG, "wait input done timeout");
return ESP_ERR_TIMEOUT;
}
if (xSemaphoreTake(ctx->out_done_sem, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
ESP_LOGE(TAG, "wait output done timeout");
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}

View File

@@ -28,7 +28,7 @@ extern "C" {
#define ISP_LL_PERIPH_NUMS 1U
#define ISP_LL_HSIZE_MAX 1920
#define ISP_LL_VSIZE_MAX 1080
#define ISP_LL_VSIZE_MAX 1280
/*---------------------------------------------------------------
Clock
@@ -535,6 +535,82 @@ static inline void isp_ll_enable_line_end_packet_exist(isp_dev_t *hw, bool en)
hw->frame_cfg.hsync_end_exist = en;
}
/**
* @brief Set DMA input data type
*
* @param[in] hw Hardware instance address
* @param[in] format color format, see `isp_color_t`
*
* @return true for valid format, false for invalid format
*/
static inline bool isp_ll_dma_set_data_type(isp_dev_t *hw, isp_color_t format)
{
bool valid = false;
switch (format) {
case ISP_COLOR_RAW8:
hw->dma_cntl.dma_data_type = 0x2A;
valid = true;
break;
case ISP_COLOR_RAW10:
hw->dma_cntl.dma_data_type = 0x2B;
valid = true;
break;
case ISP_COLOR_RAW12:
hw->dma_cntl.dma_data_type = 0x2C;
valid = true;
break;
default:
break;
}
return valid;
}
/**
* @brief Set DMA input burst length in units of 64-bit
*
* @param[in] hw Hardware instance address
* @param[in] burst_len Number of 64-bit beats in one DMA burst
*/
static inline void isp_ll_dma_set_burst_len(isp_dev_t *hw, uint32_t burst_len)
{
hw->dma_cntl.dma_burst_len = burst_len;
}
/**
* @brief Set DMA input total frame size in units of 64-bit
*
* @param[in] hw Hardware instance address
* @param[in] num64b Number of 64-bit words in one frame
*/
static inline void isp_ll_dma_set_frame_size(isp_dev_t *hw, uint32_t num64b)
{
hw->dma_raw_data.dma_raw_num_total = num64b;
hw->dma_raw_data.dma_raw_num_total_set = 1;
}
/**
* @brief Apply DMA input registers
*
* @param[in] hw Hardware instance address
*/
static inline void isp_ll_dma_apply_config(isp_dev_t *hw)
{
hw->dma_cntl.dma_update_reg = 1;
while (hw->dma_cntl.dma_update_reg);
}
/**
* @brief Trigger one DMA input frame transfer
*
* @param[in] hw Hardware instance address
*/
static inline void isp_ll_dma_trigger_frame(isp_dev_t *hw)
{
hw->dma_cntl.dma_en = 1;
}
/**
* @brief Get if demosaic is enabled
*

View File

@@ -415,6 +415,8 @@ static inline void dw_gdma_ll_channel_set_dst_master_port(dw_gdma_dev_t *dev, ui
{
if (mem_addr == MIPI_DSI_BRG_MEM_BASE) {
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MIPI_DSI;
} else if (mem_addr == MIPI_CSI_BRG_MEM_BASE) {
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MIPI_CSI;
} else {
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MEMORY;
}

View File

@@ -18,6 +18,7 @@ INPUT += \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_ccm.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_color.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_core.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_dma.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_crop.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_demosaic.h \
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_gamma.h \

View File

@@ -67,6 +67,7 @@ The ISP driver offers following services:
- :ref:`isp-resource-allocation` - covers how to allocate ISP resources with properly set of configurations. It also covers how to recycle the resources when they finished working.
- :ref:`isp-enable-disable` - covers how to enable and disable an ISP processor.
- :ref:`isp-dma-input` - covers how to feed image frames stored in memory into the ISP through DW-GDMA.
- :ref:`isp-af-statistics` - covers how to get AF statistics one-shot or continuously.
- :ref:`isp-awb-statistics` - covers how to get AWB white patches statistics one-shot or continuously.
- :ref:`isp-ae-statistics` - covers how to get AE statistics one-shot or continuously.
@@ -240,6 +241,15 @@ Before doing ISP pipeline, you need to enable the ISP processor first, by callin
Calling :cpp:func:`esp_isp_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-dma-input:
ISP DMA Input
~~~~~~~~~~~~~
Besides image streams from camera controllers, the ISP can also read image frames from system memory through DW-GDMA. To use DMA input, set :cpp:member:`esp_isp_processor_cfg_t::input_data_source` in :cpp:type:`esp_isp_processor_cfg_t` to :cpp:enumerator:`ISP_INPUT_DATA_SOURCE_DWGDMA`, and configure the input format, output format, and resolution according to the image frame.
DMA input is useful for feeding software-generated data, offline RAW images, or other test images in memory into the ISP. It can be used to validate an ISP pipeline without a camera sensor, reproduce issues with a specific input image. Call :cpp:func:`esp_isp_dma_process_frame` to send one input buffer to the ISP and write the processed image into an output buffer. The input and output buffers must be accessible by DMA; if cacheable memory is used, perform the required cache synchronization before and after the DMA transfer.
ISP AF Controller
~~~~~~~~~~~~~~~~~
@@ -957,6 +967,7 @@ Application Examples
--------------------
* :example:`peripherals/isp/multi_pipelines` demonstrates how to use the ISP pipelines to process the image signals from camera sensors and display the video on LCD screen via DSI peripheral.
* :example:`peripherals/isp/dma_input` demonstrates how to feed a RAW8 BGGR image in memory into the ISP through DW-GDMA. ``pytest_isp_dma_input.py`` saves the processed RGB888 frames as PPM images and compares them pixel by pixel with the checked-in golden image.
* `esp_video/examples <https://github.com/espressif/esp-video-components/tree/master/esp_video/examples>`_ provides some examples of enabling ISP control algorithms.
API Reference
@@ -977,5 +988,6 @@ API Reference
.. include-build-file:: inc/isp_color.inc
.. include-build-file:: inc/isp_crop.inc
.. include-build-file:: inc/isp_core.inc
.. include-build-file:: inc/isp_dma.inc
.. include-build-file:: inc/components/esp_driver_isp/include/driver/isp_types.inc
.. include-build-file:: inc/components/esp_hal_cam/include/hal/isp_types.inc

View File

@@ -67,6 +67,7 @@ ISP 驱动程序提供以下服务:
- :ref:`isp-resource-allocation` - 涵盖如何通过正确的配置来分配 ISP 资源,以及完成工作后如何回收资源。
- :ref:`isp-enable-disable` - 涵盖如何启用和禁用 ISP 处理器。
- :ref:`isp-dma-input` - 涵盖如何通过 DW-GDMA 将存储在内存中的图像帧送入 ISP。
- :ref:`isp-af-statistics` - 涵盖如何单次或连续获取 AF 统计信息。
- :ref:`isp-awb-statistics` - 涵盖如何单次或连续获取 AWB 白块统计信息。
- :ref:`isp-ae-statistics` - 涵盖如何单次或连续获取 AE 统计信息。
@@ -240,6 +241,15 @@ ISP
调用 :cpp:func:`esp_isp_disable` 函数会执行相反的操作,即将驱动程序恢复到 **init** 状态。
.. _isp-dma-input:
ISP DMA 输入
~~~~~~~~~~~~
除来自摄像头控制器的数据流外ISP 还可以通过 DW-GDMA 从系统存储中读取图像帧作为输入。使用 DMA 输入时,应在 :cpp:type:`esp_isp_processor_cfg_t` 中将 :cpp:member:`esp_isp_processor_cfg_t::input_data_source` 配置为 :cpp:enumerator:`ISP_INPUT_DATA_SOURCE_DWGDMA`,并根据输入图像格式设置输入、输出格式及分辨率。
DMA 输入适用于将软件生成的数据、离线保存的 RAW 图像或其他内存中的测试图像送入 ISP 进行处理。它可用于无摄像头传感器参与时验证 ISP 流水线、复现特定输入图像的问题。调用 :cpp:func:`esp_isp_dma_process_frame` 可以将一帧输入缓冲区送入 ISP并将处理后的图像写入输出缓冲区。输入和输出缓冲区需要满足 DMA 访问要求;若使用带 cache 的内存,请在 DMA 传输前后执行必要的 cache 同步。
ISP AF 控制器
~~~~~~~~~~~~~
@@ -956,6 +966,7 @@ Kconfig 选项 :ref:`CONFIG_ISP_CTRL_FUNC_IN_IRAM` 支持:
--------
* :example:`peripherals/isp/multi_pipelines` 演示了如何使用 ISP 流水线处理来自摄像头传感器的图像信号,并通过 DSI 外设在 LCD 屏幕上显示视频。
* :example:`peripherals/isp/dma_input` 演示了如何通过 DW-GDMA 将内存中的 RAW8 BGGR 图像送入 ISP。``pytest_isp_dma_input.py`` 会将处理后的 RGB888 帧保存为 PPM 图片,并与示例中提交的 golden 图片进行逐像素比较。
* `esp_video/examples <https://github.com/espressif/esp-video-components/tree/master/esp_video/examples>`_ 中包含自动启用 ISP 控制算法的一些示例。
API 参考
@@ -976,5 +987,6 @@ API 参考
.. include-build-file:: inc/isp_color.inc
.. include-build-file:: inc/isp_crop.inc
.. include-build-file:: inc/isp_core.inc
.. include-build-file:: inc/isp_dma.inc
.. include-build-file:: inc/components/esp_driver_isp/include/driver/isp_types.inc
.. include-build-file:: inc/components/esp_hal_cam/include/hal/isp_types.inc

View File

@@ -228,6 +228,14 @@ examples/peripherals/i3c/i3c_i2c_basic:
- *common_components
- esp_driver_i3c
examples/peripherals/isp/dma_input:
disable:
- if: SOC_ISP_SUPPORTED != 1
depends_components:
- esp_driver_dma
- esp_driver_isp
- soc
examples/peripherals/isp/multi_pipelines:
disable:
- if: SOC_MIPI_CSI_SUPPORTED != 1

View File

@@ -0,0 +1,8 @@
# 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.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(isp_dma_input)

View File

@@ -0,0 +1,49 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# ISP DMA Input Visual Test Example
## Overview
This example embeds a 240 x 280 RAW8 Bayer image of a real scene in flash, copies it into a DMA-capable PSRAM input buffer, feeds it into the ISP through DW-GDMA, and prints the RGB888 output as base64. The pytest script decodes the output into a PPM image and compares it with the checked-in golden image.
The data flow is:
1. The embedded BGGR RAW8 image is copied from flash into the ISP DMA input buffer.
2. The image is transferred into the ISP via `DW-GDMA → ISP DMA input`.
3. The ISP processes the data (demosaic, color adjustment) and outputs RGB888 (BGR24 byte layout).
4. The RGB888 frame is base64-encoded and printed with machine-parseable markers.
5. pytest decodes the payload, swaps BGR→RGB, saves one PPM file per frame, and compares it with the golden image.
## Hardware Required
- An ESP32-P4 devkit with PSRAM (this example allocates the ISP DMA input/output buffers from PSRAM).
## How to Use
Run the test locally. It builds and flashes the example, captures the serial output, and saves one PPM artifact per frame:
```
cd examples/peripherals/isp/dma_input
pytest pytest_isp_dma_input.py --target esp32p4 --port PORT
```
The pytest log directory (`dut.logdir`) contains `isp_dma_input_frame00.ppm`, the decoded RGB888 ISP output from the current hardware run. The repository includes [golden/golden.ppm](golden/golden.ppm), the checked-in reference image. The test compares the decoded RGB888 pixels with this reference image, making image-quality regressions visible in review and detectable in CI.
## Example Output
The example processes one embedded 240 x 280 BGGR RAW8 frame.
```text
Feeding 1 frames through ISP DMA input...
IMAGE_META frame=0 width=240 height=280 format=BGR24 encoding=base64
IMAGE_BASE64_BEGIN
IMAGE_BASE64 ...
IMAGE_BASE64_END
Frame 0 done
ISP DMA visual demo done.
```
## Reference
- [ESP-IDF: Image Signal Processor](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/isp.html)

Binary file not shown.

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "isp_dma_main.c"
PRIV_REQUIRES esp_driver_isp esp_mm esp_psram mbedtls
INCLUDE_DIRS ".")
target_add_binary_data(${COMPONENT_LIB}
"${CMAKE_CURRENT_LIST_DIR}/assets/sensor_240x280_bggr.raw"
BINARY RENAME_TO "sensor_raw")

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/base64.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "driver/isp_dma.h"
#include "driver/isp_core.h"
#include "driver/isp_color.h"
#define EXAMPLE_WIDTH 240
#define EXAMPLE_HEIGHT 280
#define EXAMPLE_BASE64_CHUNK_LEN 384
#define EXAMPLE_BASE64_DELAY_MS 10
#define EXAMPLE_DMA_ALIGN 64
#define EXAMPLE_FRAME_COUNT 1
extern const uint8_t sensor_raw_start[] asm("_binary_sensor_raw_start");
extern const uint8_t sensor_raw_end[] asm("_binary_sensor_raw_end");
static void *s_alloc_dma_buffer(size_t size)
{
return heap_caps_aligned_calloc(EXAMPLE_DMA_ALIGN, 1, size,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
}
static void s_configure_neutral_color(isp_proc_handle_t isp_proc)
{
esp_isp_color_config_t color_cfg = {
.color_contrast = { .integer = 1, .decimal = 0 },
.color_saturation = { .integer = 1, .decimal = 0 },
.color_hue = 0,
.color_brightness = 0,
};
ESP_ERROR_CHECK(esp_isp_color_configure(isp_proc, &color_cfg));
ESP_ERROR_CHECK(esp_isp_color_enable(isp_proc));
}
static void s_print_base64_payload(const unsigned char *encoded, size_t encoded_len)
{
printf("IMAGE_BASE64_BEGIN\n");
fflush(stdout);
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("IMAGE_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]);
fflush(stdout);
vTaskDelay(pdMS_TO_TICKS(EXAMPLE_BASE64_DELAY_MS));
}
printf("IMAGE_BASE64_END\n");
fflush(stdout);
}
void app_main(void)
{
const uint32_t h_res = EXAMPLE_WIDTH;
const uint32_t v_res = EXAMPLE_HEIGHT;
const size_t in_size = (size_t)h_res * v_res; // RAW8: 1 byte/pixel
const size_t out_size = (size_t)h_res * v_res * 3; // RGB888: 3 bytes/pixel
isp_proc_handle_t isp_proc = NULL;
esp_isp_processor_cfg_t isp_cfg = {
.clk_hz = 240 * 1000 * 1000,
.input_data_source = ISP_INPUT_DATA_SOURCE_DWGDMA,
.input_data_color_type = ISP_COLOR_RAW8,
.output_data_color_type = ISP_COLOR_RGB888,
.bayer_order = COLOR_RAW_ELEMENT_ORDER_BGGR,
.has_line_start_packet = false,
.has_line_end_packet = false,
.h_res = h_res,
.v_res = v_res,
.dma_burst_size = 8,
};
ESP_ERROR_CHECK(esp_isp_new_processor(&isp_cfg, &isp_proc));
ESP_ERROR_CHECK(esp_isp_enable(isp_proc));
s_configure_neutral_color(isp_proc);
uint8_t *isp_in_buf = s_alloc_dma_buffer(in_size);
uint8_t *isp_out_buf = s_alloc_dma_buffer(out_size);
assert(isp_in_buf && isp_out_buf);
size_t embedded_raw_size = sensor_raw_end - sensor_raw_start;
assert(embedded_raw_size == in_size);
memcpy(isp_in_buf, sensor_raw_start, embedded_raw_size);
size_t encoded_len = 0;
int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, isp_out_buf, out_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);
printf("Feeding %d frames through ISP DMA input...\n", EXAMPLE_FRAME_COUNT);
for (int frame = 0; frame < EXAMPLE_FRAME_COUNT; frame++) {
ESP_ERROR_CHECK(esp_cache_msync(isp_in_buf, in_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
ESP_ERROR_CHECK(esp_isp_dma_process_frame(isp_proc, isp_out_buf, isp_in_buf, 1000));
ESP_ERROR_CHECK(esp_cache_msync(isp_out_buf, out_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
size_t out_len = 0;
ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &out_len, isp_out_buf, out_size) == 0 ? ESP_OK : ESP_FAIL);
printf("IMAGE_META frame=%d width=%u height=%u format=BGR24 encoding=base64\n",
frame, (unsigned)h_res, (unsigned)v_res);
s_print_base64_payload(encoded, out_len);
printf("Frame %d done\n", frame);
}
printf("ISP DMA visual demo done.\n");
free(encoded);
ESP_ERROR_CHECK(esp_isp_color_disable(isp_proc));
ESP_ERROR_CHECK(esp_isp_disable(isp_proc));
ESP_ERROR_CHECK(esp_isp_del_processor(isp_proc));
heap_caps_free(isp_in_buf);
heap_caps_free(isp_out_buf);
}

View File

@@ -0,0 +1,181 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import base64
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
IMAGE_META_PATTERN = (
r'IMAGE_META frame=(?P<frame>\d+) width=(?P<width>\d+) height=(?P<height>\d+) '
r'format=(?P<format>\w+) encoding=(?P<encoding>\w+)'
)
IMAGE_META_RE = re.compile(IMAGE_META_PATTERN)
IMAGE_CHUNK_PATTERN = (
r'IMAGE_BASE64 (?P<payload>[A-Za-z0-9+/=]+?)'
r'(?=\r?\n|IMAGE_BASE64|IMAGE_BASE64_END|Frame )'
)
IMAGE_OUTPUT_TEMPLATE = 'isp_dma_input_frame{frame:02d}.ppm'
REFERENCE_IMAGE_PATH = Path(__file__).parent / 'golden' / 'golden.ppm'
EXPECTED_PIXEL_FORMAT = 'BGR24'
EXPECTED_ENCODING = 'base64'
RGB888_BYTES_PER_PIXEL = 3
PPM_MAGIC = b'P6'
PPM_MAX_VALUE = b'255'
@dataclass(frozen=True)
class ImageMetadata:
frame: int
width: int
height: int
pixel_format: str
encoding: str
@dataclass(frozen=True)
class RgbImage:
width: int
height: int
pixels_rgb888: bytes
def __post_init__(self) -> None:
expected_size = self.width * self.height * RGB888_BYTES_PER_PIXEL
if len(self.pixels_rgb888) != expected_size:
raise ValueError(f'Expected {expected_size} RGB bytes, got {len(self.pixels_rgb888)}')
def parse_image_metadata(meta_line: str) -> ImageMetadata:
match = IMAGE_META_RE.fullmatch(meta_line)
if not match:
raise ValueError(f'Invalid image metadata line: {meta_line}')
return ImageMetadata(
frame=int(match.group('frame')),
width=int(match.group('width')),
height=int(match.group('height')),
pixel_format=match.group('format'),
encoding=match.group('encoding'),
)
def collect_base64_payload(dut: Dut) -> list[str]:
payload_lines: list[str] = []
while True:
match = dut.expect(rf'IMAGE_BASE64_END|{IMAGE_CHUNK_PATTERN}', timeout=60)
if match.group(0).decode('utf-8') == 'IMAGE_BASE64_END':
return payload_lines
payload_lines.append(match.group('payload').decode('utf-8'))
def _bgr24_to_rgb888(raw_bytes: bytes) -> bytes:
rgb_bytes = bytearray(len(raw_bytes))
for offset in range(0, len(raw_bytes), 3):
blue, green, red = raw_bytes[offset : offset + 3]
rgb_bytes[offset : offset + 3] = (red, green, blue)
return bytes(rgb_bytes)
def _encode_ppm(image: RgbImage) -> bytes:
header = b'%s\n%d %d\n%s\n' % (PPM_MAGIC, image.width, image.height, PPM_MAX_VALUE)
return header + image.pixels_rgb888
def decode_bgr24_base64_image(metadata: ImageMetadata, payload_lines: list[str]) -> RgbImage:
if metadata.pixel_format != EXPECTED_PIXEL_FORMAT:
raise ValueError(f'Unsupported pixel format: {metadata.pixel_format}')
if metadata.encoding != EXPECTED_ENCODING:
raise ValueError(f'Unsupported payload encoding: {metadata.encoding}')
raw_bytes = base64.b64decode(''.join(payload_lines), validate=True)
expected_size = metadata.width * metadata.height * RGB888_BYTES_PER_PIXEL
if len(raw_bytes) != expected_size:
raise ValueError(f'Expected {expected_size} decoded bytes, got {len(raw_bytes)}')
return RgbImage(width=metadata.width, height=metadata.height, pixels_rgb888=_bgr24_to_rgb888(raw_bytes))
def save_ppm_artifact(image: RgbImage, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
output_path.write_bytes(_encode_ppm(image))
except OSError:
logging.exception('Failed to save ISP DMA artifact to %s', output_path)
return
logging.info('Saved ISP DMA artifact to %s', output_path)
def load_ppm_image(image_path: Path) -> RgbImage:
ppm_data = image_path.read_bytes()
try:
magic, dimensions, max_value, pixels_rgb888 = ppm_data.split(b'\n', 3)
width, height = (int(value) for value in dimensions.split())
except ValueError as error:
raise ValueError(f'Invalid PPM image: {image_path}') from error
if magic != PPM_MAGIC or max_value != PPM_MAX_VALUE:
raise ValueError(f'Unsupported PPM image: {image_path}')
return RgbImage(width=width, height=height, pixels_rgb888=pixels_rgb888)
def assert_image_matches_reference(result_image: RgbImage, reference_image: RgbImage) -> None:
assert (result_image.width, result_image.height) == (reference_image.width, reference_image.height), (
f'ISP output dimensions do not match reference image: '
f'{result_image.width}x{result_image.height} != {reference_image.width}x{reference_image.height}'
)
if result_image.pixels_rgb888 == reference_image.pixels_rgb888:
return
mismatch_offset = next(
offset
for offset, (actual, expected) in enumerate(zip(result_image.pixels_rgb888, reference_image.pixels_rgb888))
if actual != expected
)
pixel_index, channel = divmod(mismatch_offset, RGB888_BYTES_PER_PIXEL)
raise AssertionError(
f'ISP output does not match reference image at pixel {pixel_index}, channel {channel}: '
f'{result_image.pixels_rgb888[mismatch_offset]} != {reference_image.pixels_rgb888[mismatch_offset]}'
)
@pytest.mark.generic
@idf_parametrize('target', soc_filtered_targets('SOC_ISP_SUPPORTED == 1'), indirect=['target'])
def test_isp_dma_input_example(dut: Dut) -> None:
reference_image = load_ppm_image(REFERENCE_IMAGE_PATH)
frame_count_match = dut.expect(r'Feeding (?P<frame_count>\d+) frames through ISP DMA input...')
expected_frame_count = int(frame_count_match.group('frame_count').decode('utf-8'))
logging.info('Expecting %d ISP DMA frame(s)', expected_frame_count)
for expected_frame in range(expected_frame_count):
metadata = parse_image_metadata(dut.expect(IMAGE_META_PATTERN).group(0).decode('utf-8'))
assert metadata.frame == expected_frame, f'Out-of-order frame: expected {expected_frame}, got {metadata.frame}'
logging.info(
'Received frame metadata: frame=%d size=%dx%d format=%s',
metadata.frame,
metadata.width,
metadata.height,
metadata.pixel_format,
)
dut.expect_exact('IMAGE_BASE64_BEGIN')
logging.info('Receiving base64 image payload for frame %d', metadata.frame)
payload_lines = collect_base64_payload(dut)
logging.info('Received %d base64 chunk(s) for frame %d', len(payload_lines), metadata.frame)
result_image = decode_bgr24_base64_image(metadata, payload_lines)
logging.info('Decoded frame %d to RGB888 image', metadata.frame)
output_path = Path(dut.logdir) / IMAGE_OUTPUT_TEMPLATE.format(frame=metadata.frame)
save_ppm_artifact(result_image, output_path)
assert_image_matches_reference(result_image, reference_image)
dut.expect_exact(f'Frame {expected_frame} done')
dut.expect_exact('ISP DMA visual demo done.')

View File

@@ -0,0 +1 @@
CONFIG_SPIRAM=y