arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
MP4Parser.h
1#pragma once
2
3#include <cstdint>
4#include <cstring>
5#include <functional>
6#include <string>
7
8#include "AudioTools/CoreAudio/Buffers.h"
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];
60 BoxCallback cb;
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
107 bool begin() {
108 buffer.clear();
109 if (buffer.size() == 0) buffer.resize(2 * 1024);
110 parseOffset = 0;
111 fileOffset = 0;
112 levelStack.clear();
113 box.is_complete = true; // Start with no open box
114 box.data = nullptr;
115 box.size = 0;
116 box.level = 0;
117 box.file_offset = 0;
118 box.id = 0;
119 box.is_incremental = false;
120 box.is_complete = true;
121 return true;
122 }
123
130 size_t write(const uint8_t* data, size_t len) {
131 if (is_error) return len; // If an error occurred, skip writing
132 size_t result = buffer.writeArray(data, len);
133 parse();
134 return result;
135 }
136
143 size_t write(const char* data, size_t len) {
144 return write(reinterpret_cast<const uint8_t*>(data), len);
145 }
146
152
158 void addContainer(const char* name, int start = 0) {
159 ContainerInfo info;
160 info.name = name;
161 info.start = start; // offset of child boxes
162 }
163
170 int parseString(const uint8_t* str, int len, int fileOffset = 0,
171 int level = 0) {
172 char type[5];
173 int idx = 0;
174 Box box;
175 while (true) {
176 if (!isValidType((const char*)str + idx + 4)) {
177 return idx;
178 }
179 size_t box_size = readU32(str + idx) - 8;
180 box.data = str + 8 + idx;
181 box.size = box_size;
182 box.level = level;
184 box.file_offset = fileOffset + idx;
185 box.is_complete = true;
186 box.is_incremental = false;
187 strncpy(box.type, (char*)(str + idx + 4), 4);
188 box.type[4] = '\0';
189 idx += box.size;
191 if (idx >= len) break; // No more data to parse
192 }
193 return idx;
194 }
195
197 bool findBox(const char* name, const uint8_t* data, size_t len, Box& result) {
198 for (int j = 0; j < len - 4; j++) {
199 if (!isValidType((const char*)data + j + 4)) {
200 continue; // Skip invalid types
201 }
202 size_t box_size = readU32(data + j) - 8;
203 if (box_size < 8) continue; // Invalid box size
204 Box box;
205 box.data = data + j + 8;
206 box.size = box_size;
208 strncpy(box.type, (char*)(data + j + 4), 4);
209 box.type[4] = '\0';
210 if (StrView(box.type) == name) {
211 result = box;
212 return true; // Found the box
213 }
214 }
215 return false;
216 }
217
223 static void defaultCallback(const Box& box, void* ref) {
224 char space[box.level * 2 + 1];
225 char str_buffer[200];
226 memset(space, ' ', box.level * 2);
227 space[box.level * 2] = '\0'; // Null-terminate the string
228 snprintf(str_buffer, sizeof(str_buffer),
229 "%s- #%u %u) %s, Offset: %u, Size: %u, Data Size: %u, Available: %u", space,
230 (unsigned)box.id, (unsigned) box.seq, box.type, (unsigned)box.file_offset,
231 (unsigned)box.size, (unsigned) box.data_size, (unsigned) box.available);
232#ifdef ARDUINO
233 Serial.println(str_buffer);
234#else
235 printf("%s\n", str_buffer);
236#endif
237 }
238
239 protected:
240 BoxCallback callback = defaultCallback;
244 size_t parseOffset = 0;
245 uint64_t fileOffset = 0;
246 void* ref = this;
248 bool is_error = false;
249
254 const char* name = nullptr;
255 int start = 0;
256 };
258protected:
260 false;
263 char box_type[5] = {0};
264 int box_level = 0;
265 int box_seq = 0;
266 size_t incremental_offset = 0;
267
271 void parse() {
272 while (true) {
273 size_t bufferSize = buffer.available();
274 if (!box_in_progress) {
275 if (!tryStartNewBox(bufferSize)) break;
276 } else {
277 if (!continueIncrementalBox()) break;
278 }
279 popLevels();
280 }
282 }
283
289 bool tryStartNewBox(size_t bufferSize) {
290 if (parseOffset + 8 > bufferSize) return false;
291 char type[5];
292 box_seq = 0;
293
294 // get basic box information
296 const uint8_t* p = buffer.data() + parseOffset;
297 uint32_t size32 = readU32(p);
298 strncpy(type, (char*)(p + 4), 4);
299 type[4] = '\0';
300 uint64_t boxSize = size32;
301 size_t headerSize = 8;
302
303 if (boxSize < headerSize) return false;
304
305 int level = static_cast<int>(levelStack.size());
306 bool is_container = isContainerBox(type);
307
308 if (is_container) {
309 handleContainerBox(type, boxSize, level);
310 return true;
311 }
312
313 size_t payload_size = static_cast<size_t>(boxSize - headerSize);
314 if (parseOffset + boxSize <= bufferSize) {
315 // start with full buffer!
316 handleCompleteBox(type, p, headerSize, payload_size, level);
317 parseOffset += boxSize;
318 } else {
319 startIncrementalBox(type, p, headerSize, payload_size, level, bufferSize);
320 return false; // Wait for more data
321 }
322 return true;
323 }
324
331 void handleContainerBox(const char* type, uint64_t boxSize, int level) {
332 strcpy(box.type, type);
333 box.id = ++this->box.id;
334 box.data = nullptr;
335 box.size = static_cast<size_t>(boxSize - 8);
336 box.data_size = 0;
337 box.available = 0;
338 box.level = level;
340 box.is_incremental = false;
341 box.is_complete = true;
342 box.is_container = true;
343 box.seq = 0;
344
346
347 uint64_t absBoxOffset = fileOffset + parseOffset;
348 levelStack.push_back(absBoxOffset + boxSize);
349 parseOffset += 8;
350 }
351
360 void handleCompleteBox(const char* type, const uint8_t* p, size_t headerSize,
361 size_t payload_size, int level) {
362 strcpy(box.type, type);
363 box.id = ++this->box.id;
364 box.data = p + headerSize;
365 box.size = payload_size;
366 box.data_size = payload_size;
367 box.level = level;
369 box.is_complete = true;
370 box.is_container = false;
371 box.available = payload_size;
372 box.is_incremental = false;
373 box.seq = 0;
374
376 }
377
387 void startIncrementalBox(const char* type, const uint8_t* p,
388 size_t headerSize, size_t payload_size, int level,
389 size_t bufferSize) {
390 box_in_progress = true;
392 box_bytes_expected = payload_size;
393 strncpy(box_type, type, 5);
394 box_level = level;
395 box_seq = 0;
396
397 size_t available_payload = bufferSize - parseOffset - headerSize;
398 incremental_offset = fileOffset + parseOffset;
399 if (available_payload > 0) {
400 box_bytes_received += available_payload;
401 strcpy(box.type, box_type);
402 box.id = ++this->box.id;
403 box.data = p + headerSize;
406 box.available = available_payload;
408 box.file_offset = incremental_offset;
409 box.seq = 0;
410 box.is_incremental = true;
411 box.is_complete = false;
412 box.is_container = false;
414 }
415 // fileOffset += (bufferSize - buffer.available());
416 fileOffset += (parseOffset + payload_size + 8);
417 incremental_offset += available_payload;
418 buffer.clear();
419 parseOffset = 0;
420 }
421
428 size_t to_read = std::min((size_t)box_bytes_expected - box_bytes_received,
429 (size_t)buffer.available());
430 if (to_read == 0) return true;
431 strcpy(box.type, box_type);
432 box.id = ++this->box.id;
433 box.data = buffer.data();
436 box.available = to_read;
438 box.file_offset = incremental_offset;
440 box.is_container = false;
441 box.is_incremental = true;
442 box.seq = ++box_seq;
444 box_bytes_received += to_read;
445 // fileOffset += to_read;
446 buffer.clearArray(to_read);
447 incremental_offset += to_read;
448
450 box_in_progress = false;
451 }
452 return false;
453 }
454
459 if (parseOffset > 0) {
462 parseOffset = 0;
463 }
464 }
465
470 uint64_t currentFileOffset() { return fileOffset + parseOffset; }
471
477 static uint32_t readU32(const uint8_t* p) {
478 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
479 }
480
486 static uint64_t readU64(const uint8_t* p) {
487 return ((uint64_t)readU32(p) << 32) | readU32(p + 4);
488 }
489
490
494 void popLevels() {
495 // Pop levels if we've passed their bounds (absolute file offset)
496 while (!levelStack.empty() &&
497 (fileOffset + parseOffset) >= levelStack.back()) {
498 levelStack.pop_back();
499 }
500 }
501
509 bool is_called = false;
510 bool call_generic = true;
511 for (const auto& entry : callbacks) {
512 if (strncmp(entry.type, box.type, 4) == 0) {
513 entry.cb(box, ref);
514 is_called = true;
515 if (!entry.callGeneric) call_generic = false;
516 }
517 }
519 if ((!is_called || call_generic) && callback) callback(box, ref);
520 }
521
527 bool isContainerBox(const char* type) {
528 // fill with default values if nothing has been defined
529 if (containers.empty()) {
530 // pure containers
531 static const char* containers_str[] = {
532 "moov", "trak", "mdia", "minf", "stbl", "edts", "dinf", "udta",
533 "ilst", "moof", "traf", "mfra", "tref", "iprp", "sinf", "schi"};
534 for (const char* c : containers_str) {
535 ContainerInfo info;
536 info.name = c;
537 info.start = 0;
538 containers.push_back(info);
539 }
540 // container with data
541 ContainerInfo info;
542 info.name = "meta";
543 info.start = 4; // 4 bytes: version (1 byte) + flags (3 bytes)
544 containers.push_back(info);
545 }
546 // find the container by name
547 for (auto& cont : containers) {
548 if (StrView(type) == cont.name) return true;
549 }
550 return false;
551 }
552
558 int getContainerDataLength(const char* type) {
559 for (auto& cont : containers) {
560 if (StrView(type) == cont.name) return cont.start;
561 }
562 return 0;
563 }
564
571 bool isValidType(const char* type, int offset = 0) const {
572 // Check if the type is a valid 4-character string
573 return (type != nullptr && isalnum(type[offset]) &&
574 isalnum(type[offset + 1]) && isalnum(type[offset + 2]) &&
575 isalnum(type[offset + 3]));
576 }
577
583 size_t current = parseOffset;
584 const char* type = (char*)(buffer.data() + parseOffset + 4);
585 for (int j = 0; j < buffer.available() - parseOffset - 4; j += 4) {
586 if (isValidType(type, j)) {
587 if (j != 0) {
588 // report the data under the last valid box
589 box.size = 0;
590 box.data_size = j;
591 box.level = static_cast<int>(levelStack.size()) + 1;
594 }
595
596 return j + parseOffset;
597 }
598 }
599 return parseOffset;
600 }
601};
602
603} // namespace audio_tools
void clear()
same as reset
Definition Buffers.h:95
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:360
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:246
Vector< size_t > levelStack
Stack for container box levels.
Definition MP4Parser.h:243
size_t box_bytes_expected
Total expected bytes for the current box.
Definition MP4Parser.h:262
static void defaultCallback(const Box &box, void *ref)
Default callback that prints box information to Serial.
Definition MP4Parser.h:223
size_t checkParseOffset()
Checks and adjusts the parse offset for valid box types.
Definition MP4Parser.h:582
int getContainerDataLength(const char *type)
Gets the start offset for a subcontainer.
Definition MP4Parser.h:558
int box_level
Current box level (nesting)
Definition MP4Parser.h:264
bool begin()
Initializes the parser.
Definition MP4Parser.h:107
bool continueIncrementalBox()
Continue filling an incremental box. Returns false if not enough data.
Definition MP4Parser.h:427
Vector< CallbackEntry > callbacks
List of type-specific callbacks.
Definition MP4Parser.h:241
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:387
Vector< ContainerInfo > containers
List of container box info.
Definition MP4Parser.h:257
static uint32_t readU32(const uint8_t *p)
Reads a 32-bit big-endian unsigned integer from a buffer.
Definition MP4Parser.h:477
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:151
char box_type[5]
Current box type.
Definition MP4Parser.h:263
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:170
void finalizeParse()
Finalizes parsing, updating file offset and clearing buffer.
Definition MP4Parser.h:458
void setReference(void *ref)
Defines an optional reference. By default it is the parser itself.
Definition MP4Parser.h:69
bool isValidType(const char *type, int offset=0) const
Checks if a type string is a valid 4-character box type.
Definition MP4Parser.h:571
static uint64_t readU64(const uint8_t *p)
Reads a 64-bit big-endian unsigned integer from a buffer.
Definition MP4Parser.h:486
uint64_t currentFileOffset()
Returns the current file offset (absolute position in file).
Definition MP4Parser.h:470
void addContainer(const char *name, int start=0)
Adds a box name that will be interpreted as a container.
Definition MP4Parser.h:158
bool tryStartNewBox(size_t bufferSize)
Try to start parsing a new box. Returns false if not enough data.
Definition MP4Parser.h:289
SingleBuffer< uint8_t > buffer
Buffer for incoming data.
Definition MP4Parser.h:242
Box box
Current box being processed.
Definition MP4Parser.h:247
bool box_in_progress
True if currently parsing a box incrementally.
Definition MP4Parser.h:259
void handleContainerBox(const char *type, uint64_t boxSize, int level)
Handles a container box (box with children).
Definition MP4Parser.h:331
void processCallback(Box &box)
Processes the callback for a box. Calls the type-specific callback if present, and the generic callba...
Definition MP4Parser.h:508
size_t parseOffset
Current parse offset in buffer.
Definition MP4Parser.h:244
bool is_error
True if an error occurred.
Definition MP4Parser.h:248
bool findBox(const char *name, const uint8_t *data, size_t len, Box &result)
find box in box
Definition MP4Parser.h:197
void parse()
Main parsing loop. Handles incremental and complete boxes.
Definition MP4Parser.h:271
BoxCallback callback
Generic callback for all boxes.
Definition MP4Parser.h:240
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:261
bool isContainerBox(const char *type)
Checks if a box type is a container box.
Definition MP4Parser.h:527
size_t write(const char *data, size_t len)
Provide the data to the parser (in chunks if needed).
Definition MP4Parser.h:143
uint64_t fileOffset
Current file offset.
Definition MP4Parser.h:245
void popLevels()
Pops levels from the stack if we've passed their bounds.
Definition MP4Parser.h:494
size_t write(const uint8_t *data, size_t len)
Provide the data to the parser (in chunks if needed).
Definition MP4Parser.h:130
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:172
int available() override
provides the number of entries that are available to read
Definition Buffers.h:229
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:234
bool resize(int size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:292
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:197
T * data()
Provides address of actual data.
Definition Buffers.h:271
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:239
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
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
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:253
int start
Offset of child boxes.
Definition MP4Parser.h:255
const char * name
Name of the container box.
Definition MP4Parser.h:254