Перевод статьи Using ESP_Video in Arduino — Dong Heng, Espressif.

В этой статье — знакомство с библиотекой ESP_Video в Arduino-ядре для ESP32. Разберёмся, как устроен API захвата в стиле V4L2 для камер с интерфейсами MIPI-CSI и DVP, пройдём по настройке оборудования и получению кадров, а также запустим официальные примеры на платах с ESP32-P4 и ESP32-S3.

Обложка статьи Using ESP_Video in Arduino

Введение#

Запуск камеры на микроконтроллере обычно начинается с таблиц выводов, регистров сенсора и учёта буферов. Библиотека ESP_Video в Arduino-ядре для ESP32 оборачивает компонент esp_video из ESP-IDF и сводит всю эту работу к небольшому набору классов C++: настроить шину камеры, открыть видеоустройство, запустить поток и забирать кадры.

API захвата построен по модели V4L2 из Linux. После инициализации железа приложение работает с узлами устройств вроде /dev/video0, запрашивает MMAP-буферы и в цикле вытаскивает кадры. Типичные применения — живой предпросмотр, захват кадров для компьютерного зрения, JPEG-снимки и настройка сенсора (усиление, экспозиция, отражение и т. п.).

ESP_Video доступна, если Arduino-ядро собрано с ESP-IDF 5.4.0 или новее, а целевой чип — ESP32-S3 или ESP32-P4:

SoCMIPI-CSI (с ISP)DVP
ESP32-P4дада
ESP32-S3—да

Официальные примеры рассчитаны на ESP32-P4-Function-EV-Board (MIPI-CSI и DVP) и ESP32-S3-EYE (DVP).

Если вы раньше пользовались классическим esp_camera и примером CameraWebServer, считайте ESP_Video следующим уровнем: скетч того же вида, но с полноценной поддержкой MIPI-CSI, встроенным ISP (процессором обработки изображения) на ESP32-P4, моделью устройств V4L2 и более широким набором форматов пикселей.

В статье разобрано:

  • интерфейсы камер — MIPI-CSI против DVP и как они представлены видеоустройствами;
  • API ESP_Video — классы конфигурации, захвата и буферов;
  • начало работы в Arduino IDE — открыть пример, выбрать плату, прошить и смотреть кадры в мониторе порта;
  • примеры захвата — как внутри этих скетчей реализован захват по MIPI-CSI (ESP32-P4) и DVP (ESP32-S3 или ESP32-P4);
  • форматы пикселей и управление сенсором — где в документации Arduino ESP32 искать значения для setFormat() и методы setSensor*().

Интерфейсы камер#

Сенсору камеры от SoC нужны две вещи: шина управления и шина пикселей.

SCCB (Serial Camera Control Bus) — это, по сути, I2C под другим названием. Через неё хост определяет сенсор, загружает настройки регистров и на лету меняет усиление, экспозицию, отражение и качество JPEG.

Шина пикселей — то, по чему на самом деле приходит изображение. Сейчас ESP_Video поддерживает DVP и MIPI-CSI.

DVP#

DVP (Digital Video Port) — параллельный интерфейс камеры. Сенсор управляет восемью линиями данных (D0–D7), а также VSYNC, DE (HREF), PCLK; внешний тактовый сигнал (XCLK) генерирует SoC.

Это тот самый интерфейс, который вы знаете по платам ESP32-CAM и ESP32-S3-EYE. Разводка простая, пропускная способность ограничена параллельной шиной и трассировкой GPIO; хорошо подходит для сенсоров класса VGA и вывода в JPEG.

В ESP_Video DVP-камера без тракта ISP видна как ESP_VIDEO_DVP_DEVICE_NAME (/dev/video2).

MIPI-CSI#

MIPI-CSI — высокоскоростной последовательный интерфейс камеры. Пиксельные данные идут по дифференциальным линиям (lanes), а не по восьми GPIO. Поэтому ESP32-P4 может работать с RAW-сенсорами более высокого разрешения и пропускать изображение через встроенный ISP до того, как приложение получит RGB, YUV или JPEG.

В ESP_Video камера MIPI-CSI видна как ESP_VIDEO_MIPI_CSI_DEVICE_NAME (/dev/video0).

ESP32-P4 может работать с CSI и DVP одновременно. Каждый интерфейс инициализируется независимо и освобождается в ESPVideoClass::end() или при уничтожении объекта.

