Transform your affordable ESP32 camera module into a professional-grade ONVIF Security Camera. This firmware delivers real-time RTSP/ONVIF streaming with AI-powered motion detection, multiple ESP32 camera boards from various manufacturers and offers MJPEG streaming on all boards plus optional H.264 encoding on ESP32-P4 (hardware) and ESP32-S3 (software), automated cloud backups to Google Drive and Telegram, continuous recording, WebDAV file access, MQTT home automation integration, Bluetooth presence detection, and a modern web interface -- all running on a microcontroller with aggressive memory optimization and FreeRTOS task scheduling.
| Category | Feature | Details |
|---|---|---|
| Streaming | ONVIF Profile S | Compatible with Hikvision, Dahua, Unifi Protect, Blue Iris, Synology |
| Streaming | RTSP Server | Low-latency MJPEG at 20+ FPS, H.264 on S3/P4 |
| Intelligence | Motion Detection | Frame-difference luminance analysis, configurable threshold and cooldown |
| Intelligence | AI Object Detection | TensorFlow.js COCO-SSD model (browser-side, zero MCU overhead) |
| Cloud | Google Drive Backup | Async JPEG, settings.json, and UI preferences (localStorage) upload on motion via Google Apps Script proxy |
| Cloud | Telegram Alerts | Instant photo notifications on motion detection |
| Integration | MQTT | Home Assistant / Node-RED integration with dynamic task management |
| Storage | Continuous Recording | DashCam-style chunked .mjpeg recording to SD card (configurable 1-60 min chunks) |
| Storage | WebDAV Server | Mount SD card as a Windows/macOS/Linux network drive for drag-and-drop file access |
| Wireless | Bluetooth Presence | Auto-detect if you are home using your phone's BLE MAC address |
| Wireless | Stealth Mode | Disable all LEDs when WiFi is lost and owner is not home |
| System | OTA Updates | Over-the-air firmware updates from GitHub Releases or manual upload |
| System | Dynamic RAM Management | FreeRTOS tasks are created/destroyed at runtime based on WebUI toggles |
| System | NTP Time Sync | POSIX timezone support for accurate recording timestamps |
| UI | Modern Web Interface | Responsive SPA with dark theme, live dashboard, camera controls, system diagnostics |
Note
🚧 Work in Progress:
ESP32CAM-ONVIF is still evolving! Help make it better and faster-contributions, feedback, and ideas are warmly welcome.
Star the repo and join the project!
| Feature | ESP32-CAM | ESP32-S3 | ESP32-P4 |
|---|---|---|---|
| MJPEG Streaming | ✅ 20 FPS | ✅ 25+ FPS | ✅ 30+ FPS |
| H.264 Encoding | ❌ | ✅ Software (~17 FPS) | ✅ Hardware (30 FPS @ 1080p) |
| ONVIF Compatible | ✅ | ✅ | ✅ |
| Memory Required | 4MB Flash | 8MB Flash + PSRAM | 8MB Flash + PSRAM |
- Hikvision - HVR/NVR Series (Tested: DS-7200)
- Dahua - XVR/NVR Series
- Ubiquiti Unifi Protect - UDM Pro, Cloud Key Gen2+
- Blue Iris - PC-based NVR
- Synology Surveillance Station
- Any ONVIF Profile S compliant recorder
- PRESENCE DETECTION: Automatically detects if you are Home using your Phone's Bluetooth MAC.
- STEALTH MODE: Turns off all lights if WiFi is lost AND you are not home.
- BLUETOOTH AUDIO: Use a Bluetooth Mic/Headset as a wireless microphone (HFP).
- PRIORITY STREAMING: Smart coexistence ensures WiFi RTSP streaming never stutters, even with Bluetooth on.
ONVIF Zero-Heap Optimizations: Extreme ONVIF server optimization prevents memory fragmentation during aggressive NVR polling. All SOAP XML templates are strictly embedded into Flash (
PROGMEM), and responses are dynamically built directly into a single static C-stylechararray buffer, eliminating dangerous dynamicStringallocations entirely.
| Board | Manufacturer | Camera | Notes |
|---|---|---|---|
| ESP32-CAM | AI-Thinker | OV2640 | Most common |
| M5Stack Camera | M5Stack | OV2640 | Compact form factor |
| M5Stack Wide | M5Stack | OV2640 | Fisheye lens |
| M5Stack UnitCam | M5Stack | OV2640 | Tiny, no PSRAM |
| TTGO T-Camera | LilyGO | OV2640 | With OLED display |
| TTGO T-Journal | LilyGO | OV2640 | With mic & OLED |
| ESP-WROVER-KIT | Espressif | OV2640 | Dev board |
| ESP-EYE | Espressif | OV2640 | With mic |
| Board | Manufacturer | Camera | Notes |
|---|---|---|---|
| Freenove ESP32-S3-WROOM | Freenove | OV2640 | Best S3 option |
| Seeed XIAO ESP32S3 Sense | Seeed Studio | OV2640 | Ultra-compact |
| ESP32-S3-EYE | Espressif | OV2640 | Official dev board |
| Board | Manufacturer | Camera | Performance |
|---|---|---|---|
| ESP32-P4-Function-EV | Espressif | MIPI-CSI | 30 FPS @ 1080p, 140KB RAM |
git clone https://github.com/John-Varghese-EH/ESP32CAM-ONVIF.git
cd ESP32CAM-ONVIFEdit ESP32CAM-ONVIF/config.h:
// ==== STEP 1: SELECT YOUR BOARD ====
#define BOARD_AI_THINKER_ESP32CAM // Most common
// #define BOARD_FREENOVE_ESP32S3 // For S3 boards
// #define BOARD_ESP32P4_FUNCTION_EV // For P4 boards// ==== STEP 2: SELECT VIDEO CODEC ====
#define VIDEO_CODEC_MJPEG // Works on ALL boards (default)
// #define VIDEO_CODEC_H264 // ESP32-P4/S3 only!// ==== WiFi Settings ====
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"Arduino IDE:
- Install ESP32 Board Manager (v2.0.14+)
- Install
PubSubClientandArduinoJsonvia Library Manager - Select your board (AI Thinker ESP32-CAM, etc.)
- Upload!
PlatformIO (Recommended):
pio run -t upload
pio device monitor -b 115200ESP-IDF (Required for H.264):
idf.py set-target esp32s3 # or esp32p4
idf.py add-dependency "espressif/esp_h264^1.2.0" # For H.264
idf.py build flash monitorOpen http://<ESP32-IP> in your browser. The dashboard provides live streaming, camera controls, system diagnostics, and full integration configuration.
Note: H.264 requires ESP-IDF, not Arduino IDE.
#define BOARD_ESP32P4_FUNCTION_EV
#define VIDEO_CODEC_H264
#define H264_ENCODER_AUTO // Hardware encoder
#define H264_GOP 30 // Keyframe interval
#define H264_FPS 30 // 30 FPS at 1080p
#define H264_BITRATE 4000000 // 4 MbpsPerformance: 30 FPS @ 1920x1080, only 140KB RAM!
#define BOARD_FREENOVE_ESP32S3
#define VIDEO_CODEC_H264
#define H264_ENCODER_AUTO // Software encoder
#define H264_SW_MAX_WIDTH 640
#define H264_SW_MAX_HEIGHT 480Performance: ~17 FPS @ 320x192, requires ~1MB RAM
idf.py set-target esp32s3
idf.py add-dependency "espressif/esp_h264^1.2.0"
idf.py build flash monitor| Setting | Value |
|---|---|
| Protocol | ONVIF |
| IP Address | ESP32 IP |
| Management Port | 8000 |
| Username | admin |
| Password | esp123 |
| Transfer Protocol | TCP (recommended) |
- ✅ Ensure time is synced (NVR sends SetSystemDateAndTime)
- ✅ Use TCP transport (more reliable than UDP)
- ✅ Try rebooting both camera and NVR
- ✅ For H.264: Ensure codec is correctly configured
Some Hikvision HVRs only support H.264/H.265 via ONVIF. The ESP32-CAM produces MJPEG which these units cannot decode. Use go2rtc for real-time transcoding and onvif-server for protocol wrapping.
ESP32-CAM (MJPEG) → go2rtc (transcode to H.264) → onvif-server → Hikvision NVR
↓ ↓ ↓
Port 554 H.264 @ Port 8554 Port 8081
See the go2rtc setup guide and onvif-server for full configuration instructions.
go2rtc Quick Setup
# Install FFmpeg
winget install FFmpeg
# Install Node.js (for onvif-server)
winget install OpenJS.NodeJS.LTSDownload from go2rtc releases and extract to a folder.
Create go2rtc.yaml in the same folder:
streams:
esp32cam:
# Transcode MJPEG to H.264 (replace [ESP32-IP] with your camera IP)
- ffmpeg:rtsp://admin:esp123@[ESP32-IP]:554/mjpeg/1#video=h264
rtsp:
listen: ":8554"
api:
listen: ":1984"git clone https://github.com/daniela-hase/onvif-server.git
cd onvif-server
npm installGet-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object Name, MacAddressUse the MAC address of your main network adapter (WiFi or Ethernet).
Create config.yaml in the onvif-server folder:
onvif:
- mac: XX-XX-XX-XX-XX-XX # Your MAC address from Step 4
ports:
server: 8081 # ONVIF port for Hikvision
rtsp: 8554 # go2rtc RTSP port
name: ESP32CAM
uuid: 15b21259-77d9-441f-9913-3ccd8a82e430
highQuality:
rtsp: /esp32cam # Stream name from go2rtc
width: 640
height: 480
framerate: 25
bitrate: 2048
quality: 4
lowQuality:
rtsp: /esp32cam
width: 640
height: 480
framerate: 25
bitrate: 1024
quality: 1
target:
hostname: 127.0.0.1 # go2rtc runs locally
ports:
rtsp: 8554Terminal 1 - Start go2rtc:
cd path\to\go2rtc
.\go2rtc.exeTerminal 2 - Start onvif-server:
cd path\to\onvif-server
node main.js ./config.yaml| Setting | Value |
|---|---|
| Protocol | ONVIF |
| IP Address | Your PC's IP |
| Port | 8081 |
| Username | admin |
| Password | admin |
# Install dependencies
sudo apt update
sudo apt install -y ffmpeg nodejs npm
# Download go2rtc
wget https://github.com/AlexxIT/go2rtc/releases/latest/download/go2rtc_linux_amd64
chmod +x go2rtc_linux_amd64
sudo mv go2rtc_linux_amd64 /usr/local/bin/go2rtc
# Clone onvif-server
cd /opt
sudo git clone https://github.com/daniela-hase/onvif-server.git
cd onvif-server
sudo npm install
# Get your MAC address
ip link show | grep etherCreate systemd services:
/etc/systemd/system/go2rtc.service:
[Unit]
Description=go2rtc streaming server
After=network.target
[Service]
ExecStart=/usr/local/bin/go2rtc -config /etc/go2rtc/go2rtc.yaml
Restart=always
User=pi
[Install]
WantedBy=multi-user.target/etc/systemd/system/onvif-server.service:
[Unit]
Description=Virtual ONVIF Server
After=network.target go2rtc.service
[Service]
ExecStart=/usr/local/bin/go2rtc -config /etc/go2rtc/go2rtc.yaml
Restart=always
User=pi
[Install]
WantedBy=multi-user.target/etc/systemd/system/onvif-server.service:
[Unit]
Description=Virtual ONVIF Server
After=network.target go2rtc.service
[Service]
WorkingDirectory=/opt/onvif-server
ExecStart=/usr/bin/node main.js ./config.yaml
Restart=always
User=pi
[Install]
WantedBy=multi-user.targetsudo systemctl enable go2rtc onvif-server
sudo systemctl start go2rtc onvif-server| Issue | Solution |
|---|---|
| "Failed to find IP address for MAC" | Check MAC with Get-NetAdapter (Windows) or ip link show (Linux) |
| Hikvision shows "Offline" | Ensure both go2rtc and onvif-server are running |
| No video stream | Test RTSP in VLC: rtsp://localhost:8554/esp32cam |
| FFmpeg not found | Restart terminal after installing FFmpeg |
Motion-triggered JPEG snapshots are uploaded asynchronously to Google Drive using a Google Apps Script proxy. This approach avoids heavy OAuth flows on the microcontroller and uses PSRAM-buffered transfers to prevent blocking the RTSP stream.
You can optionally bundle your ESP32 configuration (settings.json) and your browser UI preferences (localStorage.json) directly into the GDrive multipart payload, ensuring your entire ecosystem state is safely archived alongside security footage.
- Open Google Apps Script and create a new project
- Paste the following script:
function doPost(e) {
var folder = DriveApp.getFolderById("YOUR_FOLDER_ID");
var blob = e.postData.contents;
var contentType = e.postData.type;
if (contentType.indexOf("multipart") >= 0) {
var parts = Utilities.parseCsv(blob, "\r\n");
// Handle multipart form data
var data = Utilities.newBlob(
Utilities.base64Decode(blob.split("\r\n\r\n")[1].split("\r\n--")[0]),
"image/jpeg",
"motion_" + new Date().toISOString() + ".jpg"
);
folder.createFile(data);
}
return ContentService.createTextOutput("OK");
}- Deploy as Web App (Execute as: Me, Access: Anyone)
- Copy the Web App URL and paste it into the ESP32 WebUI under System > Integrations > Google Drive
The web interface is a responsive single-page application with a dark cyberpunk theme.
Dashboard Tab
- Live MJPEG stream with CCTV-style date/time overlay (high-contrast blend modes)
- One-click snapshot, flash control, manual recording toggle
- Live Resource Monitor with dual-plotted real-time Canvas graphs for Heap and PSRAM utilization
- Mobile-optimized swipable bottom navigation bar for zero-congestion ergonomics
Camera Tab
- Resolution, quality, brightness, contrast, saturation controls
- Scene presets (Night, Daytime, Indoor, Outdoor)
- Horizontal mirror, vertical flip, auto white balance
Network Tab
- WiFi scanner with signal strength visualization
- RTSP/ONVIF connection info and stream URLs
System Tab
- Integrations panel: MQTT, Telegram, Google Drive (each with enable/disable toggles)
- Advanced Services: WebDAV, NTP/Timezone, Continuous Recording (DashCam)
- OTA firmware update (manual upload or GitHub auto-update)
- Settings export/import (JSON backup)
- Factory reset and reboot controls
Check WEB_INTERFACE_README.md for more about Web-Server
| Port | Service | Description |
|---|---|---|
| 80 | HTTP | Web interface and REST API |
| 554 | RTSP | Real-time video streaming |
| 8000 | ONVIF | Device management and discovery |
| 3702 | WS-Discovery | UDP Multicast auto-detection |
+------------------+
| NVR / HVR |
| (Hikvision, |
+-------+ RTSP/554 | Dahua, etc.) |
| ESP32 | ===============> +------------------+
| CAM |
| | ---> ONVIF/8000 ---> WS-Discovery/3702 (Auto-detect)
| |
| | ---> HTTP/80 -----> WebUI (Dashboard, Camera, Settings)
| | |
| | +---> /stream (MJPEG live feed)
| | +---> /api/settings (REST API)
| | +---> /webdav/* (Network Drive)
| |
| | ---> Motion Event ---> Telegram (Async Photo)
| | ---> Google Drive (Async JPEG Upload)
| | ---> MQTT (State Publish)
| | ---> more: ---> SD Card (Continuous .mjpeg)
+-------+
FreeRTOS Task Layout (6 pinned tasks for deterministic scheduling):
| Task | Core | Priority | Stack | Purpose |
|---|---|---|---|---|
RTSP_Task |
0 | 4 (High) | 6KB | Real-time video streaming (never blocked) |
ONVIF_HTTP_Task |
1 | 3 | 6KB | Web UI + ONVIF SOAP processing |
WiFi_Mgmt_Task |
1 | 6 | 4KB | Connectivity monitoring and reconnection |
WDT_Task |
1 | 7 (Critical) | 2KB | Watchdog, heap audit, dynamic task spawner |
Low_Prio_Task |
1 | 2 | 4KB | Motion detection, SD recording, LED, Bluetooth |
MQTT_Task |
1 | 2 | 4KB | Dynamically spawned/killed based on config |
ESP32CAM-ONVIF/
|-- config.h # User configuration (board, codec, WiFi, features)
|-- board_config.h # Board-specific GPIO pin definitions
|-- ESP32CAM-ONVIF.ino # Main entry: FreeRTOS task creation and lifecycle
|-- rtsp_server.cpp/h # RTSP streaming server
|-- onvif_server.cpp/h # ONVIF Profile S protocol handler
|-- CRtspSession.cpp/h # RTSP session management (optimized static buffers)
|-- CStreamer.cpp/h # RTP packetization
|-- web_config.cpp/h # REST API and WebServer routes
|-- config.cpp # Settings persistence (LittleFS JSON)
|-- motion_detection.cpp/h # Luminance-based motion detection
|-- sd_recorder.cpp/h # SD card recording (manual + DashCam continuous)
|-- mqtt_manager.cpp/h # MQTT client (dynamic FreeRTOS task)
|-- telegram_manager.cpp/h # Telegram Bot API integration
|-- gdrive_manager.cpp/h # Google Drive async upload (PSRAM-buffered)
|-- webdav_server.cpp/h # WebDAV PROPFIND/GET handler
|-- wifi_manager.cpp/h # WiFi connection manager with AP fallback
|-- camera_control.cpp/h # Camera sensor parameter control
|-- auto_flash.cpp/h # Automatic flash LED management
|-- status_led.cpp/h # Status LED patterns
|-- data/
| |-- index.html # WebUI structure
| |-- app.js # WebUI logic (3000+ lines)
| |-- style.css # WebUI styling (dark theme)
| Issue | Solution |
|---|---|
| Purple/Green lines | Power supply issue - use 5V 2A adapter |
| Stream disconnects | Weak WiFi - move closer or use external antenna |
| "Drop the Loop" | Memory issue - reduce resolution or disable features |
| ONVIF not discovered | Enable multicast on router, allow UDP 3702 |
| H.264 compile error | You need ESP-IDF + esp_h264 component |
| H.264 not supported | Only ESP32-P4 (HW) and ESP32-S3 (SW) support H.264 |
| Hikvision: "Stream type not supported" | ESP32-CAM outputs MJPEG but Hikvision needs H.264. Use go2rtc transcoder |
| Hikvision: "Parameter error" | Check ONVIF port (8000), user/pass (admin/esp123), use TCP transport |
| Unifi Protect: Auth failed | Fixed in v1.1+ - update firmware |
| Stack smashing / random crashes | Fixed in v1.1+ - update firmware |
| Bootloop on startup | LED/PTZ pins may conflict with board. Set FLASH_LED_ENABLED and STATUS_LED_ENABLED to false in config.h |
- Multi-board support (12+ boards)
- ONVIF Profile S compliance
- Hikvision, Dahua, Unifi Protect compatibility
- H.264 infrastructure for P4/S3
- Modern Web Interface with live dashboard
- MQTT, Telegram, and Google Drive integrations
- Continuous DashCam recording to SD
- WebDAV network drive access
- Dynamic FreeRTOS task management (zero-overhead when disabled)
- NTP time sync with POSIX timezone
- Bluetooth presence detection and stealth mode
- OTA updates (manual + GitHub auto-update)
- H.264 RTP NAL unit packetization
- ONVIF H.264 profile reporting
- H.265/HEVC support (ESP32-P4)
- Audio support (G.711/AAC)
- ONVIF Profile T (Advanced streaming)
- Multi-stream support (Main + Sub profiles)
- HomeKit Secure Video integration
Contributions are welcome. Please fork the repository, create a feature branch, and submit a pull request.
Areas where help is appreciated:
- Testing on different NVR brands and models
- New board definitions and pin configurations
- Performance profiling and memory optimization
- Documentation and translations
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Submit a pull request
Important
Attention Forkers: If you are using a fork of this repository, please sync with the upstream main branch before reporting bugs. This project is under active development, and many early-stage issues have already been patched.
Note on Issues: Please only raise issues or bug reports in the Main Upstream Repository. I do not receive notifications for issues opened on forks, and they will likely go unnoticed.
This project is currently a proof of concept for testing.
Neither the ESP32CAM, nor its SDK was meant or built for proper ONVIF/RTSP support. Bugs can occur!
Apache License 2.0 - See LICENSE for details.
Developed by John Varghese (J0X)
Built on:
- Micro-RTSP - RTSP server foundation
- ESP32-Camera - Camera driver
- esp_h264 - H.264 encoder component
This is an active, evolving project. If it helped you, consider supporting continued development
⭐ Star this repo if it helped you.
