arduino-audio-tools
Loading...
Searching...
No Matches
MP4Parser.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <cstring>
5#include <functional>
6#include <string>
7
9
10namespace audio_tools {
11
28class MP4Parser {
29 public:
33 struct Box {
34 friend class MP4Parser;
35 friend class MP4ParserExt;
37 size_t id = 0;
38 size_t seq = 0;
39 char type[5];
40 const uint8_t* data =
41 nullptr;
42 size_t data_size = 0;
43 size_t size =
44 0;
45 int level = 0;
46 uint64_t file_offset = 0;
47 int available = 0;
48 bool is_complete = false;
49 bool is_incremental = false;
50 bool is_container = false;
51 };
52
53 using BoxCallback = std::function<void(Box&, void* ref)>;
54
59 char type[5];
62 true;
63 };
64
69 void setReference(void* ref) { this->ref = ref; }
70
75 void setCallback(BoxCallback cb) { callback = cb; }
76
84 void setCallback(const char* type, BoxCallback cb, bool callGeneric = true) {
85 CallbackEntry entry;
86 strncpy(entry.type, type, 4);
87 entry.type[4] = '\0'; // Ensure null-termination
88 entry.cb = cb;
89 entry.callGeneric = callGeneric;
90 callbacks.push_back(entry);
91 };
92
98 bool resize(size_t size) {
99 buffer.resize(size);
100 return buffer.size() == size;
101 }
102
112 bool begin(uint64_t startFileOffset = 0) {
113 buffer.clear();
114 if (buffer.size() == 0) buffer.resize(2 * 1024);
115 parseOffset = 0;
116 fileOffset = startFileOffset;
118 box.is_complete = true; // Start with no open box
119 box.data = nullptr;
120 box.size = 0;
121 box.level = 0;
122 box.file_offset = startFileOffset;
123 box.id = 0;
124 box.is_incremental = false;
125 box.is_complete = true;
126 // reset incremental-box state too, in case begin() is called again
127 // mid-parse (see startFileOffset above)
128 box_in_progress = false;
131 box_seq = 0;
133 return true;
134 }
135
142 size_t write(const uint8_t* data, size_t len) {
143 if (is_error) return len; // If an error occurred, skip writing
144 size_t result = buffer.writeArray(data, len);
145 parse();
146 return result;
147 }
148
155 size_t write(const char* data, size_t len) {
156 return write(reinterpret_cast<const uint8_t*>(data), len);
157 }
158
164
170 void addContainer(const char* name, int start = 0) {
171 ContainerInfo info;
172 info.name = name;
173 info.start = start; // offset of child boxes
174 }
175
182 int parseString(const uint8_t* str, int len, int fileOffset = 0,
183 int level = 0) {
184 char type[5];
185 int idx = 0;
186 Box box;
187 while (true) {
188 if (!isValidType((const char*)str + idx + 4)) {
189 return idx;
190 }
191 size_t box_size = readU32(str + idx) - 8;
192 box.data = str + 8 + idx;
193 box.size = box_size;
194 box.level = level;
196 box.file_offset = fileOffset + idx;
197 box.is_complete = true;
198 box.is_incremental = false;
199 strncpy(box.type, (char*)(str + idx + 4), 4);
200 box.type[4] = '\0';
201 idx += box.size;
203 if (idx >= len) break; // No more data to parse
204 }
205 return idx;
206 }
207
209 bool findBox(const char* name, const uint8_t* data, size_t len, Box& result) {
210 for (int j = 0; j < len - 4; j++) {
211 if (!isValidType((const char*)data + j + 4)) {
212 continue; // Skip invalid types
213 }
214 size_t box_size = readU32(data + j) - 8;
215 if (box_size < 8) continue; // Invalid box size
216 Box box;
217 box.data = data + j + 8;
218 box.size = box_size;
220 strncpy(box.type, (char*)(data + j + 4), 4);
221 box.type[4] = '\0';
222 if (StrView(box.type) == name) {
223 result = box;
224 return true; // Found the box
225 }
226 }
227 return false;
228 }
229
235 static void defaultCallback(const Box& box, void* ref) {
236 char space[box.level * 2 + 1];
237 char str_buffer[200];
238 memset(space, ' ', box.level * 2);
239 space[box.level * 2] = '\0'; // Null-terminate the string
240 snprintf(str_buffer, sizeof(str_buffer),
241 "%s- #%u %u) %s, Offset: %u, Size: %u, Data Size: %u, Available: %u", space,
242 (unsigned)box.id, (unsigned) box.seq, box.type, (unsigned)box.file_offset,
243 (unsigned)box.size, (unsigned) box.data_size, (unsigned) box.available);
244#ifdef ARDUINO
245 Serial.println(str_buffer);
246#else
247 printf("%s\n", str_buffer);
248#endif
249 }
250
251 protected:
256 size_t parseOffset = 0;
257 uint64_t fileOffset = 0;
258 void* ref = this;
260 bool is_error = false;
261
266 const char* name = nullptr;
267 int start = 0;
268 };
270protected:
272 false;
275 char box_type[5] = {0};
276 int box_level = 0;
277 int box_seq = 0;
279
283 void parse() {
284 while (true) {
285 size_t bufferSize = buffer.available();
286 if (!box_in_progress) {
287 if (!tryStartNewBox(bufferSize)) break;
288 } else {
289 if (!continueIncrementalBox()) break;
290 }
291 popLevels();
292 }
294 }
295
301 bool tryStartNewBox(size_t bufferSize) {
302 if (parseOffset + 8 > bufferSize) return false;
303 char type[5];
304 box_seq = 0;
305
306 // get basic box information
308 const uint8_t* p = buffer.data() + parseOffset;
309 uint32_t size32 = readU32(p);
310 strncpy(type, (char*)(p + 4), 4);
311 type[4] = '\0';
312 uint64_t boxSize = size32;
313 size_t headerSize = 8;
314
315 if (boxSize < headerSize) return false;
316
317 int level = static_cast<int>(levelStack.size());
318 bool is_container = isContainerBox(type);
319
320 if (is_container) {
321 handleContainerBox(type, boxSize, level);
322 return true;
323 }
324
325 size_t payload_size = static_cast<size_t>(boxSize - headerSize);
326 if (parseOffset + boxSize <= bufferSize) {
327 // start with full buffer!
328 handleCompleteBox(type, p, headerSize, payload_size, level);
329 parseOffset += boxSize;
330 } else {
331 startIncrementalBox(type, p, headerSize, payload_size, level, bufferSize);
332 return false; // Wait for more data
333 }
334 return true;
335 }
336
343 void handleContainerBox(const char* type, uint64_t boxSize, int level) {
344 strcpy(box.type, type);
345 ++box.id;
346 box.data = nullptr;
347 box.size = static_cast<size_t>(boxSize - 8);
348 box.data_size = 0;
349 box.available = 0;
350 box.level = level;
352 box.is_incremental = false;
353 box.is_complete = true;
354 box.is_container = true;
355 box.seq = 0;
356
358
359 uint64_t absBoxOffset = fileOffset + parseOffset;
360 levelStack.push_back(absBoxOffset + boxSize);
361 parseOffset += 8;
362 }
363
372 void handleCompleteBox(const char* type, const uint8_t* p, size_t headerSize,
373 size_t payload_size, int level) {
374 strcpy(box.type, type);
375 ++box.id;
376 box.data = p + headerSize;
377 box.size = payload_size;
378 box.data_size = payload_size;
379 box.level = level;
381 box.is_complete = true;
382 box.is_container = false;
383 box.available = payload_size;
384 box.is_incremental = false;
385 box.seq = 0;
386
388 }
389
399 void startIncrementalBox(const char* type, const uint8_t* p,
400 size_t headerSize, size_t payload_size, int level,
401 size_t bufferSize) {
402 box_in_progress = true;
404 box_bytes_expected = payload_size;
405 strncpy(box_type, type, 5);
406 box_level = level;
407 box_seq = 0;
408
409 size_t available_payload = bufferSize - parseOffset - headerSize;
411 if (available_payload > 0) {
412 box_bytes_received += available_payload;
413 strcpy(box.type, box_type);
414 ++box.id;
415 box.data = p + headerSize;
418 box.available = available_payload;
421 box.seq = 0;
422 box.is_incremental = true;
423 box.is_complete = false;
424 box.is_container = false;
426 }
427 // fileOffset += (bufferSize - buffer.available());
428 fileOffset += (parseOffset + payload_size + 8);
429 incremental_offset += available_payload;
430 buffer.clear();
431 parseOffset = 0;
432 }
433
440 size_t to_read = std::min((size_t)box_bytes_expected - box_bytes_received,
441 (size_t)buffer.available());
442 if (to_read == 0) return true;
443 strcpy(box.type, box_type);
444 ++box.id;
445 box.data = buffer.data();
448 box.available = to_read;
452 box.is_container = false;
453 box.is_incremental = true;
454 box.seq = ++box_seq;
456 box_bytes_received += to_read;
457 // fileOffset += to_read;
458 buffer.clearArray(to_read);
459 incremental_offset += to_read;
460
462 box_in_progress = false;
463 }
464 return false;
465 }
466
471 if (parseOffset > 0) {
474 parseOffset = 0;
475 }
476 }
477
482 uint64_t currentFileOffset() { return fileOffset + parseOffset; }
483
489 static uint32_t readU32(const uint8_t* p) {
490 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
491 }
492
498 static uint64_t readU64(const uint8_t* p) {
499 return ((uint64_t)readU32(p) << 32) | readU32(p + 4);
500 }
501
502
506 void popLevels() {
507 // Pop levels if we've passed their bounds (absolute file offset)
508 while (!levelStack.empty() &&
511 }
512 }
513
521 bool is_called = false;
522 bool call_generic = true;
523 for (const auto& entry : callbacks) {
524 if (strncmp(entry.type, box.type, 4) == 0) {
525 entry.cb(box, ref);
526 is_called = true;
527 if (!entry.callGeneric) call_generic = false;
528 }
529 }
531 if ((!is_called || call_generic) && callback) callback(box, ref);
532 }
533
539 bool isContainerBox(const char* type) {
540 // fill with default values if nothing has been defined
541 if (containers.empty()) {
542 // pure containers
543 static const char* containers_str[] = {
544 "moov", "trak", "mdia", "minf", "stbl", "edts", "dinf", "udta",
545 "ilst", "moof", "traf", "mfra", "tref", "iprp", "sinf", "schi"};
546 for (const char* c : containers_str) {
547 ContainerInfo info;
548 info.name = c;
549 info.start = 0;
550 containers.push_back(info);
551 }
552 // container with data
553 ContainerInfo info;
554 info.name = "meta";
555 info.start = 4; // 4 bytes: version (1 byte) + flags (3 bytes)
556 containers.push_back(info);
557 }
558 // find the container by name
559 for (auto& cont : containers) {
560 if (StrView(type) == cont.name) return true;
561 }
562 return false;
563 }
564
570 int getContainerDataLength(const char* type) {
571 for (auto& cont : containers) {
572 if (StrView(type) == cont.name) return cont.start;
573 }
574 return 0;
575 }
576
583 bool isValidType(const char* type, int offset = 0) const {
584 // Check if the type is a valid 4-character string
585 return (type != nullptr && isalnum(type[offset]) &&
586 isalnum(type[offset + 1]) && isalnum(type[offset + 2]) &&
587 isalnum(type[offset + 3]));
588 }
589
595 size_t current = parseOffset;
596 const char* type = (char*)(buffer.data() + parseOffset + 4);
597 for (int j = 0; j < buffer.available() - parseOffset - 4; j += 4) {
598 if (isValidType(type, j)) {
599 if (j != 0) {
600 // report the data under the last valid box
601 box.size = 0;
602 box.data_size = j;
603 box.level = static_cast<int>(levelStack.size()) + 1;
606 }
607
608 return j + parseOffset;
609 }
610 }
611 return parseOffset;
612 }
613};
614
615} // namespace audio_tools
static HardwareSerial Serial
Definition Arduino.h:179
void clear()
same as reset
Definition Buffers.h:96
MP4Parser is a class that parses MP4 container files and extracts boxes (atoms). It provides a callba...
Definition MP4Parser.h:28
void handleCompleteBox(const char *type, const uint8_t *p, size_t headerSize, size_t payload_size, int level)
Handles a complete (non-incremental) box.
Definition MP4Parser.h:372
void setCallback(const char *type, BoxCallback cb, bool callGeneric=true)
Defines a specific callback for a box type.
Definition MP4Parser.h:84
void * ref
Reference pointer for callbacks.
Definition MP4Parser.h:258
Vector< size_t > levelStack
Stack for container box levels.
Definition MP4Parser.h:255
size_t box_bytes_expected
Total expected bytes for the current box.
Definition MP4Parser.h:274
static void defaultCallback(const Box &box, void *ref)
Default callback that prints box information to Serial.
Definition MP4Parser.h:235
size_t checkParseOffset()
Checks and adjusts the parse offset for valid box types.
Definition MP4Parser.h:594
int getContainerDataLength(const char *type)
Gets the start offset for a subcontainer.
Definition MP4Parser.h:570
int box_level
Current box level (nesting)
Definition MP4Parser.h:276
bool continueIncrementalBox()
Continue filling an incremental box. Returns false if not enough data.
Definition MP4Parser.h:439
Vector< CallbackEntry > callbacks
List of type-specific callbacks.
Definition MP4Parser.h:253
void startIncrementalBox(const char *type, const uint8_t *p, size_t headerSize, size_t payload_size, int level, size_t bufferSize)
Starts parsing a box incrementally.
Definition MP4Parser.h:399
Vector< ContainerInfo > containers
List of container box info.
Definition MP4Parser.h:269
static uint32_t readU32(const uint8_t *p)
Reads a 32-bit big-endian unsigned integer from a buffer.
Definition MP4Parser.h:489
void setCallback(BoxCallback cb)
Defines the generic callback for all boxes.
Definition MP4Parser.h:75
int availableForWrite()
Returns the available space for writing.
Definition MP4Parser.h:163
char box_type[5]
Current box type.
Definition MP4Parser.h:275
int parseString(const uint8_t *str, int len, int fileOffset=0, int level=0)
Trigger separate parsing (and callbacks) on the indicated string.
Definition MP4Parser.h:182
void finalizeParse()
Finalizes parsing, updating file offset and clearing buffer.
Definition MP4Parser.h:470
bool begin(uint64_t startFileOffset=0)
Initializes the parser.
Definition MP4Parser.h:112
void setReference(void *ref)
Defines an optional reference. By default it is the parser itself.
Definition MP4Parser.h:69
size_t incremental_offset
Definition MP4Parser.h:278
bool isValidType(const char *type, int offset=0) const
Checks if a type string is a valid 4-character box type.
Definition MP4Parser.h:583
static uint64_t readU64(const uint8_t *p)
Reads a 64-bit big-endian unsigned integer from a buffer.
Definition MP4Parser.h:498
uint64_t currentFileOffset()
Returns the current file offset (absolute position in file).
Definition MP4Parser.h:482
void addContainer(const char *name, int start=0)
Adds a box name that will be interpreted as a container.
Definition MP4Parser.h:170
bool tryStartNewBox(size_t bufferSize)
Try to start parsing a new box. Returns false if not enough data.
Definition MP4Parser.h:301
SingleBuffer< uint8_t > buffer
Buffer for incoming data.
Definition MP4Parser.h:254
Box box
Current box being processed.
Definition MP4Parser.h:259
bool box_in_progress
True if currently parsing a box incrementally.
Definition MP4Parser.h:271
void handleContainerBox(const char *type, uint64_t boxSize, int level)
Handles a container box (box with children).
Definition MP4Parser.h:343
void processCallback(Box &box)
Processes the callback for a box. Calls the type-specific callback if present, and the generic callba...
Definition MP4Parser.h:520
size_t parseOffset
Current parse offset in buffer.
Definition MP4Parser.h:256
int box_seq
Definition MP4Parser.h:277
bool is_error
True if an error occurred.
Definition MP4Parser.h:260
bool findBox(const char *name, const uint8_t *data, size_t len, Box &result)
find box in box
Definition MP4Parser.h:209
void parse()
Main parsing loop. Handles incremental and complete boxes.
Definition MP4Parser.h:283
BoxCallback callback
Generic callback for all boxes.
Definition MP4Parser.h:252
bool resize(size_t size)
Defines a specific buffer size.
Definition MP4Parser.h:98
size_t box_bytes_received
Bytes received so far for the current box.
Definition MP4Parser.h:273
bool isContainerBox(const char *type)
Checks if a box type is a container box.
Definition MP4Parser.h:539
size_t write(const char *data, size_t len)
Provide the data to the parser (in chunks if needed).
Definition MP4Parser.h:155
uint64_t fileOffset
Current file offset.
Definition MP4Parser.h:257
std::function< void(Box &, void *ref)> BoxCallback
Definition MP4Parser.h:53
void popLevels()
Pops levels from the stack if we've passed their bounds.
Definition MP4Parser.h:506
size_t write(const uint8_t *data, size_t len)
Provide the data to the parser (in chunks if needed).
Definition MP4Parser.h:142
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:189
size_t size() override
Definition Buffers.h:320
int available() override
provides the number of entries that are available to read
Definition Buffers.h:250
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:255
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:218
T * data()
Provides address of actual data.
Definition Buffers.h:301
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:322
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:269
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:28
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
void pop_back()
Definition Vector.h:214
bool empty()
Definition Vector.h:180
T & back()
Definition Vector.h:289
void push_back(T &&value)
Definition Vector.h:182
void clear()
Definition Vector.h:176
int size()
Definition Vector.h:178
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
Represents an individual box in the MP4 file.
Definition MP4Parser.h:33
size_t id
Unique box ID.
Definition MP4Parser.h:37
bool is_container
True if the box is a container.
Definition MP4Parser.h:50
const uint8_t * data
Pointer to box payload (not including header)
Definition MP4Parser.h:40
int available
Number of bytes available as data.
Definition MP4Parser.h:47
uint64_t file_offset
File offset where box starts.
Definition MP4Parser.h:46
size_t size
Size of payload including subboxes (not including header)
Definition MP4Parser.h:43
bool is_complete
True if the box data is complete.
Definition MP4Parser.h:48
bool is_incremental
True if the box is being parsed incrementally.
Definition MP4Parser.h:49
size_t seq
Sequence number for the box per id.
Definition MP4Parser.h:38
char type[5]
4-character box type (null-terminated)
Definition MP4Parser.h:39
friend class MP4ParserExt
Definition MP4Parser.h:35
int level
Nesting depth.
Definition MP4Parser.h:45
size_t data_size
Size of payload (not including header)
Definition MP4Parser.h:42
Structure for type-specific callbacks.
Definition MP4Parser.h:58
BoxCallback cb
Callback function.
Definition MP4Parser.h:60
char type[5]
4-character box type
Definition MP4Parser.h:59
bool callGeneric
If true, also call the generic callback after this one.
Definition MP4Parser.h:61
Structure for container box information.
Definition MP4Parser.h:265
int start
Offset of child boxes.
Definition MP4Parser.h:267
const char * name
Name of the container box.
Definition MP4Parser.h:266