Схема потоков данных:

  • SCCB (I2C) сенсора → блок MIPI-CSI + ISP (/dev/video0) и блок DVP (/dev/video2);
  • пиксельные данные → по последовательным линиям в MIPI-CSI либо по D0–D7 + синхросигналам в DVP;
  • оба видеоустройства → ваш скетч.

Примечание. В ESP-IDF есть и другие узлы V4L2 — для SPI-камер, USB UVC, кодеков JPEG/H.264 и устройств ISP, если включены соответствующие опции Kconfig. Примеры для Arduino в этой статье используют узлы захвата MIPI-CSI и DVP.

API ESP_Video#

Подключите заголовок библиотеки в скетче:

#include <ESP_Video.h>

API разбит на семь слоёв:

  • формат пикселей — esp_video_format_t и ESPVideoFormatClass;
  • разрешение — ESPVideoSolutionClass (ширина и высота кадра);
  • конфигурация железа — ESPVideoCamConfigClass, ESPVideoCSIConfigClass, ESPVideoDVPPinsConfigClass, ESPVideoDVPConfigClass;
  • инициализация железа — ESPVideoClass::begin();
  • устройство захвата — ESPVideoCaptureDevClass;
  • буфер — ESPVideoBufferClass;
  • управление сенсором — методы ESPVideoCaptureDevClass::setSensor*().

Полный сеанс захвата выглядит так:

  1. Скетч вызывает ESPVideoClass::begin(csi_config или dvp_config).
  2. Скетч вызывает ESPVideoCaptureDevClass::begin(путь к устройству, число буферов).
  3. setFormat(RGB565).
  4. startCapture().
  5. В цикле для каждого кадра: captureBuffer() возвращает извлечённый из очереди MMAP-буфер (ESPVideoBufferClass); скетч обрабатывает data() и уничтожает объект, а деструктор возвращает буфер драйверу через VIDIOC_QBUF.

Прежде чем смотреть код, полезно запомнить несколько принципов устройства библиотеки:

  • Объекты конфигурации — это типы-значения. Они хранят только настройки выводов и шины. Резервирование выводов через periman происходит в ESPVideoClass::begin(), а освобождение — в end() или деструкторе.
  • Открытие захвата унифицировано. ESPVideoCaptureDevClass::begin() открывает устройство, считывает и заново применяет текущий формат V4L2, запоминает ширину и высоту и запрашивает буферы захвата.
  • Формат и разрешение путешествуют вместе с кадром. И устройство захвата, и каждый извлечённый буфер наследуют метаданные формата и разрешения, так что buffer.formatName(), getWidth() и getHeight() описывают именно тот кадр, который вы только что получили.
  • Классы, владеющие ресурсами, нельзя копировать. Используйте один ESPVideoClass и один ESPVideoCaptureDevClass на всё время работы скетча. ESPVideoBufferClass можно только перемещать.

Дальше пройдём по объектам, которые вы на самом деле создаёте в setup().

Конфигурация камеры (SCCB)#

ESPVideoCamConfigClass хранит общие для CSI и DVP настройки SCCB/I2C. Вызовите begin(), затем передайте объект в конфигурацию CSI или DVP.

ESPVideoCamConfigClass cam_config;
cam_config.begin(port, scl_pin, sda_pin);

Аргументы:

  • port — порт I2C, используемый для SCCB, если шину инициализирует библиотека;
  • scl_pin / sda_pin — выводы тактирования и данных SCCB;
  • i2c_freq — необязательный, по умолчанию 100 кГц;
  • reset_pin / pwdn_pin — необязательные GPIO сброса и отключения питания сенсора; если не используются, передайте -1.

Если ваш скетч уже владеет шиной I2C master, используйте перегрузку, принимающую i2c_master_bus_handle_t. В этом случае библиотека не будет создавать второй драйвер I2C для SCCB.

Конфигурация MIPI-CSI#

ESPVideoCSIConfigClass расширяет конфигурацию камеры для связки MIPI-CSI + ISP на ESP32-P4.

ESPVideoCSIConfigClass csi_config;
csi_config.begin(cam_config);

Передайте вторым аргументом dont_init_ldo = true, если питание MIPI PHY подаётся извне и вы не хотите, чтобы драйвер CSI инициализировал LDO.

Конфигурация DVP#

DVP нужны ещё две вещи: карта параллельных выводов и частота XCLK сенсора.

ESPVideoDVPPinsConfigClass dvp_pins;
dvp_pins.begin(vsync, de, pclk, xclk, d0, d1, d2, d3, d4, d5, d6, d7);

