FastLED 3.9.15
Loading...
Searching...
No Matches
mp4_parser.cpp.hpp
Go to the documentation of this file.
2
3namespace fl {
4
5namespace {
6
7// Read a big-endian uint32 from a byte span at offset.
8// Returns 0 and sets ok=false if out of bounds.
9inline fl::u32 readU32BE(fl::span<const fl::u8> data, fl::size offset, bool& ok) {
10 if (offset + 4 > data.size()) { ok = false; return 0; }
11 return (fl::u32(data[offset]) << 24) |
12 (fl::u32(data[offset + 1]) << 16) |
13 (fl::u32(data[offset + 2]) << 8) |
14 fl::u32(data[offset + 3]);
15}
16
17inline fl::u16 readU16BE(fl::span<const fl::u8> data, fl::size offset, bool& ok) {
18 if (offset + 2 > data.size()) { ok = false; return 0; }
19 return (fl::u16(data[offset]) << 8) | fl::u16(data[offset + 1]);
20}
21
22// 4-char box type comparison
23inline bool boxIs(fl::span<const fl::u8> data, fl::size offset, const char* type) {
24 if (offset + 4 > data.size()) return false;
25 return data[offset] == fl::u8(type[0]) &&
26 data[offset + 1] == fl::u8(type[1]) &&
27 data[offset + 2] == fl::u8(type[2]) &&
28 data[offset + 3] == fl::u8(type[3]);
29}
30
31// Find a top-level box by type within a range. Returns offset of box start, or ~0 if not found.
32fl::size findBox(fl::span<const fl::u8> data, fl::size start, fl::size end, const char* type) {
33 fl::size pos = start;
34 while (pos + 8 <= end) {
35 bool ok = true;
36 fl::u32 boxSize = readU32BE(data, pos, ok);
37 if (!ok || boxSize < 8) break;
38 if (boxIs(data, pos + 4, type)) return pos;
39 pos += boxSize;
40 }
41 return fl::size(-1); // not found
42}
43
44// Parse avcC (AVC Decoder Configuration Record) at given offset.
45// ISO 14496-15 section 5.2.4.1
46bool parseAvcC(fl::span<const fl::u8> data, fl::size offset, fl::size boxEnd,
47 Mp4TrackInfo& info) {
48 // avcC box: 8 bytes header, then:
49 // u8 configVersion (1)
50 // u8 profile_idc
51 // u8 profile_compat
52 // u8 level_idc
53 // u8 lengthSizeMinusOne (& 0x03)
54 // u8 numSPS (& 0x1F)
55 // for each SPS: u16 spsLength, u8[] spsData
56 // u8 numPPS
57 // for each PPS: u16 ppsLength, u8[] ppsData
58
59 fl::size pos = offset + 8; // skip box header
60 if (pos + 6 > boxEnd) return false;
61
62 fl::u8 configVersion = data[pos++];
63 if (configVersion != 1) return false;
64
65 info.profile = data[pos++];
66 pos++; // profile_compat
67 info.level = data[pos++];
68 info.lengthSizeMinusOne = data[pos++] & 0x03;
69
70 fl::u8 numSPS = data[pos++] & 0x1F;
71 for (fl::u8 i = 0; i < numSPS; i++) {
72 bool ok = true;
73 fl::u16 spsLen = readU16BE(data, pos, ok);
74 if (!ok) return false;
75 pos += 2;
76 if (pos + spsLen > boxEnd) return false;
77 fl::vector<fl::u8> sps(spsLen);
78 for (fl::u16 j = 0; j < spsLen; j++) {
79 sps[j] = data[pos + j];
80 }
81 info.sps.push_back(sps);
82 pos += spsLen;
83 }
84
85 if (pos >= boxEnd) return false;
86 fl::u8 numPPS = data[pos++];
87 for (fl::u8 i = 0; i < numPPS; i++) {
88 bool ok = true;
89 fl::u16 ppsLen = readU16BE(data, pos, ok);
90 if (!ok) return false;
91 pos += 2;
92 if (pos + ppsLen > boxEnd) return false;
93 fl::vector<fl::u8> pps(ppsLen);
94 for (fl::u16 j = 0; j < ppsLen; j++) {
95 pps[j] = data[pos + j];
96 }
97 info.pps.push_back(pps);
98 pos += ppsLen;
99 }
100
101 return true;
102}
103
104} // namespace
105
107 Mp4TrackInfo info;
108
109 if (data.size() < 8) {
110 if (error) *error = "Data too small for MP4";
111 return info;
112 }
113
114 // Find moov box at top level
115 fl::size moovPos = findBox(data, 0, data.size(), "moov");
116 if (moovPos == fl::size(-1)) {
117 if (error) *error = "No moov box found";
118 return info;
119 }
120
121 bool ok = true;
122 fl::u32 moovSize = readU32BE(data, moovPos, ok);
123 if (!ok) {
124 if (error) *error = "Invalid moov box size";
125 return info;
126 }
127 fl::size moovEnd = moovPos + moovSize;
128 if (moovEnd > data.size()) moovEnd = data.size();
129 fl::size moovBody = moovPos + 8;
130
131 // Find trak box inside moov
132 fl::size trakPos = findBox(data, moovBody, moovEnd, "trak");
133 if (trakPos == fl::size(-1)) {
134 if (error) *error = "No trak box found";
135 return info;
136 }
137
138 fl::u32 trakSize = readU32BE(data, trakPos, ok);
139 if (!ok) {
140 if (error) *error = "Invalid trak box size";
141 return info;
142 }
143 fl::size trakEnd = trakPos + trakSize;
144 if (trakEnd > moovEnd) trakEnd = moovEnd;
145 fl::size trakBody = trakPos + 8;
146
147 // Find mdia box inside trak
148 fl::size mdiaPos = findBox(data, trakBody, trakEnd, "mdia");
149 if (mdiaPos == fl::size(-1)) {
150 if (error) *error = "No mdia box found";
151 return info;
152 }
153
154 fl::u32 mdiaSize = readU32BE(data, mdiaPos, ok);
155 if (!ok) {
156 if (error) *error = "Invalid mdia box size";
157 return info;
158 }
159 fl::size mdiaEnd = mdiaPos + mdiaSize;
160 if (mdiaEnd > trakEnd) mdiaEnd = trakEnd;
161 fl::size mdiaBody = mdiaPos + 8;
162
163 // Find minf box inside mdia
164 fl::size minfPos = findBox(data, mdiaBody, mdiaEnd, "minf");
165 if (minfPos == fl::size(-1)) {
166 if (error) *error = "No minf box found";
167 return info;
168 }
169
170 fl::u32 minfSize = readU32BE(data, minfPos, ok);
171 if (!ok) {
172 if (error) *error = "Invalid minf box size";
173 return info;
174 }
175 fl::size minfEnd = minfPos + minfSize;
176 if (minfEnd > mdiaEnd) minfEnd = mdiaEnd;
177 fl::size minfBody = minfPos + 8;
178
179 // Find stbl box inside minf
180 fl::size stblPos = findBox(data, minfBody, minfEnd, "stbl");
181 if (stblPos == fl::size(-1)) {
182 if (error) *error = "No stbl box found";
183 return info;
184 }
185
186 fl::u32 stblSize = readU32BE(data, stblPos, ok);
187 if (!ok) {
188 if (error) *error = "Invalid stbl box size";
189 return info;
190 }
191 fl::size stblEnd = stblPos + stblSize;
192 if (stblEnd > minfEnd) stblEnd = minfEnd;
193 fl::size stblBody = stblPos + 8;
194
195 // Find stsd box inside stbl
196 fl::size stsdPos = findBox(data, stblBody, stblEnd, "stsd");
197 if (stsdPos == fl::size(-1)) {
198 if (error) *error = "No stsd box found";
199 return info;
200 }
201
202 fl::u32 stsdSize = readU32BE(data, stsdPos, ok);
203 if (!ok) {
204 if (error) *error = "Invalid stsd box size";
205 return info;
206 }
207 fl::size stsdEnd = stsdPos + stsdSize;
208 if (stsdEnd > stblEnd) stsdEnd = stblEnd;
209
210 // stsd is a full box: 8 byte header + 4 byte version/flags + 4 byte entry_count
211 fl::size stsdBody = stsdPos + 8 + 4 + 4;
212 if (stsdBody > stsdEnd) {
213 if (error) *error = "stsd box too small";
214 return info;
215 }
216
217 // Look for avc1 sample entry inside stsd
218 fl::size avc1Pos = findBox(data, stsdBody, stsdEnd, "avc1");
219 if (avc1Pos == fl::size(-1)) {
220 if (error) *error = "No avc1 sample entry found (not H.264)";
221 return info;
222 }
223
224 fl::u32 avc1Size = readU32BE(data, avc1Pos, ok);
225 if (!ok) {
226 if (error) *error = "Invalid avc1 box size";
227 return info;
228 }
229 fl::size avc1End = avc1Pos + avc1Size;
230 if (avc1End > stsdEnd) avc1End = stsdEnd;
231
232 // avc1 sample entry layout:
233 // 8 bytes: box header (size + "avc1")
234 // 6 bytes: reserved
235 // 2 bytes: data_reference_index
236 // 2 bytes: pre_defined
237 // 2 bytes: reserved
238 // 12 bytes: pre_defined[3]
239 // 2 bytes: width
240 // 2 bytes: height
241 // ... then sub-boxes including avcC
242 fl::size avc1Body = avc1Pos + 8 + 6 + 2 + 2 + 2 + 12;
243 if (avc1Body + 4 > avc1End) {
244 if (error) *error = "avc1 box too small for dimensions";
245 return info;
246 }
247
248 info.width = readU16BE(data, avc1Body, ok);
249 info.height = readU16BE(data, avc1Body + 2, ok);
250 if (!ok) {
251 if (error) *error = "Failed to read dimensions from avc1";
252 return info;
253 }
254
255 // Skip to sub-boxes: avc1Body + 4 (width/height) + 4 (horiz res) + 4 (vert res) +
256 // 4 (reserved) + 2 (frame_count) + 32 (compressor_name) + 2 (depth) + 2 (pre_defined)
257 fl::size subBoxStart = avc1Body + 4 + 4 + 4 + 4 + 2 + 32 + 2 + 2;
258
259 // Find avcC box inside avc1
260 fl::size avcCPos = findBox(data, subBoxStart, avc1End, "avcC");
261 if (avcCPos == fl::size(-1)) {
262 if (error) *error = "No avcC box found inside avc1";
263 return info;
264 }
265
266 fl::u32 avcCSize = readU32BE(data, avcCPos, ok);
267 if (!ok) {
268 if (error) *error = "Invalid avcC box size";
269 return info;
270 }
271 fl::size avcCEnd = avcCPos + avcCSize;
272 if (avcCEnd > avc1End) avcCEnd = avc1End;
273
274 if (!parseAvcC(data, avcCPos, avcCEnd, info)) {
275 if (error) *error = "Failed to parse avcC configuration record";
276 return info;
277 }
278
279 info.isValid = true;
280 return info;
281}
282
284 const Mp4TrackInfo& track,
285 fl::string* error) {
286 fl::vector<fl::u8> annexB;
287
288 if (!track.isValid) {
289 if (error) *error = "Invalid track info";
290 return annexB;
291 }
292
293 fl::u8 nalLenSize = track.lengthSizeMinusOne + 1; // typically 4
294
295 // Emit SPS NAL units with Annex B start codes
296 for (const auto& sps : track.sps) {
297 annexB.push_back(0x00);
298 annexB.push_back(0x00);
299 annexB.push_back(0x00);
300 annexB.push_back(0x01);
301 for (fl::size j = 0; j < sps.size(); j++) {
302 annexB.push_back(sps[j]);
303 }
304 }
305
306 // Emit PPS NAL units
307 for (const auto& pps : track.pps) {
308 annexB.push_back(0x00);
309 annexB.push_back(0x00);
310 annexB.push_back(0x00);
311 annexB.push_back(0x01);
312 for (fl::size j = 0; j < pps.size(); j++) {
313 annexB.push_back(pps[j]);
314 }
315 }
316
317 // Find mdat box and convert AVCC NAL units to Annex B
318 fl::size mdatPos = fl::size(-1);
319 {
320 fl::size pos = 0;
321 while (pos + 8 <= data.size()) {
322 bool ok = true;
323 fl::u32 boxSize = readU32BE(data, pos, ok);
324 if (!ok || boxSize < 8) break;
325 if (boxIs(data, pos + 4, "mdat")) {
326 mdatPos = pos;
327 break;
328 }
329 pos += boxSize;
330 }
331 }
332
333 if (mdatPos == fl::size(-1)) {
334 if (error) *error = "No mdat box found";
335 return annexB;
336 }
337
338 bool ok = true;
339 fl::u32 mdatSize = readU32BE(data, mdatPos, ok);
340 if (!ok) {
341 if (error) *error = "Invalid mdat box size";
342 return annexB;
343 }
344
345 fl::size mdatBody = mdatPos + 8;
346 fl::size mdatEnd = mdatPos + mdatSize;
347 if (mdatEnd > data.size()) mdatEnd = data.size();
348
349 fl::size pos = mdatBody;
350 while (pos + nalLenSize <= mdatEnd) {
351 fl::u32 nalLen = 0;
352 for (fl::u8 k = 0; k < nalLenSize; k++) {
353 nalLen = (nalLen << 8) | data[pos + k];
354 }
355 pos += nalLenSize;
356
357 if (pos + nalLen > mdatEnd) break;
358
359 // Emit Annex B start code + NAL data
360 annexB.push_back(0x00);
361 annexB.push_back(0x00);
362 annexB.push_back(0x00);
363 annexB.push_back(0x01);
364 for (fl::u32 j = 0; j < nalLen; j++) {
365 annexB.push_back(data[pos + j]);
366 }
367 pos += nalLen;
368 }
369
370 return annexB;
371}
372
373} // namespace fl
uint8_t pos
Definition Blur.ino:11
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
bool parseAvcC(fl::span< const fl::u8 > data, fl::size offset, fl::size boxEnd, Mp4TrackInfo &info)
fl::u32 readU32BE(fl::span< const fl::u8 > data, fl::size offset, bool &ok)
bool boxIs(fl::span< const fl::u8 > data, fl::size offset, const char *type)
fl::u16 readU16BE(fl::span< const fl::u8 > data, fl::size offset, bool &ok)
fl::size findBox(fl::span< const fl::u8 > data, fl::size start, fl::size end, const char *type)
unsigned char u8
Definition s16x16x4.h:132
fl::vector< fl::u8 > extractH264NalUnits(fl::span< const fl::u8 > data, const Mp4TrackInfo &track, fl::string *error)
constexpr T * end(T(&array)[N]) FL_NOEXCEPT
Mp4TrackInfo parseMp4(fl::span< const fl::u8 > data, fl::string *error)
Base definition for an LED controller.
Definition crgb.hpp:179
fl::u8 lengthSizeMinusOne
Definition mp4_parser.h:19
fl::vector< fl::vector< fl::u8 > > pps
Definition mp4_parser.h:21
fl::vector< fl::vector< fl::u8 > > sps
Definition mp4_parser.h:20