/* Waveshare ESP32-S3-ETH + OV5640 RTSP/MJPEG camera Install first in Arduino IDE Library Manager: ESP32-RTSPServer by Richard Sachse (version 1.3.5 or newer) Open in VLC: rtsp://:8554/ This is MJPEG carried over RTSP, not H.264. The stream is 720p (1280x720). */ #include #include #include #include #include #include #include "esp_camera.h" #include constexpr int ETH_MOSI = 11, ETH_MISO = 12, ETH_SCK = 13; constexpr int ETH_CS = 14, ETH_RST = 9, ETH_INT = 10, ETH_ADDR = 1; constexpr int CAMERA_POWER = 8; // Active-low power enable on ESP32-S3-ETH. const char *otaHostname = "esp32s3-camera-node"; const char *otaPassword = "1gYh0Y"; uint32_t last_ota_time = 0; SPIClass ethSpi(FSPI); static bool ethConnected = false; RTSPServer rtspServer; TaskHandle_t videoTaskHandle = nullptr; int cameraQuality = 20; static bool startCamera() { camera_config_t config = {}; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 41; config.pin_d1 = 45; config.pin_d2 = 46; config.pin_d3 = 42; config.pin_d4 = 40; config.pin_d5 = 38; config.pin_d6 = 15; config.pin_d7 = 18; config.pin_xclk = 3; config.pin_pclk = 39; config.pin_vsync = 1; config.pin_href = 2; config.pin_sccb_sda = 48; config.pin_sccb_scl = 47; config.pin_pwdn = -1; config.pin_reset = -1; config.xclk_freq_hz = 10000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_HD; // 1280x720 (720p). Requires PSRAM. config.jpeg_quality = cameraQuality; // Higher number = smaller/lower-quality JPEG. config.fb_count = 1; config.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; esp_err_t error = esp_camera_init(&config); if (error != ESP_OK) { Serial.printf("Camera init failed: 0x%x\n", error); return false; } return true; } static void sendVideo(void *) { while (true) { if (rtspServer.readyToSendFrame()) { camera_fb_t *frame = esp_camera_fb_get(); if (frame) { rtspServer.sendRTSPFrame(frame->buf, frame->len, cameraQuality, frame->width, frame->height); esp_camera_fb_return(frame); } } vTaskDelay(pdMS_TO_TICKS(1)); } } static void startOTA() { // Explicit mDNS binding before ArduinoOTA prevents unreliable Ethernet OTA discovery. if (!MDNS.begin(otaHostname)) { Serial.println("mDNS responder setup failed!"); } ArduinoOTA.setHostname(otaHostname); ArduinoOTA.setPassword(otaPassword); ArduinoOTA .onStart([]() { String type = ArduinoOTA.getCommand() == U_FLASH ? "sketch" : "filesystem"; Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { if (millis() - last_ota_time > 500) { Serial.printf("Progress: %u%%\n", progress / (total / 100)); last_ota_time = millis(); } }) .onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(ETH.localIP()); } static void onEthEvent(arduino_event_id_t event, arduino_event_info_t info) { switch (event) { case ARDUINO_EVENT_ETH_START: Serial.println("ETH: Started, setting hostname."); ETH.setHostname(otaHostname); break; case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH: Link up."); break; case ARDUINO_EVENT_ETH_GOT_IP: Serial.print("ETH: Got IP: "); Serial.println(ETH.localIP()); ethConnected = true; break; case ARDUINO_EVENT_ETH_DISCONNECTED: Serial.println("ETH: Link down."); ethConnected = false; break; case ARDUINO_EVENT_ETH_STOP: Serial.println("ETH: Stopped."); ethConnected = false; break; default: break; } } void setup() { Serial.begin(115200); delay(1000); Serial.println("\nESP32-S3 Ethernet RTSP camera starting"); // Required for camera power on this Waveshare board. pinMode(CAMERA_POWER, OUTPUT); digitalWrite(CAMERA_POWER, LOW); delay(200); Network.onEvent(onEthEvent); ethSpi.begin(ETH_SCK, ETH_MISO, ETH_MOSI); Serial.println("Network: Initializing W5500 over SPI..."); if (!ETH.begin(ETH_PHY_W5500, ETH_ADDR, ETH_CS, ETH_INT, ETH_RST, ethSpi)) { Serial.println("Network: Fatal error - W5500 initialization failed."); while (true) delay(1000); } Serial.println("Network: Waiting for DHCP lease..."); unsigned long dhcpWaitStart = millis(); while (!ethConnected && millis() - dhcpWaitStart < 15000) { delay(100); } if (!ethConnected) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } startOTA(); if (!startCamera()) return; rtspServer.transport = RTSPServer::VIDEO_ONLY; rtspServer.rtspPort = 8554; if (!rtspServer.init(RTSPServer::VIDEO_ONLY, 8554)) { Serial.println("RTSP server failed to start"); return; } xTaskCreate(sendVideo, "RTSP video", 5 * 1024, nullptr, 5, &videoTaskHandle); Serial.printf("RTSP ready: rtsp://%s:8554/\n", ETH.localIP().toString().c_str()); } void loop() { ArduinoOTA.handle(); delay(2); }