ESPVideoDVPConfigClass dvp_config;
dvp_config.begin(cam_config, dvp_pins, xclk_freq_hz);

begin() у объекта выводов всегда настраивает 8-битную шину данных (D0–D7). xclk_freq_hz — внешняя тактовая частота, которую SoC генерирует для сенсора: 10 МГц на ESP32-S3-EYE и 20 МГц для DVP-камеры на ESP32-P4-Function-EV-Board.

Инициализация железа#

Создайте один экземпляр ESPVideoClass (глобальный или статический) и вызовите перегрузку begin(), соответствующую интерфейсу:

ESPVideoClass video;

if (!video.begin(csi_config)) {  // or dvp_config
    Serial.println("failed to init camera");
    return;
}

Этот вызов резервирует GPIO, инициализирует esp_video с флагами CSI+ISP или DVP и сохраняет конфигурацию для последующего освобождения. Он возвращает true, если инициализация прошла успешно или уже была выполнена. При ошибке зарезервированные выводы освобождаются, и функция возвращает false.

Устройство захвата и кадры#

ESPVideoCaptureDevClass открывает узел захвата V4L2, управляет MMAP-буферами и извлекает кадры из очереди.

ESPVideoCaptureDevClass capture;

if (!capture.begin(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, 2)) {
    Serial.println("failed to open capture device");
    return;
}

if (!capture.setFormat(ESP_VIDEO_FORMAT_RGB565)) {
    Serial.println("failed to set format");
    return;
}

if (!capture.startCapture()) {
    Serial.println("failed to start capture");
    return;
}
  • begin(path, buf_count) открывает устройство, применяет текущий формат и запрашивает buf_count MMAP-буферов. Берите не меньше 2 — тогда захват работает с двойной буферизацией и теряет меньше кадров.
  • setFormat() переводит значение esp_video_format_t в fourcc V4L2 и выполняет VIDIOC_S_FMT. Вызывайте после begin() и до startCapture(). Если буферы уже были выделены, а формат изменился, они будут запрошены заново автоматически.
  • startCapture() ставит все буферы в очередь и запускает поток (VIDIOC_STREAMON).

В loop() извлеките один кадр, используйте его и дайте объекту выйти из области видимости:

ESPVideoBufferClass frame = capture.captureBuffer();
if (frame.valid()) {
    uint8_t *pixels = frame.data();
    size_t nbytes = frame.size();
    // process the frame
}
// destructor re-queues the buffer

ESPVideoBufferClass — RAII-обёртка над одним извлечённым буфером. Когда объект уничтожается, из него делают move или вызывают end(), буфер возвращается драйверу через VIDIOC_QBUF. Не храните указатель data() дольше, чем живёт объект. Если удерживать буферы и не отпускать их, captureBuffer() зависнет.

Начало работы в Arduino IDE#

Понадобится:

  • Arduino IDE 2.x;
  • Arduino Core for ESP32 с библиотекой ESP_Video (ядро 3.3.10 или новее);
  • поддерживаемая плата с камерой: ESP32-P4-Function-EV-Board (MIPI-CSI или DVP) или ESP32-S3-EYE (DVP);
  • USB-кабель с линиями данных.

Если пакет плат ESP32 ещё не установлен, следуйте руководству Getting Started with ESP32 Arduino. Если нужно самое свежее ядро с ESP_Video ещё до того, как оно попадёт в стабильный пакет, используйте development-адрес для Board Manager:

https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Выбор платы#

  • Подключите плату по USB.
  • В Arduino IDE откройте Tools > Board > esp32 и выберите подходящую плату, например ESP32P4 Core Board.
  • В меню Tools включите PSRAM, если такой пункт есть. Кадры камеры большие, и без PSRAM выделение буферов, скорее всего, не удастся.
  • Выберите последовательный Port.
Меню Tools > Board в Arduino IDE с выбором платы ESP32-P4

Выберите плату, соответствующую вашему железу, в Tools > Board

Открываем официальный пример#

Примеры библиотеки сразу выдают поток корректных кадров, так что «голый» запуск драйвера можно пропустить. В Arduino IDE откройте File > Examples > ESP_Video и выберите скетч под свой интерфейс камеры:

  • mipi_csi_camera — MIPI-CSI на ESP32-P4-Function-EV-Board V1.5;
  • dvp_camera — DVP на ESP32-S3-EYE или ESP32-P4-Function-EV-Board V1.5.
