# Asynchronous Color Conversion

[中文]

This document introduces the Async Color Convert driver in ESP-IDF. The table of contents is as follows:

## Overview

ESP32-P4 provides a DMA2D engine that can offload 2D copy and color conversion work from the CPU.

This driver is useful when your application needs to:

- convert an image from one pixel format to another

- copy only a window of a larger image

- queue multiple conversions without doing the work on the CPU

- move between RGB and UYVY formats while selecting the RGB/YUV conversion standard

The Async Color Convert driver wraps DMA2D request preparation, queueing, and completion handling into a small API that supports both:

- asynchronous submission with an ISR callback

- a simpler blocking API built on top of the same request path

## Quick Start

If you are new to this driver, start with the simplest workflow:

1. Install the driver

2. Prepare one `async_color_convert_request_t`

3. Submit the conversion through either the blocking or non-blocking API

4. Consume the converted output buffer after the conversion completes

5. Either submit another request or uninstall the driver when finished

The typical usage flow is:

            flowchart TD
        install["Install driver<br/>esp_async_color_convert_install_dma2d"] --> request["Prepare request<br/>async_color_convert_request_t"]
        request --> blocking["Blocking path<br/>esp_color_convert_blocking"]
        request --> nonBlocking["Non-blocking path<br/>esp_async_color_convert"]
        nonBlocking --> callback["Wait for callback or task notification"]
        blocking --> result["Use converted buffer"]
        callback --> result
        result --> request
        result --> uninstall["Optional cleanup<br/>esp_async_color_convert_uninstall"]

## Scenario 1: Start with One Blocking Conversion

The easiest way to learn the API is to convert one image and wait until the conversion is complete.

The following flow mirrors the peripherals/dma/async_color_convert example. It converts one embedded UYVY422 image into BGR24 and then lets the application consume the converted output:

    async_color_convert_handle_t conv_hdl = NULL; // Driver handle returned by the install API
    async_color_convert_config_t config = {
        .backlog = 1,          // One in-flight request is enough for this simple blocking example
        .dma_burst_size = 16,  // Start with the default burst size used by the example
    };
    // Create one Async Color Convert driver instance backed by DMA2D.
    ESP_ERROR_CHECK(esp_async_color_convert_install_dma2d(&config, &conv_hdl));

    async_color_convert_request_t req = {
        .src_buffer = sample_96x64_uyvy_yuv_start, // Source image can be in flash or RAM, as long as DMA can access it
        .src_stride = 96,                          // Source image row stride, in pixels
        .src_height = 64,                          // Source image height, in pixels
        .src_x = 0,                                // Start from the left edge of the source image
        .src_y = 0,
        .dst_buffer = dst_bgr,                    // Destination buffer in DMA-capable RAM
        .dst_stride = 96,                         // Destination image row stride, in pixels
        .dst_height = 64,                         // Destination image height, in pixels
        .dst_x = 0,                               // Write the converted output from the top-left corner
        .dst_y = 0,
        .copy_width = 96,                         // Convert the full image width, in pixels
        .copy_height = 64,                        // Convert the full image height, in pixels
        .src_color_format = ESP_COLOR_FOURCC_UYVY,   // Source pixels are UYVY422
        .dst_color_format = ESP_COLOR_FOURCC_BGR24,  // Destination pixels are BGR24 (used as RGB888 in this driver)
        .color_conv_std = COLOR_CONV_STD_RGB_YUV_BT601, // RGB/YUV standard used for this conversion pair
    };

    // Wait until DMA2D finishes the conversion. -1 means wait forever.
    ESP_ERROR_CHECK(esp_color_convert_blocking(conv_hdl, &req, -1));

    // Release the driver after all conversions are done.
    ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl));

This flow introduces the most important ideas:

- `esp_async_color_convert_install_dma2d()` creates the driver instance

- `async_color_convert_request_t` describes the source image, destination image, and conversion window

- `esp_color_convert_blocking()` waits until the hardware finishes the conversion

- `esp_async_color_convert_uninstall()` releases the driver resources

For the blocking API, `timeout_ms = -1` means wait forever. Other timeout values are currently unsupported and return `ESP_ERR_INVALID_ARG`.

### Understanding `async_color_convert_request_t`

Most application issues come from building the request incorrectly, so it is worth understanding the structure carefully.

Important

