User confirmed the FULL pipeline works with their laptop webcam at QVGA quality 0.6 — frontend → WS → backend → worker → DLL → I²S → firmware → esp_camera_fb_get() → user sketch. [Frame #1] 8192 bytes 320x240 fmt=4 ├─ SOI (FF D8 FF): ✓ at offset 0 ├─ EOI (FF D9): ✓ at offset 8190 └─ First 16 bytes: FF D8 FF E0 00 10 4A 46 49 46 … (JFIF, real webcam) Stats: 10/10 Valid JPEGs at ~3.5 fps. Changes: - wokwi-libs/qemu-lcgamboa @ e4321d1 (picsimlab-esp32): EOFS_PER_FRAME 6→8, inject_eoi_now flag for EOI injection on the last EOF of each VSYNC burst. Handles JPEGs of arbitrary size by forcing FF D9 at offset 8190 — JPEG decoders tolerate the truncation gracefully. - backend/esp32_worker.py: throttled trace log every 30 frames (`camera_frame #N received (NNNN bytes)`) so users can confirm the frontend → worker leg is alive without flooding the log. - test/test-esp32-cam/autosearch/14: documented bug #9 (the 9th and final silent bug — real webcam JPEGs exceed the deliverable byte budget) with full forensic trace + final architecture diagram. The emulation now handles ANY user webcam → ESP32-CAM use case end to end. Standard upstream esp_camera_init() / esp_camera_fb_get() sketches work without modification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| autosearch | ||
| prototypes | ||
| sketches | ||
| tests | ||
| README.md | ||
README.md
test-esp32-cam — emulating the OV2640 over QEMU, faithful
Phase plan for emulating the ESP32-CAM camera (OV2640 + DVP + I²S +
DMA) as real QEMU peripherals — no library shim, no fakery. The
upstream espressif/esp32-camera driver runs unmodified.
Reading order
autosearch/00_overview.md— problem statement and the three candidate paths (we picked Path B: real peripherals).autosearch/01_state_of_the_art.md— what other projects do.autosearch/02_qemu_lcgamboa_audit.md— confirms our QEMU fork has no camera/DVP/I²S today; lists every ESP32 device it does ship.autosearch/03_browser_webcam_capture.md—getUserMedia→ canvas → JPEG → WebSocket plumbing.autosearch/04_proposed_architecture.md— end-to-end pipeline.autosearch/05_open_questions.md— design decisions still open.autosearch/06_existing_test_patterns.md— how the DHT22 / HC-SR04 live tests work; we mirror them.autosearch/07_ov2640_sccb_spec.md— minimum register set the QEMU OV2640 device must implement.autosearch/08_dvp_i2s_spec.md— exact I²S0 register sequence the esp32-camera driver issues, and thelldesc_tDMA descriptor format.autosearch/09_qemu_build_blueprint.md— how a Phase-2 patch inwokwi-libs/qemu-lcgamboa/reaches a running container.
Reference sources cloned into the tree
wokwi-libs/esp32-camera/ (Apache 2.0, cloned for offline
reference). Used by autosearch and the C-side QEMU device once we
ship Phase 2. Treat as read-only — never modify.
Phase plan
| Phase | What lands | Status | Validating sketch / test |
|---|---|---|---|
| 0 | Research + tests skeleton | done | static metadata + WS-mock |
| 1 | hw/i2c/esp32_ov2640.c — SCCB chip-id |
PASS | test_sccb_probe_live.py |
| 2 | hw/misc/esp32_i2s_cam.c — DMA + EOF |
PASS | test_dma_smoke_live.py |
| 3a | Host frame injection (webcam → backend → ctypes → QEMU → buf) | PASS | test_frame_roundtrip_live.py |
| 3b | Upstream esp_camera_init + fb_get round-trip |
xfail | test_camera_live.py (see autosearch/10) |
| 4 | CI build matrix + GH release upload | TODO | (build only) |
| 5 | Frontend: webcam hook + Camera button + missing pins | TODO | manual smoke |
Layout
test-esp32-cam/
├── README.md ← you are here
├── autosearch/ ← public-internet research
├── prototypes/ ← standalone validation runners
│ ├── echo_server.py ← tiny WS echo for the HTML below
│ └── webcam_capture.html ← getUserMedia → JPEG → WS prototype
├── sketches/
│ ├── camera_init/ ← Phase-3 reproducer (full upstream API)
│ ├── sccb_probe/ ← Phase-1 reproducer (I²C only, no I²S)
│ └── dma_smoke/ ← Phase-2 reproducer (raw I²S+DMA poke)
└── tests/
├── test_camera_metadata.py ← static + frontend
├── test_camera_websocket.py ← in-process WS mock
├── test_camera_live.py ← upstream esp_camera API (xfail, see autosearch/10)
├── test_sccb_probe_live.py ← Phase 1 PASS
├── test_dma_smoke_live.py ← Phase 2 PASS
├── test_frame_roundtrip_live.py ← Phase 3 e2e webcam → fb buffer PASS
└── webcam_helper.py ← OpenCV / PIL / synthetic JPEG source
Running
# Static + WS-mock layers (always green, no backend needed)
python -m pytest test/test-esp32-cam/tests -v
# Full live suite — needs a running backend with the QEMU library
# rebuilt to include the new peripherals.
cd backend && uvicorn app.main:app --port 8001 &
VELXIO_BACKEND_URL=http://localhost:8001 \
python -m pytest test/test-esp32-cam/tests -v
The three *_live.py files all start as @expectedFailure. As each
phase lands, the matching live test gets its decorator flipped off in
the same diff that lands the QEMU change. That's how the test suite
tracks emulation progress phase-by-phase.
Webcam → backend prototype (no QEMU)
pip install websockets
python test/test-esp32-cam/prototypes/echo_server.py &
# then open test/test-esp32-cam/prototypes/webcam_capture.html in a browser
Validates the browser-side path described in autosearch/03 without
needing the backend or QEMU. If frame counter climbs and the preview
panel updates, the transport is good.