forked from s60sc/ESP32-CAM_MJPEG2SD
-
Notifications
You must be signed in to change notification settings - Fork 0
/
avi.cpp
221 lines (203 loc) · 8.61 KB
/
avi.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*
Generate AVI format for recorded videos
s60sc 2020, 2022
*/
/* AVI file format:
header:
310 bytes
per jpeg:
4 byte 00dc marker
4 byte jpeg size
jpeg frame content
0-3 bytes filler to align on DWORD boundary
per PCM (audio file)
4 byte 01wb marker
4 byte pcm size
pcm content
0-3 bytes filler to align on DWORD boundary
footer:
4 byte idx1 marker
4 byte index size
per jpeg:
4 byte 00dc marker
4 byte 0000
4 byte jpeg location
4 byte jpeg size
per pcm:
4 byte 01wb marker
4 byte 0000
4 byte pcm location
4 byte pcm size
*/
#include "appGlobals.h"
// avi header data
const uint8_t dcBuf[4] = {0x30, 0x30, 0x64, 0x63}; // 00dc
const uint8_t wbBuf[4] = {0x30, 0x31, 0x77, 0x62}; // 01wb
static const uint8_t idx1Buf[4] = {0x69, 0x64, 0x78, 0x31}; // idx1
static const uint8_t zeroBuf[4] = {0x00, 0x00, 0x00, 0x00}; // 0000
static uint8_t* idxBuf[2] = {NULL, NULL};
uint8_t aviHeader[AVI_HEADER_LEN] = { // AVI header template
0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54,
0x16, 0x01, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x6C, 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, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x4C, 0x49, 0x53, 0x54, 0x56, 0x00, 0x00, 0x00,
0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x11, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66,
0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0x2B, 0x00, 0x00, 0x11, 0x2B, 0x00, 0x00,
0x02, 0x00, 0x10, 0x00, 0x00, 0x00,
0x4C, 0x49, 0x53, 0x54, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x6F, 0x76, 0x69,
};
struct frameSizeStruct {
uint8_t frameWidth[2];
uint8_t frameHeight[2];
};
// indexed by frame type - needs to be consistent with sensor.h framesize_t enum
static const frameSizeStruct frameSizeData[] = {
{{0x60, 0x00}, {0x60, 0x00}}, // 96X96
{{0xA0, 0x00}, {0x78, 0x00}}, // qqvga
{{0xB0, 0x00}, {0x90, 0x00}}, // qcif
{{0xF0, 0x00}, {0xB0, 0x00}}, // hqvga
{{0xF0, 0x00}, {0xF0, 0x00}}, // 240X240
{{0x40, 0x01}, {0xF0, 0x00}}, // qvga
{{0x90, 0x01}, {0x28, 0x01}}, // cif
{{0xE0, 0x01}, {0x40, 0x01}}, // hvga
{{0x80, 0x02}, {0xE0, 0x01}}, // vga
{{0x20, 0x03}, {0x58, 0x02}}, // svga
{{0x00, 0x04}, {0x00, 0x03}}, // xga
{{0x00, 0x05}, {0xD0, 0x02}}, // hd
{{0x00, 0x05}, {0x00, 0x04}}, // sxga
{{0x40, 0x06}, {0xB0, 0x04}} // uxga
};
#define IDX_ENTRY 16 // bytes per index entry
// separate index for motion capture and timelapse
static size_t idxPtr[2];
static size_t idxOffset[2];
static size_t moviSize[2];
static size_t audSize;
static size_t indexLen[2];
static File wavFile;
bool haveSoundFile = false;
void prepAviIndex(bool isTL) {
// prep buffer to store index data, gets appended to end of file
if (idxBuf[isTL] == NULL) idxBuf[isTL] = (uint8_t*)ps_malloc((maxFrames+1)*IDX_ENTRY); // include some space for audio index
memcpy(idxBuf[isTL], idx1Buf, 4); // index header
idxPtr[isTL] = CHUNK_HDR; // leave 4 bytes for index size
moviSize[isTL] = indexLen[isTL] = 0;
}
void buildAviHdr(uint8_t FPS, uint8_t frameType, uint16_t frameCnt, bool isTL) {
// update AVI header template with file specific details
size_t aviSize = moviSize[isTL] + AVI_HEADER_LEN + ((CHUNK_HDR+IDX_ENTRY) * (frameCnt+(haveSoundFile?1:0))); // AVI content size
// update aviHeader with relevant stats
memcpy(aviHeader+4, &aviSize, 4);
uint32_t usecs = (uint32_t)round(1000000.0f / FPS); // usecs_per_frame
memcpy(aviHeader+0x20, &usecs, 4);
memcpy(aviHeader+0x30, &frameCnt, 2);
memcpy(aviHeader+0x8C, &frameCnt, 2);
memcpy(aviHeader+0x84, &FPS, 1);
uint32_t dataSize = moviSize[isTL] + ((frameCnt+(haveSoundFile?1:0)) * CHUNK_HDR) + 4;
memcpy(aviHeader+0x12E, &dataSize, 4); // data size
uint8_t withAudio = 2; // increase number of streams for audio
if (isTL) memcpy(aviHeader+0x100, zeroBuf, 4); // no audio for timelapse
else {
if (haveSoundFile) memcpy(aviHeader+0x38, &withAudio, 1);
memcpy(aviHeader+0x100, &audSize, 4); // audio data size
}
// apply video framesize to avi header
memcpy(aviHeader+0x40, frameSizeData[frameType].frameWidth, 2);
memcpy(aviHeader+0xA8, frameSizeData[frameType].frameWidth, 2);
memcpy(aviHeader+0x44, frameSizeData[frameType].frameHeight, 2);
memcpy(aviHeader+0xAC, frameSizeData[frameType].frameHeight, 2);
// apply audio details to avi header
memcpy(aviHeader+0xF8, &SAMPLE_RATE, 4);
uint32_t bytesPerSec = SAMPLE_RATE * 2;
memcpy(aviHeader+0x104, &bytesPerSec, 4); // suggested buffer size
memcpy(aviHeader+0x11C, &SAMPLE_RATE, 4);
memcpy(aviHeader+0x120, &bytesPerSec, 4); // bytes per sec
// reset state for next recording
moviSize[isTL] = idxOffset[isTL] = idxPtr[isTL] = 0;
}
void buildAviIdx(size_t dataSize, bool isVid, bool isTL) {
// build AVI video index into buffer - 16 bytes per frame
// called from saveFrame() for each frame
moviSize[isTL] += dataSize;
if (isVid) memcpy(idxBuf[isTL]+idxPtr[isTL], dcBuf, 4);
else memcpy(idxBuf[isTL]+idxPtr[isTL], wbBuf, 4);
memcpy(idxBuf[isTL]+idxPtr[isTL]+4, zeroBuf, 4);
memcpy(idxBuf[isTL]+idxPtr[isTL]+8, &idxOffset[isTL], 4);
memcpy(idxBuf[isTL]+idxPtr[isTL]+12, &dataSize, 4);
idxOffset[isTL] += dataSize + CHUNK_HDR;
idxPtr[isTL] += IDX_ENTRY;
}
size_t writeAviIndex(byte* clientBuf, size_t buffSize, bool isTL) {
// write completed index to avi file
// called repeatedly from closeAvi() until return 0
if (idxPtr[isTL] < indexLen[isTL]) {
if (indexLen[isTL]-idxPtr[isTL] > buffSize) {
memcpy(clientBuf, idxBuf[isTL]+idxPtr[isTL], buffSize);
idxPtr[isTL] += buffSize;
return buffSize;
} else {
// final part of index
size_t final = indexLen[isTL]-idxPtr[isTL];
memcpy(clientBuf, idxBuf[isTL]+idxPtr[isTL], final);
idxPtr[isTL] = indexLen[isTL];
return final;
}
}
return idxPtr[isTL] = 0;
}
void finalizeAviIndex(uint16_t frameCnt, bool isTL) {
// update index with size
uint32_t sizeOfIndex = (frameCnt+(haveSoundFile?1:0))*IDX_ENTRY;
memcpy(idxBuf[isTL]+4, &sizeOfIndex, 4); // size of index
indexLen[isTL] = sizeOfIndex + CHUNK_HDR;
idxPtr[isTL] = 0; // pointer to index buffer
}
bool haveWavFile(bool isTL) {
haveSoundFile = false;
if (isTL) return false;
// check if wave file exists
if (!STORAGE.exists(WAVTEMP)) return 0;
// open it and get its size
audSize = 0;
wavFile = STORAGE.open(WAVTEMP, FILE_READ);
if (wavFile) {
// add sound file index
audSize = wavFile.size() - WAV_HEADER_LEN;
buildAviIdx(audSize, false);
// add sound file header
wavFile.seek(WAV_HEADER_LEN, SeekSet); // skip over header
haveSoundFile = true;
}
return haveSoundFile;
}
size_t writeWavFile(byte* clientBuf, size_t buffSize) {
// read in wav file and write to avi file
// called repeatedly from closeAvi() until return 0
static size_t offsetWav = CHUNK_HDR;
if (offsetWav) {
// add sound file header
memcpy(clientBuf, wbBuf, 4);
memcpy(clientBuf+4, &audSize, 4);
}
size_t readLen = wavFile.read(clientBuf+offsetWav, buffSize-offsetWav) + offsetWav;
offsetWav = 0;
if (readLen) return readLen;
// get here if finished
wavFile.close();
STORAGE.remove(WAVTEMP);
offsetWav = CHUNK_HDR;
return 0;
}