ESP32-CAM CameraWebServer Example: Setup and Code Walkthrough
The CameraWebServer example that ships with the ESP32 Arduino core is the first program most people run on an ESP32-CAM. It turns the board into a Wi-Fi camera with a web page for live video, still photos and dozens of sensor settings. It is also a useful reference: almost every ESP32-CAM project reuses parts of its code.
In this tutorial you will configure and upload the example, open the stream in a browser, and then look inside the code to see how the camera is initialised and which HTTP endpoints it serves. Finally you will use those endpoints from a short Python script to change settings and save photos to your computer.
Configuring and Uploading the Example
- Open File > Examples > ESP32 > Camera > CameraWebServer. The IDE opens a sketch with several tabs.
- Find the block of
#define CAMERA_MODEL_...lines. Depending on your core version this is in the main.inotab or inboard_config.h. Comment out the default model and uncomment#define CAMERA_MODEL_AI_THINKER. - Enter your Wi-Fi name and password in the
ssidandpasswordvariables. The ESP32 supports only 2.4 GHz networks. - Select the board AI Thinker ESP32-CAM and check that PSRAM is enabled for that board.
- Put the board in download mode (IO0 to GND, press RESET) and upload.
- Remove the IO0 link, open the Serial Monitor at 115200 baud and press RESET. The board prints
Camera Ready! Use 'http://192.168.x.x' to connect. - Open that address in a browser on the same network and click Start Stream.
// Select camera model (only one line uncommented)
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
#define CAMERA_MODEL_AI_THINKER // AI-Thinker ESP32-CAM
// Wi-Fi credentials (2.4 GHz network)
const char *ssid = "YourWiFiName";
const char *password = "YourWiFiPassword";
How the Example Is Organised
| File | What it contains |
|---|---|
| CameraWebServer.ino | camera_config_t setup, esp_camera_init(), sensor tweaks, Wi-Fi connection, call to startCameraServer() |
| camera_pins.h | Pin maps for each supported board, selected by the CAMERA_MODEL define |
| app_httpd.cpp | All HTTP handlers built on the ESP-IDF esp_http_server component |
| camera_index.h | The web page, stored gzip-compressed as a byte array to save flash |
| board_config.h (newer cores) | Holds the model define in recent versions instead of the .ino |
In setup() the sketch fills a camera_config_t structure with the pin numbers from camera_pins.h, sets a 20 MHz XCLK and JPEG output, and chooses frame size and buffer count. If PSRAM is found it uses a larger frame size, a better JPEG quality value and two frame buffers with CAMERA_GRAB_LATEST, so the stream always sends the newest frame. Without PSRAM it falls back to a smaller frame size and a single buffer in internal RAM.
After esp_camera_init() succeeds, the code gets a pointer to the sensor with esp_camera_sensor_get() and adjusts it, for example flipping the image for some boards. It then connects to Wi-Fi, disables Wi-Fi power saving for smoother streaming and calls startCameraServer() from app_httpd.cpp.
The HTTP Endpoints
startCameraServer() starts two separate HTTP server instances. The stream runs on its own server because an MJPEG stream never finishes; if it shared the main server, the settings page would freeze while you watch video.
| URL | Port | Purpose |
|---|---|---|
| / | 80 | Web interface (camera_index.h) |
| /capture | 80 | Returns one JPEG still image |
| /status | 80 | Returns all current sensor settings as JSON |
| /control?var=NAME&val=VALUE | 80 | Changes one setting, e.g. quality, brightness, vflip |
| /stream | 81 | Endless multipart MJPEG video stream |
The /control handler reads var and val from the query string and calls the matching sensor function, such as s->set_quality(s, val) or s->set_vflip(s, val). Settings live only in the sensor registers, so they reset after a reboot unless you also set them in setup().
The web page itself does nothing special: its sliders and drop-downs simply call /control with JavaScript whenever you change something, then call /status to refresh the displayed values. The Get Still button loads /capture into the page, and Start Stream points an image element at port 81. Knowing this, you can replace the page with your own design or remove it completely and keep only the endpoints you need.
Controlling the Camera from Python
Because the interface is plain HTTP, any program can drive it. The script below uses the requests library (install with pip install requests) to read the status, flip the image, raise JPEG quality and save five photos with timestamps.
import time
import requests
CAM = "http://192.168.1.50" # IP printed by the ESP32-CAM
def set_control(var, val):
# Change one sensor setting through /control
r = requests.get(f"{CAM}/control", params={"var": var, "val": val}, timeout=5)
print(f"{var} = {val} -> HTTP {r.status_code}")
# Read and show current settings
status = requests.get(f"{CAM}/status", timeout=5).json()
print("framesize:", status.get("framesize"), "quality:", status.get("quality"))
set_control("vflip", 1) # flip vertically
set_control("quality", 10) # lower number = better JPEG quality
time.sleep(1) # let the sensor settle
for i in range(5):
img = requests.get(f"{CAM}/capture", timeout=10)
if img.status_code == 200:
name = time.strftime("cam_%Y%m%d_%H%M%S.jpg")
with open(name, "wb") as f:
f.write(img.content)
print("saved", name, len(img.content), "bytes")
else:
print("capture failed", img.status_code)
time.sleep(2)
Common Problems
- Camera init failed with error 0x105: wrong camera model define, or the ribbon cable is loose. Confirm
CAMERA_MODEL_AI_THINKERis the only model uncommented. - Page loads but stream stays blank: some networks block port 81, or a browser extension blocks the stream. Try
http://IP:81/streamdirectly. - Only one viewer works: the stream handler serves one client at a time. A second browser tab waits or fails; close the first stream.
- Dots repeat in Serial Monitor and no IP: wrong password or a 5 GHz-only network.
- Resets when streaming starts: power supply too weak. Use a 5 V supply with enough current headroom.
Summary
CameraWebServer shows the full ESP32-CAM workflow: fill camera_config_t, call esp_camera_init(), adjust the sensor, connect to Wi-Fi and serve frames over HTTP. Its /capture, /status and /control endpoints on port 80 and /stream on port 81 can be used by any client, as the Python script demonstrates. Once you understand this example, writing your own smaller camera server becomes straightforward.