Skip to Content

Stream Video with ESP32-CAM

Stream Video with ESP32-CAM

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:

Streaming Video via local WiFi

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:

esp32cam library installed via Library Manager
esp32cam library installed via Library Manager

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 “/frontdoor”, 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:

Streaming URL printed to Serial Monitor
Streaming URL printed to 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.

Select AI Thinker ESP32-CAM  as board
Select AI Thinker ESP32-CAM as board

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 ; )

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.