@@ -16,66 +16,86 @@ namespace audio_tools {
16
16
* @brief MP4Parser is a class that parses MP4 files and extracts boxes (atoms).
17
17
* It provides a callback mechanism to process each box as it is parsed. You can
18
18
* define specific callbacks for individual box types or use a generic callback
19
- * for the undefined boxes: By default is just prints the box information to
19
+ * for the undefined boxes: By default it just prints the box information to
20
20
* Serial.
21
21
* If a container box contains data, it will be processed recursively and if it
22
22
* contains data itself, it will be reported in a second callback call.
23
- * @Note This parser expects that the buffer size is largen then the biggest
23
+ * @note This parser expects that the buffer size is larger than the biggest
24
24
* box!
25
25
* @ingroup codecs
26
26
* @author Phil Schatzmann
27
27
*/
28
-
29
28
class MP4Parser {
30
29
public:
31
- // / An individual box in the MP4 file
30
+ /* *
31
+ * @brief Represents an individual box in the MP4 file.
32
+ */
32
33
struct Box {
33
- friend class MP4Parser ; // Allow MP4Parser to access private members
34
- friend class MP4ParserExt ; // Allow MP4Parser to access private members
35
- size_t id = 0 ;
36
- char type[5 ]; // 4-character box type
37
- const uint8_t * data = nullptr ; // Pointer to box payload (not including header)
38
- size_t data_size = 0 ;
39
- size_t size = 0 ; // Size of payload (not including header)
40
- int level = 0 ; // Nesting depth
41
- uint64_t offset = 0 ; // File offset where box starts
42
- bool is_complete = false ;
43
- bool is_container = false ;
44
-
45
- private:
46
- // SingleBuffer<uint8_t> data_buffer; // Buffer to hold box data if needed
34
+ friend class MP4Parser ; // /< Allow MP4Parser to access private members
35
+ friend class MP4ParserExt ; // /< Allow MP4ParserExt to access private members
36
+ size_t id = 0 ; // /< Unique box ID
37
+ char type[5 ]; // /< 4-character box type (null-terminated)
38
+ const uint8_t * data = nullptr ; // /< Pointer to box payload (not including header)
39
+ size_t data_size = 0 ; // /< Size of payload (not including header)
40
+ size_t size = 0 ; // /< Size of payload (not including header)
41
+ int level = 0 ; // /< Nesting depth
42
+ uint64_t offset = 0 ; // /< File offset where box starts
43
+ bool is_complete = false ; // /< True if the box data is complete
44
+ bool is_container = false ; // /< True if the box is a container
47
45
};
48
46
49
47
using BoxCallback = std::function<void (Box&, void * ref)>;
50
48
51
- // / Type specific callbacks
49
+ /* *
50
+ * @brief Structure for type-specific callbacks.
51
+ */
52
52
struct CallbackEntry {
53
- char type[5 ]; // 4-character box type
54
- BoxCallback cb; // Callback function
53
+ char type[5 ]; // /< 4-character box type
54
+ BoxCallback cb; // /< Callback function
55
+ bool callGeneric = true ; // /< If true, also call the generic callback after this one
55
56
};
56
57
57
- // / Defines an optional reference. By default it is the parser itself
58
+ /* *
59
+ * @brief Defines an optional reference. By default it is the parser itself.
60
+ * @param ref Pointer to reference object.
61
+ */
58
62
void setReference (void * ref) { this ->ref = ref; }
59
63
60
- // / Defines the generic callback for all boxes
64
+ /* *
65
+ * @brief Defines the generic callback for all boxes.
66
+ * @param cb Callback function for all boxes.
67
+ */
61
68
void setCallback (BoxCallback cb) { callback = cb; }
62
69
63
- // / Defines a specific callback for a box type
64
- void setCallback (const char * type, BoxCallback cb) {
70
+ /* *
71
+ * @brief Defines a specific callback for a box type.
72
+ * @param type 4-character box type (e.g. "moov", "mdat").
73
+ * @param cb Callback function for this box type.
74
+ * @param callGeneric If true, the generic callback will also be called after the type-specific callback.
75
+ */
76
+ void setCallback (const char * type, BoxCallback cb, bool callGeneric = true ) {
65
77
CallbackEntry entry;
66
78
strncpy (entry.type , type, 4 );
67
79
entry.type [4 ] = ' \0 ' ; // Ensure null-termination
68
80
entry.cb = cb;
81
+ entry.callGeneric = callGeneric;
69
82
callbacks.push_back (entry);
70
83
};
71
84
72
- // / Defines a specific buffer size
85
+ /* *
86
+ * @brief Defines a specific buffer size.
87
+ * @param size Buffer size in bytes.
88
+ * @return true if the buffer was resized successfully.
89
+ */
73
90
bool resize (size_t size) {
74
91
buffer.resize (size);
75
92
return buffer.size () == size;
76
93
}
77
94
78
- // / Initializes the parser
95
+ /* *
96
+ * @brief Initializes the parser.
97
+ * @return true on success.
98
+ */
79
99
bool begin () {
80
100
buffer.clear ();
81
101
if (buffer.size () == 0 ) buffer.resize (1024 );
@@ -92,29 +112,52 @@ class MP4Parser {
92
112
return true ;
93
113
}
94
114
95
- // / Provide the data to the parser (in chunks if needed)
115
+ /* *
116
+ * @brief Provide the data to the parser (in chunks if needed).
117
+ * @param data Pointer to input data.
118
+ * @param len Length of input data.
119
+ * @return Number of bytes written to the buffer.
120
+ */
96
121
size_t write (const uint8_t * data, size_t len) {
97
122
if (is_error) return len; // If an error occurred, skip writing
98
123
size_t result = buffer.writeArray (data, len);
99
124
parse ();
100
125
return result;
101
126
}
102
127
103
- // / Provide the data to the parser (in chunks if needed)
128
+ /* *
129
+ * @brief Provide the data to the parser (in chunks if needed).
130
+ * @param data Pointer to input data (char*).
131
+ * @param len Length of input data.
132
+ * @return Number of bytes written to the buffer.
133
+ */
104
134
size_t write (const char * data, size_t len) {
105
135
return write (reinterpret_cast <const uint8_t *>(data), len);
106
136
}
107
137
138
+ /* *
139
+ * @brief Returns the available space for writing.
140
+ * @return Number of bytes available for writing.
141
+ */
108
142
int availableForWrite () { return buffer.availableForWrite (); }
109
143
110
- // / Adds a box name that will be interpreted as container
144
+ /* *
145
+ * @brief Adds a box name that will be interpreted as a container.
146
+ * @param name Name of the container box.
147
+ * @param start Offset of child boxes (default 0).
148
+ */
111
149
void addContainer (const char * name, int start = 0 ) {
112
150
ContainerInfo info;
113
151
info.name = name;
114
152
info.start = start; // offset of child boxes
115
153
}
116
154
117
- // / trigger separate parsing (and callbacks) on the indicated string
155
+ /* *
156
+ * @brief Trigger separate parsing (and callbacks) on the indicated string.
157
+ * @param str Pointer to the string data.
158
+ * @param len Length of the string data.
159
+ * @return Number of bytes parsed.
160
+ */
118
161
int parseString (const uint8_t * str, int len) {
119
162
char type[5 ];
120
163
int idx = 0 ;
@@ -136,25 +179,36 @@ class MP4Parser {
136
179
}
137
180
138
181
protected:
139
- BoxCallback callback = defaultCallback;
140
- Vector<CallbackEntry> callbacks;
141
- SingleBuffer<uint8_t > buffer;
142
- Vector<size_t > levelStack;
143
- size_t parseOffset = 0 ;
144
- uint64_t fileOffset = 0 ;
145
- void * ref = this ;
146
- Box box;
147
- bool is_error = false ;
182
+ BoxCallback callback = defaultCallback; // /< Generic callback for all boxes
183
+ Vector<CallbackEntry> callbacks; // /< List of type-specific callbacks
184
+ SingleBuffer<uint8_t > buffer; // /< Buffer for incoming data
185
+ Vector<size_t > levelStack; // /< Stack for container box levels
186
+ size_t parseOffset = 0 ; // /< Current parse offset in buffer
187
+ uint64_t fileOffset = 0 ; // /< Current file offset
188
+ void * ref = this ; // /< Reference pointer for callbacks
189
+ Box box; // /< Current box being processed
190
+ bool is_error = false ; // /< True if an error occurred
191
+
192
+ /* *
193
+ * @brief Structure for container box information.
194
+ */
148
195
struct ContainerInfo {
149
- const char * name = nullptr ;
150
- int start = 0 ;
196
+ const char * name = nullptr ; // /< Name of the container box
197
+ int start = 0 ; // /< Offset of child boxes
151
198
};
152
- Vector<ContainerInfo> containers;
199
+ Vector<ContainerInfo> containers; // /< List of container box info
153
200
154
- // / Returns the current file offset (absolute position in file)
201
+ /* *
202
+ * @brief Returns the current file offset (absolute position in file).
203
+ * @return Current file offset.
204
+ */
155
205
uint64_t currentFileOffset () { return fileOffset + parseOffset; }
156
206
157
- // Default callback that prints box information to Serial
207
+ /* *
208
+ * @brief Default callback that prints box information to Serial.
209
+ * @param box The box being processed.
210
+ * @param ref Optional reference pointer.
211
+ */
158
212
static void defaultCallback (const Box& box, void * ref) {
159
213
char space[box.level * 2 + 1 ];
160
214
memset (space, ' ' , box.level * 2 );
@@ -172,13 +226,27 @@ class MP4Parser {
172
226
#endif
173
227
}
174
228
229
+ /* *
230
+ * @brief Reads a 32-bit big-endian unsigned integer from a buffer.
231
+ * @param p Pointer to buffer.
232
+ * @return 32-bit unsigned integer.
233
+ */
175
234
static uint32_t readU32 (const uint8_t * p) {
176
235
return (p[0 ] << 24 ) | (p[1 ] << 16 ) | (p[2 ] << 8 ) | p[3 ];
177
236
}
178
237
238
+ /* *
239
+ * @brief Reads a 64-bit big-endian unsigned integer from a buffer.
240
+ * @param p Pointer to buffer.
241
+ * @return 64-bit unsigned integer.
242
+ */
179
243
static uint64_t readU64 (const uint8_t * p) {
180
244
return ((uint64_t )readU32 (p) << 32 ) | readU32 (p + 4 );
181
245
}
246
+
247
+ /* *
248
+ * @brief Main parsing loop. Handles parsing of boxes in the buffer.
249
+ */
182
250
virtual void parse () {
183
251
while (true ) {
184
252
size_t bufferSize = buffer.available ();
@@ -253,6 +321,9 @@ class MP4Parser {
253
321
}
254
322
}
255
323
324
+ /* *
325
+ * @brief Pops levels from the stack if we've passed their bounds.
326
+ */
256
327
void popLevels () {
257
328
// Pop levels if we've passed their bounds (absolute file offset)
258
329
while (!levelStack.empty () &&
@@ -261,18 +332,30 @@ class MP4Parser {
261
332
}
262
333
}
263
334
335
+ /* *
336
+ * @brief Processes the callback for a box.
337
+ * Calls the type-specific callback if present, and the generic callback if allowed.
338
+ * @param box The box being processed.
339
+ */
264
340
void processCallback (Box& box) {
265
341
bool is_called = false ;
342
+ bool call_generic = true ;
266
343
for (const auto & entry : callbacks) {
267
344
if (strncmp (entry.type , box.type , 4 ) == 0 ) {
268
345
entry.cb (box, ref);
269
346
is_called = true ;
347
+ if (!entry.callGeneric ) call_generic = false ;
270
348
}
271
349
}
272
- // / call generic callback
273
- if (!is_called) callback (box, ref);
350
+ // / call generic callback if allowed
351
+ if (( !is_called || call_generic) && callback ) callback (box, ref);
274
352
}
275
353
354
+ /* *
355
+ * @brief Checks if a box type is a container box.
356
+ * @param type Box type string.
357
+ * @return true if container box, false otherwise.
358
+ */
276
359
bool isContainerBox (const char * type) {
277
360
// fill with default values if nothing has been defined
278
361
if (containers.empty ()) {
@@ -294,27 +377,47 @@ class MP4Parser {
294
377
return false ;
295
378
}
296
379
380
+ /* *
381
+ * @brief Gets the start offset for a subcontainer.
382
+ * @param type Box type string.
383
+ * @return Offset of the subcontainer.
384
+ */
297
385
int getSubcontainerStart (const char * type) {
298
386
for (auto & cont : containers) {
299
387
if (StrView (type) == cont.name ) return cont.start ;
300
388
}
301
389
return 0 ;
302
390
}
303
391
392
+ /* *
393
+ * @brief Checks if a box type is a persisted box.
394
+ * @param type Box type string.
395
+ * @return true if persisted, false otherwise.
396
+ */
304
397
bool isPersistedBox (const char * type) const {
305
398
static const char * persisted[] = {" stsz" };
306
399
for (const char * p : persisted)
307
400
if (StrView (type) == p) return true ;
308
401
return false ;
309
402
}
310
403
404
+ /* *
405
+ * @brief Checks if a type string is a valid 4-character box type.
406
+ * @param type Pointer to type string.
407
+ * @param offset Offset in the string.
408
+ * @return true if valid, false otherwise.
409
+ */
311
410
bool isValidType (const char * type, int offset=0 ) const {
312
411
// Check if the type is a valid 4-character string
313
412
return (type != nullptr &&
314
413
isalnum (type[offset]) && isalnum (type[offset+1 ]) &&
315
414
isalnum (type[offset+2 ]) && isalnum (type[offset+3 ]));
316
415
}
317
416
417
+ /* *
418
+ * @brief Checks and adjusts the parse offset for valid box types.
419
+ * @return Adjusted parse offset.
420
+ */
318
421
size_t checkParseOffset () {
319
422
size_t current = parseOffset;
320
423
const char * type = (char *)(buffer.data () + parseOffset + 4 );
0 commit comments