arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
ContainerMP4.h
1#pragma once
2#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
3#include "AudioTools/AudioCodecs/CodecAACHelix.h"
4#include "AudioTools/CoreAudio/AudioBasic/Net.h"
5#include "AudioLogger.h"
6
7namespace audio_tools {
8
9class ContainerMP4;
10
16struct MP4Atom {
17 friend class ContainerMP4;
18 MP4Atom(ContainerMP4 *container) { this->container = container; };
19 MP4Atom(ContainerMP4 *container, const char *atom) {
20 strncpy(this->atom, atom, 4);
21 this->container = container;
22 }
23 // start pos in data stream
24 int start_pos = 0;
26 int total_size = 0;
27 int data_size = 0;
29 char atom[5] = {0};
32 // data
33 const uint8_t *data = nullptr;
34 // length of the data
35 // uint32_t data_size = 0;
36 // pointer to parent container
37 ContainerMP4 *container = nullptr;
39 bool is_stream = false;
40
41 void setHeader(uint8_t *data, int total);
42
44 bool is(const char *atom) { return strncmp(this->atom, atom, 4) == 0; }
45
46 // /// Incomplete atom data, we send the data in individual chunks
47 // bool isStream();
48
50 bool isStreamAtom();
51
52 operator bool() {
53 return isalpha(atom[0]) && isalpha(atom[1]) && isalpha(atom[2]) &&
54 isalpha(atom[3]) && total_size >= 0;
55 }
56
58 void setData(const uint8_t *data, int len_data) {
59 this->data = data;
60 this->data_size = len_data;
61 // assert(size == len);
62 }
63
65 void clear() {
66 total_size = 0;
67 data_size = 0;
68 memset(atom, 0, 5);
69 data = nullptr;
70 }
71
73 bool isHeader() { return is_header_atom; }
74
75 uint16_t read16(int pos) {
76 if (data_size < pos) return 0;
77 uint16_t *ptr = (uint16_t *)(data + pos);
78 return ntohs(*ptr);
79 }
80 uint32_t read32(int pos) {
81 if (data_size < pos) return 0;
82 uint32_t *ptr = (uint32_t *)(data + pos);
83 return ntohl(*ptr);
84 }
85};
86
87/***
88 * @brief Buffer which is used for parsing the mpeg4 data
89 * @author Phil Schatzmann
90 * @copyright GPLv3
91 */
93 public:
94 MP4ParseBuffer(ContainerMP4 *container) { this->container = container; };
95 // provides the data
96 size_t write(const uint8_t *data, size_t len) {
97 // initialize buffer size
98 if (buffer.size() == 0) buffer.resize(len);
99 return buffer.writeArray(data, len);
100 }
101
103 MP4Atom parse();
104
105 int available() { return buffer.available(); }
106
107 size_t readArray(uint8_t *data, size_t len) {
108 return buffer.readArray(data, len);
109 }
110
111 protected:
112 RingBuffer<uint8_t> buffer{1024};
113 ContainerMP4 *container = nullptr;
114};
115
128 friend class MP4ParseBuffer;
129
130 public:
131 ContainerMP4(const char *streamAtom = "mdat") {
132 stream_atom = streamAtom;
133 }
134
135 ContainerMP4(AudioDecoder &decoder, const char *streamAtom = "mdat") {
136 stream_atom = streamAtom;
137 p_decoder = &decoder;
138 }
139
140 ContainerMP4(AudioDecoder *decoder, const char *streamAtom = "mdat") {
141 stream_atom = streamAtom;
142 p_decoder = decoder;
143 }
144
146 bool begin() override {
147 current_pos = 0;
148 assert(p_print!=nullptr);
149 p_decoder->setOutput(*p_print);
150 bool rc = p_decoder->begin();
151 is_active = true;
152 return rc;
153 }
154
156 void end() override {
157 p_decoder->end();
158 is_active = false;
159 }
160
161 operator bool() override { return is_active; }
162
164 size_t write(const uint8_t *data, size_t len) override {
165 TRACED();
166 // initialize the max_size with copy length
167 if (max_size == 0) setMaxSize(len);
168
169 // direct output to stream
170 if (stream_out_open > 0) {
171 int len = min(stream_out_open, (int)len);
172 int result = len;
173 MP4Atom atom{this, stream_atom};
174 atom.total_size = len;
175 atom.data_size = len;
176 atom.data = data;
177 atom.start_pos = current_pos;
178
179 if (data_callback != nullptr) data_callback(atom, *this);
180 current_pos += len;
181 stream_out_open -= result;
182 return result;
183 }
184
185 // parse data and provide info via callback
186 size_t result = buffer.write(data, len);
187 MP4Atom atom = buffer.parse();
188 while (atom && !atom.is_stream) {
189 // atom.start_pos = current_pos;
190 // current_pos += atom.total_size;
191 atom = buffer.parse();
192 }
193
194 return result;
195 }
196
198 void setDataCallback(void (*cb)(MP4Atom &atom, ContainerMP4 &container)) {
199 data_callback = cb;
200 }
201
203 void setIsHeaderCallback(bool (*cb)(MP4Atom *atom, const uint8_t *data)) {
204 is_header_callback = cb;
205 }
206
208 const char *streamAtom() { return stream_atom; }
209
211 bool isHeader(MP4Atom *atom, const uint8_t *data) {
212 return is_header_callback(atom, data);
213 }
214
216 void setMaxSize(int size) { max_size = size; }
217
219 int maxSize() { return max_size; }
220
221 protected:
222 int max_size;
223 MP4ParseBuffer buffer{this};
224 int stream_out_open = 0;
225 bool is_sound = false;
226 bool is_active = false;
227 AACDecoderHelix aac_decoder;
228 AudioDecoder* p_decoder = &aac_decoder;
229 const char *stream_atom;
230 int current_pos = 0;
231 const char *current_atom = nullptr;
232 void (*data_callback)(MP4Atom &atom,
233 ContainerMP4 &container) = default_data_callback;
234 bool (*is_header_callback)(MP4Atom *atom,
235 const uint8_t *data) = default_is_header_callback;
236
238 size_t decode(const uint8_t *data, size_t len) {
239 return p_decoder->write(data, len);
240 }
241
243 void setStreamOutputSize(int size) { stream_out_open = size; }
244
246 static bool default_is_header_callback(MP4Atom *atom, const uint8_t *data) {
247 // it is a header atom when the next atom just follows it
248 bool is_header_atom = isalpha(data[12]) && isalpha(data[13]) &&
249 isalpha(data[14]) && isalpha(data[15]) &&
250 atom->data_size > 0;
251 return is_header_atom;
252 }
253
255 static void default_data_callback(MP4Atom &atom, ContainerMP4 &container) {
256 // char msg[80];
257 // snprintf(msg, 80, "%s %d-%d %d", atom.atom, (int)atom.start_pos,
258 // atom.start_pos+atom.total_size, atom.total_size);
259 LOGI("%s: 0x%06x-0x%06x %d %s", atom.atom, (int)atom.start_pos,
260 (int)atom.start_pos + atom.total_size, atom.total_size, atom.is_header_atom?" *":"");
261 if (atom.total_size > 1024) {
262 TRACED();
263 }
264 // parse ftyp to determine the subtype
265 if (atom.is("ftyp") && atom.data != nullptr) {
266 char subtype[5];
267 memcpy(subtype, atom.data, 4);
268 subtype[4] = 0;
269 LOGI(" subtype: %s", subtype);
270 }
271
272 // parse hdlr to determine if audio
273 if (atom.is("hdlr") && atom.data != nullptr) {
274 const uint8_t *sound = atom.data + 8;
275 container.is_sound = memcmp("soun", sound, 4) == 0;
276 LOGI(" is_sound: %s", container.is_sound ? "true" : "false");
277 }
278
279 // parse stsd -> audio info
280 if (atom.is("stsd")) {
281 AudioInfo info;
282 info.channels = atom.read16(0x20);
283 info.bits_per_sample = atom.read16(0x22); // not used
284 info.sample_rate = atom.read32(0x26);
285 info.logInfo();
286 container.setAudioInfo(info);
287 // init raw output
288 container.p_decoder->setAudioInfo(info);
289 }
290
292 if (atom.isStreamAtom()) { //
293 if (container.is_sound){
294 int pos = 0;
295 int open = atom.data_size;
296 while (open>0){
297 int processed = container.decode(atom.data+pos, open);
298 open -=processed;
299 pos +=processed;
300 }
301
302 } else {
303 LOGD("%s: %d bytes ignored", container.stream_atom, atom.data_size);
304 }
305 }
306 }
307};
308
309//-------------------------------------------------------------------------------------------------------------------
310// methods
311//--------------------------------------------------------------------------------------------------------------------
312
313void MP4Atom::setHeader(uint8_t *data, int len) {
314 uint32_t *p_size = (uint32_t *)data;
315 total_size = ntohl(*p_size);
316 data_size = total_size - 8;
317 memcpy(atom, data + 4, 4);
318 if (total_size==1){
319
320 }
321
322 is_header_atom = container->isHeader(this, data);
323}
324
326 const char *name = container->streamAtom();
327 return is(name);
328}
329
331 TRACED();
332 // determine atom length from header of buffer
333 MP4Atom result{container};
334 result.start_pos = container->current_pos;
335
336 // read potentially 2 headers
337 uint8_t header[16];
338 buffer.peekArray(header, 16);
339 result.setHeader(header, 16);
340 result.is_stream = false;
341 container->current_atom = result.atom;
342
343 // abort on invalid atom
344 if (!result) {
345 LOGE("Invalid atom");
346 return result;
347 }
348
349 // make sure that we fill the buffer to it's max limit
350 if (result.data_size > buffer.available() &&
351 buffer.available() != container->maxSize()) {
352 result.clear();
353 return result;
354 }
355
356 // temp buffer for data
357 uint8_t data[min(result.data_size, buffer.available())];
358
359 if (result.is_header_atom) {
360 // consume data for header atom
361 buffer.readArray(header, 8);
362 container->current_pos += 8;
363 } else {
364 if (result.total_size > buffer.available()) {
365 // not enough data use streaming
366 LOGI("total %s: 0x%06x-0x%06x - %d", container->current_atom,
367 container->current_pos, container->current_pos + result.total_size,
368 result.data_size);
369 int avail = buffer.available() - 8;
370 container->setStreamOutputSize(result.data_size - avail);
371
372 buffer.readArray(header, 8);
373 buffer.readArray(data, avail);
374 result.setData(data, avail);
375 result.total_size = avail;
376 result.data_size = avail - 8;
377 result.is_stream = true;
378 assert(buffer.available()==0);
379 container->current_pos += result.total_size;
380 } else {
381 buffer.readArray(header, 8);
382 buffer.readArray(data, result.data_size);
383 result.setData(data, result.data_size);
384 container->current_pos += result.total_size;
385 }
386 }
387
388 // callback for data
389 if (result && container->data_callback != nullptr)
390 container->data_callback(result, *container);
391
392 return result;
393}
394
395} // namespace audio_tools
Decoding of encoded audio into PCM data.
Definition AudioCodecsBase.h:18
virtual void setOutput(AudioStream &out_stream)
Defines where the decoded result is written to.
Definition AudioCodecsBase.h:36
virtual void setAudioInfo(AudioInfo from) override
for most decoders this is not needed
Definition AudioCodecsBase.h:28
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:37
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:59
Parent class for all container formats.
Definition AudioCodecsBase.h:80
Minimum flexible parser for MPEG4 data (which is based on the Quicktime format). Small atoms will be ...
Definition ContainerMP4.h:127
const char * streamAtom()
Provides the content atom name which will be written incrementally.
Definition ContainerMP4.h:208
void end() override
ends the processing
Definition ContainerMP4.h:156
size_t write(const uint8_t *data, size_t len) override
writes the data to be parsed into atoms
Definition ContainerMP4.h:164
static void default_data_callback(MP4Atom &atom, ContainerMP4 &container)
Default logic to process an atom.
Definition ContainerMP4.h:255
void setDataCallback(void(*cb)(MP4Atom &atom, ContainerMP4 &container))
Defines the callback that is executed on each atom.
Definition ContainerMP4.h:198
void setStreamOutputSize(int size)
Defines the size of open data that will be written directly w/o parsing.
Definition ContainerMP4.h:243
bool isHeader(MP4Atom *atom, const uint8_t *data)
Checks if the indicated atom is a header atom: you can define your custom method with setIsHeaderCall...
Definition ContainerMP4.h:211
static bool default_is_header_callback(MP4Atom *atom, const uint8_t *data)
Default logic to determine if a atom is a header.
Definition ContainerMP4.h:246
void setIsHeaderCallback(bool(*cb)(MP4Atom *atom, const uint8_t *data))
Defines the callback which is used to determine if an atom is a header atom.
Definition ContainerMP4.h:203
bool begin() override
starts the processing
Definition ContainerMP4.h:146
int maxSize()
Provides the maximum size.
Definition ContainerMP4.h:219
void setMaxSize(int size)
Defines the maximum size that we can submit to the decoder.
Definition ContainerMP4.h:216
size_t decode(const uint8_t *data, size_t len)
output of audio mdat to helix decoder;
Definition ContainerMP4.h:238
Definition ContainerMP4.h:92
MP4Atom parse()
returns the parsed atoms
Definition ContainerMP4.h:330
Implements a typed Ringbuffer.
Definition Buffers.h:308
virtual int available()
provides the number of entries that are available to read
Definition Buffers.h:377
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition Buffers.h:394
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:53
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59
Represents a single MPEG4 atom.
Definition ContainerMP4.h:16
int total_size
size w/o size and atom fields
Definition ContainerMP4.h:26
bool isHeader()
Returns true if the atom is a header atom.
Definition ContainerMP4.h:73
void setData(const uint8_t *data, int len_data)
Updates the data and size field.
Definition ContainerMP4.h:58
bool is_header_atom
true if atom is header w/o data content
Definition ContainerMP4.h:31
bool isStreamAtom()
Atom which is sent to print output.
Definition ContainerMP4.h:325
bool is(const char *atom)
Compares the atom name.
Definition ContainerMP4.h:44
void clear()
Clears the atom.
Definition ContainerMP4.h:65
char atom[5]
4 digit atom name
Definition ContainerMP4.h:29
bool is_stream
data is provided in
Definition ContainerMP4.h:39