arduino-audio-tools
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 
7 namespace audio_tools {
8 
9 class ContainerMP4;
10 
16 struct 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 
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: AudioCodecsBase.h:16
virtual void setOutput(AudioStream &out_stream)
Defines where the decoded result is written to.
Definition: AudioCodecsBase.h:34
virtual void setAudioInfo(AudioInfo from) override
for most decoders this is not needed
Definition: AudioCodecsBase.h:26
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: AudioCodecsBase.h:74
Minimum flexible parser for MPEG4 data (which is based on the Quicktime format). Small atoms will be ...
Definition: ContainerMP4.h:127
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
const char * streamAtom()
Provides the content atom name which will be written incrementally.
Definition: ContainerMP4.h:208
Definition: ContainerMP4.h:92
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:366
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition: Buffers.h:383
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
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