arduino-audio-tools
ContainerMP4.h
1 #pragma once
2 #include "AudioBasic/Net.h"
3 #include "AudioLogger.h"
4 #include "AudioCodecs/CodecAACHelix.h"
5 
6 namespace audio_tools {
7 
8 class ContainerMP4;
9 
15 struct MP4Atom {
16  friend class ContainerMP4;
17  MP4Atom(ContainerMP4 *container) { this->container = container; };
18  MP4Atom(ContainerMP4 *container, const char *atom) {
19  strncpy(this->atom, atom, 4);
20  this->container = container;
21  }
22  // start pos in data stream
23  int start_pos = 0;
25  int total_size = 0;
26  int data_size = 0;
28  char atom[5] = {0};
31  // data
32  const uint8_t *data = nullptr;
33  // length of the data
34  // uint32_t data_size = 0;
35  // pointer to parent container
36  ContainerMP4 *container = nullptr;
38  bool is_stream = false;
39 
40  void setHeader(uint8_t *data, int total);
41 
43  bool is(const char *atom) { return strncmp(this->atom, atom, 4) == 0; }
44 
45  // /// Incomplete atom data, we send the data in individual chunks
46  // bool isStream();
47 
49  bool isStreamAtom();
50 
51  operator bool() {
52  return isalpha(atom[0]) && isalpha(atom[1]) && isalpha(atom[2]) &&
53  isalpha(atom[3]) && total_size >= 0;
54  }
55 
57  void setData(const uint8_t *data, int len_data) {
58  this->data = data;
59  this->data_size = len_data;
60  // assert(size == len);
61  }
62 
64  void clear() {
65  total_size = 0;
66  data_size = 0;
67  memset(atom, 0, 5);
68  data = nullptr;
69  }
70 
72  bool isHeader() { return is_header_atom; }
73 
74  uint16_t read16(int pos) {
75  if (data_size < pos) return 0;
76  uint16_t *ptr = (uint16_t *)(data + pos);
77  return ntohs(*ptr);
78  }
79  uint32_t read32(int pos) {
80  if (data_size < pos) return 0;
81  uint32_t *ptr = (uint32_t *)(data + pos);
82  return ntohl(*ptr);
83  }
84 };
85 
86 /***
87  * @brief Buffer which is used for parsing the mpeg4 data
88  * @author Phil Schatzmann
89  * @copyright GPLv3
90  */
92  public:
93  MP4ParseBuffer(ContainerMP4 *container) { this->container = container; };
94  // provides the data
95  size_t write(const uint8_t *data, size_t length) {
96  // initialize buffer size
97  if (buffer.size() == 0) buffer.resize(length);
98  return buffer.writeArray(data, length);
99  }
100 
102  MP4Atom parse();
103 
104  int available() { return buffer.available(); }
105 
106  size_t readArray(uint8_t *data, size_t len) {
107  return buffer.readArray(data, len);
108  }
109 
110  protected:
111  RingBuffer<uint8_t> buffer{1024};
112  ContainerMP4 *container = nullptr;
113 };
114 
127  friend class MP4ParseBuffer;
128 
129  public:
130  ContainerMP4(const char *streamAtom = "mdat") {
131  stream_atom = streamAtom;
132  }
133 
134  ContainerMP4(AudioDecoder &decoder, const char *streamAtom = "mdat") {
135  stream_atom = streamAtom;
136  p_decoder = &decoder;
137  }
138 
139  ContainerMP4(AudioDecoder *decoder, const char *streamAtom = "mdat") {
140  stream_atom = streamAtom;
141  p_decoder = decoder;
142  }
143 
145  bool begin() override {
146  current_pos = 0;
147  assert(p_print!=nullptr);
148  p_decoder->setOutput(*p_print);
149  bool rc = p_decoder->begin();
150  is_active = true;
151  return rc;
152  }
153 
155  void end() override {
156  p_decoder->end();
157  is_active = false;
158  }
159 
160  operator bool() override { return is_active; }
161 
163  size_t write(const void *in, size_t length) override {
164  TRACED();
165  uint8_t *data = (uint8_t *)in;
166  // initialize the max_size with copy length
167  if (max_size == 0) setMaxSize(length);
168 
169  // direct output to stream
170  if (stream_out_open > 0) {
171  int len = min(stream_out_open, (int)length);
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, length);
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{false};
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 
313 void 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
Docoding of encoded audio into PCM data.
Definition: AudioEncoded.h:18
virtual void setOutput(AudioStream &out_stream)
Defines where the decoded result is written to.
Definition: AudioEncoded.h:36
virtual void setAudioInfo(AudioInfo from) override
for most decoders this is not needed
Definition: AudioEncoded.h:28
virtual int readArray(T data[], int len)
reads multiple values
Definition: Buffers.h:41
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
Parent class for all container formats.
Definition: AudioEncoded.h:78
Minimum flexible parser for MPEG4 data (which is based on the Quicktime format). Small atoms will be ...
Definition: ContainerMP4.h:126
void end() override
ends the processing
Definition: ContainerMP4.h:155
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:145
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
const char * streamAtom()
Provides the content atom name which will be written incrementally.
Definition: ContainerMP4.h:208
size_t write(const void *in, size_t length) override
writes the data to be parsed into atoms
Definition: ContainerMP4.h:163
Definition: ContainerMP4.h:91
MP4Atom parse()
returns the parsed atoms
Definition: ContainerMP4.h:330
virtual int available()
provides the number of entries that are available to read
Definition: Buffers.h:362
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition: Buffers.h:379
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:57
Represents a single MPEG4 atom.
Definition: ContainerMP4.h:15
int total_size
size w/o size and atom fields
Definition: ContainerMP4.h:25
bool isHeader()
Returns true if the atom is a header atom.
Definition: ContainerMP4.h:72
void setData(const uint8_t *data, int len_data)
Updates the data and size field.
Definition: ContainerMP4.h:57
bool is_header_atom
true if atom is header w/o data content
Definition: ContainerMP4.h:30
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:43
void clear()
Clears the atom.
Definition: ContainerMP4.h:64
char atom[5]
4 digit atom name
Definition: ContainerMP4.h:28
bool is_stream
data is provided in
Definition: ContainerMP4.h:38