Most microcontroller camera projects end at a MJPEG stream on a local IP address. That works until you want two-way audio, or you want to reach the device from outside your network, or you want more than one viewer. At that point you need WebRTC, and WebRTC on an ESP32 has a reputation for being a research project rather than a weekend build.
The blocker was never really the CPU. It is memory, and it is infrastructure. The WebRTC stack, the DTLS handshake state, encoded video frames, and audio jitter buffers all compete for internal SRAM on the S3. On top of that you need signaling and a media server before a single frame moves.
This build skips the infrastructure half. The board joins a hosted WebRTC room as an ordinary participant. It shows up in the participant list, it can be muted, and a browser tab in the same room sees its video tile and hears its microphone. The device renders the far end on its own LCD and plays it through its own speaker, so it is a genuine intercom, not a one-way camera.
What you will buildAn ESP32-S3-Korvo-2 that:
- Streams its camera and microphone into a WebRTC room.
- Decodes the remote video onto its 320x240 LCD.
- Plays the remote audio through its onboard speaker.
- Is reachable from any browser, on any network, with no port forwarding.
Total firmware for the call itself is under twenty lines of C.
Pick your board firstBoard choice decides whether the device can only send or can do full two-way. This matters more than anything else in the build, so settle it before writing code.
The XIAO Sense has a camera and microphone but no display and no speaker, so it is a send-only device. and Korvo-2 all of the four components, Calling the subscribe functions on it returns DEVICE_NOT_SUPPORTED, which is the SDK reporting missing hardware rather than a failure. For an intercom you want the Korvo-2.
You also need 8 MB of flash and PSRAM. The application image does not fit in 4 MB, and PSRAM is not optional for the WebRTC buffers.
Step 1: Install ESP-IDFThe component requires ESP-IDF 5.4.4 or newer. Older branches will not build it. These commands are macOS, see the ESP-IDF getting started guide for Linux and Windows.
brew install cmake ninja dfu-util ccache git wget flex bison gperf
brew install openssl libffi
mkdir -p ~/esp && cd ~/esp
git clone --recursive -b v5.4.4 https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32s3
source ~/esp/esp-idf/export.shYou need Python 3.11 or newer for the IDF build tools.
Step 2: Create the projectThe VideoSDK IoT SDK is published on Espressif's component registry under the MIT license, so IDF can scaffold the whole project from its example in one command.
idf.py create-project-from-example "videosdk/iot-sdk=0.3.1:video_call"That gives you a video-only call. We add audio in step 4 to make it an intercom.
Step 3: Configureidf.py set-target esp32s3
idf.py menuconfigIn menuconfig:
- Microcontroller board:
ESP32-S3-Korvo-2. The default is the XIAO, so change it. - VideoSDK Configuration: paste your access token and the room ID. You get both from the VideoSDK dashboard, and the room ID can also be minted by the web client in step 6.
- Example Connection Configuration: your 2.4 GHz Wi-Fi SSID and password. The S3 radio is 2.4 GHz only.
- Flash size: 8 MB. Keep the partition table the example ships.
Add sdkconfig to your .gitignore before your first commit. It stores your Wi-Fi password and your access token in plain text.
The whole call is a config struct, an init call, and four stream starts. The example as shipped is video only:
#include "videosdk.h"
#include "sdkconfig.h"
void app_main(void)
{
// Initialize NVS, network interfaces, event loops, and Wi-Fi...
init_config_t cfg = {
.meetingID = CONFIG_VIDEOSDK_MEETING_ID,
.token = CONFIG_VIDEOSDK_TOKEN,
.displayName = "Front-Door-Intercom",
.participantId = "",
.audioCodec = AUDIO_CODEC_PCMA,
.videoCodec = VIDEO_CODEC_JPEG,
};
if (init(&cfg) != RESULT_OK) {
return;
}
startPublishVideo();
startSubscribeVideo();
while (1) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}Two added lines turn it into a two-way intercom:
startPublishAudio(); // Onboard microphone into the room
startPublishVideo(); // Onboard camera into the room
startSubscribeAudio(); // Remote audio out the speaker
startSubscribeVideo(); // Remote video onto the LCDEvery function returns a result_t, where RESULT_OK is 0. Check them. On send-only hardware the subscribe calls return DEVICE_NOT_SUPPORTED, and you want that visible in your logs rather than silently ignored.
The codecs are JPEG for video and PCMA for audio. Those are not arbitrary. The S3 has no hardware H.264 encoder, and a software encoder loses to both the SRAM budget and the frame deadline. PCMA costs more bitrate than Opus but its encode cost is low and predictable, and predictable matters more than efficient when you are sharing cores with a video path.
Step 5: Flash and confirm the joinidf.py build
idf.py -p <PORT> flash monitorA healthy boot looks like this. Network details redacted, yours will differ:
I (21211) IOT-SDK-AUDIO: Device ID: e-ccba970e9220
I (21256) IOT-SDK-AUDIO: init: 0
I (23313) protoo: connecting websocket to in1.rm.videosdk.live
I (23562) esp-x509-crt-bundle: Certificate validated
I (24094) protoo: websocket connected
I (26178) videosdk: using 1 STUN server(s) for ICE
I (29220) peer: Created inbound SRTP session
I (29220) peer: Created outbound SRTP session
I (29474) videosdk: DataChannel transport up (ICE+DTLS)
I (29807) videosdk: audio RTP producer id=4bb13f4c-b453-4445-8924-ab09215dfd1b
I (29807) videosdk: bringing up mic
Result:0Two lines tell you it worked. Created outbound SRTP session means the DTLS handshake completed and the media path is encrypted. The audio RTP producer id means the board is actually publishing. A few Wi-Fi reconnect attempts before association are normal on a crowded 2.4 GHz channel.
If you never reach the SRTP lines, the usual causes in order are: an expired token, a room ID that does not match the one the browser is using, and 5 GHz Wi-Fi credentials.
Step 6: Answer the call from a browserNothing on the web side knows the device is a microcontroller, which is the point. The example repository carries the firmware under IoT/ and two ready-to-run browser clients under web/js and web/react.
git clone https://github.com/videosdk-live/videosdk-rtc-iot-sdk-example.git
cd videosdk-rtc-iot-sdk-example/web/react
npm install
npm startPut your token in the client's config, click New Meeting, and copy the room ID it generates into menuconfig on the device side, then reflash. Both ends now point at the same room.
When the board boots, its tile appears in the browser alongside your webcam. Speak into the laptop and the Korvo-2's speaker plays it. Speak at the board and the browser hears it. The LCD shows whoever is on the other end.
Where this goes nextThe same WebRTC connection carries a data channel, so the device can receive JSON commands over the exact link that carries the video. That is the piece that turns this from an intercom into something more useful: unlock a door, pan a servo, trigger a relay, all from the browser UI, with no second protocol and no MQTT broker to operate alongside it.
Wi-Fi quality shows up directly as frame rate and audio clarity, more than any code change will. Keep the antenna clear of the LCD ribbon, and test on the network the device will actually live on rather than the one next to your desk.
Honest limitations- JPEG has no interframe compression, so bitrate is high for the resolution. On a LAN or decent broadband that is fine. On cellular backhaul it is expensive.
- The component is at an early 0.x version. The API surface can change between releases.
- This approach assumes a hosted room. If you already run your own signaling and media infrastructure, or you need to peer directly with a device outside that system, Espressif's own esp-webrtc-solution is the better fit. The trade here is that you do not operate an SFU.









Comments