In `async_color_convert_request_t`, all geometry fields are measured in pixels, not bytes. This includes `src_stride`, `src_height`, `src_x`, `src_y`, `dst_stride`, `dst_height`, `dst_x`, `dst_y`, `copy_width`, and `copy_height`.

`src_stride` and `dst_stride` are row strides, not conversion widths. They describe how many pixels each full image row spans in memory, so they can be larger than `copy_width` when converting a window inside a larger image.

The structure describes two things at the same time:

- the full source and destination images in memory

- the rectangular window that should be converted

The key fields are:

- `async_color_convert_request_t::src_buffer`

    Base address of the source image

- `async_color_convert_request_t::src_stride`

    Source image row stride in pixels

- `async_color_convert_request_t::src_height`

    Source image height in pixels

- `async_color_convert_request_t::src_x` and `async_color_convert_request_t::src_y`

    Top-left corner of the source window

- `async_color_convert_request_t::dst_buffer`

    Base address of the destination image

- `async_color_convert_request_t::dst_stride`

    Destination image row stride in pixels

- `async_color_convert_request_t::dst_height`

    Destination image height in pixels

- `async_color_convert_request_t::dst_x` and `async_color_convert_request_t::dst_y`

    Top-left corner of where the converted window should be written

- `async_color_convert_request_t::copy_width` and `async_color_convert_request_t::copy_height`

    Size of the rectangle to convert

- `async_color_convert_request_t::src_color_format` and `async_color_convert_request_t::dst_color_format`

    Source and destination pixel formats

- `async_color_convert_request_t::color_conv_std`

    RGB/YUV conversion standard, used for RGB <-> YUV conversions

Both the source window and destination window must stay within the bounds of their corresponding images.

### Supported Conversions

The following format pairs are currently supported by this driver:

Source format | Destination format | Conversion standard
--- | --- | ---
`ESP_COLOR_FOURCC_RGB16` | `ESP_COLOR_FOURCC_RGB16` | N/A
`ESP_COLOR_FOURCC_BGR24` | `ESP_COLOR_FOURCC_BGR24` | N/A
`ESP_COLOR_FOURCC_RGB24` | `ESP_COLOR_FOURCC_RGB24` | N/A
`ESP_COLOR_FOURCC_UYVY` | `ESP_COLOR_FOURCC_UYVY` | N/A
`ESP_COLOR_FOURCC_BGR24` | `ESP_COLOR_FOURCC_RGB24` | N/A
`ESP_COLOR_FOURCC_RGB24` | `ESP_COLOR_FOURCC_BGR24` | N/A
`ESP_COLOR_FOURCC_RGB16` | `ESP_COLOR_FOURCC_BGR24` | N/A
`ESP_COLOR_FOURCC_BGR24` | `ESP_COLOR_FOURCC_RGB16` | N/A
`ESP_COLOR_FOURCC_RGB24` | `ESP_COLOR_FOURCC_RGB16` | N/A
`ESP_COLOR_FOURCC_BGR24` | `ESP_COLOR_FOURCC_UYVY` | BT.601
`ESP_COLOR_FOURCC_BGR24` | `ESP_COLOR_FOURCC_UYVY` | BT.709
`ESP_COLOR_FOURCC_RGB24` | `ESP_COLOR_FOURCC_UYVY` | BT.601
`ESP_COLOR_FOURCC_RGB24` | `ESP_COLOR_FOURCC_UYVY` | BT.709
`ESP_COLOR_FOURCC_UYVY` | `ESP_COLOR_FOURCC_BGR24` | BT.601
`ESP_COLOR_FOURCC_UYVY` | `ESP_COLOR_FOURCC_BGR24` | BT.709

Note

Always set `async_color_convert_request_t::src_color_format` and `async_color_convert_request_t::dst_color_format`. Set `async_color_convert_request_t::color_conv_std` when converting between RGB and YUV.

## Scenario 2: Use the Asynchronous API with a Callback

