├── CameraWebServerRecorder.ino ├── LICENSE.md ├── README.md ├── app_httpd.cpp ├── avi.cpp ├── camera_index.h └── camera_pins.h /CameraWebServerRecorder.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Orignial from espressif, part of arduino-esp32 3 | * 4 | * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 5 | * 6 | * forked, or copied about Aug 1, 2022 7 | * - didn't want to fork the entire arduino-esp32 for this example 8 | * 9 | */ 10 | 11 | /* 12 | 13 | CameraWebServerRecorder 14 | 15 | https://github.com/jameszah/CameraWebServerRecorder 16 | 17 | 18 | This is a modified version of the espressif ESP32-CAM CameraWebServer example for Arduino, which lets you fiddle 19 | with all the parameters on the ov2640 camera providing a web browser interface to make the changes, and view the stream, 20 | ot stills jpeg captures. 21 | 22 | The modification is to add a video recorder facilty. You can set the frame rate to record, the "speedup" to you can play a 23 | timelapse at 30 fps on your computer, and a avi segment length, so you end the movie from time to time, so you won't have a 2 GB 24 | avi file to edit later. You can break it into chunks, each with its own index. 25 | 26 | My original idea was add all the parameters to control the camera to https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior, 27 | but I left this as primarily the espressif demo program, and added a simple sd recording facility. No semaphores, and multitak 28 | communications, other than setting a global variable to start stop the recording task, using all the parameters you have already 29 | set in the espressif streaming/snapshot control panel. 30 | 31 | If you put a file called "secret.txt" on your SD, with 2 lines giving your SSID name, and SSID password, then it will 32 | join your router and you can access from your phone or computer. 33 | If there is no file, it will start in AP mode and you can connect your phone or computer the "ESP32-CAM", password "123456789", 34 | to access all the functionality. It will create a secret.txt for you with the SSID name "AP ESP32-CAM". You can edit that 35 | later to a different AP name a password. 36 | This AP funtionality is useful it you are setting up a camera away from a router, and you can connect with your phone, choose 37 | your parameters, start the recording, and walk away. When you return the SD will be full of great video! 38 | 39 | The avi recordings seem quite amenable to haveing parameters changed during the recording, including framesize! 40 | 41 | It turns off streaming when you start recording, but you can turn it back on, with some loss of sd recording speed. 42 | If you are timelapse recording like 1 fps, it doesn't really matter. 43 | (The junior program below, can record and stream 2 channels without reducing recording rate.) 44 | 45 | The filenames of the recordings are just the date and time (2022-08-14_11.45.49.avi). 46 | Date and time in AP mode is acheived by automatically sending unix time when you click the Start Record button since 47 | your phone/computer should know the time. Timezones have not been implemented, so its all GMT. 48 | 49 | If you have no SD card installed, it will show 0 freespace on the SD, and continue with other functionality. 50 | When the SD runs out of diskspce, recording will stop - no deleting old stuff implemented. (my other program have it) 51 | 52 | You can also control this CamWebServer with simple urls, if you want to configure and start it with motioneye (or 53 | equivalent) as follows, 54 | 55 | http://192.168.1.67/control?var=brightness&val=2 56 | http://192.168.1.67/control?var=contrast&val=-2 57 | http://192.168.1.67/control?var=saturation&val=2 58 | http://192.168.1.67/control?var=special_effect&val=1 59 | http://192.168.1.67/control?var=awb&val=1 60 | http://192.168.1.67/control?var=awb_gain&val=1 61 | http://192.168.1.67/control?var=ae_level&val=-1 62 | http://192.168.1.67/control?var=agc&val=0 63 | http://192.168.1.67/control?var=gainceiling&val=1 64 | http://192.168.1.67/control?var=ae_level&val=-2 65 | http://192.168.1.67/control?var=agc&val=1 66 | http://192.168.1.67/control?var=bpc&val=0 67 | http://192.168.1.67/control?var=raw_gma&val=0 68 | http://192.168.1.67/control?var=lenc&val=0 69 | http://192.168.1.67/control?var=hmirror&val=1 70 | http://192.168.1.67/control?var=dcw&val=0 71 | http://192.168.1.67/control?var=vflip&val=1 72 | http://192.168.1.67/control?var=hmirror&val=0 73 | http://192.168.1.67/control?var=dcw&val=1 74 | http://192.168.1.67/control?var=colorbar&val=1 75 | http://192.168.1.67/status 76 | http://192.168.1.67/capture 77 | http://192.168.1.67:81/stream - streaming using different port 81, so the web still responds on port 80 78 | 79 | ... and CamWebServerRecorder adds these: 80 | 81 | http://192.168.1.67/control?var=interval&val=1000 -- 1000 milliseconds between frames 82 | http://192.168.1.67/control?var=seglen&val=1800 -- avi file is closed and new file started every 1800 seconds 83 | http://192.168.1.67/control?var=speedup&val=30 -- play 1 fps recording at 30fps when you play the avi 84 | http://192.168.1.67/startrecord 85 | http://192.168.1.67/stoprecord 86 | 87 | 88 | The avi.cpp part of this code is based on heavily modified and simplfied subset of: 89 | https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior 90 | which is a heavily modifed and simplfied rework of: 91 | https://github.com/jameszah/ESP32-CAM-Video-Recorder 92 | 93 | Those two programs have various ways to control the recording, and view live streams, and download the video to you computer 94 | or phone, or upload snapshots and video to Telegram, etc. 95 | 96 | The changes to the original files: 97 | CamWebServerRecorder.ino - mount the SD card, the AP stuff, start the recorder task 98 | app_httpd.cpp - the esp32 side code to handle the new paramters 99 | camera_index.h - this has the gziped html and javascript from espressif expanded (and easily editable), 100 | and inclused all the changes to the web display for new parameters and controls 101 | avi.cpp - all the origninal simplfied code to record a mjpeg avi 102 | camer_pins.h - no changes 103 | 104 | Work-In-Progress .... but other things to do. 105 | - only works for ov2640 camera 106 | - webpage does report SD freespace, but no info on recordings, including whether it is recording (if you reload the page) 107 | - One-Click-Installer not done -- maybe tomorrow 108 | - didn't put in mdns - so you have to figure out your ip from serial monitor, or port authority 109 | (it should respond on 80 and 81, which would give you a clue, but has a defualt name like esp32-F41450 110 | 111 | James Zahary 112 | Aug 14, 2022 113 | Version 55.10 - in my mysterious numbering scheme 114 | Free coffee https://ko-fi.com/jameszah/ 115 | jamzah.plc@gmail.com 116 | 117 | jameszah/CameraWebServerRecorder is licensed under the 118 | GNU General Public License v3.0 119 | 120 | Arduino 1.8.19 121 | Arduino-ESP32 2.0.4 122 | Board: AI-Thinker ESP32-CAM 123 | Huge APP 124 | 125 | Using library WiFi at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.4\libraries\WiFi 126 | Using library SD_MMC at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.4\libraries\SD_MMC 127 | Using library FS at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.4\libraries\FS 128 | "C:\\ArduinoPortable\\arduino-1.8.19\\portable\\packages\\esp32\\tools\\xtensa-esp32-elf-gcc\\gcc8_4_0-esp-2021r2-patch3/bin/xtensa-esp32-elf-size" -A "C:\\Users\\James\\AppData\\Local\\Temp\\arduino_build_818851/CameraWebServerRecorder.55.10.ino.elf" 129 | Sketch uses 1589429 bytes (50%) of program storage space. Maximum is 3145728 bytes. 130 | Global variables use 68992 bytes (21%) of dynamic memory, leaving 258688 bytes for local variables. Maximum is 327680 bytes. 131 | 132 | 133 | */ 134 | #include "esp_camera.h" 135 | #include 136 | 137 | // 138 | // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality 139 | // Ensure ESP32 Wrover Module or other board with PSRAM is selected 140 | // Partial images will be transmitted if image exceeds buffer size 141 | // 142 | // You must select partition scheme from the board menu that has at least 3MB APP space. 143 | // Face Recognition is DISABLED for ESP32 and ESP32-S2, because it takes up from 15 144 | // seconds to process single frame. Face Detection is ENABLED if PSRAM is enabled as well 145 | 146 | // =================== 147 | // Select camera model 148 | // =================== 149 | //#define CAMERA_MODEL_WROVER_KIT // Has PSRAM 150 | //#define CAMERA_MODEL_ESP_EYE // Has PSRAM 151 | //#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM 152 | //#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM 153 | //#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM 154 | //#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM 155 | //#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM 156 | //#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM 157 | #define CAMERA_MODEL_AI_THINKER // Has PSRAM 158 | //#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM 159 | // ** Espressif Internal Boards ** 160 | //#define CAMERA_MODEL_ESP32_CAM_BOARD 161 | //#define CAMERA_MODEL_ESP32S2_CAM_BOARD 162 | //#define CAMERA_MODEL_ESP32S3_CAM_LCD 163 | 164 | #include "camera_pins.h" 165 | 166 | extern TaskHandle_t the_camera_loop_task; 167 | extern void the_camera_loop (void* pvParameter); 168 | esp_err_t init_sdcard(); 169 | 170 | void startCameraServer(); 171 | 172 | const char* def_ssid = "ESP32-CAM"; 173 | const char* def_pass = "123456789"; 174 | 175 | #include 176 | void start_wifi() { 177 | 178 | String junk; 179 | 180 | String cssid; 181 | String cssid2; 182 | String cpass; 183 | char ssidch[32]; 184 | char ssidch2[32]; 185 | char passch[64]; 186 | 187 | File config_file = SD_MMC.open("/secret.txt", "r"); 188 | if (config_file) { 189 | 190 | Serial.println("Reading secret.txt"); 191 | cssid = config_file.readStringUntil(' '); 192 | cssid2 = config_file.readStringUntil(' '); 193 | junk = config_file.readStringUntil('\n'); 194 | cpass = config_file.readStringUntil(' '); 195 | junk = config_file.readStringUntil('\n'); 196 | config_file.close(); 197 | 198 | cssid.toCharArray(ssidch, cssid.length() + 1); 199 | cssid2.toCharArray(ssidch2, cssid2.length() + 1); 200 | cpass.toCharArray(passch, cpass.length() + 1); 201 | 202 | if (String(cssid) == "ap" || String(cssid) == "AP") { 203 | WiFi.softAP(ssidch2, passch); 204 | 205 | IPAddress IP = WiFi.softAPIP(); 206 | Serial.println("Using AP mode: "); 207 | Serial.print(ssidch2); Serial.print(" / "); Serial.println(passch); 208 | Serial.print("AP IP address: "); 209 | Serial.println(IP); 210 | } else { 211 | Serial.println(ssidch); 212 | Serial.println(passch); 213 | WiFi.begin(ssidch, passch); 214 | WiFi.setSleep(false); 215 | 216 | while (WiFi.status() != WL_CONNECTED) { 217 | delay(500); 218 | Serial.print("."); 219 | } 220 | Serial.println(""); 221 | Serial.println("Using Station mode: "); 222 | Serial.print(cssid); Serial.print(" / "); Serial.println(cpass); 223 | Serial.println("WiFi connected"); 224 | 225 | Serial.print("Camera Ready! Use 'http://"); 226 | Serial.print(WiFi.localIP()); 227 | Serial.println("' to connect"); 228 | } 229 | } else { 230 | Serial.println("Failed to open config.txt - writing a default"); 231 | 232 | // lets make a simple.txt config file 233 | File new_simple = SD_MMC.open("/secret.txt", "w"); 234 | new_simple.println("ap ESP32-CAM // your ssid - ap mean access point mode, put Router123 station mode"); 235 | new_simple.println("123456789 // your ssid password"); 236 | new_simple.close(); 237 | 238 | WiFi.softAP(def_ssid, def_pass); 239 | 240 | IPAddress IP = WiFi.softAPIP(); 241 | Serial.println("Using AP mode: "); 242 | Serial.print(def_ssid); Serial.print(" / "); Serial.println(def_pass); 243 | Serial.print("AP IP address: "); 244 | Serial.println(IP); 245 | 246 | } 247 | } 248 | 249 | 250 | void setup() { 251 | Serial.begin(115200); 252 | Serial.setDebugOutput(true); 253 | Serial.println(); 254 | Serial.println("-----------------------------------------------------------"); 255 | Serial.println("https://github.com/jameszah/CameraWebServerRecorder 55.10\n"); 256 | Serial.println("-----------------------------------------------------------"); 257 | 258 | camera_config_t config; 259 | config.ledc_channel = LEDC_CHANNEL_0; 260 | config.ledc_timer = LEDC_TIMER_0; 261 | config.pin_d0 = Y2_GPIO_NUM; 262 | config.pin_d1 = Y3_GPIO_NUM; 263 | config.pin_d2 = Y4_GPIO_NUM; 264 | config.pin_d3 = Y5_GPIO_NUM; 265 | config.pin_d4 = Y6_GPIO_NUM; 266 | config.pin_d5 = Y7_GPIO_NUM; 267 | config.pin_d6 = Y8_GPIO_NUM; 268 | config.pin_d7 = Y9_GPIO_NUM; 269 | config.pin_xclk = XCLK_GPIO_NUM; 270 | config.pin_pclk = PCLK_GPIO_NUM; 271 | config.pin_vsync = VSYNC_GPIO_NUM; 272 | config.pin_href = HREF_GPIO_NUM; 273 | config.pin_sscb_sda = SIOD_GPIO_NUM; 274 | config.pin_sscb_scl = SIOC_GPIO_NUM; 275 | config.pin_pwdn = PWDN_GPIO_NUM; 276 | config.pin_reset = RESET_GPIO_NUM; 277 | config.xclk_freq_hz = 20000000; 278 | config.frame_size = FRAMESIZE_UXGA; 279 | config.pixel_format = PIXFORMAT_JPEG; // for streaming 280 | //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition 281 | config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; 282 | config.fb_location = CAMERA_FB_IN_PSRAM; 283 | config.jpeg_quality = 12; 284 | config.fb_count = 1; 285 | 286 | // if PSRAM IC present, init with UXGA resolution and higher JPEG quality 287 | // for larger pre-allocated frame buffer. 288 | if (config.pixel_format == PIXFORMAT_JPEG) { 289 | if (psramFound()) { 290 | config.jpeg_quality = 10; 291 | config.fb_count = 3; //jz 2->3 - add another frame for the avi recording 292 | config.grab_mode = CAMERA_GRAB_LATEST; 293 | } else { 294 | // Limit the frame size when PSRAM is not available 295 | config.frame_size = FRAMESIZE_SVGA; 296 | config.fb_location = CAMERA_FB_IN_DRAM; 297 | } 298 | } else { 299 | // Best option for face detection/recognition 300 | config.frame_size = FRAMESIZE_240X240; 301 | #if CONFIG_IDF_TARGET_ESP32S3 302 | config.fb_count = 2; 303 | #endif 304 | } 305 | 306 | #if defined(CAMERA_MODEL_ESP_EYE) 307 | pinMode(13, INPUT_PULLUP); 308 | pinMode(14, INPUT_PULLUP); 309 | #endif 310 | 311 | 312 | // camera init 313 | esp_err_t err = esp_camera_init(&config); 314 | if (err != ESP_OK) { 315 | Serial.printf("Camera init failed with error 0x%x", err); 316 | return; 317 | } 318 | 319 | sensor_t * s = esp_camera_sensor_get(); 320 | // initial sensors are flipped vertically and colors are a bit saturated 321 | if (s->id.PID == OV3660_PID) { 322 | s->set_vflip(s, 1); // flip it back 323 | s->set_brightness(s, 1); // up the brightness just a bit 324 | s->set_saturation(s, -2); // lower the saturation 325 | } 326 | // drop down frame size for higher initial frame rate 327 | if (config.pixel_format == PIXFORMAT_JPEG) { 328 | s->set_framesize(s, FRAMESIZE_QVGA); 329 | } 330 | 331 | #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM) 332 | s->set_vflip(s, 1); 333 | s->set_hmirror(s, 1); 334 | #endif 335 | 336 | #if defined(CAMERA_MODEL_ESP32S3_EYE) 337 | s->set_vflip(s, 1); 338 | #endif 339 | 340 | esp_err_t card_err = init_sdcard(); 341 | if (card_err != ESP_OK) { 342 | Serial.printf("SD Card init failed with error 0x%x", card_err); 343 | } 344 | 345 | start_wifi(); 346 | 347 | startCameraServer(); 348 | 349 | // 5000 stack, prio 5 same at http streamer, core 1 350 | xTaskCreatePinnedToCore( the_camera_loop, "the_camera_loop", 5000, NULL, 5, &the_camera_loop_task, 1); 351 | 352 | delay(100); 353 | } 354 | 355 | void loop() { 356 | 357 | delay(1000); 358 | } 359 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CameraWebServerRecorder 2 | Enhancement of @espressif CameraWebServer to add avi video recording to an SD Card 3 | 4 | 5 | CameraWebServerRecorder 6 | 7 | https://github.com/jameszah/CameraWebServerRecorder 8 | 9 | 10 | This is a modified version of the espressif ESP32-CAM CameraWebServer example for Arduino, which lets you fiddle 11 | with all the parameters on the ov2640 camera providing a web browser interface to make the changes, and view the stream, 12 | ot stills jpeg captures. 13 | 14 | The modification is to add a video recorder facilty. You can set the frame rate to record, the "speedup" to you can play a 15 | timelapse at 30 fps on your computer, and a avi segment length, so you end the movie from time to time, so you won't have a 2 GB 16 | avi file to edit later. You can break it into chunks, each with its own index. 17 | 18 | My original idea was add all the parameters to control the camera to https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior, 19 | but I left this as primarily the espressif demo program, and added a simple sd recording facility. No semaphores, and multitask 20 | communications, other than setting a global variable to start stop the recording task, using all the parameters you have already 21 | set in the espressif streaming/snapshot control panel. 22 | 23 | If you put a file called "secret.txt" on your SD, with 2 lines giving your SSID name, and SSID password, then it will 24 | join your router and you can access from your phone or computer. 25 | 26 | If there is no file, it will start in AP mode and you can connect your phone or computer the "ESP32-CAM", password "123456789", 27 | to access all the functionality. It will create a secret.txt for you with the SSID name "AP ESP32-CAM". You can edit that 28 | later to a different AP name a password. 29 | 30 | This AP funtionality is useful it you are setting up a camera away from a router, and you can connect with your phone, choose 31 | your parameters, start the recording, and walk away. When you return the SD will be full of great video! 32 | 33 | The avi recordings seem quite amenable to haveing parameters changed during the recording, including framesize! 34 | 35 | It turns off streaming when you start recording, but you can turn it back on, with some loss of sd recording speed. 36 | If you are timelapse recording like 1 fps, it doesn't really matter. (The junior program below, can record and stream 2 channels without 37 | reducing recording rate.) 38 | 39 | The filenames of the recordings are just the date and time (2022-08-14_11.45.49.avi). Date and time in AP mode is acheived by automatically 40 | sending unix time when you click the Start Record button since your phone/computer should know the time. Timezones have not been implemented. 41 | 42 | If you have no SD card installed, it will show 0 freespace on the SD, and continue with other functionality. 43 | When the SD runs out of diskspce, recording will stop - no deleting old stuff implemented. (my other programs have it) 44 | 45 | You can also control this CamWebServer with simple urls, if you want to configure and start it with motioneye (or 46 | equivalent) as follows, 47 | 48 | http://192.168.1.67/control?var=brightness&val=2 49 | http://192.168.1.67/control?var=contrast&val=-2 50 | http://192.168.1.67/control?var=saturation&val=2 51 | http://192.168.1.67/control?var=special_effect&val=1 52 | http://192.168.1.67/control?var=awb&val=1 53 | http://192.168.1.67/control?var=awb_gain&val=1 54 | http://192.168.1.67/control?var=ae_level&val=-1 55 | http://192.168.1.67/control?var=agc&val=0 56 | http://192.168.1.67/control?var=gainceiling&val=1 57 | http://192.168.1.67/control?var=ae_level&val=-2 58 | http://192.168.1.67/control?var=agc&val=1 59 | http://192.168.1.67/control?var=bpc&val=0 60 | http://192.168.1.67/control?var=raw_gma&val=0 61 | http://192.168.1.67/control?var=lenc&val=0 62 | http://192.168.1.67/control?var=hmirror&val=1 63 | http://192.168.1.67/control?var=dcw&val=0 64 | http://192.168.1.67/control?var=vflip&val=1 65 | http://192.168.1.67/control?var=hmirror&val=0 66 | http://192.168.1.67/control?var=dcw&val=1 67 | http://192.168.1.67/control?var=colorbar&val=1 68 | http://192.168.1.67/status 69 | http://192.168.1.67/capture 70 | http://192.168.1.67:81/stream - streaming using different port 81, so the web still responds on port 80 71 | 72 | ... and CamWebServerRecorder adds these: 73 | 74 | http://192.168.1.67/control?var=interval&val=1000 -- 1000 milliseconds between frames 75 | http://192.168.1.67/control?var=seglen&val=1800 -- avi file is closed and new file started every 1800 seconds 76 | http://192.168.1.67/control?var=speedup&val=30 -- play 1 fps recording at 30fps when you play the avi 77 | http://192.168.1.67/startrecord 78 | http://192.168.1.67/stoprecord 79 | 80 | 81 | The avi.cpp part of this code is based on heavily modified and simplfied subset of: 82 | https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior 83 | which is a heavily modifed and simplfied rework of: 84 | https://github.com/jameszah/ESP32-CAM-Video-Recorder 85 | 86 | Those two programs have various ways to control the recording, and view live streams, and download the video to you computer 87 | or phone, or upload snapshots and video to Telegram, etc. 88 | 89 | The changes to the original files: 90 | 91 | CamWebServerRecorder.ino - mount the SD card, the AP stuff, start the recorder task 92 | 93 | app_httpd.cpp - the esp32 side code to handle the new paramters 94 | 95 | camera_index.h - this has the gziped html and javascript from espressif expanded (and easily editable), and includes all the changes to the web display for new parameters and controls 96 | 97 | avi.cpp - all the origninal simplfied code to record a mjpeg avi 98 | 99 | camer_pins.h - no changes 100 | 101 | Work-In-Progress .... but other things to do. 102 | - only works for ov2640 camera 103 | - webpage does report SD freespace, but no info on recordings, including whether it is recording (if you reload the page) 104 | - One-Click-Installer not done -- maybe tomorrow 105 | - didn't put in mdns - so you have to figure out your ip from serial monitor, or port authority (it should respond on 80 and 81, which would give you a clue, but has a defualt name like esp32-F41450) 106 | 107 | James Zahary 108 | Aug 14, 2022 109 | Version 55.10 - in my mysterious numbering scheme 110 | Free coffee https://ko-fi.com/jameszah/ 111 | jamzah.plc@gmail.com 112 | 113 | jameszah/CameraWebServerRecorder is licensed under the GNU General Public License v3.0 114 | 115 | Arduino 1.8.19 116 | Arduino-ESP32 2.0.4 117 | Board: AI-Thinker ESP32-CAM 118 | Huge APP 119 | 120 | ![image](https://user-images.githubusercontent.com/36938190/184581874-a0a66c24-0a92-4854-9117-76b3b94cfffc.png) 121 | 122 | 123 | Here are the new controls. 124 | From the top: 125 | 1. How much freespace on your SD card 126 | 2. Debug parameters show 127 | 2.1 framesize 128 | 2.2 quality 129 | 2.3 interval (milliseconds per frame) 130 | 2.4 speedup (muliply record framerate for playing) 131 | 2.5 avi segment length in seconds 132 | 3. set avi segment length 133 | 4. set frames per second to record (shows as "seconds per frame" for slow recording) 134 | 5. set speedup playback over record rate - this will adjust to playback all videos at 30 fps when you change framerate, but you can readjust it) 135 | 6. Start and Stop recording 136 | -------------------------------------------------------------------------------- /app_httpd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Orignial from espressif, part of arduino-esp32 3 | * 4 | * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 5 | * 6 | * forked, or copied about Aug 1, 2022 7 | * - didn't want to fork the entire arduino-esp32 for this example 8 | * 9 | */ 10 | 11 | 12 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 13 | // 14 | // Licensed under the Apache License, Version 2.0 (the "License"); 15 | // you may not use this file except in compliance with the License. 16 | // You may obtain a copy of the License at 17 | // 18 | // http://www.apache.org/licenses/LICENSE-2.0 19 | // 20 | // Unless required by applicable law or agreed to in writing, software 21 | // distributed under the License is distributed on an "AS IS" BASIS, 22 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | // See the License for the specific language governing permissions and 24 | // limitations under the License. 25 | #include "esp_http_server.h" 26 | #include "esp_timer.h" 27 | #include "esp_camera.h" 28 | #include "img_converters.h" 29 | #include "fb_gfx.h" 30 | #include "driver/ledc.h" 31 | #include "sdkconfig.h" 32 | #include "camera_index.h" 33 | 34 | //jz additions 35 | #include 36 | extern int framesize; 37 | extern int avi_length; 38 | extern int frame_interval; 39 | extern int speed_up_factor; 40 | extern int start_record; 41 | extern int freespace; 42 | 43 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 44 | #include "esp32-hal-log.h" 45 | #define TAG "" 46 | #else 47 | #include "esp_log.h" 48 | static const char *TAG = "camera_httpd"; 49 | #endif 50 | 51 | // Face Detection will not work on boards without (or with disabled) PSRAM 52 | #ifdef BOARD_HAS_PSRAM 53 | #define CONFIG_ESP_FACE_DETECT_ENABLED 1 54 | // Face Recognition takes upward from 15 seconds per frame on chips other than ESP32S3 55 | // Makes no sense to have it enabled for them 56 | #if CONFIG_IDF_TARGET_ESP32S3 57 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 1 58 | #else 59 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 0 60 | #endif 61 | #else 62 | #define CONFIG_ESP_FACE_DETECT_ENABLED 0 63 | #define CONFIG_ESP_FACE_RECOGNITION_ENABLED 0 64 | #endif 65 | 66 | #if CONFIG_ESP_FACE_DETECT_ENABLED 67 | 68 | #include 69 | #include "human_face_detect_msr01.hpp" 70 | #include "human_face_detect_mnp01.hpp" 71 | 72 | #define TWO_STAGE 1 /* very large firmware, very slow, reboots when streaming... 81 | 82 | #define FACE_ID_SAVE_NUMBER 7 83 | #endif 84 | 85 | #define FACE_COLOR_WHITE 0x00FFFFFF 86 | #define FACE_COLOR_BLACK 0x00000000 87 | #define FACE_COLOR_RED 0x000000FF 88 | #define FACE_COLOR_GREEN 0x0000FF00 89 | #define FACE_COLOR_BLUE 0x00FF0000 90 | #define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN) 91 | #define FACE_COLOR_CYAN (FACE_COLOR_BLUE | FACE_COLOR_GREEN) 92 | #define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED) 93 | #endif 94 | 95 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 96 | int led_duty = 0; 97 | bool isStreaming = false; 98 | #ifdef CONFIG_LED_LEDC_LOW_SPEED_MODE 99 | #define CONFIG_LED_LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE 100 | #else 101 | #define CONFIG_LED_LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE 102 | #endif 103 | #endif 104 | 105 | typedef struct 106 | { 107 | httpd_req_t *req; 108 | size_t len; 109 | } jpg_chunking_t; 110 | 111 | #define PART_BOUNDARY "123456789000000000000987654321" 112 | static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; 113 | static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; 114 | static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n"; 115 | 116 | httpd_handle_t stream_httpd = NULL; 117 | httpd_handle_t camera_httpd = NULL; 118 | 119 | #if CONFIG_ESP_FACE_DETECT_ENABLED 120 | 121 | static int8_t detection_enabled = 0; 122 | 123 | // #if TWO_STAGE 124 | // static HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 125 | // static HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 126 | // #else 127 | // static HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 128 | // #endif 129 | 130 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 131 | static int8_t recognition_enabled = 0; 132 | static int8_t is_enrolling = 0; 133 | 134 | #if QUANT_TYPE 135 | // S16 model 136 | FaceRecognition112V1S16 recognizer; 137 | #else 138 | // S8 model 139 | FaceRecognition112V1S8 recognizer; 140 | #endif 141 | #endif 142 | 143 | #endif 144 | 145 | typedef struct 146 | { 147 | size_t size; //number of values used for filtering 148 | size_t index; //current value index 149 | size_t count; //value count 150 | int sum; 151 | int *values; //array to be filled with values 152 | } ra_filter_t; 153 | 154 | static ra_filter_t ra_filter; 155 | 156 | static ra_filter_t *ra_filter_init(ra_filter_t *filter, size_t sample_size) 157 | { 158 | memset(filter, 0, sizeof(ra_filter_t)); 159 | 160 | filter->values = (int *)malloc(sample_size * sizeof(int)); 161 | if (!filter->values) 162 | { 163 | return NULL; 164 | } 165 | memset(filter->values, 0, sample_size * sizeof(int)); 166 | 167 | filter->size = sample_size; 168 | return filter; 169 | } 170 | 171 | static int ra_filter_run(ra_filter_t *filter, int value) 172 | { 173 | if (!filter->values) 174 | { 175 | return value; 176 | } 177 | filter->sum -= filter->values[filter->index]; 178 | filter->values[filter->index] = value; 179 | filter->sum += filter->values[filter->index]; 180 | filter->index++; 181 | filter->index = filter->index % filter->size; 182 | if (filter->count < filter->size) 183 | { 184 | filter->count++; 185 | } 186 | return filter->sum / filter->count; 187 | } 188 | 189 | #if CONFIG_ESP_FACE_DETECT_ENABLED 190 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 191 | static void rgb_print(fb_data_t *fb, uint32_t color, const char *str) 192 | { 193 | fb_gfx_print(fb, (fb->width - (strlen(str) * 14)) / 2, 10, color, str); 194 | } 195 | 196 | static int rgb_printf(fb_data_t *fb, uint32_t color, const char *format, ...) 197 | { 198 | char loc_buf[64]; 199 | char *temp = loc_buf; 200 | int len; 201 | va_list arg; 202 | va_list copy; 203 | va_start(arg, format); 204 | va_copy(copy, arg); 205 | len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg); 206 | va_end(copy); 207 | if (len >= sizeof(loc_buf)) 208 | { 209 | temp = (char *)malloc(len + 1); 210 | if (temp == NULL) 211 | { 212 | return 0; 213 | } 214 | } 215 | vsnprintf(temp, len + 1, format, arg); 216 | va_end(arg); 217 | rgb_print(fb, color, temp); 218 | if (len > 64) 219 | { 220 | free(temp); 221 | } 222 | return len; 223 | } 224 | #endif 225 | static void draw_face_boxes(fb_data_t *fb, std::list *results, int face_id) 226 | { 227 | int x, y, w, h; 228 | uint32_t color = FACE_COLOR_YELLOW; 229 | if (face_id < 0) 230 | { 231 | color = FACE_COLOR_RED; 232 | } 233 | else if (face_id > 0) 234 | { 235 | color = FACE_COLOR_GREEN; 236 | } 237 | if (fb->bytes_per_pixel == 2) { 238 | //color = ((color >> 8) & 0xF800) | ((color >> 3) & 0x07E0) | (color & 0x001F); 239 | color = ((color >> 16) & 0x001F) | ((color >> 3) & 0x07E0) | ((color << 8) & 0xF800); 240 | } 241 | int i = 0; 242 | for (std::list::iterator prediction = results->begin(); prediction != results->end(); prediction++, i++) 243 | { 244 | // rectangle box 245 | x = (int)prediction->box[0]; 246 | y = (int)prediction->box[1]; 247 | w = (int)prediction->box[2] - x + 1; 248 | h = (int)prediction->box[3] - y + 1; 249 | if ((x + w) > fb->width) { 250 | w = fb->width - x; 251 | } 252 | if ((y + h) > fb->height) { 253 | h = fb->height - y; 254 | } 255 | fb_gfx_drawFastHLine(fb, x, y, w, color); 256 | fb_gfx_drawFastHLine(fb, x, y + h - 1, w, color); 257 | fb_gfx_drawFastVLine(fb, x, y, h, color); 258 | fb_gfx_drawFastVLine(fb, x + w - 1, y, h, color); 259 | #if TWO_STAGE 260 | // landmarks (left eye, mouth left, nose, right eye, mouth right) 261 | int x0, y0, j; 262 | for (j = 0; j < 10; j += 2) { 263 | x0 = (int)prediction->keypoint[j]; 264 | y0 = (int)prediction->keypoint[j + 1]; 265 | fb_gfx_fillRect(fb, x0, y0, 3, 3, color); 266 | } 267 | #endif 268 | } 269 | } 270 | 271 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 272 | static int run_face_recognition(fb_data_t *fb, std::list *results) 273 | { 274 | std::vector landmarks = results->front().keypoint; 275 | int id = -1; 276 | 277 | Tensor tensor; 278 | tensor.set_element((uint8_t *)fb->data).set_shape({fb->height, fb->width, 3}).set_auto_free(false); 279 | 280 | int enrolled_count = recognizer.get_enrolled_id_num(); 281 | 282 | if (enrolled_count < FACE_ID_SAVE_NUMBER && is_enrolling) { 283 | id = recognizer.enroll_id(tensor, landmarks, "", true); 284 | ESP_LOGI(TAG, "Enrolled ID: %d", id); 285 | rgb_printf(fb, FACE_COLOR_CYAN, "ID[%u]", id); 286 | } 287 | 288 | face_info_t recognize = recognizer.recognize(tensor, landmarks); 289 | if (recognize.id >= 0) { 290 | rgb_printf(fb, FACE_COLOR_GREEN, "ID[%u]: %.2f", recognize.id, recognize.similarity); 291 | } else { 292 | rgb_print(fb, FACE_COLOR_RED, "Intruder Alert!"); 293 | } 294 | return recognize.id; 295 | } 296 | #endif 297 | #endif 298 | 299 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 300 | void enable_led(bool en) 301 | { // Turn LED On or Off 302 | int duty = en ? led_duty : 0; 303 | if (en && isStreaming && (led_duty > CONFIG_LED_MAX_INTENSITY)) 304 | { 305 | duty = CONFIG_LED_MAX_INTENSITY; 306 | } 307 | ledc_set_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL, duty); 308 | ledc_update_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL); 309 | ESP_LOGI(TAG, "Set LED intensity to %d", duty); 310 | } 311 | #endif 312 | 313 | static esp_err_t bmp_handler(httpd_req_t *req) 314 | { 315 | camera_fb_t *fb = NULL; 316 | esp_err_t res = ESP_OK; 317 | uint64_t fr_start = esp_timer_get_time(); 318 | fb = esp_camera_fb_get(); 319 | if (!fb) 320 | { 321 | ESP_LOGE(TAG, "Camera capture failed"); 322 | httpd_resp_send_500(req); 323 | return ESP_FAIL; 324 | } 325 | 326 | httpd_resp_set_type(req, "image/x-windows-bmp"); 327 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp"); 328 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 329 | 330 | char ts[32]; 331 | snprintf(ts, 32, "%ld.%06ld", fb->timestamp.tv_sec, fb->timestamp.tv_usec); 332 | httpd_resp_set_hdr(req, "X-Timestamp", (const char *)ts); 333 | 334 | 335 | uint8_t * buf = NULL; 336 | size_t buf_len = 0; 337 | bool converted = frame2bmp(fb, &buf, &buf_len); 338 | esp_camera_fb_return(fb); 339 | if (!converted) { 340 | ESP_LOGE(TAG, "BMP Conversion failed"); 341 | httpd_resp_send_500(req); 342 | return ESP_FAIL; 343 | } 344 | res = httpd_resp_send(req, (const char *)buf, buf_len); 345 | free(buf); 346 | uint64_t fr_end = esp_timer_get_time(); 347 | ESP_LOGI(TAG, "BMP: %llums, %uB", (uint64_t)((fr_end - fr_start) / 1000), buf_len); 348 | return res; 349 | } 350 | 351 | static size_t jpg_encode_stream(void *arg, size_t index, const void *data, size_t len) 352 | { 353 | jpg_chunking_t *j = (jpg_chunking_t *)arg; 354 | if (!index) 355 | { 356 | j->len = 0; 357 | } 358 | if (httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) 359 | { 360 | return 0; 361 | } 362 | j->len += len; 363 | return len; 364 | } 365 | 366 | static esp_err_t capture_handler(httpd_req_t *req) 367 | { 368 | camera_fb_t *fb = NULL; 369 | esp_err_t res = ESP_OK; 370 | int64_t fr_start = esp_timer_get_time(); 371 | 372 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 373 | enable_led(true); 374 | vTaskDelay(150 / portTICK_PERIOD_MS); // The LED needs to be turned on ~150ms before the call to esp_camera_fb_get() 375 | fb = esp_camera_fb_get(); // or it won't be visible in the frame. A better way to do this is needed. 376 | enable_led(false); 377 | #else 378 | fb = esp_camera_fb_get(); 379 | #endif 380 | 381 | if (!fb) 382 | { 383 | ESP_LOGE(TAG, "Camera capture failed"); 384 | httpd_resp_send_500(req); 385 | return ESP_FAIL; 386 | } 387 | 388 | httpd_resp_set_type(req, "image/jpeg"); 389 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); 390 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 391 | 392 | char ts[32]; 393 | snprintf(ts, 32, "%ld.%06ld", fb->timestamp.tv_sec, fb->timestamp.tv_usec); 394 | httpd_resp_set_hdr(req, "X-Timestamp", (const char *)ts); 395 | 396 | #if CONFIG_ESP_FACE_DETECT_ENABLED 397 | size_t out_len, out_width, out_height; 398 | uint8_t *out_buf; 399 | bool s; 400 | bool detected = false; 401 | int face_id = 0; 402 | if (!detection_enabled || fb->width > 400) 403 | { 404 | #endif 405 | size_t fb_len = 0; 406 | if (fb->format == PIXFORMAT_JPEG) 407 | { 408 | fb_len = fb->len; 409 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 410 | } 411 | else 412 | { 413 | jpg_chunking_t jchunk = {req, 0}; 414 | res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk) ? ESP_OK : ESP_FAIL; 415 | httpd_resp_send_chunk(req, NULL, 0); 416 | fb_len = jchunk.len; 417 | } 418 | esp_camera_fb_return(fb); 419 | int64_t fr_end = esp_timer_get_time(); 420 | ESP_LOGI(TAG, "JPG: %uB %ums", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start) / 1000)); 421 | return res; 422 | #if CONFIG_ESP_FACE_DETECT_ENABLED 423 | } 424 | 425 | jpg_chunking_t jchunk = {req, 0}; 426 | 427 | if (fb->format == PIXFORMAT_RGB565 428 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 429 | && !recognition_enabled 430 | #endif 431 | ) { 432 | #if TWO_STAGE 433 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 434 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 435 | std::list &candidates = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 436 | std::list &results = s2.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}, candidates); 437 | #else 438 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 439 | std::list &results = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 440 | #endif 441 | if (results.size() > 0) { 442 | fb_data_t rfb; 443 | rfb.width = fb->width; 444 | rfb.height = fb->height; 445 | rfb.data = fb->buf; 446 | rfb.bytes_per_pixel = 2; 447 | rfb.format = FB_RGB565; 448 | detected = true; 449 | draw_face_boxes(&rfb, &results, face_id); 450 | } 451 | s = fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, PIXFORMAT_RGB565, 90, jpg_encode_stream, &jchunk); 452 | esp_camera_fb_return(fb); 453 | } else 454 | { 455 | out_len = fb->width * fb->height * 3; 456 | out_width = fb->width; 457 | out_height = fb->height; 458 | out_buf = (uint8_t*)malloc(out_len); 459 | if (!out_buf) { 460 | ESP_LOGE(TAG, "out_buf malloc failed"); 461 | httpd_resp_send_500(req); 462 | return ESP_FAIL; 463 | } 464 | s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); 465 | esp_camera_fb_return(fb); 466 | if (!s) { 467 | free(out_buf); 468 | ESP_LOGE(TAG, "to rgb888 failed"); 469 | httpd_resp_send_500(req); 470 | return ESP_FAIL; 471 | } 472 | 473 | fb_data_t rfb; 474 | rfb.width = out_width; 475 | rfb.height = out_height; 476 | rfb.data = out_buf; 477 | rfb.bytes_per_pixel = 3; 478 | rfb.format = FB_BGR888; 479 | 480 | #if TWO_STAGE 481 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 482 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 483 | std::list &candidates = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 484 | std::list &results = s2.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}, candidates); 485 | #else 486 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 487 | std::list &results = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 488 | #endif 489 | 490 | if (results.size() > 0) { 491 | detected = true; 492 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 493 | if (recognition_enabled) { 494 | face_id = run_face_recognition(&rfb, &results); 495 | } 496 | #endif 497 | draw_face_boxes(&rfb, &results, face_id); 498 | } 499 | 500 | s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk); 501 | free(out_buf); 502 | } 503 | 504 | if (!s) { 505 | ESP_LOGE(TAG, "JPEG compression failed"); 506 | httpd_resp_send_500(req); 507 | return ESP_FAIL; 508 | } 509 | 510 | int64_t fr_end = esp_timer_get_time(); 511 | ESP_LOGI(TAG, "FACE: %uB %ums %s%d", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start) / 1000), detected ? "DETECTED " : "", face_id); 512 | return res; 513 | #endif 514 | } 515 | 516 | static esp_err_t stream_handler(httpd_req_t *req) 517 | { 518 | camera_fb_t *fb = NULL; 519 | struct timeval _timestamp; 520 | esp_err_t res = ESP_OK; 521 | size_t _jpg_buf_len = 0; 522 | uint8_t *_jpg_buf = NULL; 523 | char *part_buf[128]; 524 | #if CONFIG_ESP_FACE_DETECT_ENABLED 525 | bool detected = false; 526 | int face_id = 0; 527 | int64_t fr_start = 0; 528 | int64_t fr_ready = 0; 529 | int64_t fr_face = 0; 530 | int64_t fr_recognize = 0; 531 | int64_t fr_encode = 0; 532 | 533 | size_t out_len = 0, out_width = 0, out_height = 0; 534 | uint8_t *out_buf = NULL; 535 | bool s = false; 536 | #if TWO_STAGE 537 | HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F); 538 | HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5); 539 | #else 540 | HumanFaceDetectMSR01 s1(0.3F, 0.5F, 10, 0.2F); 541 | #endif 542 | #endif 543 | 544 | static int64_t last_frame = 0; 545 | if (!last_frame) 546 | { 547 | last_frame = esp_timer_get_time(); 548 | } 549 | 550 | res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); 551 | if (res != ESP_OK) 552 | { 553 | return res; 554 | } 555 | 556 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 557 | httpd_resp_set_hdr(req, "X-Framerate", "60"); 558 | 559 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 560 | enable_led(true); 561 | isStreaming = true; 562 | #endif 563 | 564 | while (true) 565 | { 566 | #if CONFIG_ESP_FACE_DETECT_ENABLED 567 | detected = false; 568 | face_id = 0; 569 | #endif 570 | 571 | fb = esp_camera_fb_get(); 572 | if (!fb) 573 | { 574 | ESP_LOGE(TAG, "Camera capture failed"); 575 | res = ESP_FAIL; 576 | } 577 | else 578 | { 579 | _timestamp.tv_sec = fb->timestamp.tv_sec; 580 | _timestamp.tv_usec = fb->timestamp.tv_usec; 581 | #if CONFIG_ESP_FACE_DETECT_ENABLED 582 | fr_start = esp_timer_get_time(); 583 | fr_ready = fr_start; 584 | fr_face = fr_start; 585 | fr_encode = fr_start; 586 | fr_recognize = fr_start; 587 | if (!detection_enabled || fb->width > 400) 588 | { 589 | #endif 590 | if (fb->format != PIXFORMAT_JPEG) 591 | { 592 | bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); 593 | esp_camera_fb_return(fb); 594 | fb = NULL; 595 | if (!jpeg_converted) 596 | { 597 | ESP_LOGE(TAG, "JPEG compression failed"); 598 | res = ESP_FAIL; 599 | } 600 | } 601 | else 602 | { 603 | _jpg_buf_len = fb->len; 604 | _jpg_buf = fb->buf; 605 | } 606 | #if CONFIG_ESP_FACE_DETECT_ENABLED 607 | } 608 | else 609 | { 610 | if (fb->format == PIXFORMAT_RGB565 611 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 612 | && !recognition_enabled 613 | #endif 614 | ) { 615 | fr_ready = esp_timer_get_time(); 616 | #if TWO_STAGE 617 | std::list &candidates = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 618 | std::list &results = s2.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}, candidates); 619 | #else 620 | std::list &results = s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3}); 621 | #endif 622 | fr_face = esp_timer_get_time(); 623 | fr_recognize = fr_face; 624 | if (results.size() > 0) { 625 | fb_data_t rfb; 626 | rfb.width = fb->width; 627 | rfb.height = fb->height; 628 | rfb.data = fb->buf; 629 | rfb.bytes_per_pixel = 2; 630 | rfb.format = FB_RGB565; 631 | detected = true; 632 | draw_face_boxes(&rfb, &results, face_id); 633 | } 634 | s = fmt2jpg(fb->buf, fb->len, fb->width, fb->height, PIXFORMAT_RGB565, 80, &_jpg_buf, &_jpg_buf_len); 635 | esp_camera_fb_return(fb); 636 | fb = NULL; 637 | if (!s) { 638 | ESP_LOGE(TAG, "fmt2jpg failed"); 639 | res = ESP_FAIL; 640 | } 641 | fr_encode = esp_timer_get_time(); 642 | } else 643 | { 644 | out_len = fb->width * fb->height * 3; 645 | out_width = fb->width; 646 | out_height = fb->height; 647 | out_buf = (uint8_t*)malloc(out_len); 648 | if (!out_buf) { 649 | ESP_LOGE(TAG, "out_buf malloc failed"); 650 | res = ESP_FAIL; 651 | } else { 652 | s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); 653 | esp_camera_fb_return(fb); 654 | fb = NULL; 655 | if (!s) { 656 | free(out_buf); 657 | ESP_LOGE(TAG, "to rgb888 failed"); 658 | res = ESP_FAIL; 659 | } else { 660 | fr_ready = esp_timer_get_time(); 661 | 662 | fb_data_t rfb; 663 | rfb.width = out_width; 664 | rfb.height = out_height; 665 | rfb.data = out_buf; 666 | rfb.bytes_per_pixel = 3; 667 | rfb.format = FB_BGR888; 668 | 669 | #if TWO_STAGE 670 | std::list &candidates = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 671 | std::list &results = s2.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}, candidates); 672 | #else 673 | std::list &results = s1.infer((uint8_t *)out_buf, {(int)out_height, (int)out_width, 3}); 674 | #endif 675 | 676 | fr_face = esp_timer_get_time(); 677 | fr_recognize = fr_face; 678 | 679 | if (results.size() > 0) { 680 | detected = true; 681 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 682 | if (recognition_enabled) { 683 | face_id = run_face_recognition(&rfb, &results); 684 | fr_recognize = esp_timer_get_time(); 685 | } 686 | #endif 687 | draw_face_boxes(&rfb, &results, face_id); 688 | } 689 | s = fmt2jpg(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len); 690 | free(out_buf); 691 | if (!s) { 692 | ESP_LOGE(TAG, "fmt2jpg failed"); 693 | res = ESP_FAIL; 694 | } 695 | fr_encode = esp_timer_get_time(); 696 | } 697 | } 698 | } 699 | } 700 | #endif 701 | } 702 | if (res == ESP_OK) 703 | { 704 | res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); 705 | } 706 | if (res == ESP_OK) 707 | { 708 | size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec); 709 | res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); 710 | } 711 | if (res == ESP_OK) 712 | { 713 | res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); 714 | } 715 | if (fb) 716 | { 717 | esp_camera_fb_return(fb); 718 | fb = NULL; 719 | _jpg_buf = NULL; 720 | } 721 | else if (_jpg_buf) 722 | { 723 | free(_jpg_buf); 724 | _jpg_buf = NULL; 725 | } 726 | if (res != ESP_OK) 727 | { 728 | ESP_LOGE(TAG, "send frame failed failed"); 729 | break; 730 | } 731 | int64_t fr_end = esp_timer_get_time(); 732 | 733 | #if CONFIG_ESP_FACE_DETECT_ENABLED 734 | int64_t ready_time = (fr_ready - fr_start) / 1000; 735 | int64_t face_time = (fr_face - fr_ready) / 1000; 736 | int64_t recognize_time = (fr_recognize - fr_face) / 1000; 737 | int64_t encode_time = (fr_encode - fr_recognize) / 1000; 738 | int64_t process_time = (fr_encode - fr_start) / 1000; 739 | #endif 740 | 741 | int64_t frame_time = fr_end - last_frame; 742 | last_frame = fr_end; 743 | frame_time /= 1000; 744 | uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); 745 | ESP_LOGI(TAG, "MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)" 746 | #if CONFIG_ESP_FACE_DETECT_ENABLED 747 | ", %u+%u+%u+%u=%u %s%d" 748 | #endif 749 | , 750 | (uint32_t)(_jpg_buf_len), 751 | (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, 752 | avg_frame_time, 1000.0 / avg_frame_time 753 | #if CONFIG_ESP_FACE_DETECT_ENABLED 754 | , 755 | (uint32_t)ready_time, (uint32_t)face_time, (uint32_t)recognize_time, (uint32_t)encode_time, (uint32_t)process_time, 756 | (detected) ? "DETECTED " : "", face_id 757 | #endif 758 | ); 759 | } 760 | 761 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 762 | isStreaming = false; 763 | enable_led(false); 764 | #endif 765 | 766 | last_frame = 0; 767 | return res; 768 | } 769 | 770 | static esp_err_t parse_get(httpd_req_t *req, char **obuf) 771 | { 772 | char *buf = NULL; 773 | size_t buf_len = 0; 774 | 775 | buf_len = httpd_req_get_url_query_len(req) + 1; 776 | if (buf_len > 1) { 777 | buf = (char *)malloc(buf_len); 778 | if (!buf) { 779 | httpd_resp_send_500(req); 780 | return ESP_FAIL; 781 | } 782 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { 783 | *obuf = buf; 784 | return ESP_OK; 785 | } 786 | free(buf); 787 | } 788 | httpd_resp_send_404(req); 789 | return ESP_FAIL; 790 | } 791 | 792 | static esp_err_t cmd_handler(httpd_req_t *req) 793 | { 794 | char *buf = NULL; 795 | char variable[32]; 796 | char value[32]; 797 | 798 | if (parse_get(req, &buf) != ESP_OK) { 799 | return ESP_FAIL; 800 | } 801 | if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) != ESP_OK || 802 | httpd_query_key_value(buf, "val", value, sizeof(value)) != ESP_OK) { 803 | free(buf); 804 | httpd_resp_send_404(req); 805 | return ESP_FAIL; 806 | } 807 | free(buf); 808 | 809 | int val = atoi(value); 810 | ESP_LOGI(TAG, "%s = %d", variable, val); 811 | Serial.printf("%s = %d\n",variable, val); 812 | sensor_t *s = esp_camera_sensor_get(); 813 | int res = 0; 814 | 815 | if (!strcmp(variable, "framesize")) { 816 | if (s->pixformat == PIXFORMAT_JPEG) { 817 | res = s->set_framesize(s, (framesize_t)val); 818 | } 819 | } 820 | else if (!strcmp(variable, "seglen")) // jz recording parameters 821 | avi_length = val; 822 | else if (!strcmp(variable, "interval")) //jz 823 | frame_interval = val; 824 | else if (!strcmp(variable, "speedup")) //jz 825 | speed_up_factor = val; 826 | else if (!strcmp(variable, "quality")) 827 | res = s->set_quality(s, val); 828 | else if (!strcmp(variable, "contrast")) 829 | res = s->set_contrast(s, val); 830 | else if (!strcmp(variable, "brightness")) 831 | res = s->set_brightness(s, val); 832 | else if (!strcmp(variable, "saturation")) 833 | res = s->set_saturation(s, val); 834 | else if (!strcmp(variable, "gainceiling")) 835 | res = s->set_gainceiling(s, (gainceiling_t)val); 836 | else if (!strcmp(variable, "colorbar")) 837 | res = s->set_colorbar(s, val); 838 | else if (!strcmp(variable, "awb")) 839 | res = s->set_whitebal(s, val); 840 | else if (!strcmp(variable, "agc")) 841 | res = s->set_gain_ctrl(s, val); 842 | else if (!strcmp(variable, "aec")) 843 | res = s->set_exposure_ctrl(s, val); 844 | else if (!strcmp(variable, "hmirror")) 845 | res = s->set_hmirror(s, val); 846 | else if (!strcmp(variable, "vflip")) 847 | res = s->set_vflip(s, val); 848 | else if (!strcmp(variable, "awb_gain")) 849 | res = s->set_awb_gain(s, val); 850 | else if (!strcmp(variable, "agc_gain")) 851 | res = s->set_agc_gain(s, val); 852 | else if (!strcmp(variable, "aec_value")) 853 | res = s->set_aec_value(s, val); 854 | else if (!strcmp(variable, "aec2")) 855 | res = s->set_aec2(s, val); 856 | else if (!strcmp(variable, "dcw")) 857 | res = s->set_dcw(s, val); 858 | else if (!strcmp(variable, "bpc")) 859 | res = s->set_bpc(s, val); 860 | else if (!strcmp(variable, "wpc")) 861 | res = s->set_wpc(s, val); 862 | else if (!strcmp(variable, "raw_gma")) 863 | res = s->set_raw_gma(s, val); 864 | else if (!strcmp(variable, "lenc")) 865 | res = s->set_lenc(s, val); 866 | else if (!strcmp(variable, "special_effect")) 867 | res = s->set_special_effect(s, val); 868 | else if (!strcmp(variable, "wb_mode")) 869 | res = s->set_wb_mode(s, val); 870 | else if (!strcmp(variable, "ae_level")) 871 | res = s->set_ae_level(s, val); 872 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 873 | else if (!strcmp(variable, "led_intensity")) { 874 | led_duty = val; 875 | if (isStreaming) 876 | enable_led(true); 877 | } 878 | #endif 879 | 880 | #if CONFIG_ESP_FACE_DETECT_ENABLED 881 | else if (!strcmp(variable, "face_detect")) { 882 | detection_enabled = val; 883 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 884 | if (!detection_enabled) { 885 | recognition_enabled = 0; 886 | } 887 | #endif 888 | } 889 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 890 | else if (!strcmp(variable, "face_enroll")) { 891 | is_enrolling = !is_enrolling; 892 | ESP_LOGI(TAG, "Enrolling: %s", is_enrolling ? "true" : "false"); 893 | } 894 | else if (!strcmp(variable, "face_recognize")) { 895 | recognition_enabled = val; 896 | if (recognition_enabled) { 897 | detection_enabled = val; 898 | } 899 | } 900 | #endif 901 | #endif 902 | else { 903 | ESP_LOGI(TAG, "Unknown command: %s", variable); 904 | res = -1; 905 | } 906 | 907 | if (res < 0) { 908 | return httpd_resp_send_500(req); 909 | } 910 | 911 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 912 | return httpd_resp_send(req, NULL, 0); 913 | } 914 | 915 | static int print_reg(char * p, sensor_t * s, uint16_t reg, uint32_t mask) { 916 | return sprintf(p, "\"0x%x\":%u,", reg, s->get_reg(s, reg, mask)); 917 | } 918 | 919 | static esp_err_t status_handler(httpd_req_t *req) 920 | { 921 | static char json_response[1024]; 922 | 923 | sensor_t *s = esp_camera_sensor_get(); 924 | char *p = json_response; 925 | *p++ = '{'; 926 | 927 | if (s->id.PID == OV5640_PID || s->id.PID == OV3660_PID) { 928 | for (int reg = 0x3400; reg < 0x3406; reg += 2) { 929 | p += print_reg(p, s, reg, 0xFFF); //12 bit 930 | } 931 | p += print_reg(p, s, 0x3406, 0xFF); 932 | 933 | p += print_reg(p, s, 0x3500, 0xFFFF0); //16 bit 934 | p += print_reg(p, s, 0x3503, 0xFF); 935 | p += print_reg(p, s, 0x350a, 0x3FF); //10 bit 936 | p += print_reg(p, s, 0x350c, 0xFFFF); //16 bit 937 | 938 | for (int reg = 0x5480; reg <= 0x5490; reg++) { 939 | p += print_reg(p, s, reg, 0xFF); 940 | } 941 | 942 | for (int reg = 0x5380; reg <= 0x538b; reg++) { 943 | p += print_reg(p, s, reg, 0xFF); 944 | } 945 | 946 | for (int reg = 0x5580; reg < 0x558a; reg++) { 947 | p += print_reg(p, s, reg, 0xFF); 948 | } 949 | p += print_reg(p, s, 0x558a, 0x1FF); //9 bit 950 | } else if (s->id.PID == OV2640_PID) { 951 | p += print_reg(p, s, 0xd3, 0xFF); 952 | p += print_reg(p, s, 0x111, 0xFF); 953 | p += print_reg(p, s, 0x132, 0xFF); 954 | } 955 | 956 | p += sprintf(p, "\"xclk\":%u,", s->xclk_freq_hz / 1000000); 957 | p += sprintf(p, "\"pixformat\":%u,", s->pixformat); 958 | p += sprintf(p, "\"framesize\":%u,", s->status.framesize); 959 | p += sprintf(p, "\"quality\":%u,", s->status.quality); 960 | p += sprintf(p, "\"brightness\":%d,", s->status.brightness); 961 | p += sprintf(p, "\"contrast\":%d,", s->status.contrast); 962 | p += sprintf(p, "\"saturation\":%d,", s->status.saturation); 963 | p += sprintf(p, "\"sharpness\":%d,", s->status.sharpness); 964 | p += sprintf(p, "\"special_effect\":%u,", s->status.special_effect); 965 | p += sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); 966 | p += sprintf(p, "\"awb\":%u,", s->status.awb); 967 | p += sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); 968 | p += sprintf(p, "\"aec\":%u,", s->status.aec); 969 | p += sprintf(p, "\"aec2\":%u,", s->status.aec2); 970 | p += sprintf(p, "\"ae_level\":%d,", s->status.ae_level); 971 | p += sprintf(p, "\"aec_value\":%u,", s->status.aec_value); 972 | p += sprintf(p, "\"agc\":%u,", s->status.agc); 973 | p += sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); 974 | p += sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); 975 | p += sprintf(p, "\"bpc\":%u,", s->status.bpc); 976 | p += sprintf(p, "\"wpc\":%u,", s->status.wpc); 977 | p += sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); 978 | p += sprintf(p, "\"lenc\":%u,", s->status.lenc); 979 | p += sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 980 | p += sprintf(p, "\"dcw\":%u,", s->status.dcw); 981 | p += sprintf(p, "\"colorbar\":%u,", s->status.colorbar); 982 | p += sprintf(p, "\"seglen\":%d,", avi_length); //jz recording parameters 983 | p += sprintf(p, "\"interval\":%d,", frame_interval); //jz 984 | p += sprintf(p, "\"speedup\":%d,", speed_up_factor); //jz 985 | p += sprintf(p, "\"freespace\":%d", freespace); //jz 986 | #ifdef CONFIG_LED_ILLUMINATOR_ENABLED 987 | p += sprintf(p, ",\"led_intensity\":%u", led_duty); 988 | #else 989 | p += sprintf(p, ",\"led_intensity\":%d", -1); 990 | #endif 991 | #if CONFIG_ESP_FACE_DETECT_ENABLED 992 | p += sprintf(p, ",\"face_detect\":%u", detection_enabled); 993 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 994 | p += sprintf(p, ",\"face_enroll\":%u,", is_enrolling); 995 | p += sprintf(p, "\"face_recognize\":%u", recognition_enabled); 996 | #endif 997 | #endif 998 | 999 | 1000 | 1001 | *p++ = '}'; 1002 | *p++ = 0; 1003 | //Serial.println(json_response); 1004 | 1005 | httpd_resp_set_type(req, "application/json"); 1006 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1007 | return httpd_resp_send(req, json_response, strlen(json_response)); 1008 | } 1009 | 1010 | static esp_err_t xclk_handler(httpd_req_t *req) 1011 | { 1012 | char *buf = NULL; 1013 | char _xclk[32]; 1014 | 1015 | if (parse_get(req, &buf) != ESP_OK) { 1016 | return ESP_FAIL; 1017 | } 1018 | if (httpd_query_key_value(buf, "xclk", _xclk, sizeof(_xclk)) != ESP_OK) { 1019 | free(buf); 1020 | httpd_resp_send_404(req); 1021 | return ESP_FAIL; 1022 | } 1023 | free(buf); 1024 | 1025 | int xclk = atoi(_xclk); 1026 | ESP_LOGI(TAG, "Set XCLK: %d MHz", xclk); 1027 | 1028 | sensor_t *s = esp_camera_sensor_get(); 1029 | int res = s->set_xclk(s, LEDC_TIMER_0, xclk); 1030 | if (res) { 1031 | return httpd_resp_send_500(req); 1032 | } 1033 | 1034 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1035 | return httpd_resp_send(req, NULL, 0); 1036 | } 1037 | 1038 | static esp_err_t reg_handler(httpd_req_t *req) 1039 | { 1040 | char *buf = NULL; 1041 | char _reg[32]; 1042 | char _mask[32]; 1043 | char _val[32]; 1044 | 1045 | if (parse_get(req, &buf) != ESP_OK) { 1046 | return ESP_FAIL; 1047 | } 1048 | if (httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || 1049 | httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK || 1050 | httpd_query_key_value(buf, "val", _val, sizeof(_val)) != ESP_OK) { 1051 | free(buf); 1052 | httpd_resp_send_404(req); 1053 | return ESP_FAIL; 1054 | } 1055 | free(buf); 1056 | 1057 | int reg = atoi(_reg); 1058 | int mask = atoi(_mask); 1059 | int val = atoi(_val); 1060 | ESP_LOGI(TAG, "Set Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, val); 1061 | 1062 | sensor_t *s = esp_camera_sensor_get(); 1063 | int res = s->set_reg(s, reg, mask, val); 1064 | if (res) { 1065 | return httpd_resp_send_500(req); 1066 | } 1067 | 1068 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1069 | return httpd_resp_send(req, NULL, 0); 1070 | } 1071 | 1072 | static esp_err_t greg_handler(httpd_req_t *req) 1073 | { 1074 | char *buf = NULL; 1075 | char _reg[32]; 1076 | char _mask[32]; 1077 | 1078 | if (parse_get(req, &buf) != ESP_OK) { 1079 | return ESP_FAIL; 1080 | } 1081 | if (httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || 1082 | httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK) { 1083 | free(buf); 1084 | httpd_resp_send_404(req); 1085 | return ESP_FAIL; 1086 | } 1087 | free(buf); 1088 | 1089 | int reg = atoi(_reg); 1090 | int mask = atoi(_mask); 1091 | sensor_t *s = esp_camera_sensor_get(); 1092 | int res = s->get_reg(s, reg, mask); 1093 | if (res < 0) { 1094 | return httpd_resp_send_500(req); 1095 | } 1096 | ESP_LOGI(TAG, "Get Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, res); 1097 | 1098 | char buffer[20]; 1099 | const char * val = itoa(res, buffer, 10); 1100 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1101 | return httpd_resp_send(req, val, strlen(val)); 1102 | } 1103 | 1104 | static int parse_get_var(char *buf, const char * key, int def) 1105 | { 1106 | char _int[16]; 1107 | if (httpd_query_key_value(buf, key, _int, sizeof(_int)) != ESP_OK) { 1108 | return def; 1109 | } 1110 | return atoi(_int); 1111 | } 1112 | 1113 | static esp_err_t pll_handler(httpd_req_t *req) 1114 | { 1115 | char *buf = NULL; 1116 | 1117 | if (parse_get(req, &buf) != ESP_OK) { 1118 | return ESP_FAIL; 1119 | } 1120 | 1121 | int bypass = parse_get_var(buf, "bypass", 0); 1122 | int mul = parse_get_var(buf, "mul", 0); 1123 | int sys = parse_get_var(buf, "sys", 0); 1124 | int root = parse_get_var(buf, "root", 0); 1125 | int pre = parse_get_var(buf, "pre", 0); 1126 | int seld5 = parse_get_var(buf, "seld5", 0); 1127 | int pclken = parse_get_var(buf, "pclken", 0); 1128 | int pclk = parse_get_var(buf, "pclk", 0); 1129 | free(buf); 1130 | 1131 | ESP_LOGI(TAG, "Set Pll: bypass: %d, mul: %d, sys: %d, root: %d, pre: %d, seld5: %d, pclken: %d, pclk: %d", bypass, mul, sys, root, pre, seld5, pclken, pclk); 1132 | sensor_t *s = esp_camera_sensor_get(); 1133 | int res = s->set_pll(s, bypass, mul, sys, root, pre, seld5, pclken, pclk); 1134 | if (res) { 1135 | return httpd_resp_send_500(req); 1136 | } 1137 | 1138 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1139 | return httpd_resp_send(req, NULL, 0); 1140 | } 1141 | 1142 | static esp_err_t win_handler(httpd_req_t *req) 1143 | { 1144 | char *buf = NULL; 1145 | 1146 | if (parse_get(req, &buf) != ESP_OK) { 1147 | return ESP_FAIL; 1148 | } 1149 | 1150 | int startX = parse_get_var(buf, "sx", 0); 1151 | int startY = parse_get_var(buf, "sy", 0); 1152 | int endX = parse_get_var(buf, "ex", 0); 1153 | int endY = parse_get_var(buf, "ey", 0); 1154 | int offsetX = parse_get_var(buf, "offx", 0); 1155 | int offsetY = parse_get_var(buf, "offy", 0); 1156 | int totalX = parse_get_var(buf, "tx", 0); 1157 | int totalY = parse_get_var(buf, "ty", 0); 1158 | int outputX = parse_get_var(buf, "ox", 0); 1159 | int outputY = parse_get_var(buf, "oy", 0); 1160 | bool scale = parse_get_var(buf, "scale", 0) == 1; 1161 | bool binning = parse_get_var(buf, "binning", 0) == 1; 1162 | free(buf); 1163 | 1164 | ESP_LOGI(TAG, "Set Window: Start: %d %d, End: %d %d, Offset: %d %d, Total: %d %d, Output: %d %d, Scale: %u, Binning: %u", startX, startY, endX, endY, offsetX, offsetY, totalX, totalY, outputX, outputY, scale, binning); 1165 | sensor_t *s = esp_camera_sensor_get(); 1166 | int res = s->set_res_raw(s, startX, startY, endX, endY, offsetX, offsetY, totalX, totalY, outputX, outputY, scale, binning); 1167 | if (res) { 1168 | return httpd_resp_send_500(req); 1169 | } 1170 | 1171 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 1172 | return httpd_resp_send(req, NULL, 0); 1173 | } 1174 | 1175 | static esp_err_t index_handler(httpd_req_t *req) 1176 | { 1177 | //httpd_resp_set_type(req, "text/html"); 1178 | //httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 1179 | sensor_t *s = esp_camera_sensor_get(); 1180 | if (s != NULL) { 1181 | if (s->id.PID == OV3660_PID) { 1182 | httpd_resp_set_type(req, "text/html"); 1183 | httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 1184 | return httpd_resp_send(req, (const char *)index_ov3660_html_gz, index_ov3660_html_gz_len); 1185 | } else if (s->id.PID == OV5640_PID) { 1186 | httpd_resp_set_type(req, "text/html"); 1187 | httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 1188 | return httpd_resp_send(req, (const char *)index_ov5640_html_gz, index_ov5640_html_gz_len); 1189 | } else { 1190 | httpd_resp_set_type(req, "text/html"); 1191 | //httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 1192 | //index_ov2640 1193 | //return httpd_resp_send(req, (const char *)index_ov5640_html_gz, index_ov5640_html_gz_len); 1194 | int len = sizeof(index_ov2640_html); //45110 1195 | Serial.print("index_ov2640_html len "); Serial.println(len); 1196 | return httpd_resp_send(req, (const char *)index_ov2640_html, len); 1197 | } 1198 | } else { 1199 | ESP_LOGE(TAG, "Camera sensor not found"); 1200 | return httpd_resp_send_500(req); 1201 | } 1202 | } 1203 | 1204 | static esp_err_t startrecord_handler(httpd_req_t *req){ //jz 1205 | 1206 | time_t now; 1207 | struct tm timeinfo; 1208 | 1209 | char *buf = NULL; 1210 | 1211 | if (parse_get(req, &buf) == ESP_OK) { 1212 | uint32_t real = parse_get_var(buf, "now", 0) ; 1213 | //Serial.print("\nreal: "); Serial.println(real); 1214 | struct timeval tv; 1215 | tv.tv_sec = real; 1216 | tv.tv_usec = 0; 1217 | settimeofday(&tv, NULL); 1218 | } else { 1219 | Serial.println("No time in video start - time will be 1970"); 1220 | } 1221 | 1222 | //time(&now); 1223 | //Serial.print("\nLocal time: "); Serial.println(ctime(&now)); 1224 | free(buf); 1225 | 1226 | sensor_t *s = esp_camera_sensor_get(); 1227 | if (s != NULL) { 1228 | framesize = s->status.framesize; 1229 | start_record = 1; 1230 | return httpd_resp_send(req, NULL, 0); 1231 | } else { 1232 | ESP_LOGE(TAG, "Camera sensor not found"); 1233 | return httpd_resp_send_500(req); 1234 | } 1235 | } 1236 | 1237 | static esp_err_t stoprecord_handler(httpd_req_t *req){ //jz 1238 | 1239 | start_record = 0; 1240 | return httpd_resp_send(req, NULL, 0); 1241 | } 1242 | 1243 | void startCameraServer() 1244 | { 1245 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 1246 | config.max_uri_handlers = 16; 1247 | 1248 | httpd_uri_t index_uri = { 1249 | .uri = "/", 1250 | .method = HTTP_GET, 1251 | .handler = index_handler, 1252 | .user_ctx = NULL 1253 | }; 1254 | 1255 | httpd_uri_t status_uri = { 1256 | .uri = "/status", 1257 | .method = HTTP_GET, 1258 | .handler = status_handler, 1259 | .user_ctx = NULL 1260 | }; 1261 | 1262 | httpd_uri_t cmd_uri = { 1263 | .uri = "/control", 1264 | .method = HTTP_GET, 1265 | .handler = cmd_handler, 1266 | .user_ctx = NULL 1267 | }; 1268 | 1269 | httpd_uri_t capture_uri = { 1270 | .uri = "/capture", 1271 | .method = HTTP_GET, 1272 | .handler = capture_handler, 1273 | .user_ctx = NULL 1274 | }; 1275 | 1276 | httpd_uri_t startrecord_uri = { //jz 1277 | .uri = "/startrecord", 1278 | .method = HTTP_GET, 1279 | .handler = startrecord_handler, 1280 | .user_ctx = NULL 1281 | }; 1282 | 1283 | httpd_uri_t stoprecord_uri = { //jz 1284 | .uri = "/stoprecord", 1285 | .method = HTTP_GET, 1286 | .handler = stoprecord_handler, 1287 | .user_ctx = NULL 1288 | }; 1289 | 1290 | httpd_uri_t stream_uri = { 1291 | .uri = "/stream", 1292 | .method = HTTP_GET, 1293 | .handler = stream_handler, 1294 | .user_ctx = NULL 1295 | }; 1296 | 1297 | httpd_uri_t bmp_uri = { 1298 | .uri = "/bmp", 1299 | .method = HTTP_GET, 1300 | .handler = bmp_handler, 1301 | .user_ctx = NULL 1302 | }; 1303 | 1304 | httpd_uri_t xclk_uri = { 1305 | .uri = "/xclk", 1306 | .method = HTTP_GET, 1307 | .handler = xclk_handler, 1308 | .user_ctx = NULL 1309 | }; 1310 | 1311 | httpd_uri_t reg_uri = { 1312 | .uri = "/reg", 1313 | .method = HTTP_GET, 1314 | .handler = reg_handler, 1315 | .user_ctx = NULL 1316 | }; 1317 | 1318 | httpd_uri_t greg_uri = { 1319 | .uri = "/greg", 1320 | .method = HTTP_GET, 1321 | .handler = greg_handler, 1322 | .user_ctx = NULL 1323 | }; 1324 | 1325 | httpd_uri_t pll_uri = { 1326 | .uri = "/pll", 1327 | .method = HTTP_GET, 1328 | .handler = pll_handler, 1329 | .user_ctx = NULL 1330 | }; 1331 | 1332 | httpd_uri_t win_uri = { 1333 | .uri = "/resolution", 1334 | .method = HTTP_GET, 1335 | .handler = win_handler, 1336 | .user_ctx = NULL 1337 | }; 1338 | 1339 | ra_filter_init(&ra_filter, 20); 1340 | 1341 | #if CONFIG_ESP_FACE_RECOGNITION_ENABLED 1342 | recognizer.set_partition(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "fr"); 1343 | 1344 | // load ids from flash partition 1345 | recognizer.set_ids_from_flash(); 1346 | #endif 1347 | ESP_LOGI(TAG, "Starting web server on port: '%d'", config.server_port); 1348 | if (httpd_start(&camera_httpd, &config) == ESP_OK) 1349 | { 1350 | httpd_register_uri_handler(camera_httpd, &index_uri); 1351 | httpd_register_uri_handler(camera_httpd, &cmd_uri); 1352 | httpd_register_uri_handler(camera_httpd, &status_uri); 1353 | httpd_register_uri_handler(camera_httpd, &capture_uri); 1354 | httpd_register_uri_handler(camera_httpd, &bmp_uri); 1355 | 1356 | httpd_register_uri_handler(camera_httpd, &startrecord_uri); //jz 1357 | httpd_register_uri_handler(camera_httpd, &stoprecord_uri); 1358 | 1359 | httpd_register_uri_handler(camera_httpd, &xclk_uri); 1360 | httpd_register_uri_handler(camera_httpd, ®_uri); 1361 | httpd_register_uri_handler(camera_httpd, &greg_uri); 1362 | httpd_register_uri_handler(camera_httpd, &pll_uri); 1363 | httpd_register_uri_handler(camera_httpd, &win_uri); 1364 | } 1365 | 1366 | config.server_port += 1; 1367 | config.ctrl_port += 1; 1368 | ESP_LOGI(TAG, "Starting stream server on port: '%d'", config.server_port); 1369 | if (httpd_start(&stream_httpd, &config) == ESP_OK) 1370 | { 1371 | httpd_register_uri_handler(stream_httpd, &stream_uri); 1372 | } 1373 | } 1374 | -------------------------------------------------------------------------------- /avi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | heavily modified and simplfied subset of: 3 | 4 | https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior 5 | 6 | which is a heavily modifed and simplfied rework of: 7 | 8 | https://github.com/jameszah/ESP32-CAM-Video-Recorder 9 | 10 | James Zahary 11 | Aug 14, 2022 12 | 13 | jameszah/CameraWebServerRecorder is licensed under the 14 | GNU General Public License v3.0 15 | */ 16 | 17 | #include "esp_camera.h" 18 | #include "sensor.h" 19 | 20 | #include 21 | 22 | #define BUFFSIZE 512 23 | uint8_t buf[BUFFSIZE]; 24 | #define AVIOFFSET 240 // AVI main header length 25 | 26 | int framesize = FRAMESIZE_HD; 27 | int quality = 12; 28 | int avi_length = 900; 29 | int frame_interval = 0; 30 | int speed_up_factor = 1; 31 | camera_fb_t * fb_curr = NULL; 32 | long current_frame_time; 33 | long last_frame_time; 34 | int start_record = 0; 35 | int file_number = 0; 36 | int freespace = 0; 37 | 38 | #define fbs 2 // how many kb of static ram for psram -> sram buffer for sd write 39 | uint8_t framebuffer_static[fbs * 1024 + 20]; 40 | File avifile; 41 | File idxfile; 42 | 43 | char avi_file_name[100]; 44 | 45 | static int i = 0; 46 | uint16_t frame_cnt = 0; 47 | uint16_t remnant = 0; 48 | uint32_t length = 0; 49 | uint32_t startms; 50 | uint32_t elapsedms; 51 | uint32_t uVideoLen = 0; 52 | 53 | int bad_jpg = 0; 54 | int extend_jpg = 0; 55 | int normal_jpg = 0; 56 | 57 | #define blinking 0 58 | 59 | TaskHandle_t the_camera_loop_task; 60 | 61 | long avi_start_time = 0; 62 | long avi_end_time = 0; 63 | unsigned long movi_size = 0; 64 | unsigned long jpeg_size = 0; 65 | unsigned long idx_offset = 0; 66 | 67 | uint8_t zero_buf[4] = {0x00, 0x00, 0x00, 0x00}; 68 | uint8_t dc_buf[4] = {0x30, 0x30, 0x64, 0x63}; // "00dc" 69 | uint8_t dc_and_zero_buf[8] = {0x30, 0x30, 0x64, 0x63, 0x00, 0x00, 0x00, 0x00}; 70 | 71 | uint8_t avi1_buf[4] = {0x41, 0x56, 0x49, 0x31}; // "AVI1" 72 | uint8_t idx1_buf[4] = {0x69, 0x64, 0x78, 0x31}; // "idx1" 73 | 74 | 75 | struct frameSizeStruct { 76 | uint8_t frameWidth[2]; 77 | uint8_t frameHeight[2]; 78 | }; 79 | 80 | // data structure from here https://github.com/s60sc/ESP32-CAM_MJPEG2SD/blob/master/avi.cpp, extended for ov5640 81 | 82 | static const frameSizeStruct frameSizeData[] = { 83 | {{0x60, 0x00}, {0x60, 0x00}}, // FRAMESIZE_96X96, // 96x96 84 | {{0xA0, 0x00}, {0x78, 0x00}}, // FRAMESIZE_QQVGA, // 160x120 85 | {{0xB0, 0x00}, {0x90, 0x00}}, // FRAMESIZE_QCIF, // 176x144 86 | {{0xF0, 0x00}, {0xB0, 0x00}}, // FRAMESIZE_HQVGA, // 240x176 87 | {{0xF0, 0x00}, {0xF0, 0x00}}, // FRAMESIZE_240X240, // 240x240 4 88 | {{0x40, 0x01}, {0xF0, 0x00}}, // FRAMESIZE_QVGA, // 320x240 5 89 | {{0x90, 0x01}, {0x28, 0x01}}, // FRAMESIZE_CIF, // 400x296 6 90 | {{0xE0, 0x01}, {0x40, 0x01}}, // FRAMESIZE_HVGA, // 480x320 7 91 | {{0x80, 0x02}, {0xE0, 0x01}}, // FRAMESIZE_VGA, // 640x480 8 92 | // 38,400 61,440 153,600 93 | {{0x20, 0x03}, {0x58, 0x02}}, // FRAMESIZE_SVGA, // 800x600 9 94 | {{0x00, 0x04}, {0x00, 0x03}}, // FRAMESIZE_XGA, // 1024x768 10 95 | {{0x00, 0x05}, {0xD0, 0x02}}, // FRAMESIZE_HD, // 1280x720 11 96 | {{0x00, 0x05}, {0x00, 0x04}}, // FRAMESIZE_SXGA, // 1280x1024 12 97 | {{0x40, 0x06}, {0xB0, 0x04}}, // FRAMESIZE_UXGA, // 1600x1200 13 98 | // 3MP Sensors 99 | {{0x80, 0x07}, {0x38, 0x04}}, // FRAMESIZE_FHD, // 1920x1080 14 100 | {{0xD0, 0x02}, {0x00, 0x05}}, // FRAMESIZE_P_HD, // 720x1280 15 101 | {{0x60, 0x03}, {0x00, 0x06}}, // FRAMESIZE_P_3MP, // 864x1536 16 102 | {{0x00, 0x08}, {0x00, 0x06}}, // FRAMESIZE_QXGA, // 2048x1536 17 103 | // 5MP Sensors 104 | {{0x00, 0x0A}, {0xA0, 0x05}}, // FRAMESIZE_QHD, // 2560x1440 18 105 | {{0x00, 0x0A}, {0x40, 0x06}}, // FRAMESIZE_WQXGA, // 2560x1600 19 106 | {{0x38, 0x04}, {0x80, 0x07}}, // FRAMESIZE_P_FHD, // 1080x1920 20 107 | {{0x00, 0x0A}, {0x80, 0x07}} // FRAMESIZE_QSXGA, // 2560x1920 21 108 | 109 | }; 110 | 111 | const int avi_header[AVIOFFSET] PROGMEM = { 112 | 0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54, 113 | 0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00, 114 | 0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 115 | 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00, 118 | 0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73, 119 | 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66, 122 | 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 123 | 0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x46, 0x4F, 125 | 0x10, 0x00, 0x00, 0x00, 0x6A, 0x61, 0x6D, 0x65, 0x73, 0x7A, 0x61, 0x68, 0x61, 0x72, 0x79, 0x20, 126 | 0x76, 0x35, 0x35, 0x20, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69, 127 | }; 128 | 129 | 130 | // 131 | // Writes an uint32_t in Big Endian at current file position 132 | // 133 | static void inline print_quartet(unsigned long i, File fd) { 134 | 135 | uint8_t y[4]; 136 | y[0] = i % 0x100; 137 | y[1] = (i >> 8) % 0x100; 138 | y[2] = (i >> 16) % 0x100; 139 | y[3] = (i >> 24) % 0x100; 140 | size_t i1_err = fd.write(y , 4); 141 | } 142 | 143 | // 144 | // Writes 2 uint32_t in Big Endian at current file position 145 | // 146 | static void inline print_2quartet(unsigned long i, unsigned long j, File fd) { 147 | 148 | uint8_t y[8]; 149 | y[0] = i % 0x100; 150 | y[1] = (i >> 8) % 0x100; 151 | y[2] = (i >> 16) % 0x100; 152 | y[3] = (i >> 24) % 0x100; 153 | y[4] = j % 0x100; 154 | y[5] = (j >> 8) % 0x100; 155 | y[6] = (j >> 16) % 0x100; 156 | y[7] = (j >> 24) % 0x100; 157 | size_t i1_err = fd.write(y , 8); 158 | } 159 | 160 | esp_err_t init_sdcard() { 161 | 162 | 163 | int succ = SD_MMC.begin("/sdcard", true); 164 | if (succ) { 165 | 166 | pinMode(4, OUTPUT); // Blinding Disk-Avtive Light 167 | digitalWrite(4, LOW); // turn off 168 | 169 | uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); 170 | Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize); 171 | 172 | } else { 173 | Serial.printf("Failed to mount SD card VFAT filesystem. \n"); 174 | Serial.println("Do you have an SD Card installed?"); 175 | Serial.println("Check pin 12 and 13, not grounded, or grounded with 10k resistors!\n\n"); 176 | } 177 | return ESP_OK; 178 | } 179 | 180 | camera_fb_t * get_good_jpeg() { 181 | 182 | camera_fb_t * fb; 183 | 184 | int failures = 0; 185 | 186 | do { 187 | int fblen = 0; 188 | int foundffd9 = 0; 189 | 190 | fb = esp_camera_fb_get(); 191 | if (!fb) { 192 | Serial.println("Camera Capture Failed"); 193 | failures++; 194 | } else { 195 | 196 | fblen = fb->len; 197 | 198 | for (int j = 1; j <= 1025; j++) { 199 | if (fb->buf[fblen - j] != 0xD9) { 200 | // no d9, try next for 201 | } else { //Serial.println("Found a D9"); 202 | if (fb->buf[fblen - j - 1] == 0xFF ) { //Serial.print("Found the FFD9, junk is "); Serial.println(j); 203 | if (j == 1) { 204 | //normal_jpg++; 205 | } else { 206 | //extend_jpg++; 207 | } 208 | foundffd9 = 1; 209 | break; 210 | } 211 | } 212 | } 213 | 214 | if (!foundffd9) { 215 | //bad_jpg++; 216 | Serial.printf("Bad jpeg, Frame %d, Len = %d \n", frame_cnt, fblen); 217 | esp_camera_fb_return(fb); 218 | failures++; 219 | } else { 220 | break; 221 | // count up the useless bytes 222 | } 223 | } 224 | 225 | } while (failures < 10); // normally leave the loop with a break() 226 | 227 | // if we get 10 bad frames in a row, then quality parameters are too high - set them lower (+5), and start new movie 228 | if (failures == 10) { 229 | Serial.printf("10 failures"); 230 | 231 | sensor_t * ss = esp_camera_sensor_get(); 232 | int qual = ss->status.quality ; 233 | ss->set_quality(ss, qual + 5); 234 | quality = qual + 5; 235 | Serial.printf("\n\nDecreasing quality due to frame failures %d -> %d\n\n", qual, qual + 5); 236 | delay(1000); 237 | 238 | start_record = 0; 239 | 240 | } 241 | return fb; 242 | } 243 | 244 | static void start_avi() { 245 | struct tm timeinfo; 246 | time_t now; 247 | 248 | Serial.println("Starting an avi "); 249 | 250 | time(&now); 251 | localtime_r(&now, &timeinfo); 252 | char strftime_buf[64]; 253 | 254 | strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); 255 | 256 | sprintf(avi_file_name, "/%s.avi", strftime_buf); 257 | 258 | avifile = SD_MMC.open(avi_file_name, "w"); 259 | idxfile = SD_MMC.open("/idx.tmp", "w"); 260 | 261 | if (avifile) { 262 | Serial.printf("File open: %s\n", avi_file_name); 263 | } else { 264 | Serial.println("Could not open file"); 265 | } 266 | 267 | if (idxfile) { 268 | //Serial.printf("File open: %s\n", "//idx.tmp"); 269 | } else { 270 | Serial.println("Could not open file /idx.tmp"); 271 | } 272 | 273 | for ( i = 0; i < AVIOFFSET; i++) { 274 | char ch = pgm_read_byte(&avi_header[i]); 275 | buf[i] = ch; 276 | } 277 | 278 | memcpy(buf + 0x40, frameSizeData[framesize].frameWidth, 2); 279 | memcpy(buf + 0xA8, frameSizeData[framesize].frameWidth, 2); 280 | memcpy(buf + 0x44, frameSizeData[framesize].frameHeight, 2); 281 | memcpy(buf + 0xAC, frameSizeData[framesize].frameHeight, 2); 282 | 283 | size_t err = avifile.write(buf, AVIOFFSET); 284 | 285 | uint8_t ex_fps = 1; 286 | if (frame_interval == 0) { 287 | if (framesize >= 11) { 288 | ex_fps = 12.5 * speed_up_factor ; 289 | } else { 290 | ex_fps = 25.0 * speed_up_factor; 291 | } 292 | } else { 293 | ex_fps = round(1000.0 / frame_interval * speed_up_factor); 294 | } 295 | 296 | avifile.seek( 0x84 , SeekSet); 297 | print_quartet((int)ex_fps, avifile); 298 | 299 | avifile.seek( AVIOFFSET, SeekSet); 300 | 301 | Serial.print(F("\nRecording ")); 302 | Serial.print(avi_length); 303 | Serial.println(" seconds."); 304 | 305 | startms = millis(); 306 | 307 | jpeg_size = 0; 308 | movi_size = 0; 309 | uVideoLen = 0; 310 | idx_offset = 4; 311 | 312 | avifile.flush(); 313 | 314 | } // end of start avi 315 | 316 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 317 | // 318 | // another_save_avi saves another frame to the avi file, uodates index 319 | // -- pass in a fb pointer to the frame to add 320 | // 321 | 322 | static void another_save_avi(camera_fb_t * fb ) { 323 | 324 | int fblen; 325 | fblen = fb->len; 326 | 327 | int fb_block_length; 328 | uint8_t* fb_block_start; 329 | 330 | jpeg_size = fblen; 331 | 332 | remnant = (4 - (jpeg_size & 0x00000003)) & 0x00000003; 333 | 334 | long bw = millis(); 335 | long frame_write_start = millis(); 336 | 337 | framebuffer_static[0] = 0x30; // "00dc" 338 | framebuffer_static[1] = 0x30; 339 | framebuffer_static[2] = 0x64; 340 | framebuffer_static[3] = 0x63; 341 | 342 | int jpeg_size_rem = jpeg_size + remnant; 343 | 344 | framebuffer_static[4] = jpeg_size_rem % 0x100; 345 | framebuffer_static[5] = (jpeg_size_rem >> 8) % 0x100; 346 | framebuffer_static[6] = (jpeg_size_rem >> 16) % 0x100; 347 | framebuffer_static[7] = (jpeg_size_rem >> 24) % 0x100; 348 | 349 | fb_block_start = fb->buf; 350 | 351 | if (fblen > fbs * 1024 - 8 ) { // fbs is the size of frame buffer static 352 | fb_block_length = fbs * 1024; 353 | fblen = fblen - (fbs * 1024 - 8); 354 | memcpy(framebuffer_static + 8, fb_block_start, fb_block_length - 8); 355 | fb_block_start = fb_block_start + fb_block_length - 8; 356 | 357 | } else { 358 | fb_block_length = fblen + 8 + remnant; 359 | memcpy(framebuffer_static + 8, fb_block_start, fblen); 360 | fblen = 0; 361 | } 362 | 363 | size_t err = avifile.write(framebuffer_static, fb_block_length); 364 | 365 | if (err != fb_block_length) { 366 | Serial.print("Error on avi write: err = "); Serial.print(err); 367 | Serial.print(" len = "); Serial.println(fb_block_length); 368 | } 369 | 370 | while (fblen > 0) { 371 | 372 | if (fblen > fbs * 1024) { 373 | fb_block_length = fbs * 1024; 374 | fblen = fblen - fb_block_length; 375 | } else { 376 | fb_block_length = fblen + remnant; 377 | fblen = 0; 378 | } 379 | 380 | memcpy(framebuffer_static, fb_block_start, fb_block_length); 381 | 382 | size_t err = avifile.write(framebuffer_static, fb_block_length); 383 | 384 | if (err != fb_block_length) { 385 | Serial.print("Error on avi write: err = "); Serial.print(err); 386 | Serial.print(" len = "); Serial.println(fb_block_length); 387 | } 388 | 389 | fb_block_start = fb_block_start + fb_block_length; 390 | delay(0); 391 | } 392 | 393 | 394 | movi_size += jpeg_size; 395 | uVideoLen += jpeg_size; 396 | 397 | print_2quartet(idx_offset, jpeg_size, idxfile); 398 | 399 | idx_offset = idx_offset + jpeg_size + remnant + 8; 400 | 401 | movi_size = movi_size + remnant; 402 | 403 | avifile.flush(); 404 | 405 | 406 | } // end of another_pic_avi 407 | 408 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 409 | // 410 | // end_avi writes the index, and closes the files 411 | // 412 | 413 | static void end_avi() { 414 | 415 | unsigned long current_end = avifile.position(); 416 | 417 | Serial.println("End of avi - closing the files"); 418 | 419 | if (frame_cnt < 5 ) { 420 | Serial.println("Recording screwed up, less than 5 frames, forget index\n"); 421 | idxfile.close(); 422 | avifile.close(); 423 | int xx = SD_MMC.remove("/idx.tmp"); 424 | int yy = SD_MMC.remove(avi_file_name); 425 | 426 | } else { 427 | 428 | elapsedms = millis() - startms; 429 | 430 | float fRealFPS = (1000.0f * (float)frame_cnt) / ((float)elapsedms) * speed_up_factor; 431 | 432 | float fmicroseconds_per_frame = 1000000.0f / fRealFPS; 433 | uint8_t iAttainedFPS = round(fRealFPS) ; 434 | uint32_t us_per_frame = round(fmicroseconds_per_frame); 435 | 436 | //Modify the MJPEG header from the beginning of the file, overwriting various placeholders 437 | 438 | avifile.seek( 4 , SeekSet); 439 | print_quartet(movi_size + 240 + 16 * frame_cnt + 8 * frame_cnt, avifile); 440 | 441 | avifile.seek( 0x20 , SeekSet); 442 | print_quartet(us_per_frame, avifile); 443 | 444 | unsigned long max_bytes_per_sec = (1.0f * movi_size * iAttainedFPS) / frame_cnt; 445 | 446 | avifile.seek( 0x24 , SeekSet); 447 | print_quartet(max_bytes_per_sec, avifile); 448 | 449 | avifile.seek( 0x30 , SeekSet); 450 | print_quartet(frame_cnt, avifile); 451 | 452 | avifile.seek( 0x8c , SeekSet); 453 | print_quartet(frame_cnt, avifile); 454 | 455 | avifile.seek( 0x84 , SeekSet); 456 | print_quartet((int)iAttainedFPS, avifile); 457 | 458 | avifile.seek( 0xe8 , SeekSet); 459 | print_quartet(movi_size + frame_cnt * 8 + 4, avifile); 460 | 461 | Serial.println(F("\n*** Video recorded and saved ***\n")); 462 | 463 | Serial.printf("Recorded %5d frames in %5d seconds\n", frame_cnt, elapsedms / 1000); 464 | Serial.printf("File size is %u bytes\n", movi_size + 12 * frame_cnt + 4); 465 | Serial.printf("Adjusted FPS is %5.2f\n", fRealFPS); 466 | Serial.printf("Max data rate is %lu bytes/s\n", max_bytes_per_sec); 467 | Serial.printf("Frame duration is %d us\n", us_per_frame); 468 | Serial.printf("Average frame length is %d bytes\n", uVideoLen / frame_cnt); 469 | 470 | Serial.printf("Writng the index, %d frames\n", frame_cnt); 471 | 472 | avifile.seek( current_end , SeekSet); 473 | 474 | idxfile.close(); 475 | 476 | size_t i1_err = avifile.write(idx1_buf, 4); 477 | 478 | print_quartet(frame_cnt * 16, avifile); 479 | 480 | idxfile = SD_MMC.open("/idx.tmp", "r"); 481 | 482 | if (idxfile) { 483 | //Serial.printf("File open: %s\n", "//idx.tmp"); 484 | } else { 485 | Serial.println("Could not open index file"); 486 | } 487 | 488 | char * AteBytes; 489 | AteBytes = (char*) malloc (8); 490 | 491 | for (int i = 0; i < frame_cnt; i++) { 492 | size_t res = idxfile.readBytes( AteBytes, 8); 493 | size_t i1_err = avifile.write(dc_buf, 4); 494 | size_t i2_err = avifile.write(zero_buf, 4); 495 | size_t i3_err = avifile.write((uint8_t *)AteBytes, 8); 496 | } 497 | 498 | free(AteBytes); 499 | 500 | idxfile.close(); 501 | avifile.close(); 502 | 503 | int xx = SD_MMC.remove("/idx.tmp"); 504 | } 505 | 506 | Serial.println("---"); 507 | } 508 | 509 | void the_camera_loop (void* pvParameter) { 510 | 511 | Serial.print("the camera loop, core "); Serial.print(xPortGetCoreID()); 512 | Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); 513 | 514 | init_sdcard(); 515 | frame_cnt = 0; 516 | speed_up_factor = 1; 517 | avi_length = 900; 518 | frame_interval = 0; 519 | 520 | Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); 521 | Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); 522 | uint64_t total = SD_MMC.totalBytes(); 523 | uint64_t used = SD_MMC.usedBytes(); 524 | freespace = (int)((total - used) / (1024 * 1024)); 525 | 526 | start_record = 0; 527 | 528 | delay(1000); 529 | 530 | while (1) { 531 | 532 | // if (frame_cnt == 0 && start_record == 0) // do nothing 533 | // if (frame_cnt == 0 && start_record == 1) // start a movie 534 | // if (frame_cnt > 0 && start_record == 0) // stop the movie 535 | // if (frame_cnt > 0 && start_record != 0) // another frame 536 | 537 | /////////////////// NOTHING TO DO ////////////////// 538 | if (frame_cnt == 0 && start_record == 0) { 539 | 540 | //Serial.println("Do nothing"); 541 | delay(500); 542 | 543 | /////////////////// START A MOVIE ////////////////// 544 | } else if (frame_cnt == 0 && start_record == 1) { 545 | 546 | //Serial.println("Ready to start"); 547 | 548 | avi_start_time = millis(); 549 | Serial.printf("\nStart the avi ... at %d\n", avi_start_time); 550 | Serial.printf("Framesize %d, length %d seconds\n\n", framesize, avi_length); 551 | 552 | frame_cnt++; 553 | fb_curr = get_good_jpeg(); 554 | 555 | start_avi(); 556 | 557 | another_save_avi( fb_curr); 558 | esp_camera_fb_return(fb_curr); 559 | 560 | if (blinking) digitalWrite(33, frame_cnt % 2); // blink 561 | 562 | /////////////////// END THE MOVIE ////////////////// 563 | } else if ( (frame_cnt > 0 && start_record == 0) || millis() > (avi_start_time + avi_length * 1000)) { // end the avi 564 | 565 | Serial.println("End the Avi"); 566 | 567 | end_avi(); // end the movie 568 | 569 | if (blinking) digitalWrite(33, HIGH); // light off 570 | 571 | delay(50); 572 | 573 | avi_end_time = millis(); 574 | 575 | float fps = 1.0 * frame_cnt / ((avi_end_time - avi_start_time) / 1000) ; 576 | 577 | Serial.printf("End the avi at %d. It was %d frames, %d ms at %.2f fps...\n", millis(), frame_cnt, avi_end_time, avi_end_time - avi_start_time, fps); 578 | Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); 579 | Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); 580 | uint64_t total = SD_MMC.totalBytes(); 581 | uint64_t used = SD_MMC.usedBytes(); 582 | freespace = (int)((total - used) / (1024 * 1024)); 583 | 584 | frame_cnt = 0; // start recording again on the next loop 585 | 586 | /////////////////// ANOTHER FRAME ////////////////// 587 | } else if (frame_cnt > 0 && start_record != 0) { // another frame of the avi 588 | 589 | //Serial.println("Another frame"); 590 | 591 | current_frame_time = millis(); 592 | if (current_frame_time - last_frame_time < frame_interval) { 593 | delay(frame_interval - (current_frame_time - last_frame_time)); // delay for timelapse 594 | } 595 | last_frame_time = millis(); 596 | 597 | frame_cnt++; 598 | fb_curr = get_good_jpeg(); 599 | another_save_avi( fb_curr); 600 | esp_camera_fb_return(fb_curr); 601 | 602 | if (blinking) digitalWrite(33, frame_cnt % 2); 603 | 604 | } 605 | yield(); //delay(5); 606 | } 607 | } 608 | -------------------------------------------------------------------------------- /camera_pins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Orignial from espressif, part of arduino-esp32 3 | * 4 | * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 5 | * 6 | * forked, or copied about Aug 1, 2022 7 | * - didn't want to fork the entire arduino-esp32 for this example 8 | * 9 | */ 10 | 11 | 12 | #if defined(CAMERA_MODEL_WROVER_KIT) 13 | #define PWDN_GPIO_NUM -1 14 | #define RESET_GPIO_NUM -1 15 | #define XCLK_GPIO_NUM 21 16 | #define SIOD_GPIO_NUM 26 17 | #define SIOC_GPIO_NUM 27 18 | 19 | #define Y9_GPIO_NUM 35 20 | #define Y8_GPIO_NUM 34 21 | #define Y7_GPIO_NUM 39 22 | #define Y6_GPIO_NUM 36 23 | #define Y5_GPIO_NUM 19 24 | #define Y4_GPIO_NUM 18 25 | #define Y3_GPIO_NUM 5 26 | #define Y2_GPIO_NUM 4 27 | #define VSYNC_GPIO_NUM 25 28 | #define HREF_GPIO_NUM 23 29 | #define PCLK_GPIO_NUM 22 30 | 31 | #elif defined(CAMERA_MODEL_ESP_EYE) 32 | #define PWDN_GPIO_NUM -1 33 | #define RESET_GPIO_NUM -1 34 | #define XCLK_GPIO_NUM 4 35 | #define SIOD_GPIO_NUM 18 36 | #define SIOC_GPIO_NUM 23 37 | 38 | #define Y9_GPIO_NUM 36 39 | #define Y8_GPIO_NUM 37 40 | #define Y7_GPIO_NUM 38 41 | #define Y6_GPIO_NUM 39 42 | #define Y5_GPIO_NUM 35 43 | #define Y4_GPIO_NUM 14 44 | #define Y3_GPIO_NUM 13 45 | #define Y2_GPIO_NUM 34 46 | #define VSYNC_GPIO_NUM 5 47 | #define HREF_GPIO_NUM 27 48 | #define PCLK_GPIO_NUM 25 49 | 50 | #elif defined(CAMERA_MODEL_M5STACK_PSRAM) 51 | #define PWDN_GPIO_NUM -1 52 | #define RESET_GPIO_NUM 15 53 | #define XCLK_GPIO_NUM 27 54 | #define SIOD_GPIO_NUM 25 55 | #define SIOC_GPIO_NUM 23 56 | 57 | #define Y9_GPIO_NUM 19 58 | #define Y8_GPIO_NUM 36 59 | #define Y7_GPIO_NUM 18 60 | #define Y6_GPIO_NUM 39 61 | #define Y5_GPIO_NUM 5 62 | #define Y4_GPIO_NUM 34 63 | #define Y3_GPIO_NUM 35 64 | #define Y2_GPIO_NUM 32 65 | #define VSYNC_GPIO_NUM 22 66 | #define HREF_GPIO_NUM 26 67 | #define PCLK_GPIO_NUM 21 68 | 69 | #elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM) 70 | #define PWDN_GPIO_NUM -1 71 | #define RESET_GPIO_NUM 15 72 | #define XCLK_GPIO_NUM 27 73 | #define SIOD_GPIO_NUM 22 74 | #define SIOC_GPIO_NUM 23 75 | 76 | #define Y9_GPIO_NUM 19 77 | #define Y8_GPIO_NUM 36 78 | #define Y7_GPIO_NUM 18 79 | #define Y6_GPIO_NUM 39 80 | #define Y5_GPIO_NUM 5 81 | #define Y4_GPIO_NUM 34 82 | #define Y3_GPIO_NUM 35 83 | #define Y2_GPIO_NUM 32 84 | #define VSYNC_GPIO_NUM 25 85 | #define HREF_GPIO_NUM 26 86 | #define PCLK_GPIO_NUM 21 87 | 88 | #elif defined(CAMERA_MODEL_M5STACK_WIDE) 89 | #define PWDN_GPIO_NUM -1 90 | #define RESET_GPIO_NUM 15 91 | #define XCLK_GPIO_NUM 27 92 | #define SIOD_GPIO_NUM 22 93 | #define SIOC_GPIO_NUM 23 94 | 95 | #define Y9_GPIO_NUM 19 96 | #define Y8_GPIO_NUM 36 97 | #define Y7_GPIO_NUM 18 98 | #define Y6_GPIO_NUM 39 99 | #define Y5_GPIO_NUM 5 100 | #define Y4_GPIO_NUM 34 101 | #define Y3_GPIO_NUM 35 102 | #define Y2_GPIO_NUM 32 103 | #define VSYNC_GPIO_NUM 25 104 | #define HREF_GPIO_NUM 26 105 | #define PCLK_GPIO_NUM 21 106 | 107 | #elif defined(CAMERA_MODEL_M5STACK_ESP32CAM) 108 | #define PWDN_GPIO_NUM -1 109 | #define RESET_GPIO_NUM 15 110 | #define XCLK_GPIO_NUM 27 111 | #define SIOD_GPIO_NUM 25 112 | #define SIOC_GPIO_NUM 23 113 | 114 | #define Y9_GPIO_NUM 19 115 | #define Y8_GPIO_NUM 36 116 | #define Y7_GPIO_NUM 18 117 | #define Y6_GPIO_NUM 39 118 | #define Y5_GPIO_NUM 5 119 | #define Y4_GPIO_NUM 34 120 | #define Y3_GPIO_NUM 35 121 | #define Y2_GPIO_NUM 17 122 | #define VSYNC_GPIO_NUM 22 123 | #define HREF_GPIO_NUM 26 124 | #define PCLK_GPIO_NUM 21 125 | 126 | #elif defined(CAMERA_MODEL_M5STACK_UNITCAM) 127 | #define PWDN_GPIO_NUM -1 128 | #define RESET_GPIO_NUM 15 129 | #define XCLK_GPIO_NUM 27 130 | #define SIOD_GPIO_NUM 25 131 | #define SIOC_GPIO_NUM 23 132 | 133 | #define Y9_GPIO_NUM 19 134 | #define Y8_GPIO_NUM 36 135 | #define Y7_GPIO_NUM 18 136 | #define Y6_GPIO_NUM 39 137 | #define Y5_GPIO_NUM 5 138 | #define Y4_GPIO_NUM 34 139 | #define Y3_GPIO_NUM 35 140 | #define Y2_GPIO_NUM 32 141 | #define VSYNC_GPIO_NUM 22 142 | #define HREF_GPIO_NUM 26 143 | #define PCLK_GPIO_NUM 21 144 | 145 | #elif defined(CAMERA_MODEL_AI_THINKER) 146 | #define PWDN_GPIO_NUM 32 147 | #define RESET_GPIO_NUM -1 148 | #define XCLK_GPIO_NUM 0 149 | #define SIOD_GPIO_NUM 26 150 | #define SIOC_GPIO_NUM 27 151 | 152 | #define Y9_GPIO_NUM 35 153 | #define Y8_GPIO_NUM 34 154 | #define Y7_GPIO_NUM 39 155 | #define Y6_GPIO_NUM 36 156 | #define Y5_GPIO_NUM 21 157 | #define Y4_GPIO_NUM 19 158 | #define Y3_GPIO_NUM 18 159 | #define Y2_GPIO_NUM 5 160 | #define VSYNC_GPIO_NUM 25 161 | #define HREF_GPIO_NUM 23 162 | #define PCLK_GPIO_NUM 22 163 | 164 | #elif defined(CAMERA_MODEL_TTGO_T_JOURNAL) 165 | #define PWDN_GPIO_NUM 0 166 | #define RESET_GPIO_NUM 15 167 | #define XCLK_GPIO_NUM 27 168 | #define SIOD_GPIO_NUM 25 169 | #define SIOC_GPIO_NUM 23 170 | 171 | #define Y9_GPIO_NUM 19 172 | #define Y8_GPIO_NUM 36 173 | #define Y7_GPIO_NUM 18 174 | #define Y6_GPIO_NUM 39 175 | #define Y5_GPIO_NUM 5 176 | #define Y4_GPIO_NUM 34 177 | #define Y3_GPIO_NUM 35 178 | #define Y2_GPIO_NUM 17 179 | #define VSYNC_GPIO_NUM 22 180 | #define HREF_GPIO_NUM 26 181 | #define PCLK_GPIO_NUM 21 182 | 183 | 184 | #elif defined(CAMERA_MODEL_ESP32_CAM_BOARD) 185 | // The 18 pin header on the board has Y5 and Y3 swapped 186 | #define USE_BOARD_HEADER 0 187 | #define PWDN_GPIO_NUM 32 188 | #define RESET_GPIO_NUM 33 189 | #define XCLK_GPIO_NUM 4 190 | #define SIOD_GPIO_NUM 18 191 | #define SIOC_GPIO_NUM 23 192 | 193 | #define Y9_GPIO_NUM 36 194 | #define Y8_GPIO_NUM 19 195 | #define Y7_GPIO_NUM 21 196 | #define Y6_GPIO_NUM 39 197 | #if USE_BOARD_HEADER 198 | #define Y5_GPIO_NUM 13 199 | #else 200 | #define Y5_GPIO_NUM 35 201 | #endif 202 | #define Y4_GPIO_NUM 14 203 | #if USE_BOARD_HEADER 204 | #define Y3_GPIO_NUM 35 205 | #else 206 | #define Y3_GPIO_NUM 13 207 | #endif 208 | #define Y2_GPIO_NUM 34 209 | #define VSYNC_GPIO_NUM 5 210 | #define HREF_GPIO_NUM 27 211 | #define PCLK_GPIO_NUM 25 212 | 213 | #elif defined(CAMERA_MODEL_ESP32S3_CAM_LCD) 214 | #define PWDN_GPIO_NUM -1 215 | #define RESET_GPIO_NUM -1 216 | #define XCLK_GPIO_NUM 40 217 | #define SIOD_GPIO_NUM 17 218 | #define SIOC_GPIO_NUM 18 219 | 220 | #define Y9_GPIO_NUM 39 221 | #define Y8_GPIO_NUM 41 222 | #define Y7_GPIO_NUM 42 223 | #define Y6_GPIO_NUM 12 224 | #define Y5_GPIO_NUM 3 225 | #define Y4_GPIO_NUM 14 226 | #define Y3_GPIO_NUM 47 227 | #define Y2_GPIO_NUM 13 228 | #define VSYNC_GPIO_NUM 21 229 | #define HREF_GPIO_NUM 38 230 | #define PCLK_GPIO_NUM 11 231 | 232 | #elif defined(CAMERA_MODEL_ESP32S2_CAM_BOARD) 233 | // The 18 pin header on the board has Y5 and Y3 swapped 234 | #define USE_BOARD_HEADER 0 235 | #define PWDN_GPIO_NUM 1 236 | #define RESET_GPIO_NUM 2 237 | #define XCLK_GPIO_NUM 42 238 | #define SIOD_GPIO_NUM 41 239 | #define SIOC_GPIO_NUM 18 240 | 241 | #define Y9_GPIO_NUM 16 242 | #define Y8_GPIO_NUM 39 243 | #define Y7_GPIO_NUM 40 244 | #define Y6_GPIO_NUM 15 245 | #if USE_BOARD_HEADER 246 | #define Y5_GPIO_NUM 12 247 | #else 248 | #define Y5_GPIO_NUM 13 249 | #endif 250 | #define Y4_GPIO_NUM 5 251 | #if USE_BOARD_HEADER 252 | #define Y3_GPIO_NUM 13 253 | #else 254 | #define Y3_GPIO_NUM 12 255 | #endif 256 | #define Y2_GPIO_NUM 14 257 | #define VSYNC_GPIO_NUM 38 258 | #define HREF_GPIO_NUM 4 259 | #define PCLK_GPIO_NUM 3 260 | 261 | #elif defined(CAMERA_MODEL_ESP32S3_EYE) 262 | #define PWDN_GPIO_NUM -1 263 | #define RESET_GPIO_NUM -1 264 | #define XCLK_GPIO_NUM 15 265 | #define SIOD_GPIO_NUM 4 266 | #define SIOC_GPIO_NUM 5 267 | 268 | #define Y2_GPIO_NUM 11 269 | #define Y3_GPIO_NUM 9 270 | #define Y4_GPIO_NUM 8 271 | #define Y5_GPIO_NUM 10 272 | #define Y6_GPIO_NUM 12 273 | #define Y7_GPIO_NUM 18 274 | #define Y8_GPIO_NUM 17 275 | #define Y9_GPIO_NUM 16 276 | 277 | #define VSYNC_GPIO_NUM 6 278 | #define HREF_GPIO_NUM 7 279 | #define PCLK_GPIO_NUM 13 280 | 281 | #else 282 | #error "Camera model not selected" 283 | #endif 284 | --------------------------------------------------------------------------------