In this tutorial you will learn how to stream video from a Seeed Studio XIAO-ESP32-S3 Sense board over your local Wi-Fi network to your Webbrowser. This allows you to build your own wireless surveillance camera system that you can monitor from your computer.
If you haven’t used the XIAO-ESP32-S3 Sense before, have a look at the Getting started with XIAO-ESP32-S3-Sense tutorial first, which gets you started with the board.
Required Parts
Obviously, you will need a XIAO-ESP32-S3 Sense board by Seeed Studio to try out the code examples. Note that the board can get very hot if you, for instance, when streaming video with a high framerate. I recommend you attach a small heatsink at the back of the board (see the listed part below).
Seeed Studio XIAO ESP32 S3 Sense
USB C Cable
Small Heatsink 9×9 mm
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 XIAO-ESP32-S3 Sense. The Camera of the XIAO-ESP32-S3 Sense takes images and serves them over Wi-Fi. The Wi-Fi router transports these images to the Web Browser on your PC, which displays the images as a video stream. The picture below illustrates the architecture of the system:
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.40/stream
. Only someone with access to your Wi-Fi network will be able to see the video stream.
Code for a Video Streaming Server with XIAO-ESP32-S3-Sense
The Getting Started Wiki by Seeed Studio for XIAO-ESP32-S3-Sense provides rich information on the board and also has example code for a Video Streaming Server. However, that code is more complex than the code below.
The simplification is largely due to the esp32cam library, which 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, which uses the esp32cam library. Have a quick look first and then we will discuss the 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 auto RESOLUTION = esp32cam::Resolution::find(800, 600); const int FRAMERATE = 10; 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::XiaoSense); 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 esp32cam library.
Constants
Next, we define constants for Wi-Fi and camera settings:
const char* WIFI_SSID = "SSID"; const char* WIFI_PASS = "PASSWORD"; const char* URL = "/stream"; const auto RESOLUTION = esp32cam::Resolution::find(800, 600); const int FRAMERATE = 10;
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 RESOLUTION
sets the desired resolution for the camera. Below 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
The FRAMERATE
variable defines how many frames per second will be sent to the client. This allows you to slow the video stream down and avoid over-heating of the board. If you set the framerate too high, it will be automatically be limited to what the ESP32 can deliver for the specific camera resolution.
Objects
The following line creates the 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 the loop, the function first 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, and sent to the client:
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"); }
initCamera
The initCamera()
function configures and starts the camera:
void initCamera() { using namespace esp32cam; Config cfg; cfg.setPins(pins::XiaoSense); cfg.setResolution(RESOLUTION); cfg.setBufferCount(2); cfg.setJpeg(80); Camera.begin(cfg); }
First, a Config
object is created. The setPins
function configures the camera pins based on the XIAO-ESP32-S3 Sense board. You can find the pin configurations of other supported boards here.
setResolution
defines the resolution to use, while setBufferCount
determines how many image buffers are used internally, which can help with faster framerates.
The setJpeg
function sets the JPEG compression quality between 0 and 100. 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 XIAO-ESP32-S3 Sense board 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 board but you can change the suffix “stream”. For my board, I see the following URL on the Serial Monitor:
Therefore, I copy “http://192.168.2.40/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 starts the serial communication, connects to Wi-Fi, initializes the camera, and sets up the web server:
void setup() { Serial.begin(115200); initWifi(); initCamera(); initServer(); }
loop
Finally, the loop()
function continuously handles incoming HTTP requests:
void loop() { server.handleClient(); }
This line allows the XIAO-ESP32-S3 Sense 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 XIAO-ESP32-S3 Sense.
For downloading that code to your XIAO-ESP32-S3 Sense, select the XIAO_ESP32S3
board and press the download button.
XIAO_ESP32S3
as boardIf you need more help with this, have a look at the Getting started with XIAO-ESP32-S3-Sense tutorial.
Conclusions
In this tutorial you learned how to stream video from an XIAO-ESP32-S3 Sense over your local Wi-Fi network to your Webbrowser.
If you want to detect objects in the video stream have a look at the Object Detection with ESP32-CAM and YOLO tutorial. It is written for the ESP32-CAM but requires only a small change (cfg.setPins(pins::XiaoSense)
) to work with the XIAO-ESP32-S3 Sense
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.