In this tutorial you will learn how to stream video from an ESP32-CAM over your local Wi-Fi network to your Webbrowser. This will allow you, for instance, to build a wireless surveillance camera that you can monitor from your computer.
Required Parts
You will need an ESP32-CAM to try out the code examples. You can get a ESP32-CAM with a USB-TTL Shield for programming or an FTDI USB-TTL Adapter. I recommend the FTDI USB-TTL Adapter, since it is more convenient for programming, but I listed both options below.
ESP32-CAM with USB-TTL Shield
FTDI USB-TTL Adapter
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Streaming Video
In this project we are going to implement a video streaming server that runs on the ESP32-CAM. The ESP32-CAM takes images and serves them over Wi-Fi. The Wi-Fi router transports these images to the Web Browser, which displays the images as a video stream. The picture below illustrates the set up:
Note that the video stream is not encrypted or secure but will be only visible at an URL within your local Wi-Fi network, e.g. 192.168.2.39/stream. Only someone with access to your Wi-Fi network will be able to see the video stream.
Code for Video Server
The following Arduino sketch sets up the video streaming server that sends a continuous MJPEG stream from the ESP32-CAM camera to any web browser connected to the same Wi-Fi network.
For this code we will use the esp32cam library, which makes the handling of the camera simpler. You can install it via the Library Manager in the Arduino IDE. Just search for “esp32cam” and press INSTALL. The picture below shows the completed installation:
Below you will find the complete code for the video streaming server. Have a quick look first and then we will discuss its details:
#include "WebServer.h" #include "WiFi.h" #include "esp32cam.h" const char* WIFI_SSID = "SSID"; const char* WIFI_PASS = "PASSWORD"; const char* URL = "/stream"; const int FRAMERATE = 10; const auto RESOLUTION = esp32cam::Resolution::find(1024, 768); WebServer server(80); void handleStream() { static char head[128]; WiFiClient client = server.client(); server.sendContent("HTTP/1.1 200 OK\r\n" "Content-Type: multipart/x-mixed-replace; " "boundary=frame\r\n\r\n"); while (client.connected()) { auto frame = esp32cam::capture(); if (frame) { sprintf(head, "--frame\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %ul\r\n\r\n", frame->size()); client.write(head, strlen(head)); frame->writeTo(client); client.write("\r\n"); } delay(1000 / FRAMERATE); } } void initCamera() { using namespace esp32cam; Config cfg; cfg.setPins(pins::AiThinker); cfg.setResolution(RESOLUTION); cfg.setBufferCount(2); cfg.setJpeg(80); Camera.begin(cfg); } void initWifi() { WiFi.persistent(false); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { delay(100); } Serial.printf("Stream at: http://%s%s\n", WiFi.localIP().toString().c_str(), URL); } void initServer() { server.on(URL, handleStream); server.begin(); } void setup() { Serial.begin(115200); initWifi(); initCamera(); initServer(); } void loop() { server.handleClient(); }
Libraries
The sketch begins by including the necessary libraries:
#include "WebServer.h" #include "WiFi.h" #include "esp32cam.h"
These libraries enable HTTP server functionality (WebServer.h
), Wi-Fi connectivity (WiFi.h
), and camera control using the ESP32-CAM (esp32cam.h
).
Constants
Next, a few constants are defined:
const char* WIFI_SSID = "SSID"; const char* WIFI_PASS = "PASSWORD"; const char* URL = "/stream"; const int FRAMERATE = 10; const auto RESOLUTION = esp32cam::Resolution::find(1024, 768);
The WIFI_SSID
and WIFI_PASS
variables store the credentials for the Wi-Fi network. You will have to replace SSID and PASSWORD with the actual login details for your Wi-Fi- network.
The URL
is the path on which the video stream will be served. You can change this to anything you like. For instance, “/video
” or “/frontdoo
r”, just make sure you keep the slash (‘/’) at the beginning.
The FRAMERATE
variable defines how many frames per second will be sent to the client. And the RESOLUTION
sets the desired resolution for the camera using the esp32cam
library.
Here is a list of the possible values for the camera resolution, though depending on the camera not all of them may work:
- 96×96
- 160×120
- 128×128
- 176×144
- 240×176
- 240×240
- 320×240
- 320×320
- 400×296
- 480×320
- 640×480
- 800×600
- 1024×768
- 1280×720
- 1280×1024
- 1600×1200
You can print the list of camera resolutions using the following function:
void printResolutions() { Serial.println("Camera resolutions:"); for (auto res : esp32cam::Camera.listResolutions()) { Serial.printf("%dx%d\n", res.getWidth(), res.getHeight()); } }
Note that the resolution will affect the maximum framerate. With a 1600×1200 resolution you will not get much more than 10 frames per second.
Objects
The following line creates an HTTP server object that listens on port 80. That is our web server that provides the video stream.
WebServer server(80);
handleStream
The function handleStream()
handles the video stream whenever a client accesses the /stream
path:
void handleStream() { static char head[128]; WiFiClient client = server.client();
This line obtains the current client connected to the server. The response is then prepared:
server.sendContent("HTTP/1.1 200 OK\r\n" "Content-Type: multipart/x-mixed-replace; " "boundary=frame\r\n\r\n");
This header tells the browser that it will receive a multipart MJPEG stream, where each part is separated by a boundary called frame
.
Inside a loop, the function captures a frame from the camera:
while (client.connected()) { auto frame = esp32cam::capture();
If a frame is successfully captured, a header is constructed describing the JPEG image:
if (frame) { sprintf(head, "--frame\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %ul\r\n\r\n", frame->size()); client.write(head, strlen(head)); frame->writeTo(client); client.write("\r\n"); }
The image is sent to the client as a JPEG, and then the loop waits briefly according to the frame rate:
delay(1000 / FRAMERATE);
Note that this FRAMERATE
constant only allows you to slow down the framerate below the maximum framerate the ESP32-CAM can produce for the given resolution. If you always want to run with the maximum framerate, just remove this line.
initCamera
The initCamera()
function configures and starts the ESP32-CAM hardware:
void initCamera() { using namespace esp32cam; Config cfg; cfg.setPins(pins::AiThinker); cfg.setResolution(RESOLUTION); cfg.setBufferCount(2); cfg.setJpeg(80); Camera.begin(cfg); }
Here, a Config
object is created. The setPins
function configures the camera pins based on the AiThinker board layout. setResolution
defines the resolution to use, while setBufferCount
determines how many image buffers are used internally.
The setJpeg
function sets the JPEG compression quality. 80 is a good balance between quality and size. 90 gives you better quality but a smaller reduction in image size. For faster video you can reduce the resolution and the JPEG compression but obviously you will get smaller images with lower quality.
Finally, Camera.begin(cfg)
initializes the camera with these settings.
initWifi
The initWifi()
function connects the ESP32-CAM to the specified Wi-Fi network:
void initWifi() { WiFi.persistent(false); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { delay(100); } Serial.printf("Stream at: http://%s%s\n", WiFi.localIP().toString().c_str(), URL); }
WiFi.persistent(false)
disables writing the credentials to flash, which speeds up reconnection. The device is set to station mode (WIFI_STA
), and it attempts to connect to the Wi-Fi using the given credentials.
Once connected, the IP address and stream path are printed to the serial monitor. You will need to copy and past this printer URL into your browsers address bar to see the video stream. The actual URL will depend on your ESP32-CAM but you can change the suffix “stream”. In my case I see the following URL on the Serial Monitor:
Therefore, I copy “http://192.168.2.39/stream” into the address bar of my Chrome browser to access the video.
initServer
The initServer()
function links the streaming path to the handler and starts the server:
void initServer() { server.on(URL, handleStream); server.begin(); }
This maps any request to /stream
to the handleStream()
function, and then starts the server.
setup
The setup()
function runs once at boot:
void setup() { Serial.begin(115200); initWifi(); initCamera(); initServer(); }
It starts the serial communication, connects to Wi-Fi, initializes the camera, and sets up the web server.
loop
Finally, the loop()
function continuously handles incoming HTTP requests:
void loop() { server.handleClient(); }
This line allows the ESP32-CAM to respond to client requests by invoking any registered handlers such as handleStream()
.
Altogether, this code creates a simple but effective MJPEG streaming server that can be accessed from any web browser within the same network, providing a live camera feed from the ESP32-CAM.
For downloading that code to your ESP32-CAM module, select the AI Thinker ESP32-CAM
board and then switch the ESP32-CAM into download mode.
If you need help with this, have a look at the Programming the ESP32-CAM tutorial.
Conclusions
In this tutorial you learned how to stream video from an ESP32-CAM over your local Wi-Fi network to your Webbrowser.
If you want to detect objects in the video stream have a look at our Object Detection with ESP32-CAM and YOLO tutorial. Should you want to build a motion activated surveillance system, the Motion Activated ESP32-CAM tutorial might be useful.
Also have a look at the More GPIO pins for ESP32-CAM tutorial, if you need more GPIO pins to control your surveillance system. Alternatively you could also consider a different board with more GPIOs such as the XIAO-ESP32-S3-Sense or the ESP32-WROVER CAM.
Finally, you may want to switch on the flash LED of the ESP32-CAM if you run the camera in a dark environment. Have a look at the Control ESP32-CAM Flash LED tutorial for more details.
If you have any questions, feel free to leave them in the comment section.
Happy Tinkering ; )
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.
Carl
Sunday 4th of May 2025
Great tutorial. I am using a raspberry pi 4B 8G for the web server. Lowered the delay with the framerate from 1000 to 100 and all 3 streams appear to be in sink.
Next step is to record these stream to an SSD drive attached to the Rpi4B. Any suggestions?
Stefan Maetschke
Sunday 4th of May 2025
You could use the ffmpeg tool to save the video stream, e.g. ffmpeg -i http://192.168.1.10:80/stream -c copy /mnt/ssd/video_output.mp4
Obviously, you will need to replace the url with yours. Or you write some Python code for a client that captures the stream.