Skip to content

Instantly share code, notes, and snippets.

@dsseng
Created May 27, 2023 18:59
Show Gist options
  • Save dsseng/b609c80117a183088cdef8ed3ebf9ba3 to your computer and use it in GitHub Desktop.
Save dsseng/b609c80117a183088cdef8ed3ebf9ba3 to your computer and use it in GitHub Desktop.
A snippet to stream from ESP32-CAM using local UDP transport (no error correction etc). Just sends JPEG in chunks, it turns out to be the most performant way. Is not a complete example
#include <Arduino.h>
#include <esp_camera.h>
#include <ESPAsyncWebServer.h>
#include <AsyncUDP.h>
#include <stdint.h>
AsyncUDP vid_udp;
IPAddress client_ip;
uint16_t client_port;
// MTU for data in packets
uint16_t chunk_size = 1350;
unsigned long prev_req = 0;
AsyncWebServer camera_ctl_server(8080);
// Mutex for locking capture while params are being modified from async http
bool cap_mutex = false;
// Awaiting to change props, please don't capture
bool want_mod = false;
// Zero-copy chunk and send the buffer, no allocations needed
// Allows to read out PSRAM
void send_chunked(const uint8_t* buf, uint16_t len) {
const uint8_t* ptr = buf;
uint16_t left = len;
while (left > chunk_size) {
vid_udp.writeTo(ptr, chunk_size, client_ip, client_port);
ptr += chunk_size;
left -= chunk_size;
}
// Tail less than a chunk/last chunk
if (left)
vid_udp.writeTo(ptr, left, client_ip, client_port);
}
void vid_listener(AsyncUDPPacket packet) {
client_ip = packet.remoteIP();
client_port = packet.remotePort();
prev_req = millis();
}
void startCameraServer() {
camera_config_t config;
config.pin_pwdn = 32;
config.pin_reset = -1;
config.pin_xclk = 0;
config.pin_sscb_sda = 26;
config.pin_sscb_scl = 27;
config.pin_d7 = 35;
config.pin_d6 = 34;
config.pin_d5 = 39;
config.pin_d4 = 36;
config.pin_d3 = 21;
config.pin_d2 = 19;
config.pin_d1 = 18;
config.pin_d0 = 5;
config.pin_vsync = 25;
config.pin_href = 23;
config.pin_pclk = 22;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.xclk_freq_hz = 20000000;
// hw codec
config.pixel_format = PIXFORMAT_JPEG;
// Expected to run on Ai-Thinker board with PSRAM
config.grab_mode = CAMERA_GRAB_LATEST;
config.fb_location = CAMERA_FB_IN_PSRAM;
// Has to be max at start, dropped to default SVGA later
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("ERR 0x%x\n", err);
return;
}
sensor_t* s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_SVGA);
camera_ctl_server.on("/control", HTTP_GET, [](AsyncWebServerRequest* request) {
want_mod = true;
unsigned long to = millis();
while (millis() < to + 5000) {
delay(5);
if (!cap_mutex)
break;
}
want_mod = false;
if (cap_mutex) {
request->send(502, "text/plain", "Timeout");
return;
} else {
cap_mutex = true;
}
String v = "empty";
/// TODO: expose more sensor controls
if (request->hasParam("framesize")) {
v = request->getParam("framesize")->value();
sensor_t* s = esp_camera_sensor_get();
s->set_framesize(s, (framesize_t)v.toInt());
}
cap_mutex = false;
request->send(200, "text/plain", v);
});
camera_ctl_server.begin();
if (vid_udp.listen(55181))
vid_udp.onPacket(vid_listener);
}
void cameraLoop() {
// Do not send to 0.0.0.0:0, stop capture after 15s since last request
if (!client_port || millis() > prev_req + 15000) {
delay(50);
return;
}
// Also skip frames while mutex is locked and settings are being modified
if (cap_mutex || want_mod) {
delay(10);
return;
}
cap_mutex = true;
camera_fb_t* fb = NULL;
fb = esp_camera_fb_get();
if (!fb) {
Serial.print("Error swapping fb: ");
Serial.println((uint32_t)fb);
// Not returning invalid fb
cap_mutex = false;
return;
}
if (fb->format != PIXFORMAT_JPEG) {
Serial.print("Unsupported framebuffer format, expected JPEG: ");
Serial.println(fb->format);
// Still swap it back to the chain
esp_camera_fb_return(fb);
cap_mutex = false;
return;
}
send_chunked((const uint8_t*)fb->buf, fb->len);
esp_camera_fb_return(fb);
cap_mutex = false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment