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