Once the blocking flow is clear, the next step is to queue a request and let the driver notify you from interrupt context when it is finished.

    static bool color_conv_done_cb(async_color_convert_handle_t conv_hdl,
                                   async_color_convert_event_data_t *edata,
                                   void *cb_args)
    {
        BaseType_t high_task_wakeup = pdFALSE; // Required by FreeRTOS when an ISR wakes a task
        SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args; // User context passed at submit time
        // Notify a waiting task that the conversion has finished.
        xSemaphoreGiveFromISR(sem, &high_task_wakeup);
        // Return true when the unblocked task should run immediately after the ISR.
        return high_task_wakeup == pdTRUE;
    }

    async_color_convert_request_t req = {
        .src_buffer = src_buf,   // Source image base address
        .src_stride = src_width, // Source image row stride, in pixels
        .src_height = src_height,
        .src_x = 0,
        .src_y = 0,
        .dst_buffer = dst_buf,   // Destination image base address
        .dst_stride = dst_width, // Destination image row stride, in pixels
        .dst_height = dst_height,
        .dst_x = 0,
        .dst_y = 0,
        .copy_width = copy_width,
        .copy_height = copy_height,
        .src_color_format = ESP_COLOR_FOURCC_RGB16,
        .dst_color_format = ESP_COLOR_FOURCC_BGR24,
    };

    // Queue one asynchronous request. The callback runs later in ISR context.
    ESP_ERROR_CHECK(esp_async_color_convert(conv_hdl, &req, color_conv_done_cb, sem));
    // Wait in task context until the callback gives the semaphore.
    xSemaphoreTake(sem, portMAX_DELAY);

The callback runs in ISR context, so keep it short and only use ISR-safe APIs such as `xSemaphoreGiveFromISR` or `xQueueSendFromISR`.

## Operational Notes

### Driver Configuration

The driver configuration fields are:

- `async_color_convert_config_t::backlog`

    Maximum number of in-flight or pending requests. `0` uses a driver default.

- `async_color_convert_config_t::dma_burst_size`

    DMA burst size in bytes. `0` uses a driver default.

- `async_color_convert_config_t::intr_priority`

    DMA2D interrupt priority. `0` uses the default low/medium priority.

### DMA Burst Size

The `dma_burst_size` affects DMA transfer efficiency:

- Larger burst sizes may improve throughput

- Larger burst sizes can also increase bus occupancy, so they are not always best for every workload

- Common starting values are 16, 32, and 64 bytes

The best value depends on the chip's DMA controller capabilities and how much memory bandwidth is shared with other active components in the system.

### Thread Safety and ISR Rules

- The driver is thread-safe. Requests from different tasks are serialized through the internal queue.

- `esp_async_color_convert()` can be called from tasks to enqueue requests.

- The callback type `async_color_convert_isr_cb_t` runs in ISR context.

- Do not call blocking APIs from the callback.

- `esp_color_convert_blocking()` must not be called from ISR context.

### Uninstalling the Driver

When the driver is no longer needed:

    // Uninstall only after all queued conversions have completed.
    ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl));

If requests are still pending, `esp_async_color_convert_uninstall()` returns `ESP_ERR_INVALID_STATE`.

## Application Example

- peripherals/dma/async_color_convert shows a beginner-friendly blocking conversion flow:

  - an embedded `.yuv` image is read directly from mapped flash

  - DMA2D converts the image from UYVY422 to BGR24

  - the converted output is base64-encoded and printed to the console

  - pytest reconstructs the image as a PNG artifact and compares it against a golden reference image

## API Reference

### Async Color Convert Driver Functions

#### Header File

- components/esp_driver_dma/include/esp_async_color_convert.h

- This header file can be included with:

  >     #include "esp_async_color_convert.h"

- This header file is a part of the API provided by the `esp_driver_dma` component. To declare that your component depends on `esp_driver_dma`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_dma

  > or

  >     PRIV_REQUIRES esp_driver_dma

#### Functions

esp_err_t esp_async_color_convert_install_dma2d(const async_color_convert_config_t *config, async_color_convert_handle_t *ret_hdl)

  Install async color conversion driver with the DMA2D backend.

  This API allocates internal resources and creates a conversion context.

  Parameters:

    - config -- [in] Driver configuration

    - ret_hdl -- [out] Returned driver handle

  Returns:

    - ESP_OK: Driver installed successfully

    - ESP_ERR_INVALID_ARG: Invalid argument

    - ESP_ERR_NO_MEM: Out of memory

    - ESP_ERR_NOT_FOUND: Required DMA2D resource is unavailable

    - others: Error from lower-level DMA2D driver