Меню File > Examples > ESP_Video в Arduino IDE

Откройте File > Examples > ESP_Video и выберите скетч под свой интерфейс камеры

Определения выводов в начале каждого скетча соответствуют этим платам. Если камера подключена иначе, перед загрузкой поменяйте макросы EXAMPLE_*.

Для ESP32-P4 конфигурация CI примера MIPI-CSI использует флеш объёмом 16 МБ и увеличенную схему разделов. Если раздела по умолчанию не хватает для приложения вместе с буферами кадров, выберите схему побольше в Tools > Partition Scheme.

Загружаем и смотрим на кадры#

  • Нажмите Upload.
  • Откройте Tools > Serial Monitor на скорости 115200 бод.
  • После строки Arduino camera example started на каждый кадр должна выводиться одна строка: указатель на буфер, размер, название формата, ширина и высота.
Монитор порта Arduino IDE со строками о захваченных кадрах

Монитор порта на 115200 бод: по строке на каждый извлечённый кадр

Успешный запуск выглядит так (значения зависят от сенсора и формата):

Arduino camera example started
captured buffer: 0x3c0a0000 size: 614400 format: RGB565 width: 640 height: 480
captured buffer: 0x3c110000 size: 614400 format: RGB565 width: 640 height: 480

Конкретные адреса зависят от раскладки PSRAM. Если инициализация не удалась, скетч выведет failed to init CSI camera / failed to init DVP camera или failed to open capture device и дальше будет просто ждать в loop(). Проверьте выбор платы, PSRAM, выводы SCCB и то, что подключён поддерживаемый сенсор.

Важно. Публичный API в ESP_Video.h компилируется, только если включена хотя бы одна из опций CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE или CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE. Arduino-ядро включает их для ESP32-P4 и ESP32-S3 в стандартной конфигурации SDK, на которой собираются примеры. Если вы собираете Arduino как компонент ESP-IDF со своим sdkconfig, включите нужную опцию сами.

Захват по MIPI-CSI на ESP32-P4#

В этом разделе разберём mipi_csi_camera.ino. Разводка по умолчанию — MIPI-CSI-камера на ESP32-P4-Function-EV-Board V1.5: SCCB на порту I2C 0, SCL — GPIO 8, SDA — GPIO 7.

Настраиваем SCCB и CSI#

ESPVideoCamConfigClass cam_config;
cam_config.begin(EXAMPLE_MIPI_CSI_SCCB_I2C_PORT,
                 EXAMPLE_MIPI_CSI_SCCB_I2C_SCL_PIN,
                 EXAMPLE_MIPI_CSI_SCCB_I2C_SDA_PIN);

ESPVideoCSIConfigClass csi_config;
csi_config.begin(cam_config);

if (!video.begin(csi_config)) {
    Serial.println("failed to init CSI camera");
    return;
}

video.begin(csi_config) инициализирует MIPI-CSI и ISP (ESP_VIDEO_INIT_FLAGS_MIPI_CSI | ESP_VIDEO_INIT_FLAGS_ISP). После возврата из функции появляется /dev/video0, а драйвер сенсора уже нашёл камеру на шине SCCB.

Открываем устройство, ставим RGB565, запускаем поток#

const size_t kCaptureBufferCount = 2;

if (!capture_dev.begin(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, kCaptureBufferCount)) {
    Serial.println("failed to open capture device");
    return;
}

if (!capture_dev.setFormat(ESP_VIDEO_FORMAT_RGB565)) {
    Serial.println("failed to set format");
    return;
}

if (!capture_dev.startCapture()) {
    Serial.println("failed to start capture");
    return;
}

RGB565 — удобный формат для предпросмотра: два байта на пиксель, легко выводить на RGB565-дисплей. Если сенсор или ISP не умеют его выдавать, setFormat() вернёт false — попробуйте ESP_VIDEO_FORMAT_YUV422_YUYV, ESP_VIDEO_FORMAT_JPEG или оставьте формат драйвера по умолчанию, пропустив setFormat().

Извлекаем кадры в loop()#

void loop() {
    if (!capture_dev.isOpened() || !capture_dev.isCaptureStarted()) {
        delay(1000);
        return;
    }

    ESPVideoBufferClass buffer = capture_dev.captureBuffer();
    if (!buffer.valid()) {
        Serial.println("failed to capture buffer");
        delay(10);
        return;
    }

    Serial.printf(
        "captured buffer: %p size: %lu format: %s width: %lu height: %lu\n",
        buffer.data(), (unsigned long)buffer.size(), buffer.formatName(),
        (unsigned long)buffer.getWidth(), (unsigned long)buffer.getHeight());
}

