Skip to content

Commit 1ff1a78

Browse files
committed
DRAFT MPEG4
1 parent f99485a commit 1ff1a78

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed

src/AudioCodecs/ContainerMPEG4.h

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
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

Comments
 (0)