esp_err_t esp_async_color_convert_uninstall(async_color_convert_handle_t conv_hdl)

  Uninstall async color conversion driver.

  Parameters:

    conv_hdl -- [in] Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d`

  Returns:

    - ESP_OK: Driver uninstalled successfully

    - ESP_ERR_INVALID_ARG: Invalid argument

    - ESP_ERR_INVALID_STATE: There are pending requests in the queue

esp_err_t esp_async_color_convert(async_color_convert_handle_t conv_hdl, const async_color_convert_request_t *request, async_color_convert_isr_cb_t cb_isr, void *cb_args)

  Submit an asynchronous 2D color conversion request.

  The request is enqueued and completed later in DMA2D interrupt context. The callback can be NULL if no completion notification is needed.

  Parameters:

    - conv_hdl -- [in] Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d`

    - request -- [in] Color conversion request

    - cb_isr -- [in] ISR callback invoked on conversion completion, can be NULL

    - cb_args -- [in] User context passed to `cb_isr`

  Returns:

    - ESP_OK: Request accepted

    - ESP_ERR_INVALID_ARG: Invalid argument or invalid request fields

    - ESP_ERR_INVALID_STATE: No free internal transaction slot (queue full)

    - others: Error from lower-level DMA2D driver

esp_err_t esp_color_convert_blocking(async_color_convert_handle_t conv_hdl, const async_color_convert_request_t *request, int32_t timeout_ms)

  Blocking 2D color conversion API built on async request path.

  Note

  This API must not be called from ISR context.

  Parameters:

    - conv_hdl -- [in] Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d`

    - request -- [in] Color conversion request

    - timeout_ms -- [in] Timeout in milliseconds. Currently only `-1` is supported, which waits forever.

  Returns:

    - ESP_OK: Conversion completed successfully

    - ESP_ERR_INVALID_ARG: Invalid argument, unsupported timeout, or invalid request fields

    - ESP_ERR_INVALID_STATE: Called from ISR context, or queue unavailable

    - others: Error from lower-level DMA2D driver

#### Structures

struct async_color_convert_event_data_t

  Async color conversion event data.

struct async_color_convert_config_t

  Async color conversion driver configuration.

  Public Members

  uint32_t backlog

    Number of in-flight/pending requests. 0 means driver default.

  size_t dma_burst_size

    DMA burst length in bytes. 0 means driver default.

  uint32_t intr_priority

    Interrupt priority. 0 means default low/medium priority.

struct async_color_convert_request_t

  Async color conversion request.

  Coordinates and size are in pixels.

  The source and destination windows are:

  - source: [src_x, src_x + copy_width) x [src_y, src_y + copy_height)

  - destination: [dst_x, dst_x + copy_width) x [dst_y, dst_y + copy_height)

  Both windows must be fully inside their corresponding image bounds.

  Conversion rule is inferred from source and destination formats:

  - If source and destination are the same format, it performs 2D copy only.

  Public Members

  const void *src_buffer

    Source picture base address

  uint32_t src_stride

    Source picture row stride in pixels

  uint32_t src_height

    Source picture height in pixels

  uint32_t src_x

    Source window x offset in pixels

  uint32_t src_y

    Source window y offset in pixels

  void *dst_buffer

    Destination picture base address

  uint32_t dst_stride

    Destination picture row stride in pixels

  uint32_t dst_height

    Destination picture height in pixels

  uint32_t dst_x

    Destination window x offset in pixels

  uint32_t dst_y

    Destination window y offset in pixels

  uint32_t copy_width

    Conversion window width in pixels

  uint32_t copy_height

    Conversion window height in pixels

  esp_color_fourcc_t src_color_format

    Source pixel format

  esp_color_fourcc_t dst_color_format

    Destination pixel format

  color_conv_std_rgb_yuv_t color_conv_std

    RGB/YUV conversion standard for RGB888<->UYVY422

#### Type Definitions

typedef struct async_color_convert_context_t *async_color_convert_handle_t

  Opaque handle of async color conversion driver instance.

typedef bool (*async_color_convert_isr_cb_t)(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *edata, void *cb_args)

  Async color conversion callback type.

  Note

  This callback runs in ISR context.

  Param conv_hdl:

    [in] Driver handle that produced this event

  Param edata:

    [in] Event data for the completed request

  Param cb_args:

    [in] User context passed to :cpp:func:`esp_async_color_convert`

  Return:

    - true: a higher-priority task was woken and a yield is requested

    - false: no yield request

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.