1
+ #pragma once
2
+ #include " AudioLogger.h"
3
+
4
+ #define htonl (x ) \
5
+ (((x) << 24 & 0xFF000000UL ) | ((x) << 8 & 0x00FF0000UL ) | \
6
+ ((x) >> 8 & 0x0000FF00UL ) | ((x) >> 24 & 0x000000FFUL ))
7
+ #define ntohl (x ) htonl(x)
8
+ #define htons (x ) (((uint16_t )(x)&0xff00 ) >> 8 ) | (((uint16_t )(x)&0X00FF ) << 8 )
9
+ #define ntohs (x ) htons(x)
10
+
11
+ namespace audio_tools {
12
+
13
+ /* *
14
+ * @brief Represents a single MPEG4 atom
15
+ * @author Phil Schatzmann
16
+ * @copyright GPLv3
17
+ */
18
+ struct MP4Atom {
19
+ MP4Atom () = default ;
20
+ MP4Atom (const char *atom) { strncpy (this ->atom , atom, 4 ); }
21
+ // start pos in data stream
22
+ size_t start_pos;
23
+
24
+ // / size w/o size filed and atom
25
+ uint32_t size = 0 ;
26
+ // / 4 digit atom name
27
+ char atom[5 ] = {0 };
28
+ // / true if atom is header w/o data content
29
+ bool is_header_atom;
30
+ // data
31
+ const uint8_t *data = nullptr ;
32
+ // length of the data
33
+ uint32_t data_size = 0 ;
34
+
35
+ void setHeader (uint8_t *data, int len) {
36
+ uint32_t *p_size = (uint32_t *)data;
37
+ size = ntohl (*p_size) - 8 ;
38
+ memcpy (atom, data + 4 , 4 );
39
+ // it is a header atom when the next atom just follows it
40
+ is_header_atom = isalpha (data[12 ]) && isalpha (data[13 ]) &&
41
+ isalpha (data[14 ]) && isalpha (data[15 ]);
42
+ LOGI (" %s %d - %s" , atom, size, is_header_atom ? " header" : " atom" );
43
+ }
44
+
45
+ bool is (const char *atom) { return strncmp (this ->atom , atom, 4 ) == 0 ; }
46
+
47
+ void setData (const uint8_t *data, int len) {
48
+ this ->data = data;
49
+ data_size = len;
50
+ // assert(size == len);
51
+ }
52
+
53
+ void clear () {
54
+ size = 0 ;
55
+ memset (atom, 0 , 5 );
56
+ data = nullptr ;
57
+ data_size = 0 ;
58
+ }
59
+
60
+ bool isHeader () { return is_header_atom; }
61
+ operator bool () { return strlen (atom) == 4 ; }
62
+
63
+ uint16_t read16 (int pos) {
64
+ if (size < pos) return 0 ;
65
+ uint16_t *ptr = (uint16_t *)data + pos;
66
+ return ntohs (*ptr);
67
+ }
68
+ uint32_t read32 (int pos) {
69
+ if (size < pos) return 0 ;
70
+ uint32_t *ptr = (uint32_t *)data + pos;
71
+ return ntohl (*ptr);
72
+ }
73
+ };
74
+
75
+ /* **
76
+ * @brief Buffer which is used for parsing the mpeg4 data
77
+ * @author Phil Schatzmann
78
+ * @copyright GPLv3
79
+ */
80
+ class MP4ParseBuffer {
81
+ public:
82
+ MP4ParseBuffer () = default ;
83
+ // provides the data
84
+ size_t write (const uint8_t *data, size_t length) {
85
+ // initialize buffer size
86
+ if (buffer.size () == 0 ) buffer.resize (length);
87
+ return buffer.writeArray (data, length);
88
+ }
89
+
90
+ // / returns the parsed atoms
91
+ MP4Atom parse () {
92
+ // determine atom length from header of buffer
93
+ MP4Atom result;
94
+ while (true ) {
95
+ if (buffer.available () < 64 ) break ;
96
+
97
+ uint8_t header[64 ];
98
+ buffer.peekArray (header, 64 );
99
+ result.setHeader (header, 64 );
100
+ int16_t len = peekLength ();
101
+
102
+ if (result.is_header_atom ) {
103
+ // consume data for header atom
104
+ buffer.readArray (header, 32 );
105
+ } else {
106
+ if (len <= buffer.available ()) {
107
+ // not enough data
108
+ break ;
109
+ } else {
110
+ buffer.readArray (header, 32 );
111
+ uint8_t data[len - 32 ];
112
+ buffer.readArray (data, len - 32 );
113
+ result.setData (data, len - 32 );
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ }
119
+
120
+ int available () { return buffer.available (); }
121
+
122
+ size_t readArray (uint8_t *data, size_t len) {
123
+ return buffer.readArray (data, len);
124
+ }
125
+
126
+ protected:
127
+ RingBuffer<uint8_t > buffer{0 };
128
+
129
+ // determines the actual length attribute value from the atom at the head of
130
+ // the buffer
131
+ int16_t peekLength () {
132
+ int16_t len;
133
+ buffer.peekArray ((uint8_t *)&len, 4 );
134
+ len = ntohl (len);
135
+ return len;
136
+ }
137
+ };
138
+
139
+ /* *
140
+ * @brief Minimum flexible parser for MPEG4 data (which is based on the
141
+ * Quicktime format). Small atoms will be make available via a callback method.
142
+ * The big (audio) content is written to the Print object which was specified in
143
+ * the constructor.
144
+ * @ingroup codecs
145
+ * @ingroup decoder
146
+ * @ingroup video
147
+ * @author Phil Schatzmann
148
+ * @copyright GPLv3
149
+ */
150
+ class ContainerMP4 : public AudioStream {
151
+ public:
152
+ ContainerMP4 (Print &out, const char *streamAtom = " mdat" ) {
153
+ p_print = &out;
154
+ stream_atom = streamAtom;
155
+ }
156
+
157
+ bool begin () { current_pos = 0 ; }
158
+
159
+ // / writes the next atom
160
+ size_t write (const uint8_t *data, size_t length) override {
161
+ // direct output to stream
162
+ if (stream_out_open > 0 ) {
163
+ size_t len = min (stream_out_open, length);
164
+ size_t result = len;
165
+ MP4Atom atom{stream_atom};
166
+ atom.size = len;
167
+ atom.data = data;
168
+ if (callback != nullptr ) callback (atom, *this );
169
+ current_pos += len;
170
+ stream_out_open -= result;
171
+ return result;
172
+ }
173
+
174
+ // parse data and provide info via callback
175
+ size_t result = buffer.write (data, length);
176
+ MP4Atom atom = buffer.parse ();
177
+ while (atom) {
178
+ atom.start_pos = current_pos;
179
+ if (callback != nullptr ) callback (atom, *this );
180
+ current_pos += atom.size + 8 ;
181
+ atom = buffer.parse ();
182
+ }
183
+
184
+ // write data of mdat to print
185
+ if (atom.is (stream_atom)) {
186
+ setStreamOutputSize (atom.size );
187
+ size_t len = buffer.available ();
188
+ uint8_t tmp[len];
189
+ buffer.readArray (tmp, len);
190
+
191
+ MP4Atom atom{stream_atom};
192
+ atom.start_pos = current_pos;
193
+ atom.size = len;
194
+ atom.data = tmp;
195
+ if (callback != nullptr ) callback (atom, *this );
196
+ current_pos += len;
197
+
198
+ stream_out_open -= result;
199
+ }
200
+
201
+ return result;
202
+ }
203
+
204
+ // / Defines the callback that is executed on each atom
205
+ void setCallback (void (*cb)(MP4Atom atom, ContainerMP4 &container)) {
206
+ callback = cb;
207
+ }
208
+
209
+ // / output of mdat to p_print;
210
+ size_t print (const uint8_t *data, size_t len) {
211
+ return p_print == nullptr ? 0 : p_print->write (data, len);
212
+ }
213
+
214
+ // / Provides the content atom which will be written incrementally
215
+ const char *streamAtom () { return stream_atom; }
216
+
217
+ protected:
218
+ MP4ParseBuffer buffer;
219
+ int stream_out_open = 0 ;
220
+ Print *p_print = nullptr ;
221
+ const char *stream_atom;
222
+ size_t current_pos = 0 ;
223
+ void (*callback)(MP4Atom atom, ContainerMP4 &container) = default_callback;
224
+
225
+ void setStreamOutputSize (int size) { stream_out_open = size; }
226
+
227
+ static void default_callback (MP4Atom atom, ContainerMP4 &container) {
228
+ // parse ftyp to determine the subtype
229
+ if (atom.is (" ftyp" )) {
230
+ char subtype[5 ];
231
+ strncpy (subtype, (char *)atom.data + 8 , 4 );
232
+ LOGI (" subtype: %s" , subtype);
233
+ }
234
+
235
+ // parse stsd -> audio info
236
+ if (atom.is (" stsd" )) {
237
+ AudioInfo info;
238
+ info.channels = atom.read16 (8 + 0x20 );
239
+ info.bits_per_sample = atom.read16 (8 + 0x22 ); // not used
240
+ info.sample_rate = atom.read32 (8 + 0x26 );
241
+ info.logInfo ();
242
+ container.setAudioInfo (info);
243
+ }
244
+
245
+ // / output of mdat to p_print;
246
+ if (atom.is (container.streamAtom ())) {
247
+ container.print (atom.data , atom.data_size );
248
+ }
249
+ }
250
+ };
251
+
252
+ } // namespace audio_tools
0 commit comments