| 281 | } |
| 282 | |
| 283 | fl::vector<fl::u8> extractH264NalUnits(fl::span<const fl::u8> data, |
| 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) { |