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