Вот и весь путь захвата на стороне приложения. Замените Serial.printf на свой предпросмотр, кодировщик или подачу на вход нейросети. Держите объект ESPVideoBufferClass только пока обрабатываете этот один кадр.

Полный пример для MIPI-CSI — mipi_csi_camera.ino:

#include "Arduino.h"
#include <ESP_Video.h>

#define EXAMPLE_MIPI_CSI_SCCB_I2C_PORT    0
#define EXAMPLE_MIPI_CSI_SCCB_I2C_SCL_PIN 8
#define EXAMPLE_MIPI_CSI_SCCB_I2C_SDA_PIN 7

ESPVideoClass video;
ESPVideoCaptureDevClass capture_dev;
const size_t kCaptureBufferCount = 2;

void setup() {
    Serial.begin(115200);

    ESPVideoCamConfigClass cam_config;
    cam_config.begin(EXAMPLE_MIPI_CSI_SCCB_I2C_PORT,
                     EXAMPLE_MIPI_CSI_SCCB_I2C_SCL_PIN,
                     EXAMPLE_MIPI_CSI_SCCB_I2C_SDA_PIN);

    ESPVideoCSIConfigClass csi_config;
    csi_config.begin(cam_config);

    if (!video.begin(csi_config)) {
        Serial.println("failed to init CSI camera");
        return;
    }

    if (!capture_dev.begin(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, kCaptureBufferCount)) {
        Serial.println("failed to open capture device");
        return;
    }

    if (!capture_dev.setFormat(ESP_VIDEO_FORMAT_RGB565)) {
        Serial.println("failed to set format");
        return;
    }

    if (!capture_dev.startCapture()) {
        Serial.println("failed to start capture");
        return;
    }

    Serial.println("Arduino camera example started");
}

void loop() {
    if (!capture_dev.isOpened() || !capture_dev.isCaptureStarted()) {
        delay(1000);
        return;
    }

    ESPVideoBufferClass buffer = capture_dev.captureBuffer();
    if (!buffer.valid()) {
        Serial.println("failed to capture buffer");
        delay(10);
        return;
    }

    Serial.printf(
        "captured buffer: %p size: %lu format: %s width: %lu height: %lu\n",
        buffer.data(), (unsigned long)buffer.size(), buffer.formatName(),
        (unsigned long)buffer.getWidth(), (unsigned long)buffer.getHeight());
}

Захват по DVP на ESP32-S3 или ESP32-P4#

dvp_camera.ino устроен так же. Дополнительный шаг — заполнить ESPVideoDVPPinsConfigClass перед ESPVideoDVPConfigClass.

Карты выводов по умолчанию.

ESP32-S3-EYE:

СигналGPIO
SCCB SCL / SDA5 / 4
XCLK / PCLK15 / 13
VSYNC / DE6 / 7
D0–D711, 9, 8, 10, 12, 18, 17, 16
Частота XCLK10 МГц

ESP32-P4-Function-EV-Board V1.5:

СигналGPIO
SCCB SCL / SDA8 / 7
XCLK / PCLK20 / 4
VSYNC / DE37 / 22
D0–D72, 32, 33, 23, 3, 6, 5, 21
Частота XCLK20 МГц

Настраиваем SCCB, выводы и DVP#

ESPVideoCamConfigClass cam_config;
cam_config.begin(EXAMPLE_DVP_SCCB_I2C_PORT,
                 EXAMPLE_DVP_SCCB_I2C_SCL_PIN,
                 EXAMPLE_DVP_SCCB_I2C_SDA_PIN);

ESPVideoDVPPinsConfigClass dvp_pins;
dvp_pins.begin(
    EXAMPLE_DVP_VSYNC_PIN, EXAMPLE_DVP_DE_PIN,
    EXAMPLE_DVP_PCLK_PIN, EXAMPLE_DVP_XCLK_PIN,
    EXAMPLE_DVP_D0_PIN, EXAMPLE_DVP_D1_PIN, EXAMPLE_DVP_D2_PIN, EXAMPLE_DVP_D3_PIN,
    EXAMPLE_DVP_D4_PIN, EXAMPLE_DVP_D5_PIN, EXAMPLE_DVP_D6_PIN, EXAMPLE_DVP_D7_PIN);

