Advertisement
MrLunk

Video Capture Using the ESP32-CAM Board

Feb 7th, 2021
803
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 50.52 KB | None | 0 0
  1. // SOURCE https://github.com/bnbe-club/video-recording-with-esp32-cam-diy-11/blob/master/diy-e11/diy-e11.ino
  2. // instructions: https://www.instructables.com/Video-Capture-Using-the-ESP32-CAM-Board/
  3. /***********************************************************************************************************************************************************
  4. * TITLE: This sketch is derived from the following Github repository and is mainly for testing the current work done by the team. It allows you
  5. * to record video onto a microSD card, using the ESP32-CAM board. Videos can be remotely obtained by accessing the FTP server contained within the sketch.
  6. *
  7. * By Frenoy Osburn
  8. * YouTube Video: https://youtu.be/lc_gXfkoRZo
  9. * BnBe Post: https://www.bitsnblobs.com/video-recording-with-the-esp32-cam-board
  10. ***********************************************************************************************************************************************************/
  11.  
  12. /********************************************************************************************************************
  13. * Board Settings:
  14. * Board: "ESP32 Wrover Module"
  15. * Upload Speed: "921600"
  16. * Flash Frequency: "80MHz"
  17. * Flash Mode: "QIO"
  18. * Partition Scheme: "Hue APP (3MB No OTA/1MB SPIFFS)"
  19. * Core Debug Level: "None"
  20. * COM Port: Depends *On Your System*
  21. *********************************************************************************************************************/
  22.  
  23. /*
  24. TimeLapseAvi
  25.  
  26. ESP32-CAM Video Recorder
  27.  
  28. This program records an AVI video on the SD Card of an ESP32-CAM.
  29.  
  30. by James Zahary July 20, 2019 TimeLapseAvi23x.ino
  31. jamzah.plc@gmail.com
  32.  
  33. https://github.com/jameszah/ESP32-CAM-Video-Recorder
  34. jameszah/ESP32-CAM-Video-Recorder is licensed under the
  35. GNU General Public License v3.0
  36.  
  37. ~~~
  38. Update Sep 15, 2019 TimeLapseAvi39x.ino
  39. - work-in-progress
  40. - I'm publishing this as a few people have been asking or working on this
  41.  
  42. - program now uses both cpu's with cpu 0 taking pictures and queue'ing them
  43. and a separate task on cpu 1 moving the pictures from the queue and adding
  44. them to the avi file on the sd card
  45. - the loop() task on cpu 1 now just handles the ftp system and http server
  46. - dropped fixed ip and switch to mDNS with name "desklens", which can be typed into
  47. browser, and also used as wifi name on router
  48. - small change to ftp to cooperate with WinSCP program
  49. - fixed bug so Windows would calulcate the correct length (time length) of avi
  50. - when queue of frames gets full, it slips every other frame to try to catch up
  51. - camera is re-configued when changing from UXGA <> VGA to allow for more buffers
  52. with the smaller frames
  53. ~~~
  54.  
  55. The is Arduino code, with standard setup for ESP32-CAM
  56. - Board ESP32 Wrover Module
  57. - Partition Scheme Huge APP (3MB No OTA)
  58.  
  59. This program records an AVI video on the SD Card of an ESP32-CAM.
  60.  
  61. It will record realtime video at limited framerates, or timelapses with the full resolution of the ESP32-CAM.
  62. It is controlled by a web page it serves to stop and start recordings with many parameters, and look through the viewfinder.
  63.  
  64. You can control framesize (UXGA, VGA, ...), quality, length, and fps to record, and fps to playback later, etc.
  65.  
  66. There is also an ftp server to download the recordings to a PC.
  67.  
  68. Instructions:
  69.  
  70. The program uses a fixed IP of 192.168.1.188, so you can browse to it from your phone or computer.
  71.  
  72. http://192.168.1.188/ -- this gives you the status of the recording in progress and lets you look through the viewfinder
  73.  
  74. http://192.168.1.188/stop -- this stops the recording in progress and displays some sample commands to start new recordings
  75.  
  76. ftp://192.168.1.188/ -- gives you the ftp server
  77.  
  78. The ftp for esp32 seems to not be a full ftp. The Chrome Browser and the Windows command line ftp's did not work with this, but
  79. the Raspbarian command line ftp works fine, and an old Windows ftp I have called CoffeeCup Free FTP also works, which is what I have been using.
  80. You can download at about 450 KB/s -- which is better than having to retreive the SD chip if you camera is up in a tree!
  81.  
  82. http://192.168.1.188/start?framesize=VGA&length=1800&interval=250&quality=10&repeat=100&speed=1&gray=0 -- this is a sample to start a new recording
  83.  
  84. framesize can be UXGA, SVGA, VGA, CIF (default VGA)
  85. length is length in seconds of the recording 0..3600 (default 1800)
  86. interval is the milli-seconds between frames (default 200)
  87. quality is a number 5..50 for the jpeg - smaller number is higher quality with bigger and more detailed jpeg (default 10)
  88. repeat is a number of who many of the same recordings should be made (default 100)
  89. speed is a factor to speed up realtime for a timelapse recording - 1 is realtime (default 1)
  90. gray is 1 for a grayscale video (default 0 - color)
  91.  
  92. These factors have to be within the limit of the SD chip to receive the data.
  93. For example, using a LEXAR 300x 32GB microSDHC UHS-I, the following works for me:
  94.  
  95. UXGA quality 10, 2 fps (or interval of 500ms)
  96. SVGA quality 10, 5 fps (200ms)
  97. VGA quality 10, 10 fps (100ms)
  98. CIG quality 10, 20 fps (50ms)
  99.  
  100. If you increase fps, you might have to reduce quality or framesize to keep it from dropping frames as it writes all the data to the SD chip.
  101.  
  102. Also, other SD chips will be faster or slower. I was using a SanDisk 16GB microSDHC "Up to 653X" - which was slower and more unpredictable than the LEXAR ???
  103.  
  104. Search for "zzz" to find places to modify the code for:
  105. 1. Your wifi name and password
  106. 2. Your preferred ip address (with default gateway, etc)
  107. 3. Your Timezone for use in filenames
  108. 4. Defaults for framesize, quality, ... and if the recording should start on reboot of the ESP32 without receiving a command
  109.  
  110. Acknowlegements:
  111.  
  112. 1. https://robotzero.one/time-lapse-esp32-cameras/
  113. Timelapse programs for ESP32-CAM version that sends snapshots of screen.
  114. 2. https://github.com/nailbuster/esp8266FTPServer
  115. ftp server (slightly modifed to get the directory function working)
  116. 3. https://github.com/ArduCAM/Arduino/tree/master/ArduCAM/examples/mini
  117. ArduCAM Mini demo (C)2017 LeeWeb: http://www.ArduCAM.com
  118. I copied the structure of the avi file, some calculations.
  119.  
  120. */
  121.  
  122.  
  123. //#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
  124. #include "esp_log.h"
  125. #include "esp_http_server.h"
  126. #include "esp_camera.h"
  127.  
  128. //#include <WiFi.h> // redundant
  129. #include <ESPmDNS.h>
  130.  
  131. #include "ESP32FtpServer.h"
  132. #include <HTTPClient.h>
  133.  
  134. FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial
  135.  
  136.  
  137. // Time
  138. #include "time.h"
  139. #include "lwip/err.h"
  140. #include "lwip/apps/sntp.h"
  141.  
  142. // MicroSD
  143. #include "driver/sdmmc_host.h"
  144. #include "driver/sdmmc_defs.h"
  145. #include "sdmmc_cmd.h"
  146. #include "esp_vfs_fat.h"
  147.  
  148. long current_millis;
  149. long last_capture_millis = 0;
  150. static esp_err_t cam_err;
  151. static esp_err_t card_err;
  152. char strftime_buf[64];
  153. int file_number = 0;
  154. bool internet_connected = false;
  155. struct tm timeinfo;
  156. time_t now;
  157.  
  158. char *filename ;
  159. char *stream ;
  160. int newfile = 0;
  161. int frames_so_far = 0;
  162. FILE *myfile;
  163. long bp;
  164. long ap;
  165. long bw;
  166. long aw;
  167. long totalp;
  168. long totalw;
  169. float avgp;
  170. float avgw;
  171. int overtime_count = 0;
  172.  
  173. // CAMERA_MODEL_AI_THINKER
  174. #define PWDN_GPIO_NUM 32
  175. #define RESET_GPIO_NUM -1
  176. #define XCLK_GPIO_NUM 0
  177. #define SIOD_GPIO_NUM 26
  178. #define SIOC_GPIO_NUM 27
  179. #define Y9_GPIO_NUM 35
  180. #define Y8_GPIO_NUM 34
  181. #define Y7_GPIO_NUM 39
  182. #define Y6_GPIO_NUM 36
  183. #define Y5_GPIO_NUM 21
  184. #define Y4_GPIO_NUM 19
  185. #define Y3_GPIO_NUM 18
  186. #define Y2_GPIO_NUM 5
  187. #define VSYNC_GPIO_NUM 25
  188. #define HREF_GPIO_NUM 23
  189. #define PCLK_GPIO_NUM 22
  190.  
  191.  
  192. // GLOBALS
  193. #define BUFFSIZE 512
  194.  
  195. // global variable used by these pieces
  196.  
  197. char str[20];
  198. uint16_t n;
  199. uint8_t buf[BUFFSIZE];
  200.  
  201. static int i = 0;
  202. uint8_t temp = 0, temp_last = 0;
  203. unsigned long fileposition = 0;
  204. uint16_t frame_cnt = 0;
  205. uint16_t remnant = 0;
  206. uint32_t length = 0;
  207. uint32_t startms;
  208. uint32_t elapsedms;
  209. uint32_t uVideoLen = 0;
  210. bool is_header = false;
  211. long bigdelta = 0;
  212. int other_cpu_active = 0;
  213. int skipping = 0;
  214. int skipped = 0;
  215.  
  216. int fb_max = 12;
  217.  
  218. camera_fb_t * fb_q[30];
  219. int fb_in = 0;
  220. int fb_out = 0;
  221.  
  222. camera_fb_t * fb = NULL;
  223.  
  224. FILE *avifile = NULL;
  225. FILE *idxfile = NULL;
  226.  
  227. //
  228. //
  229. // EDIT ssid and password
  230. //
  231. // zzz
  232. const char* ssid = "Network";
  233. const char* password = "Password";
  234.  
  235.  
  236. // these are just declarations -- look below to edit defaults
  237.  
  238. int capture_interval = 200; // microseconds between captures
  239. int total_frames = 300; // default updated below
  240. int recording = 0; // turned off until start of setup
  241. int framesize = 6; // vga
  242. int repeat = 100; // capture 100 videos
  243. int quality = 10;
  244. int xspeed = 1;
  245. int xlength = 3;
  246. int gray = 0;
  247. int new_config = 0;
  248.  
  249. #define AVIOFFSET 240 // AVI main header length
  250.  
  251. unsigned long movi_size = 0;
  252. unsigned long jpeg_size = 0;
  253. unsigned long idx_offset = 0;
  254.  
  255. uint8_t zero_buf[4] = {0x00, 0x00, 0x00, 0x00};
  256. uint8_t dc_buf[4] = {0x30, 0x30, 0x64, 0x63}; // "00dc"
  257. uint8_t avi1_buf[4] = {0x41, 0x56, 0x49, 0x31}; // "AVI1"
  258. uint8_t idx1_buf[4] = {0x69, 0x64, 0x78, 0x31}; // "idx1"
  259.  
  260. uint8_t vga_w[2] = {0x80, 0x02}; // 640
  261. uint8_t vga_h[2] = {0xE0, 0x01}; // 480
  262. uint8_t cif_w[2] = {0x90, 0x01}; // 400
  263. uint8_t cif_h[2] = {0x28, 0x01}; // 296
  264. uint8_t svga_w[2] = {0x20, 0x03}; //
  265. uint8_t svga_h[2] = {0x58, 0x02}; //
  266. uint8_t uxga_w[2] = {0x40, 0x06}; // 1600
  267. uint8_t uxga_h[2] = {0xB0, 0x04}; // 1200
  268.  
  269.  
  270. const int avi_header[AVIOFFSET] PROGMEM = {
  271. 0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54,
  272. 0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00,
  273. 0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
  274. 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  275. 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  276. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00,
  277. 0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73,
  278. 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  279. 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
  280. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66,
  281. 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
  282. 0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  283. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x46, 0x4F,
  284. 0x10, 0x00, 0x00, 0x00, 0x6A, 0x61, 0x6D, 0x65, 0x73, 0x7A, 0x61, 0x68, 0x61, 0x72, 0x79, 0x20,
  285. 0x76, 0x33, 0x39, 0x20, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69,
  286. };
  287.  
  288. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  289. //
  290. // AviWriterTask runs on cpu 1 to write the avi file
  291. //
  292.  
  293. TaskHandle_t CameraTask, AviWriterTask;
  294. SemaphoreHandle_t baton;
  295. int counter = 0;
  296.  
  297. void codeForAviWriterTask( void * parameter )
  298. {
  299.  
  300. print_stats("AviWriterTask runs on Core: ");
  301.  
  302. for (;;) {
  303. make_avi();
  304. delay(1);
  305. }
  306. }
  307.  
  308. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  309. //
  310. // CameraTask runs on cpu 0 to take pictures and drop them in a queue
  311. //
  312.  
  313. void codeForCameraTask( void * parameter )
  314. {
  315.  
  316. print_stats("CameraTask runs on Core: ");
  317.  
  318. for (;;) {
  319.  
  320. if (other_cpu_active == 1 ) {
  321. current_millis = millis();
  322. if (current_millis - last_capture_millis > capture_interval) {
  323.  
  324. last_capture_millis = millis();
  325.  
  326. xSemaphoreTake( baton, portMAX_DELAY );
  327.  
  328. if ( ( (fb_in + fb_max - fb_out) % fb_max) + 1 == fb_max ) {
  329. xSemaphoreGive( baton );
  330.  
  331. Serial.print(" S "); // the queue is full
  332. skipped++;
  333. skipping = 1;
  334.  
  335. //Serial.print(" Q: "); Serial.println( (fb_in + fb_max - fb_out) % fb_max );
  336. //Serial.print(fb_in); Serial.print(" / "); Serial.print(fb_out); Serial.print(" / "); Serial.println(fb_max);
  337. //delay(1);
  338.  
  339. } if (skipping > 0 ) {
  340.  
  341. if (skipping % 2 == 0) { // skip every other frame until queue is cleared
  342.  
  343. frames_so_far = frames_so_far + 1;
  344. frame_cnt++;
  345.  
  346. fb_in = (fb_in + 1) % fb_max;
  347. bp = millis();
  348. fb_q[fb_in] = esp_camera_fb_get();
  349. totalp = totalp - bp + millis();
  350.  
  351. } else {
  352. //Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print("-s "); // skip an extra frame to empty the queue
  353. skipped++;
  354. }
  355. skipping = skipping + 1;
  356. if (((fb_in + fb_max - fb_out) % fb_max) == 0 ) {
  357. skipping = 0;
  358. Serial.print(" == ");
  359. }
  360.  
  361. xSemaphoreGive( baton );
  362.  
  363. } else {
  364.  
  365. skipping = 0;
  366. frames_so_far = frames_so_far + 1;
  367. frame_cnt++;
  368.  
  369. fb_in = (fb_in + 1) % fb_max;
  370. bp = millis();
  371. fb_q[fb_in] = esp_camera_fb_get();
  372. totalp = totalp - bp + millis();
  373. xSemaphoreGive( baton );
  374.  
  375. }
  376.  
  377.  
  378. } else {
  379. //delay(5); // waiting to take next picture
  380. }
  381. } else {
  382. //delay(50); // big delay if not recording
  383. }
  384. delay(1);
  385. }
  386. }
  387.  
  388.  
  389. //
  390. // Writes an uint32_t in Big Endian at current file position
  391. //
  392. static void inline print_quartet(unsigned long i, FILE * fd)
  393. {
  394. uint8_t x[1];
  395.  
  396. x[0] = i % 0x100;
  397. size_t i1_err = fwrite(x , 1, 1, fd);
  398. i = i >> 8; x[0] = i % 0x100;
  399. size_t i2_err = fwrite(x , 1, 1, fd);
  400. i = i >> 8; x[0] = i % 0x100;
  401. size_t i3_err = fwrite(x , 1, 1, fd);
  402. i = i >> 8; x[0] = i % 0x100;
  403. size_t i4_err = fwrite(x , 1, 1, fd);
  404. }
  405.  
  406.  
  407. void startCameraServer();
  408. httpd_handle_t camera_httpd = NULL;
  409.  
  410. char the_page[3000];
  411.  
  412. char localip[20];
  413. WiFiEventId_t eventID;
  414.  
  415. #include "soc/soc.h"
  416. #include "soc/rtc_cntl_reg.h"
  417.  
  418. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  419. //
  420. // setup() runs on cpu 1
  421. //
  422.  
  423. void setup() {
  424. //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems
  425.  
  426. Serial.begin(115200);
  427.  
  428. Serial.setDebugOutput(true);
  429.  
  430. // zzz
  431. Serial.println(" ");
  432. Serial.println("-------------------------------------");
  433. Serial.println("ESP-CAM Video Recorder v39\n");
  434. Serial.println(" http://desklens.local - to access the camera\n");
  435. Serial.println("-------------------------------------");
  436.  
  437. print_stats("Begin setup Core: ");
  438.  
  439. pinMode(33, OUTPUT); // little red led on back of chip
  440.  
  441. digitalWrite(33, LOW); // turn on the red LED on the back of chip
  442.  
  443.  
  444. eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
  445. Serial.print("WiFi lost connection. Reason: ");
  446. Serial.println(info.disconnected.reason);
  447.  
  448. if (WiFi.status() == WL_CONNECTED) {
  449. Serial.println("*** connected/disconnected issue! WiFi disconnected ???...");
  450. WiFi.disconnect();
  451. } else {
  452. Serial.println("*** WiFi disconnected ???...");
  453. }
  454. }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
  455.  
  456.  
  457. if (init_wifi()) { // Connected to WiFi
  458. internet_connected = true;
  459. Serial.println("Internet connected");
  460. sprintf(localip, "%s", WiFi.localIP().toString().c_str());
  461.  
  462. if (!MDNS.begin("desklens")) {
  463. Serial.println("Error setting up MDNS responder!");
  464. } else {
  465. Serial.println("mDNS responder started");
  466. }
  467.  
  468. init_time();
  469. time(&now);
  470.  
  471. //setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1);
  472.  
  473. // zzz
  474. setenv("TZ", "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00", 1); // mountain time zone
  475. tzset();
  476. delay(1000);
  477. time(&now);
  478. Serial.print("After timezone : "); Serial.println(ctime(&now));
  479. }
  480.  
  481. baton = xSemaphoreCreateMutex();
  482.  
  483. xTaskCreatePinnedToCore(
  484. codeForCameraTask,
  485. "CameraTask",
  486. 10000,
  487. NULL,
  488. 1,
  489. &CameraTask,
  490. 0);
  491.  
  492. delay(500);
  493.  
  494. xTaskCreatePinnedToCore(
  495. codeForAviWriterTask,
  496. "AviWriterTask",
  497. 10000,
  498. NULL,
  499. 2,
  500. &AviWriterTask,
  501. 1);
  502.  
  503. delay(500);
  504.  
  505. print_stats("After Task 1 Core: ");
  506.  
  507. if (psramFound()) {
  508. } else {
  509. Serial.println("paraFound wrong - major fail");
  510. major_fail();
  511. }
  512.  
  513. // SD camera init
  514. card_err = init_sdcard();
  515. if (card_err != ESP_OK) {
  516. Serial.printf("SD Card init failed with error 0x%x", card_err);
  517. major_fail();
  518. return;
  519. }
  520.  
  521. print_stats("After SD init Core: ");
  522.  
  523. startCameraServer();
  524.  
  525. print_stats("After Server init Core: ");
  526.  
  527. // zzz username and password for ftp server
  528.  
  529. ftpSrv.begin("esp", "esp");
  530.  
  531. print_stats("After ftp init Core: ");
  532.  
  533. digitalWrite(33, HIGH);
  534.  
  535. //
  536. // startup defaults -- EDIT HERE
  537. // zzz
  538.  
  539. framesize = 10; // uxga
  540. repeat = 100; // 100 files
  541. xspeed = 30; // 30x playback speed
  542. gray = 0; // not gray
  543. quality = 10; // 10 on the 0..64 scale, or 10..50 subscale
  544. capture_interval = 1000; // 1000 ms or 1 second
  545. total_frames = 1800; // 1800 frames or 60 x 30 = 30 minutes
  546. xlength = total_frames * capture_interval / 1000;
  547.  
  548. new_config = 5; // 5 means we have not configured the camera
  549. // 1 setup as vga, 2 setup as uxga
  550. // 3 move from uxga -> vga
  551. // 4 move from vga -> uxga
  552.  
  553. newfile = 0; // no file is open // don't fiddle with this!
  554. recording = 0; // we are NOT recording
  555.  
  556. config_camera();
  557.  
  558. recording = 1; // we are recording
  559.  
  560.  
  561. Serial.print("Camera Ready! Use 'http://");
  562. Serial.print(WiFi.localIP());
  563. Serial.println("' to connect");
  564. }
  565.  
  566. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  567. //
  568. // print_stats to keep track of memory during debugging
  569. //
  570.  
  571. void print_stats(char *the_message) {
  572.  
  573. Serial.print(the_message); Serial.println(xPortGetCoreID());
  574. Serial.print(" Free Heap: "); Serial.print(ESP.getFreeHeap());
  575. Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL));
  576.  
  577. printf(" Himem is %dKiB, Himem free %dKiB, ", (int)ESP.getPsramSize() / 1024, (int)ESP.getFreePsram() / 1024);
  578. printf("Flash is %dKiB, Sketch is %dKiB \n", (int)ESP.getFlashChipSize() / 1024, (int)ESP.getSketchSize() / 1024);
  579.  
  580. Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out);
  581. Serial.println(" ");
  582. }
  583.  
  584. //
  585. // if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS
  586. //
  587. void major_fail() {
  588.  
  589. for (int i = 0; i < 10; i++) {
  590. digitalWrite(33, LOW); delay(150);
  591. digitalWrite(33, HIGH); delay(150);
  592. digitalWrite(33, LOW); delay(150);
  593. digitalWrite(33, HIGH); delay(150);
  594. digitalWrite(33, LOW); delay(150);
  595. digitalWrite(33, HIGH); delay(150);
  596.  
  597. delay(1000);
  598.  
  599. digitalWrite(33, LOW); delay(500);
  600. digitalWrite(33, HIGH); delay(500);
  601. digitalWrite(33, LOW); delay(500);
  602. digitalWrite(33, HIGH); delay(500);
  603. digitalWrite(33, LOW); delay(500);
  604. digitalWrite(33, HIGH); delay(500);
  605.  
  606. delay(1000);
  607. }
  608.  
  609. ESP.restart();
  610.  
  611. }
  612.  
  613.  
  614. bool init_wifi()
  615. {
  616. int connAttempts = 0;
  617. WiFi.mode(WIFI_STA);
  618.  
  619. WiFi.setHostname("desklens");
  620. WiFi.printDiag(Serial);
  621. WiFi.begin(ssid, password);
  622. while (WiFi.status() != WL_CONNECTED ) {
  623. delay(500);
  624. Serial.print(".");
  625. if (connAttempts > 10) {
  626. Serial.println("Cannot connect - try again");
  627. WiFi.begin(ssid, password);
  628. WiFi.printDiag(Serial);
  629. }
  630. if (connAttempts > 20) {
  631. Serial.println("Cannot connect - fail");
  632. major_fail();
  633. return false;
  634. WiFi.printDiag(Serial);
  635. major_fail();
  636. return false;
  637. }
  638.  
  639. connAttempts++;
  640. }
  641.  
  642. WiFi.printDiag(Serial);
  643. return true;
  644.  
  645. /*
  646. // this is the fixed ip stuff that does not work with with router
  647. // zzz
  648. // Set your Static IP address
  649. IPAddress local_IP(192, 168, 1, 225);
  650. //IPAddress local_IP(192, 169, 1, 225);
  651.  
  652. // Set your Gateway IP address
  653. IPAddress gateway(192, 168, 1, 254);
  654. //IPAddress gateway(192, 169, 1, 1);
  655.  
  656. IPAddress subnet(255, 255, 0, 0);
  657. IPAddress primaryDNS(8, 8, 8, 8); // optional
  658. IPAddress secondaryDNS(8, 8, 4, 4); // optional
  659.  
  660. WiFi.mode(WIFI_STA);
  661.  
  662. WiFi.setHostname("ESP32CAM225"); // does not seem to do anything with my wifi router ???
  663.  
  664. if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
  665. Serial.println("STA Failed to configure");
  666. major_fail();
  667. }
  668.  
  669. WiFi.printDiag(Serial);
  670.  
  671. WiFi.begin(ssid, password);
  672. while (WiFi.status() != WL_CONNECTED ) {
  673. delay(500);
  674. Serial.print(".");
  675. if (connAttempts > 10) {
  676. Serial.println("Cannot connect");
  677. WiFi.printDiag(Serial);
  678. major_fail();
  679. return false;
  680. }
  681. connAttempts++;
  682. }
  683. return true;
  684.  
  685. */
  686. }
  687.  
  688. void init_time()
  689. {
  690.  
  691. do_time();
  692.  
  693. sntp_setoperatingmode(SNTP_OPMODE_POLL);
  694. sntp_setservername(0, "pool.ntp.org");
  695. sntp_setservername(1, "time.windows.com");
  696. sntp_setservername(2, "time.nist.gov");
  697.  
  698. sntp_init();
  699.  
  700. // wait for time to be set
  701. time_t now = 0;
  702. timeinfo = { 0 };
  703. int retry = 0;
  704. const int retry_count = 10;
  705. while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) {
  706. Serial.printf("Waiting for system time to be set... (%d/%d) -- %d\n", retry, retry_count, timeinfo.tm_year);
  707. delay(2000);
  708. time(&now);
  709. localtime_r(&now, &timeinfo);
  710. Serial.println(ctime(&now));
  711. }
  712.  
  713. if (timeinfo.tm_year < (2016 - 1900)) {
  714. major_fail();
  715. }
  716. }
  717.  
  718. static esp_err_t init_sdcard()
  719. {
  720. esp_err_t ret = ESP_FAIL;
  721. sdmmc_host_t host = SDMMC_HOST_DEFAULT();
  722. sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
  723. esp_vfs_fat_sdmmc_mount_config_t mount_config = {
  724. .format_if_mount_failed = false,
  725. .max_files = 10,
  726. };
  727. sdmmc_card_t *card;
  728.  
  729. Serial.println("Mounting SD card...");
  730. ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
  731.  
  732. if (ret == ESP_OK) {
  733. Serial.println("SD card mount successfully!");
  734. } else {
  735. Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret));
  736. major_fail();
  737. }
  738.  
  739. Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ??
  740. }
  741.  
  742.  
  743. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  744. //
  745. // Make the avi move in 4 pieces
  746. //
  747. // make_avi() called in every loop, which calls below, depending on conditions
  748. // start_avi() - open the file and write headers
  749. // another_pic_avi() - write one more frame of movie
  750. // end_avi() - write the final parameters and close the file
  751.  
  752.  
  753.  
  754. void make_avi( ) {
  755.  
  756. // we are recording, but no file is open
  757.  
  758. if (newfile == 0 && recording == 1) { // open the file
  759.  
  760. digitalWrite(33, HIGH);
  761. newfile = 1;
  762. start_avi();
  763.  
  764. } else {
  765.  
  766. // we have a file open, but not recording
  767.  
  768. if (newfile == 1 && recording == 0) { // got command to close file
  769.  
  770. digitalWrite(33, LOW);
  771. end_avi();
  772.  
  773. Serial.println("Done capture due to command");
  774.  
  775. frames_so_far = total_frames;
  776.  
  777. newfile = 0; // file is closed
  778. recording = 0; // DO NOT start another recording
  779.  
  780. } else {
  781.  
  782. if (newfile == 1 && recording == 1) { // regular recording
  783.  
  784. if (frames_so_far >= total_frames) { // we are done the recording
  785.  
  786. Serial.println("Done capture for total frames!");
  787.  
  788. digitalWrite(33, LOW); // close the file
  789. end_avi();
  790.  
  791. frames_so_far = 0;
  792. newfile = 0; // file is closed
  793.  
  794. if (repeat > 0) {
  795. recording = 1; // start another recording
  796. repeat = repeat - 1;
  797. } else {
  798. recording = 0;
  799. }
  800.  
  801. } else if ((millis() - startms) > (total_frames * capture_interval)) {
  802.  
  803. Serial.println (" "); Serial.println("Done capture for time");
  804. Serial.print("Time Elapsed: "); Serial.print(millis() - startms); Serial.print(" Frames: "); Serial.println(frame_cnt);
  805. Serial.print("Config: "); Serial.print(total_frames * capture_interval ) ; Serial.print(" (");
  806. Serial.print(total_frames); Serial.print(" x "); Serial.print(capture_interval); Serial.println(")");
  807.  
  808. digitalWrite(33, LOW); // close the file
  809.  
  810. end_avi();
  811.  
  812. frames_so_far = 0;
  813. newfile = 0; // file is closed
  814. if (repeat > 0) {
  815. recording = 1; // start another recording
  816. repeat = repeat - 1;
  817. } else {
  818. recording = 0;
  819. }
  820.  
  821. } else { // regular
  822.  
  823. another_save_avi();
  824.  
  825. }
  826. }
  827. }
  828. }
  829. }
  830.  
  831. static esp_err_t config_camera() {
  832.  
  833. camera_config_t config;
  834.  
  835. Serial.println("config camera");
  836.  
  837. if (new_config > 2) {
  838.  
  839. config.ledc_channel = LEDC_CHANNEL_0;
  840. config.ledc_timer = LEDC_TIMER_0;
  841. config.pin_d0 = Y2_GPIO_NUM;
  842. config.pin_d1 = Y3_GPIO_NUM;
  843. config.pin_d2 = Y4_GPIO_NUM;
  844. config.pin_d3 = Y5_GPIO_NUM;
  845. config.pin_d4 = Y6_GPIO_NUM;
  846. config.pin_d5 = Y7_GPIO_NUM;
  847. config.pin_d6 = Y8_GPIO_NUM;
  848. config.pin_d7 = Y9_GPIO_NUM;
  849. config.pin_xclk = XCLK_GPIO_NUM;
  850. config.pin_pclk = PCLK_GPIO_NUM;
  851. config.pin_vsync = VSYNC_GPIO_NUM;
  852. config.pin_href = HREF_GPIO_NUM;
  853. config.pin_sscb_sda = SIOD_GPIO_NUM;
  854. config.pin_sscb_scl = SIOC_GPIO_NUM;
  855. config.pin_pwdn = PWDN_GPIO_NUM;
  856. config.pin_reset = RESET_GPIO_NUM;
  857. config.xclk_freq_hz = 20000000;
  858. config.pixel_format = PIXFORMAT_JPEG;
  859.  
  860. if (new_config == 3) {
  861.  
  862. config.frame_size = FRAMESIZE_VGA;
  863. fb_max = 20; // from 12
  864. new_config = 1;
  865. } else {
  866. config.frame_size = FRAMESIZE_UXGA;
  867. fb_max = 4;
  868. new_config = 2;
  869. }
  870.  
  871. config.jpeg_quality = 5;
  872. config.fb_count = fb_max;
  873.  
  874. print_stats("Before deinit() runs on Core: ");
  875.  
  876. esp_camera_deinit();
  877.  
  878. print_stats("After deinit() runs on Core: ");
  879.  
  880. // camera init
  881. cam_err = esp_camera_init(&config);
  882. if (cam_err != ESP_OK) {
  883. Serial.printf("Camera init failed with error 0x%x", cam_err);
  884. major_fail();
  885. }
  886.  
  887. print_stats("After the new init runs on Core: ");
  888.  
  889. delay(500);
  890. }
  891.  
  892. sensor_t * ss = esp_camera_sensor_get();
  893. ss->set_quality(ss, quality);
  894. ss->set_framesize(ss, (framesize_t)framesize);
  895. if (gray == 1) {
  896. ss->set_special_effect(ss, 2); // 0 regular, 2 grayscale
  897. } else {
  898. ss->set_special_effect(ss, 0); // 0 regular, 2 grayscale
  899. }
  900.  
  901. //Serial.println("after the sensor stuff");
  902.  
  903. for (int j = 0; j < 5; j++) {
  904. do_fb(); // start the camera ... warm it up
  905. delay(20);
  906. }
  907.  
  908. }
  909.  
  910. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  911. //
  912. // start_avi - open the files and write in headers
  913. //
  914.  
  915. static esp_err_t start_avi() {
  916.  
  917. Serial.println("Starting an avi ");
  918.  
  919. config_camera();
  920.  
  921. time(&now);
  922. localtime_r(&now, &timeinfo);
  923.  
  924. strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo);
  925.  
  926. char fname[100];
  927.  
  928. if (framesize == 6) {
  929. sprintf(fname, "/sdcard/%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed);
  930. } else if (framesize == 7) {
  931. sprintf(fname, "/sdcard/%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed);
  932. } else if (framesize == 10) {
  933. sprintf(fname, "/sdcard/%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed);
  934. } else if (framesize == 5) {
  935. sprintf(fname, "/sdcard/%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed);
  936. } else {
  937. Serial.println("Wrong framesize");
  938. sprintf(fname, "/sdcard/%s_xxx_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed);
  939. }
  940.  
  941. Serial.print("\nFile name will be >"); Serial.print(fname); Serial.println("<");
  942.  
  943. avifile = fopen(fname, "w");
  944. idxfile = fopen("/sdcard/idx.tmp", "w");
  945.  
  946. if (avifile != NULL) {
  947.  
  948. //Serial.printf("File open: %s\n", fname);
  949.  
  950. } else {
  951. Serial.println("Could not open file");
  952. major_fail();
  953. }
  954.  
  955. if (idxfile != NULL) {
  956.  
  957. //Serial.printf("File open: %s\n", "/sdcard/idx.tmp");
  958.  
  959. } else {
  960. Serial.println("Could not open file");
  961. major_fail();
  962. }
  963.  
  964.  
  965. for ( i = 0; i < AVIOFFSET; i++)
  966. {
  967. char ch = pgm_read_byte(&avi_header[i]);
  968. buf[i] = ch;
  969. }
  970.  
  971. size_t err = fwrite(buf, 1, AVIOFFSET, avifile);
  972.  
  973. if (framesize == 6) {
  974.  
  975. fseek(avifile, 0x40, SEEK_SET);
  976. err = fwrite(vga_w, 1, 2, avifile);
  977. fseek(avifile, 0xA8, SEEK_SET);
  978. err = fwrite(vga_w, 1, 2, avifile);
  979. fseek(avifile, 0x44, SEEK_SET);
  980. err = fwrite(vga_h, 1, 2, avifile);
  981. fseek(avifile, 0xAC, SEEK_SET);
  982. err = fwrite(vga_h, 1, 2, avifile);
  983.  
  984. } else if (framesize == 10) {
  985.  
  986. fseek(avifile, 0x40, SEEK_SET);
  987. err = fwrite(uxga_w, 1, 2, avifile);
  988. fseek(avifile, 0xA8, SEEK_SET);
  989. err = fwrite(uxga_w, 1, 2, avifile);
  990. fseek(avifile, 0x44, SEEK_SET);
  991. err = fwrite(uxga_h, 1, 2, avifile);
  992. fseek(avifile, 0xAC, SEEK_SET);
  993. err = fwrite(uxga_h, 1, 2, avifile);
  994.  
  995. } else if (framesize == 7) {
  996.  
  997. fseek(avifile, 0x40, SEEK_SET);
  998. err = fwrite(svga_w, 1, 2, avifile);
  999. fseek(avifile, 0xA8, SEEK_SET);
  1000. err = fwrite(svga_w, 1, 2, avifile);
  1001. fseek(avifile, 0x44, SEEK_SET);
  1002. err = fwrite(svga_h, 1, 2, avifile);
  1003. fseek(avifile, 0xAC, SEEK_SET);
  1004. err = fwrite(svga_h, 1, 2, avifile);
  1005.  
  1006. } else if (framesize == 5) {
  1007.  
  1008. fseek(avifile, 0x40, SEEK_SET);
  1009. err = fwrite(cif_w, 1, 2, avifile);
  1010. fseek(avifile, 0xA8, SEEK_SET);
  1011. err = fwrite(cif_w, 1, 2, avifile);
  1012. fseek(avifile, 0x44, SEEK_SET);
  1013. err = fwrite(cif_h, 1, 2, avifile);
  1014. fseek(avifile, 0xAC, SEEK_SET);
  1015. err = fwrite(cif_h, 1, 2, avifile);
  1016. }
  1017.  
  1018. fseek(avifile, AVIOFFSET, SEEK_SET);
  1019.  
  1020. Serial.print(F("\nRecording "));
  1021. Serial.print(total_frames);
  1022. Serial.println(F(" video frames ...\n"));
  1023.  
  1024. startms = millis();
  1025. bigdelta = millis();
  1026. totalp = 0;
  1027. totalw = 0;
  1028. overtime_count = 0;
  1029. jpeg_size = 0;
  1030. movi_size = 0;
  1031. uVideoLen = 0;
  1032. idx_offset = 4;
  1033.  
  1034.  
  1035. frame_cnt = 0;
  1036. frames_so_far = 0;
  1037.  
  1038. skipping = 0;
  1039. skipped = 0;
  1040.  
  1041. newfile = 1;
  1042.  
  1043. other_cpu_active = 1;
  1044.  
  1045. } // end of start avi
  1046.  
  1047. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1048. //
  1049. // another_save_avi runs on cpu 1, saves another frame to the avi file
  1050. //
  1051. // the "baton" semaphore makes sure that only one cpu is using the camera subsystem at a time
  1052. //
  1053.  
  1054. static esp_err_t another_save_avi() {
  1055.  
  1056. xSemaphoreTake( baton, portMAX_DELAY );
  1057.  
  1058. if (fb_in == fb_out) { // nothing to do
  1059.  
  1060. xSemaphoreGive( baton );
  1061.  
  1062. } else {
  1063.  
  1064. //if ( (fb_in + fb_max - fb_out) % fb_max > 3) { // more than 1 in queue ??
  1065. //Serial.print(millis()); Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out);
  1066. //}
  1067.  
  1068. fb_out = (fb_out + 1) % fb_max;
  1069.  
  1070. int fblen;
  1071. fblen = fb_q[fb_out]->len;
  1072.  
  1073. //xSemaphoreGive( baton );
  1074.  
  1075. digitalWrite(33, LOW);
  1076.  
  1077. jpeg_size = fblen;
  1078. movi_size += jpeg_size;
  1079. uVideoLen += jpeg_size;
  1080.  
  1081. bw = millis();
  1082. size_t dc_err = fwrite(dc_buf, 1, 4, avifile);
  1083. size_t ze_err = fwrite(zero_buf, 1, 4, avifile);
  1084.  
  1085. //bw = millis();
  1086. size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile);
  1087. if (err == 0 ) {
  1088. Serial.println("Error on avi write");
  1089. major_fail();
  1090. }
  1091.  
  1092. //xSemaphoreTake( baton, portMAX_DELAY );
  1093. esp_camera_fb_return(fb_q[fb_out]); // release that buffer back to the camera system
  1094. xSemaphoreGive( baton );
  1095.  
  1096. remnant = (4 - (jpeg_size & 0x00000003)) & 0x00000003;
  1097.  
  1098. print_quartet(idx_offset, idxfile);
  1099. print_quartet(jpeg_size, idxfile);
  1100.  
  1101. idx_offset = idx_offset + jpeg_size + remnant + 8;
  1102.  
  1103. jpeg_size = jpeg_size + remnant;
  1104. movi_size = movi_size + remnant;
  1105. if (remnant > 0) {
  1106. size_t rem_err = fwrite(zero_buf, 1, remnant, avifile);
  1107. }
  1108.  
  1109. fileposition = ftell (avifile); // Here, we are at end of chunk (after padding)
  1110. fseek(avifile, fileposition - jpeg_size - 4, SEEK_SET); // Here we are the the 4-bytes blank placeholder
  1111.  
  1112. print_quartet(jpeg_size, avifile); // Overwrite placeholder with actual frame size (without padding)
  1113.  
  1114. fileposition = ftell (avifile);
  1115.  
  1116. fseek(avifile, fileposition + 6, SEEK_SET); // Here is the FOURCC "JFIF" (JPEG header)
  1117. // Overwrite "JFIF" (still images) with more appropriate "AVI1"
  1118.  
  1119. size_t av_err = fwrite(avi1_buf, 1, 4, avifile);
  1120.  
  1121. fileposition = ftell (avifile);
  1122. fseek(avifile, fileposition + jpeg_size - 10 , SEEK_SET);
  1123. //Serial.println("Write done");
  1124. totalw = totalw + millis() - bw;
  1125.  
  1126. //if (((fb_in + fb_max - fb_out) % fb_max) > 0 ) {
  1127. // Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print(" ");
  1128. //}
  1129.  
  1130. digitalWrite(33, HIGH);
  1131. }
  1132. } // end of another_pic_avi
  1133.  
  1134. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1135. //
  1136. // end_avi runs on cpu 1, empties the queue of frames, writes the index, and closes the files
  1137. //
  1138.  
  1139. static esp_err_t end_avi() {
  1140.  
  1141. unsigned long current_end = 0;
  1142.  
  1143. other_cpu_active = 0 ;
  1144.  
  1145. Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out);
  1146.  
  1147. for (int i = 0; i < fb_max; i++) { // clear the queue
  1148. another_save_avi();
  1149. }
  1150.  
  1151. Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out);
  1152.  
  1153. current_end = ftell (avifile);
  1154.  
  1155. Serial.println("End of avi - closing the files");
  1156.  
  1157. elapsedms = millis() - startms;
  1158. float fRealFPS = (1000.0f * (float)frame_cnt) / ((float)elapsedms) * xspeed;
  1159. float fmicroseconds_per_frame = 1000000.0f / fRealFPS;
  1160. uint8_t iAttainedFPS = round(fRealFPS);
  1161. uint32_t us_per_frame = round(fmicroseconds_per_frame);
  1162.  
  1163.  
  1164. //Modify the MJPEG header from the beginning of the file, overwriting various placeholders
  1165.  
  1166. fseek(avifile, 4 , SEEK_SET);
  1167. print_quartet(movi_size + 240 + 16 * frame_cnt + 8 * frame_cnt, avifile);
  1168.  
  1169. fseek(avifile, 0x20 , SEEK_SET);
  1170. print_quartet(us_per_frame, avifile);
  1171.  
  1172. unsigned long max_bytes_per_sec = movi_size * iAttainedFPS / frame_cnt;
  1173.  
  1174. fseek(avifile, 0x24 , SEEK_SET);
  1175. print_quartet(max_bytes_per_sec, avifile);
  1176.  
  1177. fseek(avifile, 0x30 , SEEK_SET);
  1178. print_quartet(frame_cnt, avifile);
  1179.  
  1180. fseek(avifile, 0x8c , SEEK_SET);
  1181. print_quartet(frame_cnt, avifile);
  1182.  
  1183. fseek(avifile, 0x84 , SEEK_SET);
  1184. print_quartet((int)iAttainedFPS, avifile);
  1185.  
  1186. fseek(avifile, 0xe8 , SEEK_SET);
  1187. print_quartet(movi_size + frame_cnt * 8 + 4, avifile);
  1188.  
  1189. Serial.println(F("\n*** Video recorded and saved ***\n"));
  1190. Serial.print(F("Recorded "));
  1191. Serial.print(elapsedms / 1000);
  1192. Serial.print(F("s in "));
  1193. Serial.print(frame_cnt);
  1194. Serial.print(F(" frames\nFile size is "));
  1195. Serial.print(movi_size + 12 * frame_cnt + 4);
  1196. Serial.print(F(" bytes\nActual FPS is "));
  1197. Serial.print(fRealFPS, 2);
  1198. Serial.print(F("\nMax data rate is "));
  1199. Serial.print(max_bytes_per_sec);
  1200. Serial.print(F(" byte/s\nFrame duration is ")); Serial.print(us_per_frame); Serial.println(F(" us"));
  1201. Serial.print(F("Average frame length is ")); Serial.print(uVideoLen / frame_cnt); Serial.println(F(" bytes"));
  1202. Serial.print("Average picture time (ms) "); Serial.println( totalp / frame_cnt );
  1203. Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt );
  1204. Serial.print("Frames Skipped % "); Serial.println( 100.0 * skipped / frame_cnt, 1 );
  1205.  
  1206. Serial.println("Writing the index");
  1207.  
  1208. fseek(avifile, current_end, SEEK_SET);
  1209.  
  1210. fclose(idxfile);
  1211.  
  1212. size_t i1_err = fwrite(idx1_buf, 1, 4, avifile);
  1213.  
  1214. print_quartet(frame_cnt * 16, avifile);
  1215.  
  1216. idxfile = fopen("/sdcard/idx.tmp", "r");
  1217.  
  1218. if (idxfile != NULL) {
  1219.  
  1220. //Serial.printf("File open: %s\n", "/sdcard/idx.tmp");
  1221.  
  1222. } else {
  1223. Serial.println("Could not open file");
  1224. //major_fail();
  1225. }
  1226.  
  1227. char * AteBytes;
  1228. AteBytes = (char*) malloc (8);
  1229.  
  1230. for (int i = 0; i < frame_cnt; i++) {
  1231.  
  1232. size_t res = fread ( AteBytes, 1, 8, idxfile);
  1233. size_t i1_err = fwrite(dc_buf, 1, 4, avifile);
  1234. size_t i2_err = fwrite(zero_buf, 1, 4, avifile);
  1235. size_t i3_err = fwrite(AteBytes, 1, 8, avifile);
  1236.  
  1237. }
  1238.  
  1239. free(AteBytes);
  1240.  
  1241. fclose(idxfile);
  1242. fclose(avifile);
  1243.  
  1244. Serial.println("---");
  1245. //WiFi.printDiag(Serial);
  1246.  
  1247. }
  1248.  
  1249. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1250. //
  1251. // do_fb - just takes a picture and discards it
  1252. //
  1253.  
  1254. static esp_err_t do_fb() {
  1255. xSemaphoreTake( baton, portMAX_DELAY );
  1256. camera_fb_t * fb = esp_camera_fb_get();
  1257.  
  1258. Serial.print("Pic, len="); Serial.println(fb->len);
  1259.  
  1260. esp_camera_fb_return(fb);
  1261. xSemaphoreGive( baton );
  1262. }
  1263.  
  1264. void do_time() {
  1265.  
  1266. int numberOfNetworks = WiFi.scanNetworks();
  1267.  
  1268. Serial.print("Number of networks found: ");
  1269. Serial.println(numberOfNetworks);
  1270.  
  1271. }
  1272.  
  1273. ////////////////////////////////////////////////////////////////////////////////////
  1274. //
  1275. // some globals for the loop()
  1276. //
  1277.  
  1278. long wakeup;
  1279. long last_wakeup = 0;
  1280.  
  1281.  
  1282. void loop()
  1283. {
  1284.  
  1285. if (WiFi.status() != WL_CONNECTED) {
  1286. init_wifi();
  1287. Serial.println("***** WiFi reconnect *****");
  1288. }
  1289.  
  1290. wakeup = millis();
  1291. if (wakeup - last_wakeup > (10 * 60 * 1000) ) { // 10 minutes
  1292. last_wakeup = millis();
  1293.  
  1294. print_stats("Wakeup in loop() Core: ");
  1295. }
  1296.  
  1297. ftpSrv.handleFTP();
  1298. delay(10);
  1299.  
  1300. }
  1301.  
  1302.  
  1303. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1304. //
  1305. //
  1306.  
  1307. static esp_err_t capture_handler(httpd_req_t *req) {
  1308.  
  1309. camera_fb_t * fb = NULL;
  1310. esp_err_t res = ESP_OK;
  1311. char fname[100];
  1312. xSemaphoreTake( baton, portMAX_DELAY );
  1313. fb = esp_camera_fb_get();
  1314.  
  1315. if (!fb) {
  1316. Serial.println("Camera capture failed");
  1317. httpd_resp_send_500(req);
  1318. xSemaphoreGive( baton );
  1319. return ESP_FAIL;
  1320. }
  1321.  
  1322. file_number++;
  1323.  
  1324. sprintf(fname, "inline; filename=capture_%d.jpg", file_number);
  1325.  
  1326. httpd_resp_set_type(req, "image/jpeg");
  1327. httpd_resp_set_hdr(req, "Content-Disposition", fname);
  1328.  
  1329. size_t out_len, out_width, out_height;
  1330. size_t fb_len = 0;
  1331. fb_len = fb->len;
  1332. res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
  1333. esp_camera_fb_return(fb);
  1334. xSemaphoreGive( baton );
  1335. return res;
  1336. }
  1337.  
  1338. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1339. //
  1340. //
  1341. static esp_err_t stop_handler(httpd_req_t *req) {
  1342.  
  1343. esp_err_t res = ESP_OK;
  1344.  
  1345. recording = 0;
  1346. Serial.println("stopping recording");
  1347.  
  1348. do_stop("Stopping previous recording");
  1349.  
  1350. httpd_resp_send(req, the_page, strlen(the_page));
  1351. return ESP_OK;
  1352.  
  1353. }
  1354.  
  1355. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1356. //
  1357. //
  1358. static esp_err_t start_handler(httpd_req_t *req) {
  1359.  
  1360. esp_err_t res = ESP_OK;
  1361.  
  1362. char buf[80];
  1363. size_t buf_len;
  1364. char new_res[20];
  1365.  
  1366. if (recording == 1) {
  1367. const char* resp = "You must Stop recording, before starting a new one. Start over ...";
  1368. httpd_resp_send(req, resp, strlen(resp));
  1369.  
  1370. return ESP_OK;
  1371. return res;
  1372.  
  1373. } else {
  1374. //recording = 1;
  1375. Serial.println("starting recording");
  1376.  
  1377. sensor_t * s = esp_camera_sensor_get();
  1378.  
  1379. int new_interval = capture_interval;
  1380. int new_length = capture_interval * total_frames;
  1381.  
  1382. int new_framesize = s->status.framesize;
  1383. int new_quality = s->status.quality;
  1384. int new_repeat = 0;
  1385. int new_xspeed = 1;
  1386. int new_xlength = 3;
  1387. int new_gray = 0;
  1388.  
  1389. Serial.println("");
  1390. Serial.println("Current Parameters :");
  1391. Serial.print(" Capture Interval = "); Serial.print(capture_interval); Serial.println(" ms");
  1392. Serial.print(" Length = "); Serial.print(capture_interval * total_frames / 1000); Serial.println(" s");
  1393. Serial.print(" Quality = "); Serial.println(new_quality);
  1394. Serial.print(" Framesize = "); Serial.println(new_framesize);
  1395. Serial.print(" Repeat = "); Serial.println(repeat);
  1396. Serial.print(" Speed = "); Serial.println(xspeed);
  1397.  
  1398. buf_len = httpd_req_get_url_query_len(req) + 1;
  1399. if (buf_len > 1) {
  1400. if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
  1401. ESP_LOGI(TAG, "Found URL query => %s", buf);
  1402. char param[32];
  1403. /* Get value of expected key from query string */
  1404. Serial.println(" ... parameters");
  1405. if (httpd_query_key_value(buf, "length", param, sizeof(param)) == ESP_OK) {
  1406.  
  1407. int x = atoi(param);
  1408. if (x >= 5 && x <= 3600 * 24 ) { // 5 sec to 24 hours
  1409. new_length = x;
  1410. }
  1411.  
  1412. ESP_LOGI(TAG, "Found URL query parameter => length=%s", param);
  1413.  
  1414. }
  1415. if (httpd_query_key_value(buf, "repeat", param, sizeof(param)) == ESP_OK) {
  1416. int x = atoi(param);
  1417. if (x >= 0 ) {
  1418. new_repeat = x;
  1419. }
  1420.  
  1421. ESP_LOGI(TAG, "Found URL query parameter => repeat=%s", param);
  1422. }
  1423. if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) {
  1424. if (strcmp(new_res, "UXGA") == 0) {
  1425. new_framesize = 10;
  1426. } else if (strcmp(new_res, "SVGA") == 0) {
  1427. new_framesize = 7;
  1428. } else if (strcmp(new_res, "VGA") == 0) {
  1429. new_framesize = 6;
  1430. } else if (strcmp(new_res, "CIF") == 0) {
  1431. new_framesize = 5;
  1432. } else {
  1433. Serial.println("Only UXGA, SVGA, VGA, and CIF are valid!");
  1434.  
  1435. }
  1436. ESP_LOGI(TAG, "Found URL query parameter => framesize=%s", new_res);
  1437. }
  1438. if (httpd_query_key_value(buf, "quality", param, sizeof(param)) == ESP_OK) {
  1439.  
  1440. int x = atoi(param);
  1441. if (x >= 5 && x <= 50) {
  1442. new_quality = x;
  1443. }
  1444.  
  1445. ESP_LOGI(TAG, "Found URL query parameter => quality=%s", param);
  1446. }
  1447.  
  1448. if (httpd_query_key_value(buf, "speed", param, sizeof(param)) == ESP_OK) {
  1449.  
  1450. int x = atoi(param);
  1451. if (x >= 1 && x <= 100) {
  1452. new_xspeed = x;
  1453. }
  1454.  
  1455. ESP_LOGI(TAG, "Found URL query parameter => speed=%s", param);
  1456. }
  1457.  
  1458. if (httpd_query_key_value(buf, "gray", param, sizeof(param)) == ESP_OK) {
  1459.  
  1460. int x = atoi(param);
  1461. if (x == 1 ) {
  1462. new_gray = x;
  1463. }
  1464.  
  1465. ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param);
  1466. }
  1467.  
  1468. if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) {
  1469.  
  1470. int x = atoi(param);
  1471. if (x >= 1 && x <= 108000) { // 108,000 ms = 30 min
  1472. new_interval = x;
  1473. }
  1474. ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param);
  1475. }
  1476. }
  1477. }
  1478.  
  1479. framesize = new_framesize;
  1480. capture_interval = new_interval;
  1481. xlength = new_length;
  1482. total_frames = new_length * 1000 / capture_interval;
  1483. repeat = new_repeat;
  1484. quality = new_quality;
  1485. xspeed = new_xspeed;
  1486. gray = new_gray;
  1487.  
  1488. if ((new_config == 1) && (framesize > 6)) {
  1489. new_config = 4;
  1490. Serial.println("from VGA to UXGA");
  1491. } else if ((new_config == 2) && (framesize < 7)) {
  1492. new_config = 3;
  1493. Serial.println("from UXGA to VGA");
  1494. }
  1495.  
  1496.  
  1497. do_start("Starting a new AVI");
  1498. httpd_resp_send(req, the_page, strlen(the_page));
  1499.  
  1500.  
  1501.  
  1502. recording = 1;
  1503. return ESP_OK;
  1504. }
  1505. }
  1506.  
  1507. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1508. //
  1509. //
  1510. void do_start(char *the_message) {
  1511.  
  1512. Serial.print("do_start "); Serial.println(the_message);
  1513.  
  1514. const char msg[] PROGMEM = R"rawliteral(<!doctype html>
  1515. <html>
  1516. <head>
  1517. <meta charset="utf-8">
  1518. <meta name="viewport" content="width=device-width,initial-scale=1">
  1519. <title>ESP32-CAM Video Recorder</title>
  1520. </head>
  1521. <body>
  1522. <h1>ESP32-CAM Video Recorder v39</h1><br>
  1523. <h2>Message is <font color="red">%s</font></h2><br>
  1524. Recording = %d (1 is active)<br>
  1525. Capture Interval = %d ms<br>
  1526. Length = %d seconds<br>
  1527. Quality = %d (5 best to 50 worst)<br>
  1528. Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)<br>
  1529. Repeat = %d<br>
  1530. Speed = %d<br>
  1531. Gray = %d<br><br>
  1532.  
  1533. <br>
  1534. <br><div id="image-container"></div>
  1535.  
  1536. </body>
  1537. </html>)rawliteral";
  1538.  
  1539.  
  1540. sprintf(the_page, msg, the_message, recording, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray);
  1541. Serial.println(strlen(msg));
  1542.  
  1543. }
  1544.  
  1545. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1546. //
  1547. //
  1548. void do_stop(char *the_message) {
  1549.  
  1550. Serial.print("do_stop "); Serial.println(the_message);
  1551.  
  1552.  
  1553. const char msg[] PROGMEM = R"rawliteral(<!doctype html>
  1554. <html>
  1555. <head>
  1556. <meta charset="utf-8">
  1557. <meta name="viewport" content="width=device-width,initial-scale=1">
  1558. <title>ESP32-CAM Video Recorder</title>
  1559. </head>
  1560. <body>
  1561. <h1>ESP32-CAM Video Recorder v39</h1><br>
  1562. <h2>Message is <font color="red">%s</font></h2><br>
  1563. <h2><a href="http://%s/">http://%s/</a></h2><br>
  1564. Information and viewfinder<br><br>
  1565. <h2><a href="http://%s/start?framesize=VGA&length=1800&interval=83&quality=10&repeat=100&speed=1&gray=0">http://%s/start?framesize=VGA&length=1800&interval=83&quality=10&repeat=100&speed=1&gray=0</a></h2><br>
  1566. VGA 12 fps - VGA 640x480, video of 1800 seconds (30 min), picture every 83 ms, jpeg quality 10, repeat for 100 more of the same and play back at 1x actual fps, and don't make it grayscale<br><br>
  1567. <h2><a href="http://%s/start?framesize=UXGA&length=1800&interval=1000&quality=10&repeat=100&speed=30&gray=0">UXGA 1 sec per frame, for 30 minutes repeat, 30x playback</a></h2><br>
  1568. <h2><a href="http://%s/start?framesize=UXGA&length=1800&interval=500&quality=10&repeat=100&speed=1&gray=0">UXGA 2 fps for 30 minutes repeat</a></h2><br>
  1569. <h2><a href="http://%s/start?framesize=CIF&length=1800&interval=42&quality=10&repeat=100&speed=1&gray=0">CIF 24 fps second for 30 minutes repeat</a></h2><br>
  1570.  
  1571. <br>
  1572. <br><div id="image-container"></div>
  1573.  
  1574. </body>
  1575. </html>)rawliteral";
  1576.  
  1577.  
  1578. sprintf(the_page, msg, the_message, localip, localip, localip, localip, localip, localip, localip);
  1579.  
  1580. }
  1581.  
  1582.  
  1583. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1584. //
  1585. //
  1586. void do_status(char *the_message) {
  1587.  
  1588. Serial.print("do_status "); Serial.println(the_message);
  1589.  
  1590. const char msg[] PROGMEM = R"rawliteral(<!doctype html>
  1591. <html>
  1592. <head>
  1593. <meta charset="utf-8">
  1594. <meta name="viewport" content="width=device-width,initial-scale=1">
  1595. <title>ESP32-CAM Video Recorder</title>
  1596. </head>
  1597. <body>
  1598. <h1>ESP32-CAM Video Recorder v39<br><font color="red">%s</font></h1><br>
  1599. <h2>Message is <font color="red">%s</font></h2><br>
  1600. Recording = %d (1 is active)<br>
  1601. Frame %d of %d, Skipped %d<br><br>
  1602. Capture Interval = %d ms<br>
  1603. Length = %d seconds<br>
  1604. Quality = %d (5 best to 50 worst)<br>
  1605. Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)<br>
  1606. Repeat = %d<br>
  1607. Playback Speed = %d<br>
  1608. Gray = %d<br><br>
  1609. Commands as follows for your ESP's ip address:<br><br>
  1610. <h2><a href="http://%s/">http://%s/</a></h2><br>
  1611. Information and viewfinder<br><br>
  1612. <h2><a href="http://%s/stop">http://%s/stop ... and restart</a></h2><br>
  1613. You must "stop" before starting with new parameters<br><br>
  1614. <br>
  1615. <h2><a href="ftp://%s/">ftp://%s/</a></h2><br>
  1616. Username: esp, Password: esp ... to download the files<br><br>
  1617. Red LED on back of ESP will flash with every frame, and flash SOS if camera or sd card not working.<br>
  1618.  
  1619. <br>
  1620. Check camera position with the frames below every 5 seconds for 5 pictures <br>
  1621. Refresh page for more.<br>
  1622. <br><div id="image-container"></div>
  1623. <script>
  1624. document.addEventListener('DOMContentLoaded', function() {
  1625. var c = document.location.origin;
  1626. const ic = document.getElementById('image-container');
  1627. var i = 1;
  1628.  
  1629. var timing = 5000;
  1630.  
  1631. function loop() {
  1632. ic.insertAdjacentHTML('beforeend','<img src="'+`${c}/capture?_cb=${Date.now()}`+'">')
  1633. i = i + 1;
  1634. if ( i < 6 ) {
  1635. window.setTimeout(loop, timing);
  1636. }
  1637. }
  1638. loop();
  1639.  
  1640. })
  1641. </script><br>
  1642. </body>
  1643. </html>)rawliteral";
  1644.  
  1645. time(&now);
  1646. const char *strdate = ctime(&now);
  1647.  
  1648.  
  1649. sprintf(the_page, msg, strdate, the_message, recording, frames_so_far, total_frames, skipped, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip);
  1650.  
  1651. }
  1652.  
  1653.  
  1654. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1655. //
  1656. //
  1657. static esp_err_t index_handler(httpd_req_t *req) {
  1658.  
  1659. print_stats("Index Handler Core: ");
  1660.  
  1661. do_status("Refresh Status");
  1662. httpd_resp_send(req, the_page, strlen(the_page));
  1663. return ESP_OK;
  1664. }
  1665.  
  1666. void startCameraServer() {
  1667. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  1668.  
  1669. httpd_uri_t index_uri = {
  1670. .uri = "/",
  1671. .method = HTTP_GET,
  1672. .handler = index_handler,
  1673. .user_ctx = NULL
  1674. };
  1675. httpd_uri_t capture_uri = {
  1676. .uri = "/capture",
  1677. .method = HTTP_GET,
  1678. .handler = capture_handler,
  1679. .user_ctx = NULL
  1680. };
  1681.  
  1682. httpd_uri_t file_stop = {
  1683. .uri = "/stop",
  1684. .method = HTTP_GET,
  1685. .handler = stop_handler,
  1686. .user_ctx = NULL
  1687. };
  1688.  
  1689. httpd_uri_t file_start = {
  1690. .uri = "/start",
  1691. .method = HTTP_GET,
  1692. .handler = start_handler,
  1693. .user_ctx = NULL
  1694. };
  1695.  
  1696. if (httpd_start(&camera_httpd, &config) == ESP_OK) {
  1697. httpd_register_uri_handler(camera_httpd, &index_uri);
  1698. httpd_register_uri_handler(camera_httpd, &capture_uri);
  1699. httpd_register_uri_handler(camera_httpd, &file_start);
  1700. httpd_register_uri_handler(camera_httpd, &file_stop);
  1701. }
  1702. }
  1703.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement