936 wiersze
29 KiB
C++
936 wiersze
29 KiB
C++
/*
|
|
|
|
|
|
ESP32-CAM-Video-Recorder-junior
|
|
|
|
This program records an mjpeg avi video to the sd card of an ESP32-CAM.
|
|
|
|
It is the junior version of https://github.com/jameszah/ESP32-CAM-Video-Recorder
|
|
which has 100 other features of wifi, streaming video, http control, telegram updates, pir control,
|
|
touch control, ftp downloads, .... and other things that make it very big and complex.
|
|
|
|
This one is written in simple arduino code without any semaphores, tasks, priorities, RTOS stuff ....
|
|
|
|
Just set 4 parameters, compile and download, and it will record on power-on, until sd is full, or power-off.
|
|
Then pull out the sd and move it to your computer, and you will see all but the last file avi which died during the unplug.
|
|
|
|
Update: I added some complexity.
|
|
Connect Pin 12 to GND to stop a video -- no dead videos -- and prevent recording.
|
|
Release Pin 12 and it will record forever.
|
|
|
|
Compile Time Parameters
|
|
1. framesize 10,9,7,6,5 for 10 - UXGA (1600x1200 @ 6 fps), 9 - SXGA (1280x1024 @ 6 fps), 7 - SVGA(800x600 @ 24 fps), 6 - VGA(640x480 @ 24 fps), 5 - CIF(400x296 @ 50 fps)
|
|
2. quality - 1 to 63 - 10 is a good start, increase to 20 to get more frames per second - must be higher than jpeg_quality below
|
|
3. avi_length - seconds for each avi - it closes files, and starts another file after this time - like 60 or 1800
|
|
4. devname - a text name for your camera when the files are on your computer
|
|
|
|
Note that framesize and high quality will produce lots of bytes which have to written to the sd. Those frame rates above are
|
|
for the OV2640 camera, and your sd card will have to be able to swallow all that data before the next frame. If the
|
|
sd card cannot take all that data, then the camera will be idle waiting for the sd. Lower the framesize (UXGA -> SVGA),
|
|
and lower the quality (10 -> 15 -> 20, higher number is lower quality) to improve framerate to the camera limits.
|
|
If you have a fast enough sd card, it will record at the full speed of the camera.
|
|
|
|
Using a Lexar 633x circle10, U3, V30 SD card, with quality set at 20, it will record at full speed of the camera -- in dull indoor light.
|
|
In bright outdoor light - looking at the sun - it will slow down by half - to about 12 fps SVGA. You could lower quality to keep that
|
|
at a higher fps, if you insist on looking at the sun.
|
|
|
|
Using a dollarstore SD card - EAGET circle10, U1 - it will record at about half of the camera capacity.
|
|
|
|
You can look at the blinking red led on the back of the chip to see the recording rate -- sd chips for video are made to be more predictable.
|
|
|
|
The files will have the name such as:
|
|
|
|
desklens 10.3 + 120s.avi
|
|
|
|
"desklens" is your devname
|
|
10 - is a number stored in eprom that will increase everytime your device boots
|
|
3 - is the 3rd file created during the current boot
|
|
+120s - is an indictation of how long since we started recording on this boot
|
|
- this is the 3rd file, and started 120 seconds after the boot, so the files must be 60 seconds long
|
|
|
|
Small red led on the back blinks with every frame.
|
|
|
|
|
|
by James Zahary Sep 12, 2020
|
|
jamzah.plc@gmail.com
|
|
|
|
https://github.com/jameszah/ESP32-CAM-Video-Recorder-junior
|
|
https://github.com/jameszah/ESP32-CAM-Video-Recorder
|
|
|
|
jameszah/ESP32-CAM-Video-Recorder is licensed under the
|
|
GNU General Public License v3.0
|
|
|
|
jameszah/ESP32-CAM-Video-Recorder-junior is licensed under the
|
|
GNU General Public License v3.0
|
|
|
|
The is Arduino code, with standard setup for ESP32-CAM
|
|
- Board ESP32 Wrover Module
|
|
- Partition Scheme Huge APP (3MB No OTA)
|
|
|
|
Compiled with Arduino 1.8.12, which used these libraries:
|
|
|
|
Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC
|
|
Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS
|
|
Using library EEPROM at version 1.0.3 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\EEPROM
|
|
|
|
*/
|
|
|
|
static const char vernum[] = "v02";
|
|
static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames
|
|
|
|
int framesize = 7; // 10 UXGA, 9 SXGA, 7 SVGA, 6 VGA, 5 CIF
|
|
int quality = 20; // quality on the 1..63 scale - lower is better quality and bigger files - must be higher than the jpeg_quality in camera_config
|
|
int avi_length = 180; // how long a movie in seconds
|
|
|
|
int MagicNumber = 11; // change this number to reset the epron in your esp32
|
|
|
|
//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
|
#include "esp_log.h"
|
|
#include "esp_http_server.h"
|
|
#include "esp_camera.h"
|
|
|
|
|
|
// CAMERA_MODEL_AI_THINKER
|
|
#define PWDN_GPIO_NUM 32
|
|
#define RESET_GPIO_NUM -1
|
|
#define XCLK_GPIO_NUM 0
|
|
#define SIOD_GPIO_NUM 26
|
|
#define SIOC_GPIO_NUM 27
|
|
#define Y9_GPIO_NUM 35
|
|
#define Y8_GPIO_NUM 34
|
|
#define Y7_GPIO_NUM 39
|
|
#define Y6_GPIO_NUM 36
|
|
#define Y5_GPIO_NUM 21
|
|
#define Y4_GPIO_NUM 19
|
|
#define Y3_GPIO_NUM 18
|
|
#define Y2_GPIO_NUM 5
|
|
#define VSYNC_GPIO_NUM 25
|
|
#define HREF_GPIO_NUM 23
|
|
#define PCLK_GPIO_NUM 22
|
|
|
|
camera_fb_t * fb_curr = NULL;
|
|
camera_fb_t * fb_next = NULL;
|
|
|
|
#include <stdio.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_system.h"
|
|
#include "nvs_flash.h"
|
|
#include "nvs.h"
|
|
#include "soc/soc.h"
|
|
#include "soc/cpu.h"
|
|
#include "soc/rtc_cntl_reg.h"
|
|
|
|
static esp_err_t cam_err;
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Avi Writer Stuff here
|
|
|
|
|
|
// MicroSD
|
|
#include "driver/sdmmc_host.h"
|
|
#include "driver/sdmmc_defs.h"
|
|
#include "sdmmc_cmd.h"
|
|
#include "esp_vfs_fat.h"
|
|
#include <SD_MMC.h>
|
|
|
|
FILE *avifile = NULL;
|
|
FILE *idxfile = NULL;
|
|
|
|
long bp;
|
|
long ap;
|
|
long bw;
|
|
long aw;
|
|
|
|
int diskspeed = 0;
|
|
char fname[100];
|
|
|
|
static int i = 0;
|
|
uint8_t temp = 0, temp_last = 0;
|
|
unsigned long fileposition = 0;
|
|
uint16_t frame_cnt = 0;
|
|
uint16_t remnant = 0;
|
|
uint32_t length = 0;
|
|
uint32_t startms;
|
|
uint32_t elapsedms;
|
|
uint32_t uVideoLen = 0;
|
|
bool is_header = false;
|
|
int bad_jpg = 0;
|
|
int extend_jpg = 0;
|
|
int normal_jpg = 0;
|
|
|
|
int file_number = 0;
|
|
int file_group = 0;
|
|
long boot_time = 0;
|
|
|
|
long totalp;
|
|
long totalw;
|
|
float avgp;
|
|
float avgw;
|
|
|
|
#define BUFFSIZE 512
|
|
|
|
uint8_t buf[BUFFSIZE];
|
|
|
|
#define AVIOFFSET 240 // AVI main header length
|
|
|
|
unsigned long movi_size = 0;
|
|
unsigned long jpeg_size = 0;
|
|
unsigned long idx_offset = 0;
|
|
|
|
uint8_t zero_buf[4] = {0x00, 0x00, 0x00, 0x00};
|
|
uint8_t dc_buf[4] = {0x30, 0x30, 0x64, 0x63}; // "00dc"
|
|
uint8_t dc_and_zero_buf[8] = {0x30, 0x30, 0x64, 0x63, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
uint8_t avi1_buf[4] = {0x41, 0x56, 0x49, 0x31}; // "AVI1"
|
|
uint8_t idx1_buf[4] = {0x69, 0x64, 0x78, 0x31}; // "idx1"
|
|
|
|
uint8_t vga_w[2] = {0x80, 0x02}; // 640
|
|
uint8_t vga_h[2] = {0xE0, 0x01}; // 480
|
|
uint8_t cif_w[2] = {0x90, 0x01}; // 400
|
|
uint8_t cif_h[2] = {0x28, 0x01}; // 296
|
|
uint8_t svga_w[2] = {0x20, 0x03}; // 800
|
|
uint8_t svga_h[2] = {0x58, 0x02}; // 600
|
|
uint8_t sxga_w[2] = {0x00, 0x05}; // 1280
|
|
uint8_t sxga_h[2] = {0x00, 0x04}; // 1024
|
|
uint8_t uxga_w[2] = {0x40, 0x06}; // 1600
|
|
uint8_t uxga_h[2] = {0xB0, 0x04}; // 1200
|
|
|
|
|
|
const int avi_header[AVIOFFSET] PROGMEM = {
|
|
0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54,
|
|
0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00,
|
|
0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
|
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00,
|
|
0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73,
|
|
0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66,
|
|
0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
|
|
0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x46, 0x4F,
|
|
0x10, 0x00, 0x00, 0x00, 0x6A, 0x61, 0x6D, 0x65, 0x73, 0x7A, 0x61, 0x68, 0x61, 0x72, 0x79, 0x20,
|
|
0x76, 0x30, 0x32, 0x20, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69,
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// Writes an uint32_t in Big Endian at current file position
|
|
//
|
|
static void inline print_quartet(unsigned long i, FILE * fd)
|
|
{
|
|
uint8_t y[4];
|
|
|
|
y[0] = i % 0x100;
|
|
y[1] = (i >> 8) % 0x100;
|
|
y[2] = (i >> 16) % 0x100;
|
|
y[3] = (i >> 24) % 0x100;
|
|
|
|
size_t i1_err = fwrite(y , 1, 4, fd);
|
|
}
|
|
|
|
//
|
|
// Writes 2 uint32_t in Big Endian at current file position
|
|
//
|
|
static void inline print_2quartet(unsigned long i, unsigned long j, FILE * fd)
|
|
{
|
|
uint8_t y[8];
|
|
|
|
y[0] = i % 0x100;
|
|
y[1] = (i >> 8) % 0x100;
|
|
y[2] = (i >> 16) % 0x100;
|
|
y[3] = (i >> 24) % 0x100;
|
|
y[4] = j % 0x100;
|
|
y[5] = (j >> 8) % 0x100;
|
|
y[6] = (j >> 16) % 0x100;
|
|
y[7] = (j >> 24) % 0x100;
|
|
|
|
size_t i1_err = fwrite(y , 1, 8, fd);
|
|
}
|
|
|
|
//
|
|
// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS
|
|
//
|
|
void major_fail() {
|
|
|
|
Serial.println(" ");
|
|
|
|
for (int i = 0; i < 10; i++) { // 10 loops or about 100 seconds then reboot
|
|
for (int j = 0; j < 3; j++) {
|
|
digitalWrite(33, LOW); delay(150);
|
|
digitalWrite(33, HIGH); delay(150);
|
|
}
|
|
delay(1000);
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
digitalWrite(33, LOW); delay(500);
|
|
digitalWrite(33, HIGH); delay(500);
|
|
}
|
|
delay(1000);
|
|
Serial.print("Major Fail "); Serial.print(i); Serial.print(" / "); Serial.println(10);
|
|
}
|
|
|
|
ESP.restart();
|
|
}
|
|
|
|
static esp_err_t config_camera() {
|
|
|
|
camera_config_t config;
|
|
|
|
//Serial.println("config camera");
|
|
|
|
config.ledc_channel = LEDC_CHANNEL_0;
|
|
config.ledc_timer = LEDC_TIMER_0;
|
|
config.pin_d0 = Y2_GPIO_NUM;
|
|
config.pin_d1 = Y3_GPIO_NUM;
|
|
config.pin_d2 = Y4_GPIO_NUM;
|
|
config.pin_d3 = Y5_GPIO_NUM;
|
|
config.pin_d4 = Y6_GPIO_NUM;
|
|
config.pin_d5 = Y7_GPIO_NUM;
|
|
config.pin_d6 = Y8_GPIO_NUM;
|
|
config.pin_d7 = Y9_GPIO_NUM;
|
|
config.pin_xclk = XCLK_GPIO_NUM;
|
|
config.pin_pclk = PCLK_GPIO_NUM;
|
|
config.pin_vsync = VSYNC_GPIO_NUM;
|
|
config.pin_href = HREF_GPIO_NUM;
|
|
config.pin_sscb_sda = SIOD_GPIO_NUM;
|
|
config.pin_sscb_scl = SIOC_GPIO_NUM;
|
|
config.pin_pwdn = PWDN_GPIO_NUM;
|
|
config.pin_reset = RESET_GPIO_NUM;
|
|
|
|
config.xclk_freq_hz = 10000000; // 10000000 or 20000000 -- 10 is faster !!
|
|
|
|
config.pixel_format = PIXFORMAT_JPEG;
|
|
|
|
config.frame_size = FRAMESIZE_UXGA; // edit in framesizes below -- this must be better than the framesize specified at the top
|
|
/*
|
|
FRAMESIZE_96X96, // 96x96
|
|
FRAMESIZE_QQVGA, // 160x120
|
|
FRAMESIZE_QCIF, // 176x144
|
|
FRAMESIZE_HQVGA, // 240x176
|
|
FRAMESIZE_240X240, // 240x240
|
|
FRAMESIZE_QVGA, // 320x240
|
|
FRAMESIZE_CIF, // 400x296
|
|
FRAMESIZE_HVGA, // 480x320
|
|
FRAMESIZE_VGA, // 640x480
|
|
FRAMESIZE_SVGA, // 800x600
|
|
FRAMESIZE_XGA, // 1024x768
|
|
FRAMESIZE_HD, // 1280x720
|
|
FRAMESIZE_SXGA, // 1280x1024
|
|
FRAMESIZE_UXGA, // 1600x1200
|
|
*/
|
|
|
|
config.jpeg_quality = 6; // 1 to 63 - smaller number is higher quality and more data - must be lower rhat the quality parameter at the top
|
|
|
|
config.fb_count = 7;
|
|
|
|
// camera init
|
|
cam_err = esp_camera_init(&config);
|
|
if (cam_err != ESP_OK) {
|
|
Serial.printf("Camera init failed with error 0x%x", cam_err);
|
|
}
|
|
|
|
|
|
sensor_t * ss = esp_camera_sensor_get();
|
|
ss->set_quality(ss, quality);
|
|
ss->set_framesize(ss, (framesize_t)framesize);
|
|
|
|
ss->set_brightness(ss, 1); //up the blightness just a bit
|
|
ss->set_saturation(ss, -2); //lower the saturation
|
|
|
|
delay(500);
|
|
for (int j = 0; j < 5; j++) {
|
|
camera_fb_t * fb = esp_camera_fb_get();
|
|
//Serial.print("Pic, len="); Serial.println(fb->len);
|
|
esp_camera_fb_return(fb);
|
|
delay(50);
|
|
}
|
|
|
|
}
|
|
|
|
static esp_err_t init_sdcard()
|
|
{
|
|
|
|
pinMode(13, PULLUP);
|
|
|
|
esp_err_t ret = ESP_FAIL;
|
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
|
host.flags = SDMMC_HOST_FLAG_1BIT; // using 1 bit mode
|
|
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
|
diskspeed = host.max_freq_khz;
|
|
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
|
slot_config.width = 1; // using 1 bit mode
|
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
|
.format_if_mount_failed = false,
|
|
.max_files = 8,
|
|
};
|
|
|
|
sdmmc_card_t *card;
|
|
|
|
ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
|
|
|
|
if (ret == ESP_OK) {
|
|
Serial.println("SD card mount successfully!");
|
|
} else {
|
|
Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret));
|
|
major_fail();
|
|
}
|
|
|
|
sdmmc_card_print_info(stdout, card);
|
|
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// get_good_jpeg() - take a picture and make sure it has a good jpeg
|
|
//
|
|
camera_fb_t * get_good_jpeg() {
|
|
|
|
camera_fb_t * fb;
|
|
|
|
do {
|
|
bp = millis();
|
|
fb = esp_camera_fb_get();
|
|
totalp = totalp + millis() - bp;
|
|
|
|
int x = fb->len;
|
|
int foundffd9 = 0;
|
|
|
|
for (int j = 1; j <= 1025; j++) {
|
|
if (fb->buf[x - j] != 0xD9) {
|
|
// no d9, try next for
|
|
} else {
|
|
|
|
//Serial.println("Found a D9");
|
|
if (fb->buf[x - j - 1] == 0xFF ) {
|
|
//Serial.print("Found the FFD9, junk is "); Serial.println(j);
|
|
if (j == 1) {
|
|
normal_jpg++;
|
|
} else {
|
|
extend_jpg++;
|
|
}
|
|
if (j > 1000) { // never happens. but > 1 does, usually 400-500
|
|
Serial.print("Frame "); Serial.print(frame_cnt);
|
|
Serial.print(", Len = "); Serial.print(x);
|
|
Serial.print(", Corrent Len = "); Serial.print(x - j + 1);
|
|
Serial.print(", Extra Bytes = "); Serial.println( j - 1);
|
|
}
|
|
foundffd9 = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundffd9) {
|
|
bad_jpg++;
|
|
Serial.print("Bad jpeg, Len = "); Serial.println(x);
|
|
esp_camera_fb_return(fb);
|
|
|
|
} else {
|
|
break;
|
|
// count up the useless bytes
|
|
}
|
|
|
|
} while (1);
|
|
|
|
return fb;
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// eprom functions - increment the file_group, so files are always unique
|
|
//
|
|
|
|
#include <EEPROM.h>
|
|
|
|
struct eprom_data {
|
|
int eprom_good;
|
|
int file_group;
|
|
};
|
|
|
|
void do_eprom_read() {
|
|
|
|
eprom_data ed;
|
|
|
|
EEPROM.begin(200);
|
|
EEPROM.get(0, ed);
|
|
|
|
if (ed.eprom_good == MagicNumber) {
|
|
Serial.println("Good settings in the EPROM ");
|
|
file_group = ed.file_group;
|
|
file_group++;
|
|
Serial.print("New File Group "); Serial.println(file_group );
|
|
} else {
|
|
Serial.println("No settings in EPROM - Starting with File Group 1 ");
|
|
file_group = 1;
|
|
}
|
|
do_eprom_write();
|
|
file_number = 1;
|
|
}
|
|
|
|
void do_eprom_write() {
|
|
|
|
eprom_data ed;
|
|
|
|
ed.eprom_good = MagicNumber;
|
|
ed.file_group = file_group;
|
|
|
|
Serial.println("Writing to EPROM ...");
|
|
|
|
EEPROM.begin(200);
|
|
EEPROM.put(0, ed);
|
|
EEPROM.commit();
|
|
EEPROM.end();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// Make the avi functions
|
|
//
|
|
// start_avi() - open the file and write headers
|
|
// another_pic_avi() - write one more frame of movie
|
|
// end_avi() - write the final parameters and close the file
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// start_avi - open the files and write in headers
|
|
//
|
|
|
|
|
|
static esp_err_t start_avi() {
|
|
|
|
Serial.println("Starting an avi ");
|
|
|
|
sprintf(fname, "/sdcard/%s %d.%d + %ds.avi", devname, file_group, file_number, (millis() - boot_time) / 1000);
|
|
|
|
file_number++;
|
|
|
|
avifile = fopen(fname, "w");
|
|
idxfile = fopen("/sdcard/idx.tmp", "w");
|
|
|
|
if (avifile != NULL) {
|
|
Serial.printf("File open: %s\n", fname);
|
|
} else {
|
|
Serial.println("Could not open file");
|
|
major_fail();
|
|
}
|
|
|
|
if (idxfile != NULL) {
|
|
Serial.printf("File open: %s\n", "/sdcard/idx.tmp");
|
|
} else {
|
|
Serial.println("Could not open file");
|
|
major_fail();
|
|
}
|
|
|
|
for ( i = 0; i < AVIOFFSET; i++)
|
|
{
|
|
char ch = pgm_read_byte(&avi_header[i]);
|
|
buf[i] = ch;
|
|
}
|
|
|
|
size_t err = fwrite(buf, 1, AVIOFFSET, avifile);
|
|
|
|
if (framesize == 6) {
|
|
|
|
fseek(avifile, 0x40, SEEK_SET);
|
|
err = fwrite(vga_w, 1, 2, avifile);
|
|
fseek(avifile, 0xA8, SEEK_SET);
|
|
err = fwrite(vga_w, 1, 2, avifile);
|
|
fseek(avifile, 0x44, SEEK_SET);
|
|
err = fwrite(vga_h, 1, 2, avifile);
|
|
fseek(avifile, 0xAC, SEEK_SET);
|
|
err = fwrite(vga_h, 1, 2, avifile);
|
|
|
|
} else if (framesize == 10) {
|
|
|
|
fseek(avifile, 0x40, SEEK_SET);
|
|
err = fwrite(uxga_w, 1, 2, avifile);
|
|
fseek(avifile, 0xA8, SEEK_SET);
|
|
err = fwrite(uxga_w, 1, 2, avifile);
|
|
fseek(avifile, 0x44, SEEK_SET);
|
|
err = fwrite(uxga_h, 1, 2, avifile);
|
|
fseek(avifile, 0xAC, SEEK_SET);
|
|
err = fwrite(uxga_h, 1, 2, avifile);
|
|
|
|
} else if (framesize == 9) {
|
|
|
|
fseek(avifile, 0x40, SEEK_SET);
|
|
err = fwrite(sxga_w, 1, 2, avifile);
|
|
fseek(avifile, 0xA8, SEEK_SET);
|
|
err = fwrite(sxga_w, 1, 2, avifile);
|
|
fseek(avifile, 0x44, SEEK_SET);
|
|
err = fwrite(sxga_h, 1, 2, avifile);
|
|
fseek(avifile, 0xAC, SEEK_SET);
|
|
err = fwrite(sxga_h, 1, 2, avifile);
|
|
|
|
} else if (framesize == 7) {
|
|
|
|
fseek(avifile, 0x40, SEEK_SET);
|
|
err = fwrite(svga_w, 1, 2, avifile);
|
|
fseek(avifile, 0xA8, SEEK_SET);
|
|
err = fwrite(svga_w, 1, 2, avifile);
|
|
fseek(avifile, 0x44, SEEK_SET);
|
|
err = fwrite(svga_h, 1, 2, avifile);
|
|
fseek(avifile, 0xAC, SEEK_SET);
|
|
err = fwrite(svga_h, 1, 2, avifile);
|
|
|
|
} else if (framesize == 5) {
|
|
|
|
fseek(avifile, 0x40, SEEK_SET);
|
|
err = fwrite(cif_w, 1, 2, avifile);
|
|
fseek(avifile, 0xA8, SEEK_SET);
|
|
err = fwrite(cif_w, 1, 2, avifile);
|
|
fseek(avifile, 0x44, SEEK_SET);
|
|
err = fwrite(cif_h, 1, 2, avifile);
|
|
fseek(avifile, 0xAC, SEEK_SET);
|
|
err = fwrite(cif_h, 1, 2, avifile);
|
|
}
|
|
|
|
fseek(avifile, AVIOFFSET, SEEK_SET);
|
|
|
|
Serial.print(F("\nRecording "));
|
|
Serial.print(avi_length);
|
|
Serial.println(" seconds.");
|
|
|
|
startms = millis();
|
|
|
|
totalp = 0;
|
|
totalw = 0;
|
|
|
|
jpeg_size = 0;
|
|
movi_size = 0;
|
|
uVideoLen = 0;
|
|
idx_offset = 4;
|
|
|
|
frame_cnt = 0;
|
|
|
|
bad_jpg = 0;
|
|
extend_jpg = 0;
|
|
normal_jpg = 0;
|
|
|
|
} // end of start avi
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// another_save_avi saves another frame to the avi file, uodates index
|
|
// -- pass in a fb pointer to the frame to add
|
|
//
|
|
//
|
|
|
|
static esp_err_t another_save_avi(camera_fb_t * fb ) {
|
|
|
|
int fblen;
|
|
fblen = fb->len;
|
|
|
|
jpeg_size = fblen;
|
|
movi_size += jpeg_size;
|
|
uVideoLen += jpeg_size;
|
|
|
|
bw = millis();
|
|
|
|
size_t dc_err = fwrite(dc_and_zero_buf, 1, 8, avifile);
|
|
|
|
size_t err = fwrite(fb->buf, 1, fb->len, avifile);
|
|
if (err != fb->len) {
|
|
Serial.print("Error on avi write: err = "); Serial.print(err);
|
|
Serial.print(" len = "); Serial.println(fb->len);
|
|
}
|
|
|
|
remnant = (4 - (jpeg_size & 0x00000003)) & 0x00000003;
|
|
|
|
print_2quartet(idx_offset, jpeg_size, idxfile);
|
|
|
|
idx_offset = idx_offset + jpeg_size + remnant + 8;
|
|
|
|
jpeg_size = jpeg_size + remnant;
|
|
movi_size = movi_size + remnant;
|
|
if (remnant > 0) {
|
|
size_t rem_err = fwrite(zero_buf, 1, remnant, avifile);
|
|
}
|
|
|
|
fileposition = ftell (avifile); // Here, we are at end of chunk (after padding)
|
|
fseek(avifile, fileposition - jpeg_size - 4, SEEK_SET); // Here we are the the 4-bytes blank placeholder
|
|
|
|
print_quartet(jpeg_size, avifile); // Overwrite placeholder with actual frame size (without padding)
|
|
|
|
fileposition = ftell (avifile);
|
|
|
|
fseek(avifile, fileposition + jpeg_size , SEEK_SET);
|
|
|
|
totalw = totalw + millis() - bw;
|
|
|
|
} // end of another_pic_avi
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// end_avi writes the index, and closes the files
|
|
//
|
|
|
|
static esp_err_t end_avi() {
|
|
|
|
unsigned long current_end = 0;
|
|
|
|
current_end = ftell (avifile);
|
|
|
|
Serial.println("End of avi - closing the files");
|
|
|
|
elapsedms = millis() - startms;
|
|
|
|
float fRealFPS = (1000.0f * (float)frame_cnt) / ((float)elapsedms);
|
|
|
|
float fmicroseconds_per_frame = 1000000.0f / fRealFPS;
|
|
uint8_t iAttainedFPS = round(fRealFPS);
|
|
uint32_t us_per_frame = round(fmicroseconds_per_frame);
|
|
|
|
//Modify the MJPEG header from the beginning of the file, overwriting various placeholders
|
|
|
|
fseek(avifile, 4 , SEEK_SET);
|
|
print_quartet(movi_size + 240 + 16 * frame_cnt + 8 * frame_cnt, avifile);
|
|
|
|
fseek(avifile, 0x20 , SEEK_SET);
|
|
print_quartet(us_per_frame, avifile);
|
|
|
|
unsigned long max_bytes_per_sec = movi_size * iAttainedFPS / frame_cnt;
|
|
|
|
fseek(avifile, 0x24 , SEEK_SET);
|
|
print_quartet(max_bytes_per_sec, avifile);
|
|
|
|
fseek(avifile, 0x30 , SEEK_SET);
|
|
print_quartet(frame_cnt, avifile);
|
|
|
|
fseek(avifile, 0x8c , SEEK_SET);
|
|
print_quartet(frame_cnt, avifile);
|
|
|
|
fseek(avifile, 0x84 , SEEK_SET);
|
|
print_quartet((int)iAttainedFPS, avifile);
|
|
|
|
fseek(avifile, 0xe8 , SEEK_SET);
|
|
print_quartet(movi_size + frame_cnt * 8 + 4, avifile);
|
|
|
|
Serial.println(F("\n*** Video recorded and saved ***\n"));
|
|
Serial.print(F("Recorded "));
|
|
Serial.print(elapsedms / 1000);
|
|
Serial.print(F("s in "));
|
|
Serial.print(frame_cnt);
|
|
Serial.print(F(" frames\nFile size is "));
|
|
Serial.print(movi_size + 12 * frame_cnt + 4);
|
|
Serial.print(F(" bytes\nActual FPS is "));
|
|
Serial.print(fRealFPS, 2);
|
|
Serial.print(F("\nMax data rate is "));
|
|
Serial.print(max_bytes_per_sec);
|
|
Serial.print(F(" byte/s\nFrame duration is ")); Serial.print(us_per_frame); Serial.println(F(" us"));
|
|
Serial.print(F("Average frame length is ")); Serial.print(uVideoLen / frame_cnt); Serial.println(F(" bytes"));
|
|
Serial.print("Average picture time (ms) "); Serial.println( 1.0 * totalp / frame_cnt);
|
|
Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt );
|
|
Serial.print("Normal jpg % "); Serial.println( 100.0 * normal_jpg / frame_cnt, 1 );
|
|
Serial.print("Extend jpg % "); Serial.println( 100.0 * extend_jpg / frame_cnt, 1 );
|
|
Serial.print("Bad jpg % "); Serial.println( 100.0 * bad_jpg / frame_cnt, 1 );
|
|
|
|
|
|
Serial.printf("Writng the index, %d frames\n", frame_cnt);
|
|
fseek(avifile, current_end, SEEK_SET);
|
|
|
|
fclose(idxfile);
|
|
|
|
size_t i1_err = fwrite(idx1_buf, 1, 4, avifile);
|
|
|
|
print_quartet(frame_cnt * 16, avifile);
|
|
|
|
idxfile = fopen("/sdcard/idx.tmp", "r");
|
|
|
|
if (idxfile != NULL) {
|
|
Serial.printf("File open: %s\n", "/sdcard/idx.tmp");
|
|
} else {
|
|
Serial.println("Could not open index file");
|
|
major_fail();
|
|
}
|
|
|
|
char * AteBytes;
|
|
AteBytes = (char*) malloc (8);
|
|
|
|
for (int i = 0; i < frame_cnt; i++) {
|
|
size_t res = fread ( AteBytes, 1, 8, idxfile);
|
|
size_t i1_err = fwrite(dc_buf, 1, 4, avifile);
|
|
size_t i2_err = fwrite(zero_buf, 1, 4, avifile);
|
|
size_t i3_err = fwrite(AteBytes, 1, 8, avifile);
|
|
}
|
|
|
|
free(AteBytes);
|
|
fclose(idxfile);
|
|
fclose(avifile);
|
|
int xx = remove("/sdcard/idx.tmp");
|
|
|
|
Serial.println("---");
|
|
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
Serial.println("\n\n---");
|
|
|
|
pinMode(33, OUTPUT); // little red led on back of chip
|
|
digitalWrite(33, LOW); // turn on the red LED on the back of chip
|
|
|
|
pinMode(4, OUTPUT); // Blinding Disk-Avtive Light
|
|
digitalWrite(4, LOW); // turn off
|
|
|
|
pinMode(12, INPUT_PULLUP); // pull this down to stop recording
|
|
|
|
//Serial.setDebugOutput(true);
|
|
|
|
// zzz
|
|
Serial.println(" ");
|
|
Serial.println("-------------------------------------");
|
|
Serial.printf("ESP32-CAM-Video-Recorder-junior %s\n", vernum);
|
|
Serial.println("-------------------------------------");
|
|
|
|
Serial.print("setup, core "); Serial.print(xPortGetCoreID());
|
|
Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL));
|
|
|
|
// put your setup code here, to run once:
|
|
Serial.printf("Internal Total heap %d, internal Free Heap %d\n", ESP.getHeapSize(), ESP.getFreeHeap());
|
|
Serial.printf("SPIRam Total heap %d, SPIRam Free Heap %d\n", ESP.getPsramSize(), ESP.getFreePsram());
|
|
|
|
Serial.println("Setting up the camera ...");
|
|
|
|
config_camera();
|
|
|
|
Serial.printf("Internal Total heap %d, internal Free Heap %d\n", ESP.getHeapSize(), ESP.getFreeHeap());
|
|
Serial.printf("SPIRam Total heap %d, SPIRam Free Heap %d\n", ESP.getPsramSize(), ESP.getFreePsram());
|
|
|
|
// SD camera init
|
|
esp_err_t card_err = init_sdcard();
|
|
if (card_err != ESP_OK) {
|
|
Serial.printf("SD Card init failed with error 0x%x", card_err);
|
|
major_fail();
|
|
return;
|
|
}
|
|
|
|
Serial.printf("Internal Total heap %d, internal Free Heap %d\n", ESP.getHeapSize(), ESP.getFreeHeap());
|
|
Serial.printf("SPIRam Total heap %d, SPIRam Free Heap %d\n", ESP.getPsramSize(), ESP.getFreePsram());
|
|
|
|
digitalWrite(33, HIGH); // red light turns off when setup is complete
|
|
|
|
Serial.println("Warming up the camera ... here are some frames sizes ...");
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
camera_fb_t * fb = esp_camera_fb_get();
|
|
Serial.printf("frame %d, len %d\n", i, fb->len);
|
|
esp_camera_fb_return(fb);
|
|
delay(100);
|
|
}
|
|
|
|
do_eprom_read();
|
|
|
|
Serial.println(" End of setup()\n\n");
|
|
|
|
boot_time = millis();
|
|
}
|
|
|
|
|
|
|
|
int first = 1;
|
|
int frames = 0;
|
|
long frame_start = 0;
|
|
long frame_end = 0;
|
|
long frame_total = 0;
|
|
long frame_average = 0;
|
|
long loop_average = 0;
|
|
long loop_total = 0;
|
|
long total_frame_data = 0;
|
|
long last_frame_length = 0;
|
|
int done = 0;
|
|
long avi_start_time = 0;
|
|
long avi_end_time = 0;
|
|
int stop = 0;
|
|
int we_are_already_stopped = 0;
|
|
|
|
void loop() {
|
|
if (first) {
|
|
Serial.print("the loop, core "); Serial.print(xPortGetCoreID());
|
|
Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL));
|
|
first = 0;
|
|
}
|
|
|
|
frames++;
|
|
frame_cnt = frames;
|
|
|
|
stop = digitalRead(12);
|
|
|
|
if (frames == 1 ) { // start the avi
|
|
|
|
if (stop == 0) {
|
|
|
|
if (we_are_already_stopped == 0) Serial.println("\n\nCannot start video, as Pin 12 is connected to GND\n\n");
|
|
frames--;
|
|
we_are_already_stopped = 1;
|
|
delay(1000);
|
|
|
|
} else {
|
|
we_are_already_stopped = 0;
|
|
|
|
avi_start_time = millis();
|
|
Serial.printf("Start the avi ... at %d\n", avi_start_time);
|
|
|
|
fb_curr = get_good_jpeg(); // should take zero time
|
|
|
|
start_avi();
|
|
|
|
fb_next = get_good_jpeg(); // should take nearly zero time due to time spent writing header
|
|
|
|
another_save_avi( fb_curr); // put first frame in avi
|
|
|
|
digitalWrite(33, frames % 2); // blink
|
|
esp_camera_fb_return(fb_curr); // get rid of first frame
|
|
}
|
|
} else if ( stop == 0 || millis() > (avi_start_time + avi_length * 1000)) { // end the avi
|
|
|
|
fb_curr = fb_next;
|
|
another_save_avi(fb_curr); // save final frame of avi
|
|
digitalWrite(33, frames % 2);
|
|
esp_camera_fb_return(fb_curr);
|
|
|
|
end_avi(); // end the movie
|
|
|
|
digitalWrite(33, HIGH); // light off
|
|
avi_end_time = millis();
|
|
|
|
float fps = frames / ((avi_end_time - avi_start_time) / 1000) ;
|
|
Serial.printf("End the avi at %d. It was %d frames, %d ms at %.1f fps...\n", millis(), frames, avi_end_time, avi_end_time - avi_start_time, fps);
|
|
|
|
frames = 0; // start recording again on the next loop
|
|
|
|
} else { // another frame of the avi
|
|
|
|
|
|
fb_curr = fb_next; // we will write a frame, and get the camera preparing a new one
|
|
|
|
fb_next = get_good_jpeg(); // should take near zero, unless the sd is faster than the camera, when we will have to wait for the camera
|
|
|
|
another_save_avi(fb_curr);
|
|
|
|
digitalWrite(33, frames % 2);
|
|
|
|
esp_camera_fb_return(fb_curr);
|
|
|
|
if (frames % 100 == 0 ) { // print some status every 100 frames
|
|
|
|
float fps = 1.0 * frames / ((millis() - avi_start_time) / 1000) ;
|
|
Serial.printf("So far: %d frames, %d ms at %.2f fps...\n", frames, millis() - avi_start_time, fps);
|
|
}
|
|
}
|
|
}
|