commit 71b9bb55c74fb6a24dc215c03f7c6f58e7c070b3 Author: ashus3868 Date: Wed Jun 9 13:34:06 2021 +0530 Upload Esp32 cam Liv streaming code diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2a87d2b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2015-2020, Anatoli Arkhipenko. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea72eba --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# ESP32 MJPEG Streaming Server + +This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM and ESP-EYE modules. + +This is tested to work with **VLC** and **Blynk** video widget. + + + +Inspired by and based on this Instructable: [$9 RTSP Video Streamer Using the ESP32-CAM Board](https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + +Full story: https://www.hackster.io/anatoli-arkhipenko/multi-client-mjpeg-streaming-from-esp32-47768f + + +------ + +##### Other repositories that may be of interest + +###### ESP32 MJPEG streaming server servicing a single client: + +https://github.com/arkhipenko/esp32-cam-mjpeg + + + +###### ESP32 MJPEG streaming server servicing multiple clients (FreeRTOS based): + +https://github.com/arkhipenko/esp32-cam-mjpeg-multiclient + + + +###### ESP32 MJPEG streaming server servicing multiple clients (FreeRTOS based) with the latest camera drivers from espressif. + +https://github.com/arkhipenko/esp32-mjpeg-multiclient-espcam-drivers + + + +###### Cooperative multitasking library: + +https://github.com/arkhipenko/TaskScheduler + diff --git a/esp32_camera_mjpeg/camera_pins.h b/esp32_camera_mjpeg/camera_pins.h new file mode 100644 index 0000000..7855722 --- /dev/null +++ b/esp32_camera_mjpeg/camera_pins.h @@ -0,0 +1,99 @@ + +#if defined(CAMERA_MODEL_WROVER_KIT) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 21 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 19 +#define Y4_GPIO_NUM 18 +#define Y3_GPIO_NUM 5 +#define Y2_GPIO_NUM 4 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#elif defined(CAMERA_MODEL_ESP_EYE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 4 +#define SIOD_GPIO_NUM 18 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 36 +#define Y8_GPIO_NUM 37 +#define Y7_GPIO_NUM 38 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 35 +#define Y4_GPIO_NUM 14 +#define Y3_GPIO_NUM 13 +#define Y2_GPIO_NUM 34 +#define VSYNC_GPIO_NUM 5 +#define HREF_GPIO_NUM 27 +#define PCLK_GPIO_NUM 25 + +#elif defined(CAMERA_MODEL_M5STACK_PSRAM) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 25 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 22 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_M5STACK_WIDE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 22 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#else +#error "Camera model not selected" +#endif diff --git a/esp32_camera_mjpeg/esp32_camera_mjpeg.ino b/esp32_camera_mjpeg/esp32_camera_mjpeg.ino new file mode 100644 index 0000000..427ad13 --- /dev/null +++ b/esp32_camera_mjpeg/esp32_camera_mjpeg.ino @@ -0,0 +1,164 @@ +/* + + This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM and + ESP32-EYE modules. + This is tested to work with VLC and Blynk video widget. + + Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board + (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + Board: AI-Thinker ESP32-CAM + +*/ + +#include "src/OV2640.h" +#include +#include +#include + +// Select camera model +//#define CAMERA_MODEL_WROVER_KIT +#define CAMERA_MODEL_ESP_EYE +//#define CAMERA_MODEL_M5STACK_PSRAM +//#define CAMERA_MODEL_M5STACK_WIDE +//#define CAMERA_MODEL_AI_THINKER + +#include "camera_pins.h" + + +#define SSID1 "ssid" +#define PWD1 "password" + + +OV2640 cam; + +WebServer server(80); + +const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: *\r\n" \ + "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; +const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; +const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; +const int hdrLen = strlen(HEADER); +const int bdrLen = strlen(BOUNDARY); +const int cntLen = strlen(CTNTTYPE); + +void handle_jpg_stream(void) +{ + char buf[32]; + int s; + + WiFiClient client = server.client(); + + client.write(HEADER, hdrLen); + client.write(BOUNDARY, bdrLen); + + while (true) + { + if (!client.connected()) break; + cam.run(); + s = cam.getSize(); + client.write(CTNTTYPE, cntLen); + sprintf( buf, "%d\r\n\r\n", s ); + client.write(buf, strlen(buf)); + client.write((char *)cam.getfb(), s); + client.write(BOUNDARY, bdrLen); + } +} + +const char JHEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Content-disposition: inline; filename=capture.jpg\r\n" \ + "Content-type: image/jpeg\r\n\r\n"; +const int jhdLen = strlen(JHEADER); + +void handle_jpg(void) +{ + WiFiClient client = server.client(); + + cam.run(); + if (!client.connected()) return; + + client.write(JHEADER, jhdLen); + client.write((char *)cam.getfb(), cam.getSize()); +} + +void handleNotFound() +{ + String message = "Server is running!\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + server.send(200, "text / plain", message); +} + +void setup() +{ + + Serial.begin(115200); + //while (!Serial); //wait for serial connection. + + camera_config_t config; + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; + config.pin_sscb_sda = SIOD_GPIO_NUM; + config.pin_sscb_scl = SIOC_GPIO_NUM; + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; + + // Frame parameters + // config.frame_size = FRAMESIZE_UXGA; + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 12; + config.fb_count = 2; + +#if defined(CAMERA_MODEL_ESP_EYE) + pinMode(13, INPUT_PULLUP); + pinMode(14, INPUT_PULLUP); +#endif + + cam.init(config); + + IPAddress ip; + + WiFi.mode(WIFI_STA); + WiFi.begin(SSID1, PWD1); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + ip = WiFi.localIP(); + Serial.println(F("WiFi connected")); + Serial.println(""); + Serial.println(ip); + Serial.print("Stream Link: http://"); + Serial.print(ip); + Serial.println("/mjpeg/1"); + server.on("/mjpeg/1", HTTP_GET, handle_jpg_stream); + server.on("/jpg", HTTP_GET, handle_jpg); + server.onNotFound(handleNotFound); + server.begin(); +} + +void loop() +{ + server.handleClient(); +} diff --git a/esp32_camera_mjpeg/src/OV2640.cpp b/esp32_camera_mjpeg/src/OV2640.cpp new file mode 100644 index 0000000..02d04d5 --- /dev/null +++ b/esp32_camera_mjpeg/src/OV2640.cpp @@ -0,0 +1,193 @@ +#include "OV2640.h" + +#define TAG "OV2640" + +// definitions appropriate for the ESP32-CAM devboard (and most clones) +camera_config_t esp32cam_config{ + + .pin_pwdn = -1, // FIXME: on the TTGO T-Journal I think this is GPIO 0 + .pin_reset = 15, + + .pin_xclk = 27, + + .pin_sscb_sda = 25, + .pin_sscb_scl = 23, + + .pin_d7 = 19, + .pin_d6 = 36, + .pin_d5 = 18, + .pin_d4 = 39, + .pin_d3 = 5, + .pin_d2 = 34, + .pin_d1 = 35, + .pin_d0 = 17, + .pin_vsync = 22, + .pin_href = 26, + .pin_pclk = 21, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space + // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer + // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb + .frame_size = FRAMESIZE_SVGA, + .jpeg_quality = 12, //0-63 lower numbers are higher quality + .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg +}; + +camera_config_t esp32cam_aithinker_config{ + + .pin_pwdn = 32, + .pin_reset = -1, + + .pin_xclk = 0, + + .pin_sscb_sda = 26, + .pin_sscb_scl = 27, + + // Note: LED GPIO is apparently 4 not sure where that goes + // per https://github.com/donny681/ESP32_CAMERA_QR/blob/e4ef44549876457cd841f33a0892c82a71f35358/main/led.c + .pin_d7 = 35, + .pin_d6 = 34, + .pin_d5 = 39, + .pin_d4 = 36, + .pin_d3 = 21, + .pin_d2 = 19, + .pin_d1 = 18, + .pin_d0 = 5, + .pin_vsync = 25, + .pin_href = 23, + .pin_pclk = 22, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_1, + .ledc_channel = LEDC_CHANNEL_1, + .pixel_format = PIXFORMAT_JPEG, + // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space + // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer + // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb + .frame_size = FRAMESIZE_SVGA, + .jpeg_quality = 12, //0-63 lower numbers are higher quality + .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg +}; + +camera_config_t esp32cam_ttgo_t_config{ + + .pin_pwdn = 26, + .pin_reset = -1, + + .pin_xclk = 32, + + .pin_sscb_sda = 13, + .pin_sscb_scl = 12, + + .pin_d7 = 39, + .pin_d6 = 36, + .pin_d5 = 23, + .pin_d4 = 18, + .pin_d3 = 15, + .pin_d2 = 4, + .pin_d1 = 14, + .pin_d0 = 5, + .pin_vsync = 27, + .pin_href = 25, + .pin_pclk = 19, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + .frame_size = FRAMESIZE_SVGA, + .jpeg_quality = 12, //0-63 lower numbers are higher quality + .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg +}; + +void OV2640::run(void) +{ + if (fb) + //return the frame buffer back to the driver for reuse + esp_camera_fb_return(fb); + + fb = esp_camera_fb_get(); +} + +void OV2640::runIfNeeded(void) +{ + if (!fb) + run(); +} + +int OV2640::getWidth(void) +{ + runIfNeeded(); + return fb->width; +} + +int OV2640::getHeight(void) +{ + runIfNeeded(); + return fb->height; +} + +size_t OV2640::getSize(void) +{ + runIfNeeded(); + if (!fb) + return 0; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? + return fb->len; +} + +uint8_t *OV2640::getfb(void) +{ + runIfNeeded(); + if (!fb) + return NULL; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? + + return fb->buf; +} + +framesize_t OV2640::getFrameSize(void) +{ + return _cam_config.frame_size; +} + +void OV2640::setFrameSize(framesize_t size) +{ + _cam_config.frame_size = size; +} + +pixformat_t OV2640::getPixelFormat(void) +{ + return _cam_config.pixel_format; +} + +void OV2640::setPixelFormat(pixformat_t format) +{ + switch (format) + { + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_JPEG: + _cam_config.pixel_format = format; + break; + default: + _cam_config.pixel_format = PIXFORMAT_GRAYSCALE; + break; + } +} + +esp_err_t OV2640::init(camera_config_t config) +{ + memset(&_cam_config, 0, sizeof(_cam_config)); + memcpy(&_cam_config, &config, sizeof(config)); + + esp_err_t err = esp_camera_init(&_cam_config); + if (err != ESP_OK) + { + printf("Camera probe failed with error 0x%x", err); + return err; + } + // ESP_ERROR_CHECK(gpio_install_isr_service(0)); + + return ESP_OK; +} diff --git a/esp32_camera_mjpeg/src/OV2640.h b/esp32_camera_mjpeg/src/OV2640.h new file mode 100644 index 0000000..b9b5706 --- /dev/null +++ b/esp32_camera_mjpeg/src/OV2640.h @@ -0,0 +1,43 @@ +#ifndef OV2640_H_ +#define OV2640_H_ + +#include +#include +#include +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_camera.h" + +extern camera_config_t esp32cam_config, esp32cam_aithinker_config, esp32cam_ttgo_t_config; + +class OV2640 +{ +public: + OV2640(){ + fb = NULL; + }; + ~OV2640(){ + }; + esp_err_t init(camera_config_t config); + void run(void); + size_t getSize(void); + uint8_t *getfb(void); + int getWidth(void); + int getHeight(void); + framesize_t getFrameSize(void); + pixformat_t getPixelFormat(void); + + void setFrameSize(framesize_t size); + void setPixelFormat(pixformat_t format); + +private: + void runIfNeeded(); // grab a frame if we don't already have one + + // camera_framesize_t _frame_size; + // camera_pixelformat_t _pixel_format; + camera_config_t _cam_config; + + camera_fb_t *fb; +}; + +#endif //OV2640_H_