ESPVideoDVPConfigClass dvp_config;
dvp_config.begin(cam_config, dvp_pins, EXAMPLE_DVP_XCLK_FREQ);

if (!video.begin(dvp_config)) {
    Serial.println("failed to init DVP camera");
    return;
}

Открываем /dev/video2 и захватываем#

if (!capture_dev.begin(ESP_VIDEO_DVP_DEVICE_NAME, kCaptureBufferCount)) {
    Serial.println("failed to open capture device");
    return;
}

if (!capture_dev.startCapture()) {
    Serial.println("failed to start capture");
    return;
}

Пример для DVP не вызывает setFormat() и оставляет формат драйвера по умолчанию. Если нужен конкретный fourcc, setFormat() всё равно можно вызвать после begin(). loop() полностью совпадает со скетчем MIPI-CSI: извлечь кадр, вывести метаданные, отпустить.

Форматы пикселей#

Вызывайте setFormat() со значением esp_video_format_t после begin(). Какие форматы реально поддерживает сенсор, зависит от драйвера и активного видеоустройства. Перечисление, соответствие fourcc V4L2 и строковые названия форматов описаны в разделе Pixel format документации Arduino ESP32.

Управление сенсором#

Когда устройство захвата открыто, можно подстраивать усиление, экспозицию, отражение, качество JPEG и другие параметры. Каждый метод возвращает true, если драйвер принял значение. Поддержка зависит от сенсора и текущего формата. Полный список методов setSensor*() и их соответствие элементам управления V4L2 — в разделе Sensor controls документации Arduino ESP32.

capture.setSensorGain(16);
capture.setSensorExposure(800);
capture.setSensorVFlip(true);
capture.setSensorHFlip(false);
capture.setSensorJPEGQuality(12);

При запуске новой платы пригодится тестовый шаблон: если setSensorTestPattern(true) выдаёт известную картинку, значит, шина пикселей жива, и отлаживать SCCB или настройки ISP можно отдельно.

Что дальше#

Примеры из статьи ограничиваются выводом метаданных кадра. Отсюда можно:

  • выводить кадры RGB565 на LCD для локального предпросмотра;
  • подавать буферы YUV или RGB в ESP-WHO или ESP-DL для обнаружения и распознавания;
  • снимать JPEG-кадры и раздавать их по HTTP — в духе CameraWebServer;
  • сделать Matter-камеру на ESP32-P4, как описано в статье Introducing Matter Cameras.

Полный справочник по классам и методам:

Заключение#

Мы познакомились с ESP_Video — стеком для работы с камерой из Arduino на ESP32-P4 и ESP32-S3: MIPI-CSI со встроенным ISP, DVP и цикл захвата в стиле V4L2, который умещается в setup() и loop(). Мы запустили официальный пример из Arduino IDE, убедились, что поток корректных кадров идёт, а затем разобрали, как эти скетчи настраивают SCCB и шину пикселей, открывают узел устройства и извлекают RAII-буферы. Форматы пикселей и управление сенсором описаны в API Arduino ESP32, поэтому статья сосредоточилась на пути от работающего захвата к приложению.

Этот путь начинается в loop(). Примеры печатают указатель, размер, формат и разрешение; всё остальное — просто другое использование того же объекта ESPVideoBufferClass, пока он находится в области видимости.

Самый очевидный следующий шаг — локальный предпросмотр. RGB565 — это два байта на пиксель, и он хорошо ложится на многие LCD: выведите data() как изображение размером getWidth() на getHeight(), а затем позвольте деструктору буфера вернуть слот драйверу. Если дисплей ожидает другой формат или ориентацию, помогут те же setFormat() и setSensorVFlip() / setSensorHFlip(), что уже показаны для захвата.

Компьютерное зрение устроено так же. Кадры YUV или RGB можно передавать в ESP-WHO или ESP-DL для обнаружения и распознавания прямо на устройстве, без ПК в цепочке. JPEG-снимки идут в обратную сторону: кодируйте их на сенсоре или в ISP и раздавайте по HTTP в духе CameraWebServer. На ESP32-P4 тот же стек камеры служит основой для продукта — Matter-камеры, как описано в Introducing Matter Cameras.

Когда кадры пошли, самое интересное — уже не запуск драйвера. Это выбор формата, короткое время жизни буферов и передача пикселей в предпросмотр, инференс, веб-интерфейс или конвейер умного дома.