arduino-audio-tools
Loading...
Searching...
No Matches
ContainerMP4.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstring>
5
14
15namespace audio_tools {
16
71class DemuxerMP4 : public Demuxer {
72 public:
74
87
88 DemuxerMP4(Print& video_out, Print& audio_out) {
90 setOutputAudio(audio_out);
91 setOutputVideo(video_out);
92 }
93
94 DemuxerMP4(VideoOutput& video_out, Print& audio_out) {
96 setOutputAudio(audio_out);
97 setOutputVideo(video_out);
98 }
99
108 setupParser();
109 setSeekSource(seekSource);
110 }
111
121 setupParser();
122 setSpoolStorageFactory(spoolFactory);
123 }
124
126
129 void setSeekSource(SeekableSource& seekSource) { p_seek_source = &seekSource; }
130
134 p_spool_factory = &spoolFactory;
135 }
136
138 const char* mimeVideo() override { return "video/mp4"; }
141 const char* mime() override { return toMime(audio_format); }
142
146 bool isValid(const uint8_t* data, size_t len) override {
147 return len >= 8 && memcmp(data + 4, "ftyp", 4) == 0;
148 }
149
150 bool begin() override {
151 freeTracks();
152 current_track = nullptr;
153 p_video_track = nullptr;
154 p_audio_track = nullptr;
155 current_chunk_track = nullptr;
157 mdat_seen = false;
158 box_accum_active = false;
160 stsz_header_pending = true;
164 current_sample_track = nullptr;
166 is_wav_header_sent = false;
169 parser.begin();
170 is_active = true;
171 return true;
172 }
173
174 void end() override { is_active = false; }
175
176 operator bool() override { return is_active; }
177
181 void setOutputAudio(Print& out) override { p_output_audio = &out; }
182
187 void setOutput(Print& out) override { setOutputAudio(out); }
188
189 void setOutputVideo(Print& out) override { p_video_out = &out; }
190 void setOutputVideo(VideoOutput& out) override { p_video_out_video = &out; }
191
204 VideoInfo result;
206 // width==0 means 'avc1'/'hev1' hasn't been parsed yet (p_video_track can
207 // already be set from 'hdlr' alone) - keep the all-UNKNOWN default then
208 if (p_video_track == nullptr || p_video_track->width == 0) return result;
209 result.width = p_video_track->width;
210 result.height = p_video_track->height;
213 result.frame_size = (uint32_t)videoFrameSizeBytes(
214 result.format, result.width, result.height);
215 // Constant-frame-rate assumption: fps from the track's timescale and
216 // its first 'stts' entry's sample_delta (ticks/sample) - true for the
217 // overwhelming majority of real-world encoders; a genuinely variable
218 // frame rate would need a per-sample duration, which nothing here
219 // currently schedules against anyway (see PacedVideoOutput).
220 if (p_video_track->timescale > 0 && p_video_track->stts->size() > 0) {
221 SttsEntry first = p_video_track->stts->get(0);
222 if (first.sample_delta > 0)
223 result.fps = (float)p_video_track->timescale / first.sample_delta;
224 }
225 return result;
226 }
237 AudioInfoFormat result(info);
238 result.format = audio_format;
239 return result;
240 }
248
253 size_t write(const uint8_t* data, size_t len) override {
254 if (!is_active) return 0;
255 // After quickStart(), 'moov' still physically trails 'mdat' on disk
256 // (only relocated logically) - cap feeding at 'mdat's declared size
257 // and discard the trailing, already-parsed 'moov' bytes instead of
258 // re-parsing them.
259 size_t feed_len = len;
260 bool truncated = false;
262 (int64_t)feed_len > quickstart_bytes_remaining) {
263 feed_len = (size_t)quickstart_bytes_remaining;
264 truncated = true;
265 }
266 size_t written = feed_len > 0 ? parser.write(data, feed_len) : 0;
267 total_bytes_received += written;
269 // Only swallow the truncated remainder if 'feed_len' was fully
270 // accepted - otherwise this is an ordinary partial write.
271 return (truncated && written == feed_len) ? len : written;
272 }
273
289 bool quickStart() {
290 if (p_seek_source == nullptr) return false;
291
292 uint64_t pos = 0, moov_offset = 0, moov_size = 0, mdat_offset = 0,
293 mdat_size = 0;
294 bool moov_found = false, mdat_found = false;
295 const int max_top_level_boxes = 64; // safety bound against a corrupt file
296
297 for (int i = 0; i < max_top_level_boxes; i++) {
298 uint8_t hdr[8];
299 if (!p_seek_source->seek((size_t)pos)) break;
300 if (p_seek_source->readBytes(hdr, 8) < 8) break;
301 uint64_t box_size = readU32(hdr);
302 // 64-bit ('size'==1) and "extends to EOF" ('size'==0) boxes aren't
303 // understood here or by MP4Parser's own walker - bail out and let
304 // the caller fall back to normal sequential parsing.
305 if (box_size < 8) break;
306
307 if (memcmp(hdr + 4, "moov", 4) == 0) {
308 moov_offset = pos;
309 moov_size = box_size;
310 moov_found = true;
311 } else if (memcmp(hdr + 4, "mdat", 4) == 0) {
312 mdat_offset = pos;
313 mdat_size = box_size;
314 mdat_found = true;
315 }
316 if (moov_found && mdat_found) break;
317 pos += box_size;
318 }
319
320 if (!moov_found || !mdat_found || moov_offset < mdat_offset) {
321 return false; // nothing found, or already faststart
322 }
323
324 LOGI("DemuxerMP4::quickStart: moov at %u (size %u), mdat at %u - "
325 "parsing moov directly",
326 (unsigned)moov_offset, (unsigned)moov_size, (unsigned)mdat_offset);
327
328 // Feed exactly 'moov's bytes through the same callback-driven parser
329 // used for normal streaming - just sourced via seek()+readBytes().
330 parser.begin(moov_offset);
331 uint8_t buf[512];
332 uint64_t remaining = moov_size;
333 uint64_t off = moov_offset;
334 while (remaining > 0) {
335 size_t chunk = (size_t)std::min<uint64_t>(remaining, sizeof(buf));
336 if (!p_seek_source->seek((size_t)off)) break;
337 size_t got = p_seek_source->readBytes(buf, chunk);
338 if (got == 0) break;
339 parser.write(buf, got);
340 off += got;
341 remaining -= got;
342 }
343
344 // Hand off: reset the parser's box state machine to expect a fresh
345 // box at 'mdat's offset, and reposition the seek source there so the
346 // caller's normal write()-loop resumes from 'mdat', not offset 0.
347 parser.begin(mdat_offset);
348 p_seek_source->seek((size_t)mdat_offset);
349 // caps write() at 'mdat's size so the trailing 'moov' isn't re-parsed
350 quickstart_bytes_remaining = (int64_t)mdat_size;
351 return true;
352 }
353
354 protected:
356 struct StscEntry {
357 uint32_t first_chunk;
359 };
360
364 struct SttsEntry {
365 uint32_t sample_count;
366 uint32_t sample_delta;
367 };
368
370 struct Track {
371 // Kind alias kept for source compatibility with any existing
372 // Track::Kind::X references - the real type is TrackKind
373 // (SampleTableStore.h), shared with SpoolStorageFactory.
377 bool spool_configured = false;
378
379 // audio
380 Codec audio_codec = Codec::Unknown;
383
384 // video (H.264 only in v1)
385 uint16_t width = 0, height = 0;
386 uint8_t nal_length_size = 4;
388 bool avc_config_sent = false;
390
391 // sample tables, populated while parsing 'moov' - RAM-backed
392 // (chunked allocation, see ChunkedSampleTableStore) by default; see
393 // DemuxerMP4::setSeekSource()/onTrak() for the alternative that
394 // re-reads entries from the original file on demand instead of
395 // keeping them all resident
397 uint32_t fixed_sample_size = 0;
398 uint32_t fixed_sample_count = 0;
401
402 // playback timing: this track's own 'mdhd' timescale (ticks/second)
403 // and its 'stts' time-to-sample table - both needed to turn a sample
404 // index into a scheduled presentation time.
405 uint32_t timescale = 0;
407
409 delete sample_sizes;
410 delete stsc;
411 delete chunk_offsets;
412 delete stts;
413 }
414
415 // runtime cursor, used while walking the merge schedule
416 uint32_t next_sample_index = 0;
417
418 // runtime cursor into chunk_offsets - which chunk of this track is
419 // next up for the incremental cross-track merge (see
420 // DemuxerMP4::advanceChunkIfNeeded())
421 uint32_t next_chunk_index = 0;
422 // runtime cursor into stsc for samplesInChunk() - valid only under
423 // monotonically increasing chunkIndex0 calls (true for the sequential
424 // per-chunk consumption above)
425 uint32_t stsc_cursor = 0;
426
427 uint32_t sampleSize(uint32_t idx) {
428 if (fixed_sample_size > 0) return fixed_sample_size;
429 if (idx >= sample_sizes->size()) return 0;
430 return sample_sizes->get(idx);
431 }
432
433 uint32_t sampleCount() {
435 : (uint32_t)sample_sizes->size();
436 }
437 };
438
440 // heap-allocated (not Vector<Track>): current_track/p_video_track/
441 // p_audio_track are captured *during* moov parsing, while more trak
442 // boxes (and thus more push_back calls) may still arrive - Vector<T>
443 // reallocates its backing array on growth, which would dangle any
444 // pointer taken directly into it. Pointers to heap objects stay valid
445 // regardless of how the Vector<Track*> itself grows.
448 nullptr;
449
450 void freeTracks() {
451 for (size_t i = 0; i < tracks.size(); i++) delete tracks[i];
452 tracks.clear();
453 }
454 Track* p_video_track = nullptr;
455 Track* p_audio_track = nullptr;
465
469
470 // Incremental cross-track merge state (replaces a precomputed
471 // whole-file schedule - see advanceChunkIfNeeded()): which track's
472 // chunk is currently being consumed, and how many of its samples are
473 // still left in that chunk.
476 bool mdat_seen = false;
477
479 Print* p_video_out = nullptr;
485 bool send_wav_header = false;
487 bool is_wav_header_sent = false;
491 bool is_active = false;
492
493 // sample accumulation (mdat streaming)
500
501 // scratch accumulation buffer, reused sequentially for stsz/stsc/stco/co64
502 // (only one of these is ever "in progress" at a time in a linear parse)
504 // Explicit "are we mid-accumulation" state for box_accum, decoupled from
505 // its fill level: box_accum.available()==0 is NOT a reliable "start of a
506 // new box" signal, because some handlers (onStsz) legitimately drain it
507 // to empty via clearArray() *while still mid-box* (e.g. right after
508 // consuming a fixed-size sub-header, with more payload still to come) -
509 // available()==0 in that case would be misread as a fresh box starting.
510 bool box_accum_active = false;
515
520 bool beginBoxAccum(const MP4Parser::Box& box) {
521 if (box_accum_active) return false;
522 box_accum.resize(box.size);
524 box_accum_active = true;
525 // box.file_offset is the absolute file offset of *this box's own*
526 // 8-byte header (size+type) - captured only here, on the box's FIRST
527 // delivery, because MP4Parser advances it per incremental delivery
528 // (see startIncrementalBox()/continueIncrementalBox() in
529 // MP4Parser.h): by the time a large box (e.g. a 147K-entry stco) is
530 // fully accumulated and its entries are actually iterated, box.file_
531 // offset reflects only the *last* delivery's start, not the box's -
532 // capturing it here instead of computing it later from
533 // total_bytes_received/box_accum.available() is what makes
534 // SourceSeekSampleTableStore's offsets land correctly.
536 return true;
537 }
538
540 void endBoxAccum() {
542 box_accum_active = false;
543 }
544
546
547 static uint32_t readU32(const uint8_t* p) {
548 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
549 }
550 static uint64_t readU64(const uint8_t* p) {
551 return ((uint64_t)readU32(p) << 32) | readU32(p + 4);
552 }
553 static uint16_t readU16(const uint8_t* p) { return (p[0] << 8) | p[1]; }
554
555 // decoder callbacks for SourceSeekSampleTableStore - see its comment on
556 // why get() can't just memcpy raw on-disk bytes into T
557 static uint64_t readU32Widened(const uint8_t* p) { return (uint64_t)readU32(p); }
558 static StscEntry readStscEntry(const uint8_t* p) {
559 StscEntry e;
560 e.first_chunk = readU32(p);
561 e.samples_per_chunk = readU32(p + 4);
562 return e;
563 }
564 static SttsEntry readSttsEntry(const uint8_t* p) {
565 SttsEntry e;
566 e.sample_count = readU32(p);
567 e.sample_delta = readU32(p + 4);
568 return e;
569 }
570
571 void setupParser() {
572 parser.setReference(this);
573 // suppress MP4Parser's default behavior of printing every box we don't
574 // otherwise handle (mvhd, tkhd, stts, ...) to Serial/stdout
575 parser.setCallback([](MP4Parser::Box&, void*) {});
576
578 "trak",
579 [](MP4Parser::Box& box, void* ref) {
580 static_cast<DemuxerMP4*>(ref)->onTrak();
581 },
582 false);
583
585 "hdlr",
586 [](MP4Parser::Box& box, void* ref) {
587 static_cast<DemuxerMP4*>(ref)->onHdlr(box);
588 },
589 false);
590
592 "stsd",
593 [](MP4Parser::Box& box, void* ref) {
594 static_cast<DemuxerMP4*>(ref)->onStsd(box);
595 },
596 false);
597
599 "mp4a",
600 [](MP4Parser::Box& box, void* ref) {
601 static_cast<DemuxerMP4*>(ref)->onMp4a(box);
602 },
603 false);
605 "alac",
606 [](MP4Parser::Box& box, void* ref) {
607 static_cast<DemuxerMP4*>(ref)->onAlac(box);
608 },
609 false);
611 "ac-3",
612 [](MP4Parser::Box& box, void* ref) {
613 static_cast<DemuxerMP4*>(ref)->onAc3(box);
614 },
615 false);
617 "esds",
618 [](MP4Parser::Box& box, void* ref) {
619 static_cast<DemuxerMP4*>(ref)->onEsds(box);
620 },
621 false);
622
624 "avc1",
625 [](MP4Parser::Box& box, void* ref) {
626 static_cast<DemuxerMP4*>(ref)->onAvc1(box);
627 },
628 false);
630 "hev1",
631 [](MP4Parser::Box& box, void* ref) {
632 static_cast<DemuxerMP4*>(ref)->onHevc(box);
633 },
634 false);
636 "hvc1",
637 [](MP4Parser::Box& box, void* ref) {
638 static_cast<DemuxerMP4*>(ref)->onHevc(box);
639 },
640 false);
642 "avcC",
643 [](MP4Parser::Box& box, void* ref) {
644 static_cast<DemuxerMP4*>(ref)->onAvcC(box);
645 },
646 false);
647
649 "mdhd",
650 [](MP4Parser::Box& box, void* ref) {
651 static_cast<DemuxerMP4*>(ref)->onMdhd(box);
652 },
653 false);
655 "stts",
656 [](MP4Parser::Box& box, void* ref) {
657 static_cast<DemuxerMP4*>(ref)->onStts(box);
658 },
659 false);
660
662 "stsz",
663 [](MP4Parser::Box& box, void* ref) {
664 static_cast<DemuxerMP4*>(ref)->onStsz(box);
665 },
666 false);
668 "stsc",
669 [](MP4Parser::Box& box, void* ref) {
670 static_cast<DemuxerMP4*>(ref)->onStsc(box);
671 },
672 false);
674 "stco",
675 [](MP4Parser::Box& box, void* ref) {
676 static_cast<DemuxerMP4*>(ref)->onStco(box, false);
677 },
678 false);
680 "co64",
681 [](MP4Parser::Box& box, void* ref) {
682 static_cast<DemuxerMP4*>(ref)->onStco(box, true);
683 },
684 false);
685
687 "mdat",
688 [](MP4Parser::Box& box, void* ref) {
689 static_cast<DemuxerMP4*>(ref)->onMdat(box);
690 },
691 false);
692 }
693
694 // ---- moov / track parsing ----
695
696 void onTrak() {
697 Track* t = new Track();
698 if (p_seek_source != nullptr) {
699 // RAM-backed defaults from Track's own member initializers aren't
700 // needed when a seek source is configured - swap them for
701 // SourceSeekSampleTableStore instead of paying for both.
702 delete t->sample_sizes;
704 *p_seek_source, sizeof(uint32_t), readU32);
705 delete t->chunk_offsets;
706 // on-disk width (4 bytes for 'stco', 8 for 'co64') isn't known yet
707 // here - defaults to 'stco' width and is corrected in onStco() via
708 // setOnDiskEntrySize() once the actual box type is seen, always
709 // before any get() call (which only happens much later, during
710 // 'mdat' playback).
712 *p_seek_source, sizeof(uint32_t), readU32Widened);
713 delete t->stsc;
714 // stsc's on-disk entries are 12 bytes (first_chunk, samples_per_chunk,
715 // sample_description_index - all u32), matching onStsc()'s own i*12
716 // parsing stride below, even though readStscEntry() only decodes the
717 // first two fields into StscEntry. Passing 8 here previously
718 // (2 fields' worth) misaligned every get() past index 0, since
719 // get()'s offset math is entries*onDiskSize - reproducibly corrupted
720 // chunk-to-sample mapping for any track whose stsc has more than one
721 // entry (typically audio, not video, since a video track commonly
722 // only ever needs one stsc entry for the whole file).
724 *p_seek_source, sizeof(uint32_t) * 3, readStscEntry);
725 delete t->stts;
727 *p_seek_source, sizeof(uint32_t) * 2, readSttsEntry);
728 }
729 tracks.push_back(t);
730 current_track = t;
731 }
732
734 if (current_track == nullptr) return;
735 beginBoxAccum(box);
737 if (!box.is_complete || box_accum.available() < 12) return;
738 const uint8_t* handler = box_accum.data() + 8;
739 if (memcmp(handler, "vide", 4) == 0) {
740 current_track->kind = Track::Kind::Video;
741 if (p_video_track == nullptr) p_video_track = current_track;
742 } else if (memcmp(handler, "soun", 4) == 0) {
743 current_track->kind = Track::Kind::Audio;
744 if (p_audio_track == nullptr) p_audio_track = current_track;
745 }
746 // Spool-backed tables need to know the track's kind (to name/select
747 // spool files) - unlike source-seek, which is set up eagerly in
748 // onTrak() since it doesn't care about kind, this can only happen
749 // here, once 'hdlr' has actually told us what this track is.
750 if (p_spool_factory != nullptr && current_track->kind != Track::Kind::Unknown) {
752 }
753 endBoxAccum();
754 }
755
776
778 beginBoxAccum(box);
780 if (box.is_complete && box_accum.available() >= 8) {
781 // sample entries (mp4a/alac/avc1/hev1/...) parsed generically, same
782 // mechanism as M4ACommonDemuxer::onStsd
784 box.file_offset + 8 + 8, box.level + 1);
785 endBoxAccum();
786 }
787 }
788
789 void onMp4a(const MP4Parser::Box& box) {
790 if (current_track == nullptr || !box.is_complete) return;
791 current_track->aacProfile = 2; // default AAC LC
792 current_track->sampleRateIdx = 4; // default 44100 Hz
793 current_track->channelCfg = 2; // default stereo
794 current_track->audio_codec = Codec::AAC;
796 // AudioSampleEntry fixed header is 28 bytes (incl. the 8 bytes already
797 // stripped as the box header) - esds (child box) follows
798 int pos = 36 - 8;
799 if (box.data_size > (size_t)pos)
800 parser.parseString(box.data + pos, box.data_size - pos,
801 box.file_offset + 8 + pos, box.level + 1);
802 }
803
809 void setupAudioInfo(AudioFormat format, const uint8_t* entryData,
810 size_t entrySize) {
811 if (entrySize < 28) return;
812 info.channels = readU16(entryData + 16);
813 info.sample_rate = readU16(entryData + 24);
815 audio_format = format;
816 info.logInfo();
820 }
821
828 void setSendWavHeader(bool flag) override {
829 send_wav_header = flag;
831 }
832
842 if (is_wav_header_sent || p_output_audio == nullptr) return;
843 WAVAudioInfo winfo(info);
844 winfo.format = audio_format;
845 winfo.is_streamed = true;
847 winfo.byte_rate = info.sample_rate * winfo.block_align;
848 WAVHeader wav_header;
849 wav_header.setAudioInfo(winfo);
850 wav_header.writeHeader(p_output_audio);
851 is_wav_header_sent = true;
852 }
853
854 void onEsds(const MP4Parser::Box& box) {
855 if (current_track == nullptr) return;
857 if (!esdsParser.parse(box.data, box.data_size)) {
858 LOGE("Failed to parse esds box");
859 return;
860 }
864 }
865
866 void onAlac(const MP4Parser::Box& box) {
867 if (current_track == nullptr || !box.is_complete) return;
868 current_track->audio_codec = Codec::ALAC;
870 MP4Parser::Box alac;
871 if (parser.findBox("alac", box.data, box.data_size, alac)) {
873 memcpy(current_track->alacMagicCookie.data(), alac.data + 4,
874 alac.data_size - 4);
875 }
876 }
877
881 void onAc3(const MP4Parser::Box& box) {
882 if (current_track == nullptr || !box.is_complete) return;
883 current_track->audio_codec = Codec::AC3;
885 }
886
887 void onAvc1(const MP4Parser::Box& box) {
888 if (current_track == nullptr || !box.is_complete) return;
889 current_track->kind = Track::Kind::Video;
890 if (p_video_track == nullptr) p_video_track = current_track;
891 if (box.data_size < 28) return;
892 current_track->width = readU16(box.data + 24);
893 current_track->height = readU16(box.data + 26);
894 // VisualSampleEntry fixed header is 78 bytes (already excl. the 8 byte
895 // box header) - avcC (child box) follows
896 const int pos = 78;
897 if (box.data_size > (size_t)pos)
898 parser.parseString(box.data + pos, box.data_size - pos,
899 box.file_offset + 8 + pos, box.level + 1);
900 }
901
902 void onHevc(const MP4Parser::Box& box) {
903 if (current_track == nullptr) return;
904 current_track->kind = Track::Kind::Video;
905 if (p_video_track == nullptr) p_video_track = current_track;
907 LOGE("HEVC (hev1/hvc1) video is not supported - only H.264 (avc1)");
909 }
910 }
911
912 void onAvcC(const MP4Parser::Box& box) {
913 if (current_track == nullptr || !box.is_complete || box.data_size < 6)
914 return;
915 const uint8_t* d = box.data;
916 current_track->nal_length_size = (d[4] & 0x03) + 1;
917 int numSps = d[5] & 0x1F;
918 size_t pos = 6;
919 auto appendUnit = [&](const uint8_t* unit, size_t len) {
920 static const uint8_t startCode[4] = {0, 0, 0, 1};
921 auto& v = current_track->sps_pps_annexb;
922 size_t off = v.size();
923 v.resize(off + 4 + len);
924 memcpy(v.data() + off, startCode, 4);
925 memcpy(v.data() + off + 4, unit, len);
926 };
927 for (int i = 0; i < numSps && pos + 2 <= box.data_size; i++) {
928 uint16_t len = readU16(d + pos);
929 pos += 2;
930 if (pos + len > box.data_size) break;
931 appendUnit(d + pos, len);
932 pos += len;
933 }
934 if (pos >= box.data_size) return;
935 int numPps = d[pos++];
936 for (int i = 0; i < numPps && pos + 2 <= box.data_size; i++) {
937 uint16_t len = readU16(d + pos);
938 pos += 2;
939 if (pos + len > box.data_size) break;
940 appendUnit(d + pos, len);
941 pos += len;
942 }
943 }
944
949 if (current_track == nullptr) return;
950 beginBoxAccum(box);
952 if (!box.is_complete) return;
953 if (box_accum.available() < 4) {
954 endBoxAccum();
955 return;
956 }
957 uint8_t version = box_accum.data()[0];
958 // v0: creation(4)+modification(4)+timescale(4)+duration(4)
959 // v1: creation(8)+modification(8)+timescale(4)+duration(8)
960 size_t timescaleOffset = version == 1 ? 20 : 12;
961 if (box_accum.available() >= timescaleOffset + 4) {
962 current_track->timescale = readU32(box_accum.data() + timescaleOffset);
963 }
964 endBoxAccum();
965 }
966
987 return box_start_file_offset + 8;
988 }
989
994 if (current_track == nullptr) return;
995 beginBoxAccum(box);
997 if (!box.is_complete) return;
998 if (box_accum.available() < 8) {
999 endBoxAccum();
1000 return;
1001 }
1002 uint32_t entryCount = readU32(box_accum.data() + 4);
1003 LOGD("DemuxerMP4: stts entryCount=%u", (unsigned)entryCount);
1004 const uint8_t* p = box_accum.data() + 8;
1005 size_t avail = box_accum.available() - 8;
1006 for (uint32_t i = 0; i < entryCount && (i + 1) * 8 <= avail; i++) {
1007 SttsEntry e;
1008 e.sample_count = readU32(p + i * 8);
1009 e.sample_delta = readU32(p + i * 8 + 4);
1010 // +8: this table's own version/flags+entryCount sub-header
1011 current_track->stts->setNextEntryOffset(boxPayloadFileOffset() + 8);
1012 current_track->stts->append(e);
1013 }
1014 LOGD("DemuxerMP4: stts parsed %u entries", (unsigned)current_track->stts->size());
1015 endBoxAccum();
1016 }
1017
1019 if (current_track == nullptr) return;
1020 if (beginBoxAccum(box))
1021 stsz_header_pending = true; // starting a fresh stsz box
1023
1024 // The 12-byte header (version+flags, sampleSize, sampleCount) must be
1025 // fully consumed *once* before any bytes are interpreted as per-sample
1026 // sizes. Using "no sizes pushed yet" as a stand-in for "header not
1027 // parsed yet" (as tried previously) is wrong: in the variable-size
1028 // case, both are simultaneously true right after the header IS parsed,
1029 // so under fine-grained incremental delivery the per-sample loop below
1030 // could start consuming still-unparsed header bytes as if they were
1031 // sample sizes. An explicit flag avoids that ambiguity.
1032 if (stsz_header_pending) {
1033 if (box_accum.available() < 12)
1034 return; // wait for the rest of the header
1035 Track* t = current_track;
1036 uint32_t sampleSize = readU32(box_accum.data() + 4);
1037 uint32_t sampleCount = readU32(box_accum.data() + 8);
1038 LOGD("DemuxerMP4: stsz sampleSize=%u sampleCount=%u", (unsigned)sampleSize,
1039 (unsigned)sampleCount);
1041 stsz_header_pending = false;
1042 if (sampleSize != 0) {
1043 t->fixed_sample_size = sampleSize;
1044 t->fixed_sample_count = sampleCount;
1045 }
1046 // else: sample_sizes is filled below via append(), one entry at a
1047 // time as bytes arrive - do NOT pre-resize it (a RAM-backed store's
1048 // Vector::push_back always appends, unlike BaseBuffer::write()
1049 // which fills pre-sized capacity in place)
1050 }
1051 // incrementally consume per-sample sizes (fixed-size case has none left
1052 // to read; the table is only present in the box when sampleSize==0)
1053 if (current_track->fixed_sample_size == 0) {
1054 while (box_accum.available() >= 4) {
1055 // +12: this table's own version/flags+sampleSize+sampleCount
1056 // sub-header (already drained from box_accum via clearArray(12)
1057 // above, but boxPayloadFileOffset() itself doesn't know that)
1061 size_t n = current_track->sample_sizes->size();
1062 if (n % 20000 == 0)
1063 LOGD("DemuxerMP4: stsz sample_sizes progress: %u", (unsigned)n);
1064 }
1065 }
1066 if (box.is_complete) {
1067 LOGD("DemuxerMP4: stsz complete, %u sample_sizes",
1068 (unsigned)current_track->sample_sizes->size());
1069 endBoxAccum();
1070 }
1071 }
1072
1074 if (current_track == nullptr) return;
1075 beginBoxAccum(box);
1077 if (!box.is_complete) return;
1078 if (box_accum.available() < 8) {
1079 endBoxAccum();
1080 return;
1081 }
1082 uint32_t entryCount = readU32(box_accum.data() + 4);
1083 LOGD("DemuxerMP4: stsc entryCount=%u", (unsigned)entryCount);
1084 const uint8_t* p = box_accum.data() + 8;
1085 size_t avail = box_accum.available() - 8;
1086 for (uint32_t i = 0; i < entryCount && (i + 1) * 12 <= avail; i++) {
1087 StscEntry e;
1088 e.first_chunk = readU32(p + i * 12);
1089 e.samples_per_chunk = readU32(p + i * 12 + 4);
1090 // +8: this table's own version/flags+entryCount sub-header
1091 current_track->stsc->setNextEntryOffset(boxPayloadFileOffset() + 8);
1092 current_track->stsc->append(e);
1093 }
1094 LOGD("DemuxerMP4: stsc parsed %u entries", (unsigned)current_track->stsc->size());
1095 endBoxAccum();
1096 }
1097
1098 void onStco(MP4Parser::Box& box, bool is64) {
1099 if (current_track == nullptr) return;
1100 beginBoxAccum(box);
1102 if (!box.is_complete) return;
1103 if (box_accum.available() < 8) {
1104 endBoxAccum();
1105 return;
1106 }
1107 uint32_t entryCount = readU32(box_accum.data() + 4);
1108 LOGD("DemuxerMP4: %s entryCount=%u", is64 ? "co64" : "stco",
1109 (unsigned)entryCount);
1110 // no-op unless a SourceSeekSampleTableStore is active (see onTrak());
1111 // corrects its assumed on-disk stride if this turns out to be 'co64'
1112 // rather than the default 'stco' - always before any get() call.
1114 const uint8_t* p = box_accum.data() + 8;
1115 size_t avail = box_accum.available() - 8;
1116 size_t entrySize = is64 ? 8 : 4;
1117 for (uint32_t i = 0; i < entryCount && (i + 1) * entrySize <= avail; i++) {
1118 uint64_t off = is64 ? readU64(p + i * 8) : readU32(p + i * 4);
1119 // +8: this table's own version/flags+entryCount sub-header
1122 size_t n = current_track->chunk_offsets->size();
1123 if (n % 20000 == 0)
1124 LOGD("DemuxerMP4: chunk_offsets progress: %u", (unsigned)n);
1125 }
1126 LOGD("DemuxerMP4: stco parsed %u chunk_offsets",
1127 (unsigned)current_track->chunk_offsets->size());
1128 endBoxAccum();
1129 }
1130
1131 // ---- mdat / schedule / sample dispatch ----
1132
1134 if (!mdat_seen) {
1135 mdat_seen = true;
1136 LOGI("DemuxerMP4: mdat reached");
1137 }
1138 feed(box.data, box.available);
1139 }
1140
1146 uint32_t samplesInChunk(Track& t, uint32_t chunkIndex0) {
1147 if (t.stsc->size() == 0) return 0;
1148 uint32_t chunk1 = chunkIndex0 + 1; // stsc uses 1-based chunk numbers
1149 while (t.stsc_cursor + 1 < t.stsc->size() &&
1150 t.stsc->get(t.stsc_cursor + 1).first_chunk <= chunk1) {
1151 t.stsc_cursor++;
1152 }
1153 return t.stsc->get(t.stsc_cursor).samples_per_chunk;
1154 }
1155
1165 while (samples_left_in_chunk == 0) {
1166 Track* next = nullptr;
1167 uint64_t best_offset = 0;
1168 if (p_video_track != nullptr &&
1171 next = p_video_track;
1172 }
1173 if (p_audio_track != nullptr &&
1176 if (next == nullptr || off < best_offset) {
1177 best_offset = off;
1178 next = p_audio_track;
1179 }
1180 }
1181 if (next == nullptr) {
1182 current_chunk_track = nullptr; // nothing more expected
1183 return;
1184 }
1185 uint32_t samples = samplesInChunk(*next, next->next_chunk_index);
1186 next->next_chunk_index++;
1187 if (samples == 0) continue; // empty chunk entry - keep looking
1188 current_chunk_track = next;
1189 samples_left_in_chunk = samples;
1190 }
1191 }
1192
1197 void feed(const uint8_t* data, size_t len) {
1198 size_t pos = 0;
1199 while (pos < len) {
1200 if (current_sample_track == nullptr) {
1202 if (current_chunk_track == nullptr) return; // nothing more expected
1204 uint32_t size = t->sampleSize(t->next_sample_index);
1205 if (size == 0) {
1206 // no more sizes for this track - stop
1208 continue;
1209 }
1211 current_sample_size = size;
1213 // Vector::resize() sets the logical size immediately (unlike a
1214 // capacity reserve), so track how much of it is actually filled
1215 // separately rather than relying on sample_buffer.size().
1216 sample_buffer.resize(size);
1218 }
1219
1221 size_t take = std::min(need, len - pos);
1222 memcpy(sample_buffer.data() + sample_buffer_filled, data + pos, take);
1223 sample_buffer_filled += take;
1224 pos += take;
1225
1231 current_sample_track = nullptr;
1233 if (dispatched_sample_count % 5000 == 0) {
1234 LOGI("DemuxerMP4: dispatched %u samples so far",
1235 (unsigned)dispatched_sample_count);
1236 }
1237 }
1238 }
1239 }
1240
1241 void dispatchSample(Track& t, const uint8_t* data, size_t size,
1242 bool isFirst) {
1243 if (&t == p_video_track) {
1244 dispatchVideo(t, data, size, isFirst);
1245 } else if (&t == p_audio_track) {
1246 dispatchAudio(t, data, size, isFirst);
1247 }
1248 }
1249
1250 void dispatchVideo(Track& t, const uint8_t* data, size_t size, bool isFirst) {
1251 // No wall-clock scheduling or skip decisions here anymore: every
1252 // sample is always converted and forwarded immediately, in full.
1253 // Pacing/scheduling is the caller's responsibility now - see
1254 // PacedVideoOutput (Video/PacedVideoOutput.h), which schedules
1255 // frames from its own background task instead of blocking this
1256 // dispatch loop (and, with it, audio delivery) the way an inline
1257 // wait here used to.
1258 if ((p_video_out == nullptr && p_video_out_video == nullptr) ||
1260 return;
1261
1262 // Prepend the SPS/PPS config (already Annex-B, same start-code
1263 // format the conversion below produces - see onAvcC()'s appendUnit())
1264 // into the *same* buffer as the frame's own NAL units, rather than a
1265 // separate writeVideo() call before them - so a downstream VideoOutput
1266 // always gets exactly one write() call per frame (matches DemuxerAVI/
1267 // DemuxerMPG; see PacedVideoOutput, the only current consumer that
1268 // cares about the distinction).
1269 nal_tmp.clear();
1270 bool prependConfig =
1271 isFirst && !t.avc_config_sent && t.sps_pps_annexb.size() > 0;
1272 if (prependConfig) {
1274 memcpy(nal_tmp.data(), t.sps_pps_annexb.data(), t.sps_pps_annexb.size());
1275 t.avc_config_sent = true;
1276 }
1277
1278 // convert AVCC length-prefixed NAL units to Annex-B start codes
1279 size_t pos = 0;
1280 size_t lenSize = t.nal_length_size;
1281 while (pos + lenSize <= size) {
1282 uint32_t nalLen = 0;
1283 for (size_t i = 0; i < lenSize; i++)
1284 nalLen = (nalLen << 8) | data[pos + i];
1285 pos += lenSize;
1286 if (pos + nalLen > size) break;
1287 size_t off = nal_tmp.size();
1288 static const uint8_t startCode[4] = {0, 0, 0, 1};
1289 nal_tmp.resize(off + 4 + nalLen);
1290 memcpy(nal_tmp.data() + off, startCode, 4);
1291 memcpy(nal_tmp.data() + off + 4, data + pos, nalLen);
1292 pos += nalLen;
1293 }
1294
1295 if (nal_tmp.size() > 0) writeVideo(nal_tmp.data(), nal_tmp.size());
1296 flushVideo();
1297 }
1298
1299 void writeVideo(uint8_t* data, size_t size) {
1300 if (p_video_out == nullptr && p_video_out_video == nullptr) return;
1301 if (size > 0) {
1302 if (p_video_out != nullptr) p_video_out->write(data, size);
1303 if (p_video_out_video != nullptr) p_video_out_video->write(data, size);
1304 }
1305 }
1306
1307 void flushVideo() {
1308 if (p_video_out != nullptr) p_video_out->flush();
1309 if (p_video_out_video != nullptr) p_video_out_video->flush();
1310 }
1311
1312 void dispatchAudio(Track& t, const uint8_t* data, size_t size, bool isFirst) {
1313 if (p_output_audio == nullptr) return;
1314 if (t.audio_codec == Codec::AAC) {
1315 uint8_t adts[7];
1317 (int)size);
1318 p_output_audio->write(adts, sizeof(adts));
1319 p_output_audio->write(data, size);
1320 } else {
1321 // ALAC (magic cookie is exposed via audioALACMagicCookie() for the
1322 // caller to configure their own decoder with) and any other codec:
1323 // raw payload as-is.
1324 p_output_audio->write(data, size);
1325 }
1326 }
1327
1328 static void writeAdtsHeader(uint8_t* adts, int aacProfile, int sampleRateIdx,
1329 int channelCfg, int frameLen) {
1330 adts[0] = 0xFF;
1331 adts[1] = 0xF1;
1332 adts[2] = ((aacProfile - 1) << 6) | (sampleRateIdx << 2) |
1333 ((channelCfg >> 2) & 0x1);
1334 adts[3] = ((channelCfg & 0x3) << 6) | ((frameLen + 7) >> 11);
1335 adts[4] = ((frameLen + 7) >> 3) & 0xFF;
1336 adts[5] = (((frameLen + 7) & 0x7) << 5) | 0x1F;
1337 adts[6] = 0xFC;
1338 }
1339};
1340
1346 public:
1348
1349 void clear() { buffer.clear(); }
1350 size_t size() { return buffer.size(); }
1351 const uint8_t* data() { return buffer.data(); }
1352
1353 void u8(uint8_t v) { buffer.push_back(v); }
1354 void u16(uint16_t v) {
1355 u8((uint8_t)(v >> 8));
1356 u8((uint8_t)v);
1357 }
1358 void u24(uint32_t v) {
1359 u8((uint8_t)(v >> 16));
1360 u8((uint8_t)(v >> 8));
1361 u8((uint8_t)v);
1362 }
1363 void u32(uint32_t v) {
1364 u8((uint8_t)(v >> 24));
1365 u8((uint8_t)(v >> 16));
1366 u8((uint8_t)(v >> 8));
1367 u8((uint8_t)v);
1368 }
1369 void fourcc(const char* type) { bytes((const uint8_t*)type, 4); }
1370 void bytes(const uint8_t* data, size_t len) {
1371 size_t off = buffer.size();
1372 buffer.resize(off + len);
1373 memcpy(buffer.data() + off, data, len);
1374 }
1375 void zeros(size_t len) {
1376 size_t off = buffer.size();
1377 buffer.resize(off + len);
1378 memset(buffer.data() + off, 0, len);
1379 }
1380 void cstr(const char* s) {
1381 while (*s) u8((uint8_t)*s++);
1382 u8(0);
1383 }
1384
1387 size_t beginBox(const char* type) {
1388 size_t pos = buffer.size();
1389 u32(0);
1390 fourcc(type);
1391 return pos;
1392 }
1395 void endBox(size_t pos) {
1396 uint32_t sz = (uint32_t)(buffer.size() - pos);
1397 buffer[pos] = (uint8_t)(sz >> 24);
1398 buffer[pos + 1] = (uint8_t)(sz >> 16);
1399 buffer[pos + 2] = (uint8_t)(sz >> 8);
1400 buffer[pos + 3] = (uint8_t)sz;
1401 }
1402};
1403
1406
1486class MuxerMP4 : public Muxer {
1487 public:
1489 MuxerMP4(Print& out) : MuxerMP4() { setOutput(out); }
1490
1491 const char* mimeVideo() override { return "video/mp4"; }
1492
1494 void setOutput(Print& out) override { p_out = &out; }
1495
1497 void setVideoInfo(MuxerVideoConfig config) override { video_cfg = config; }
1498
1501
1510 void setAudioInfo(AudioInfoFormat info) override {
1512 audio_info = info;
1513 has_audio = true;
1514 }
1516 AudioInfoFormat& audioInfo() override { return audio_info; }
1520 void setAudioProfile(int aacProfile) { audio_profile = aacProfile; }
1521
1529 bool begin() override {
1530 if (p_out == nullptr) {
1531 LOGE("output not defined");
1532 return false;
1533 }
1534 if (video_cfg.width == 0 || video_cfg.height == 0) {
1535 LOGE("invalid video size: %d x %d", (int)video_cfg.width,
1536 (int)video_cfg.height);
1537 return false;
1538 }
1544 LOGE(
1545 "unsupported video format: %d - MuxerMP4 writes H264, MJPEG, "
1546 "YUV422, RGB565 or I420",
1547 (int)video_cfg.format);
1548 return false;
1549 }
1550 video_timescale = 90000;
1552 video_cfg.fps > 0 ? (uint32_t)(video_timescale / video_cfg.fps) : 0;
1554 // AAC: fixed 1024 samples/frame (LC). PCM: duration varies per call
1555 // (addAudioFrame() computes it from the actual byte length instead),
1556 // so this default is not used for PCM - trun always states it
1557 // explicitly per fragment regardless of codec.
1559
1560 sps_data.clear();
1561 pps_data.clear();
1562 moov_written = false;
1563 video_seq = 0;
1564 video_base_time = 0;
1565 audio_seq = 0;
1566 audio_base_time = 0;
1567 fragment_seq = 0;
1570 is_open = true;
1571 // MJPEG needs no stream-derived config (unlike H264's SPS/PPS), so
1572 // this writes 'moov' immediately; for H264 it's a no-op here and
1573 // happens lazily once SPS/PPS have been captured (see addVideoFrame()).
1574 tryWriteMoov();
1575 return true;
1576 }
1577
1579 void end() override { is_open = false; }
1580
1581 operator bool() override { return is_open; }
1582
1587 void setStreamType(StreamContentType type) override {
1588 write_stream_type = type;
1589 }
1592
1600 size_t write(const uint8_t* data, size_t len) override {
1602 return addAudioFrame(data, len);
1603 }
1604
1605 switch (video_cfg.format) {
1606 case VideoFormat::MJPEG:
1607 return addJpegFrame(data, len);
1609 return addYUV422Frame(data, len);
1611 return addRGB565Frame(data, len);
1612 case VideoFormat::I420:
1613 return addI420Frame(data, len);
1614 default:
1615 return addVideoFrame(data, len);
1616 }
1617 }
1618
1630 size_t addVideoFrame(const uint8_t* data, size_t len,
1631 bool isKeyFrame = true) override {
1632 if (!is_open || video_cfg.format != VideoFormat::H264) return 0;
1633 setVideoConfigData(data, len);
1634 if (!tryWriteMoov()) {
1635 LOGW("dropping video frame: SPS/PPS not seen yet");
1636 return 0;
1637 }
1638 nal_tmp.clear();
1639 forEachAnnexBNal(data, len, [this](const uint8_t* nal, size_t nalLen) {
1640 if (nalLen == 0) return;
1641 uint8_t nalType = nal[0] & 0x1F;
1642 if (nalType == 7 || nalType == 8) return; // SPS/PPS: already in avcC
1643 size_t off = nal_tmp.size();
1644 nal_tmp.resize(off + 4 + nalLen);
1645 uint8_t* p = nal_tmp.data() + off;
1646 p[0] = (uint8_t)(nalLen >> 24);
1647 p[1] = (uint8_t)(nalLen >> 16);
1648 p[2] = (uint8_t)(nalLen >> 8);
1649 p[3] = (uint8_t)nalLen;
1650 memcpy(p + 4, nal, nalLen);
1651 });
1652 if (nal_tmp.size() == 0) return 0;
1657 return len;
1658 }
1659
1664 size_t addJpegFrame(const uint8_t* data, size_t len) override {
1665 if (video_cfg.format != VideoFormat::MJPEG) return 0;
1666 return addRawFrame(data, len);
1667 }
1668
1677 size_t addYUV422Frame(const uint8_t* data, size_t len) override {
1679 return addRawFrame(data, len);
1680 }
1681
1688 size_t addRGB565Frame(const uint8_t* data, size_t len) override {
1690 return addRawFrame(data, len);
1691 }
1692
1704 size_t addI420Frame(const uint8_t* data, size_t len) override {
1706 return addRawFrame(data, len);
1707 }
1708
1718 size_t addAudioFrame(const uint8_t* data, size_t len) override {
1719 if (!is_open || !has_audio || len == 0) return 0;
1720 if (!moov_written) {
1721 LOGW(
1722 "dropping audio frame: moov not written yet (video SPS/PPS not "
1723 "seen)");
1724 return 0;
1725 }
1726 uint32_t duration = audio_sample_duration; // AAC: fixed 1024
1728 int bytesPerFrame =
1730 duration = bytesPerFrame > 0 ? (uint32_t)(len / bytesPerFrame) : 0;
1731 }
1732 writeMoofMdat(kAudioTrackId, data, len, duration,
1733 /*isKeyFrame*/ true, audio_base_time);
1734 audio_base_time += duration;
1736 return len;
1737 }
1738
1740 uint32_t videoFrameCount() { return video_frame_count; }
1742 uint32_t audioFrameCount() { return audio_frame_count; }
1743
1744 protected:
1745 static const uint32_t kVideoTrackId = 1;
1746 static const uint32_t kAudioTrackId = 2;
1747
1748 Print* p_out = nullptr;
1752
1754 bool has_audio = false;
1755 int audio_profile = 2; // AAC LC
1756
1757 bool is_open = false;
1760 bool moov_written = false;
1762 uint32_t video_timescale = 90000;
1764 uint32_t audio_timescale = 0;
1765 uint32_t audio_sample_duration = 1024;
1766
1767 uint32_t video_seq = 0;
1768 uint32_t video_base_time = 0;
1769 uint32_t audio_seq = 0;
1770 uint32_t audio_base_time = 0;
1771 uint32_t fragment_seq = 0;
1772 uint32_t video_frame_count = 0;
1773 uint32_t audio_frame_count = 0;
1774
1777
1783 void setVideoConfigData(const uint8_t* spsPpsAnnexB, size_t len) {
1784 forEachAnnexBNal(spsPpsAnnexB, len,
1785 [this](const uint8_t* nal, size_t nalLen) {
1786 if (nalLen == 0) return;
1787 uint8_t nalType = nal[0] & 0x1F;
1788 if (nalType == 7) {
1789 sps_data.resize(nalLen);
1790 memcpy(sps_data.data(), nal, nalLen);
1791 } else if (nalType == 8) {
1792 pps_data.resize(nalLen);
1793 memcpy(pps_data.data(), nal, nalLen);
1794 }
1795 });
1796 }
1797
1803 if (moov_written) return true;
1805 (sps_data.size() == 0 || pps_data.size() == 0)) {
1806 return false;
1807 }
1808 writeFtypMoov();
1809 moov_written = true;
1810 return true;
1811 }
1812
1814 if (video_cfg.format != expected) {
1815 LOGW("getVideoInfo().format does not match the addXxxFrame() called");
1816 }
1817 }
1818
1821 void checkRawFrame(VideoFormat expected, size_t len) {
1822 checkVideoFormat(expected);
1823 size_t expectedSize =
1825 if (expectedSize > 0 && len != expectedSize) {
1826 LOGW("frame size %d does not match the expected %d bytes for %d x %d",
1827 (int)len, (int)expectedSize, (int)video_cfg.width,
1828 (int)video_cfg.height);
1829 }
1830 }
1831
1839 size_t addRawFrame(const uint8_t* data, size_t len) {
1840 if (!is_open || len == 0) return 0;
1841 if (!tryWriteMoov()) return 0;
1843 /*isKeyFrame*/ true, video_base_time);
1846 return len;
1847 }
1848
1852 template <typename F>
1853 static void forEachAnnexBNal(const uint8_t* data, size_t len, F callback) {
1854 size_t i = 0;
1855 while (i + 3 <= len) {
1856 size_t scLen = 0;
1857 if (i + 4 <= len && data[i] == 0 && data[i + 1] == 0 &&
1858 data[i + 2] == 0 && data[i + 3] == 1) {
1859 scLen = 4;
1860 } else if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
1861 scLen = 3;
1862 }
1863 if (scLen == 0) {
1864 i++;
1865 continue;
1866 }
1867 size_t nalStart = i + scLen;
1868 size_t j = nalStart;
1869 size_t nextStart = len;
1870 while (j + 3 <= len) {
1871 if (data[j] == 0 && data[j + 1] == 0 &&
1872 (data[j + 2] == 1 ||
1873 (j + 3 < len && data[j + 2] == 0 && data[j + 3] == 1))) {
1874 nextStart = j;
1875 break;
1876 }
1877 j++;
1878 }
1879 if (nextStart > nalStart) callback(data + nalStart, nextStart - nalStart);
1880 i = nextStart;
1881 }
1882 }
1883
1884 static int aacSampleRateIndex(uint32_t sampleRate) {
1885 static const uint32_t rates[13] = {96000, 88200, 64000, 48000, 44100,
1886 32000, 24000, 22050, 16000, 12000,
1887 11025, 8000, 7350};
1888 for (int i = 0; i < 13; i++)
1889 if (rates[i] == sampleRate) return i;
1890 return 4; // default: 44100
1891 }
1892
1894 size_t pos = b.beginBox("avcC");
1895 b.u8(1); // configurationVersion
1896 b.u8(sps_data[1]); // AVCProfileIndication
1897 b.u8(sps_data[2]); // profile_compatibility
1898 b.u8(sps_data[3]); // AVCLevelIndication
1899 b.u8(0xFF); // reserved(6) + lengthSizeMinusOne(2) = 4-byte lengths
1900 b.u8(0xE1); // reserved(3) + numOfSPS(5) = 1
1901 b.u16((uint16_t)sps_data.size());
1903 b.u8(1); // numOfPPS
1904 b.u16((uint16_t)pps_data.size());
1906 b.endBox(pos);
1907 }
1908
1915 size_t pos = b.beginBox("esds");
1916 b.u32(0); // version + flags
1917 b.u8(0x03);
1918 // size: ES_ID(2)+flags(1)+DecoderConfigDescr(15 incl. its own tag+size)
1919 // +SLConfigDescr(3 incl. its own tag+size) = 21
1920 b.u8(21);
1921 b.u16(kVideoTrackId); // ES_ID
1922 b.u8(0); // flags
1923 b.u8(0x04);
1924 b.u8(13); // size: objType(1)+streamType(1)+bufSize(3)+maxBr(4)+avgBr(4)
1925 b.u8(0x6C); // objectTypeIndication: JPEG (ISO/IEC 10918-1)
1926 b.u8((4 << 2) | 1); // streamType: visual(4), reserved bit set
1927 b.u24(0); // bufferSizeDB
1928 b.u32(0); // maxBitrate (0 = unspecified)
1929 b.u32(0); // avgBitrate
1930 b.u8(0x06);
1931 b.u8(1);
1932 b.u8(0x02);
1933 b.endBox(pos);
1934 }
1935
1937 int sampleRateIdx = aacSampleRateIndex(audio_info.sample_rate);
1938 uint8_t asc[2];
1939 asc[0] = (uint8_t)((audio_profile << 3) | (sampleRateIdx >> 1));
1940 asc[1] = (uint8_t)(((sampleRateIdx & 1) << 7) | (audio_info.channels << 3));
1941
1942 size_t pos = b.beginBox("esds");
1943 b.u32(0); // version + flags
1944 // ES_Descriptor
1945 b.u8(0x03);
1946 // size: ES_ID(2)+flags(1)+DecoderConfigDescr(19 incl. its own tag+size)
1947 // +SLConfigDescr(3 incl. its own tag+size) = 25
1948 b.u8(25);
1949 b.u16(kAudioTrackId); // ES_ID
1950 b.u8(0); // flags
1951 // DecoderConfigDescr
1952 b.u8(0x04);
1953 b.u8(
1954 17); // size:
1955 // objType(1)+streamType(1)+bufSize(3)+maxBr(4)+avgBr(4)+DecSpecificInfo(4)
1956 b.u8(0x40); // objectTypeIndication: AAC
1957 b.u8((5 << 2) | 1); // streamType: audio(5), upstream=0, reserved=1
1958 b.u24(0); // bufferSizeDB
1959 b.u32(128000); // maxBitrate
1960 b.u32(128000); // avgBitrate
1961 // DecSpecificInfo (the 2-byte AudioSpecificConfig)
1962 b.u8(0x05);
1963 b.u8(2);
1964 b.bytes(asc, 2);
1965 // SLConfigDescr
1966 b.u8(0x06);
1967 b.u8(1);
1968 b.u8(0x02);
1969 b.endBox(pos);
1970 }
1971
1973 size_t pos = b.beginBox("mvhd");
1974 b.u32(0); // version + flags
1975 b.u32(0); // creation_time
1976 b.u32(0); // modification_time
1977 b.u32(1000); // timescale
1978 b.u32(0); // duration (unknown - fragmented)
1979 b.u32(0x00010000); // rate 1.0
1980 b.u16(0x0100); // volume 1.0
1981 b.u16(0); // reserved
1982 b.u32(0);
1983 b.u32(0); // reserved[2]
1984 // unity matrix
1985 static const uint32_t identity[9] = {
1986 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000};
1987 for (int i = 0; i < 9; i++) b.u32(identity[i]);
1988 b.zeros(24); // pre_defined[6]
1989 b.u32(has_audio ? kAudioTrackId + 1 : kVideoTrackId + 1); // next_track_ID
1990 b.endBox(pos);
1991 }
1992
1993 void writeTkhd(MP4BoxWriter& b, uint32_t trackId, bool isVideo) {
1994 size_t pos = b.beginBox("tkhd");
1995 b.u32(0x000007); // version 0 + flags: enabled|in_movie|in_preview
1996 b.u32(0); // creation_time
1997 b.u32(0); // modification_time
1998 b.u32(trackId);
1999 b.u32(0); // reserved
2000 b.u32(0); // duration (unknown)
2001 b.u32(0);
2002 b.u32(0); // reserved[2]
2003 b.u16(0); // layer
2004 b.u16(0); // alternate_group
2005 b.u16(isVideo ? 0 : 0x0100); // volume
2006 b.u16(0); // reserved
2007 static const uint32_t identity[9] = {
2008 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000};
2009 for (int i = 0; i < 9; i++) b.u32(identity[i]);
2010 b.u32(isVideo ? ((uint32_t)video_cfg.width << 16) : 0);
2011 b.u32(isVideo ? ((uint32_t)video_cfg.height << 16) : 0);
2012 b.endBox(pos);
2013 }
2014
2015 void writeMdhd(MP4BoxWriter& b, uint32_t timescale) {
2016 size_t pos = b.beginBox("mdhd");
2017 b.u32(0); // version + flags
2018 b.u32(0); // creation_time
2019 b.u32(0); // modification_time
2020 b.u32(timescale);
2021 b.u32(0); // duration (unknown)
2022 b.u16(0x55C4); // language: und
2023 b.u16(0);
2024 b.endBox(pos);
2025 }
2026
2027 void writeHdlr(MP4BoxWriter& b, const char* handlerType, const char* name) {
2028 size_t pos = b.beginBox("hdlr");
2029 b.u32(0); // version + flags
2030 b.u32(0); // pre_defined
2031 b.fourcc(handlerType);
2032 b.zeros(12); // reserved
2033 b.cstr(name);
2034 b.endBox(pos);
2035 }
2036
2038 size_t pos = b.beginBox("dinf");
2039 size_t drefPos = b.beginBox("dref");
2040 b.u32(0); // version + flags
2041 b.u32(1); // entry_count
2042 size_t urlPos = b.beginBox("url ");
2043 b.u32(1); // version + flags: self-contained (data in this file)
2044 b.endBox(urlPos);
2045 b.endBox(drefPos);
2046 b.endBox(pos);
2047 }
2048
2050 size_t pos = b.beginBox("stbl");
2051 size_t stsdPos = b.beginBox("stsd");
2052 b.u32(0); // version + flags
2053 b.u32(1); // entry_count
2054 // VisualSampleEntry: same fixed 78-byte header for every fourCC below
2055 const char* fourcc = "avc1";
2056 uint16_t depth = 0x0018; // 24, nominal - not critical for playback
2057 switch (video_cfg.format) {
2058 case VideoFormat::MJPEG:
2059 fourcc = "mp4v";
2060 break;
2062 fourcc = "yuvs"; // matches what ffmpeg itself writes for YUY2-in-MOV
2063 break;
2065 fourcc = "L565"; // matches what ffmpeg itself writes for rgb565le
2066 depth = 16; // actual bit depth, unlike the nominal 24 above
2067 break;
2068 case VideoFormat::I420:
2069 fourcc = "I420"; // see addI420Frame()'s note: unverified in MP4
2070 break;
2071 default:
2072 break; // H264: 'avc1'
2073 }
2074 size_t entryPos = b.beginBox(fourcc);
2075 b.zeros(6); // reserved
2076 b.u16(1); // data_reference_index
2077 b.u16(0); // pre_defined
2078 b.u16(0); // reserved
2079 b.zeros(12); // pre_defined[3]
2080 b.u16(video_cfg.width);
2081 b.u16(video_cfg.height);
2082 b.u32(0x00480000); // horizresolution 72dpi
2083 b.u32(0x00480000); // vertresolution 72dpi
2084 b.u32(0); // reserved
2085 b.u16(1); // frame_count
2086 b.zeros(32); // compressorname
2087 b.u16(depth);
2088 b.u16(0xFFFF); // pre_defined
2089 switch (video_cfg.format) {
2090 case VideoFormat::MJPEG:
2091 writeEsdsMjpeg(b);
2092 break;
2095 case VideoFormat::I420:
2096 break; // raw formats: no child config box needed
2097 default:
2098 writeAvcC(b); // H264
2099 break;
2100 }
2101 b.endBox(entryPos);
2102 b.endBox(stsdPos);
2103 // empty sample tables - samples are described per-fragment instead
2104 size_t sttsPos = b.beginBox("stts");
2105 b.u32(0);
2106 b.u32(0);
2107 b.endBox(sttsPos);
2108 size_t stscPos = b.beginBox("stsc");
2109 b.u32(0);
2110 b.u32(0);
2111 b.endBox(stscPos);
2112 size_t stszPos = b.beginBox("stsz");
2113 b.u32(0);
2114 b.u32(0);
2115 b.u32(0);
2116 b.endBox(stszPos);
2117 size_t stcoPos = b.beginBox("stco");
2118 b.u32(0);
2119 b.u32(0);
2120 b.endBox(stcoPos);
2121 b.endBox(pos);
2122 }
2123
2129 b.zeros(6); // reserved
2130 b.u16(1); // data_reference_index
2131 b.zeros(8); // reserved[2]
2132 b.u16((uint16_t)audio_info.channels);
2133 b.u16((uint16_t)audio_info.bits_per_sample);
2134 b.u16(0); // pre_defined
2135 b.u16(0); // reserved
2136 b.u32((uint32_t)audio_info.sample_rate << 16);
2137 }
2138
2140 size_t pos = b.beginBox("stbl");
2141 size_t stsdPos = b.beginBox("stsd");
2142 b.u32(0); // version + flags
2143 b.u32(1); // entry_count
2145 // 'sowt': little-endian signed PCM - the fixed AudioSampleEntry
2146 // header alone is a complete sample description, no child box
2147 // needed (unlike 'mp4a', which needs 'esds' for the AAC config).
2148 size_t sowtPos = b.beginBox("sowt");
2150 b.endBox(sowtPos);
2151 } else {
2152 size_t mp4aPos = b.beginBox("mp4a");
2154 writeEsds(b);
2155 b.endBox(mp4aPos);
2156 }
2157 b.endBox(stsdPos);
2158 size_t sttsPos = b.beginBox("stts");
2159 b.u32(0);
2160 b.u32(0);
2161 b.endBox(sttsPos);
2162 size_t stscPos = b.beginBox("stsc");
2163 b.u32(0);
2164 b.u32(0);
2165 b.endBox(stscPos);
2166 size_t stszPos = b.beginBox("stsz");
2167 b.u32(0);
2168 b.u32(0);
2169 b.u32(0);
2170 b.endBox(stszPos);
2171 size_t stcoPos = b.beginBox("stco");
2172 b.u32(0);
2173 b.u32(0);
2174 b.endBox(stcoPos);
2175 b.endBox(pos);
2176 }
2177
2178 void writeTrak(MP4BoxWriter& b, uint32_t trackId, bool isVideo) {
2179 size_t pos = b.beginBox("trak");
2180 writeTkhd(b, trackId, isVideo);
2181 size_t mdiaPos = b.beginBox("mdia");
2183 writeHdlr(b, isVideo ? "vide" : "soun",
2184 isVideo ? "VideoHandler" : "SoundHandler");
2185 size_t minfPos = b.beginBox("minf");
2186 if (isVideo) {
2187 size_t vmhdPos = b.beginBox("vmhd");
2188 b.u32(1); // version + flags
2189 b.zeros(8); // graphicsmode + opcolor
2190 b.endBox(vmhdPos);
2191 } else {
2192 size_t smhdPos = b.beginBox("smhd");
2193 b.u32(0); // version + flags
2194 b.u16(0); // balance
2195 b.u16(0); // reserved
2196 b.endBox(smhdPos);
2197 }
2198 writeDinf(b);
2199 if (isVideo) {
2200 writeVideoStbl(b);
2201 } else {
2202 writeAudioStbl(b);
2203 }
2204 b.endBox(minfPos);
2205 b.endBox(mdiaPos);
2206 b.endBox(pos);
2207 }
2208
2210 size_t pos = b.beginBox("mvex");
2211 size_t trexVideoPos = b.beginBox("trex");
2212 b.u32(0); // version + flags
2213 b.u32(kVideoTrackId);
2214 b.u32(1); // default_sample_description_index
2216 b.u32(0); // default_sample_size
2217 b.u32(0); // default_sample_flags
2218 b.endBox(trexVideoPos);
2219 if (has_audio) {
2220 size_t trexAudioPos = b.beginBox("trex");
2221 b.u32(0);
2222 b.u32(kAudioTrackId);
2223 b.u32(1);
2225 b.u32(0);
2226 b.u32(0);
2227 b.endBox(trexAudioPos);
2228 }
2229 b.endBox(pos);
2230 }
2231
2233 box.clear();
2234 size_t ftypPos = box.beginBox("ftyp");
2235 box.fourcc("isom");
2236 box.u32(0x200);
2237 box.fourcc("isom");
2238 box.fourcc("iso5");
2239 box.fourcc("iso6");
2240 box.fourcc("mp41");
2241 box.endBox(ftypPos);
2242
2243 size_t moovPos = box.beginBox("moov");
2244 writeMvhd(box);
2245 writeTrak(box, kVideoTrackId, true);
2246 if (has_audio) writeTrak(box, kAudioTrackId, false);
2247 writeMvex(box);
2248 box.endBox(moovPos);
2249
2250 p_out->write(box.data(), box.size());
2251 }
2252
2258 void writeMoofMdat(uint32_t trackId, const uint8_t* data, size_t len,
2259 uint32_t sampleDuration, bool isKeyFrame,
2260 uint32_t baseTime) {
2261 if (p_out == nullptr) return;
2262 box.clear();
2263 size_t moofPos = box.beginBox("moof");
2264 size_t mfhdPos = box.beginBox("mfhd");
2265 box.u32(0); // version + flags
2267 box.endBox(mfhdPos);
2268
2269 size_t trafPos = box.beginBox("traf");
2270 size_t tfhdPos = box.beginBox("tfhd");
2271 box.u32(0x020000); // version 0 + flags: default-base-is-moof
2272 box.u32(trackId);
2273 box.endBox(tfhdPos);
2274
2275 size_t tfdtPos = box.beginBox("tfdt");
2276 box.u32(0); // version 0 + flags
2277 box.u32(baseTime);
2278 box.endBox(tfdtPos);
2279
2280 bool isVideo = (trackId == kVideoTrackId);
2281 // data-offset | sample-duration | sample-size (always explicit per
2282 // fragment - relying on trex's constant default doesn't hold once a
2283 // track's per-sample duration can vary, as with PCM audio)
2284 uint32_t trunFlags = 0x000001 | 0x000100 | 0x000200;
2285 if (isVideo) trunFlags |= 0x000400; // + sample-flags
2286
2287 size_t trunPos = box.beginBox("trun");
2288 box.u32(trunFlags);
2289 box.u32(1); // sample_count
2290 size_t dataOffsetFieldPos = box.size();
2291 box.u32(0); // data_offset placeholder
2292 // per-sample fields, in the fixed order the spec mandates: duration,
2293 // size, flags
2294 box.u32(sampleDuration);
2295 box.u32((uint32_t)len);
2296 if (isVideo) {
2297 // sample_depends_on / sample_is_non_sync_sample
2298 box.u32(isKeyFrame ? 0x02000000 : 0x01010000);
2299 }
2300 box.endBox(trunPos);
2301 box.endBox(trafPos);
2302 box.endBox(moofPos);
2303
2304 uint32_t dataOffset = (uint32_t)(box.size() + 8); // + mdat header
2305 box.buffer[dataOffsetFieldPos] = (uint8_t)(dataOffset >> 24);
2306 box.buffer[dataOffsetFieldPos + 1] = (uint8_t)(dataOffset >> 16);
2307 box.buffer[dataOffsetFieldPos + 2] = (uint8_t)(dataOffset >> 8);
2308 box.buffer[dataOffsetFieldPos + 3] = (uint8_t)dataOffset;
2309
2310 p_out->write(box.data(), box.size());
2311
2312 uint8_t mdatHeader[8];
2313 uint32_t mdatSize = (uint32_t)(len + 8);
2314 mdatHeader[0] = (uint8_t)(mdatSize >> 24);
2315 mdatHeader[1] = (uint8_t)(mdatSize >> 16);
2316 mdatHeader[2] = (uint8_t)(mdatSize >> 8);
2317 mdatHeader[3] = (uint8_t)mdatSize;
2318 mdatHeader[4] = 'm';
2319 mdatHeader[5] = 'd';
2320 mdatHeader[6] = 'a';
2321 mdatHeader[7] = 't';
2322 p_out->write(mdatHeader, 8);
2323 p_out->write(data, len);
2324
2325 fragment_seq++;
2326 }
2327};
2328
2329} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
Definition Arduino.h:56
virtual size_t write(const uint8_t *data, size_t len)
Definition Arduino.h:120
virtual void flush()
Definition Arduino.h:130
AudioInfo info
Definition AudioCodecsBase.h:80
void notifyAudioChange(AudioInfo info)
Definition AudioTypes.h:175
void clear()
same as reset
Definition Buffers.h:96
RAM-backed like RamSampleTableStore, but grows by allocating additional fixed-size chunks instead of ...
Definition SampleTableStore.h:131
Common interface for demuxers (DemuxerAVI, DemuxerMP4) that split a container's video and (optional) ...
Definition ContainerCommon.h:164
DemuxerMP4 extracts both the audio and (H.264) video track from a general, interleaved MP4/ISO-BMFF s...
Definition ContainerMP4.h:71
MP4Parser parser
Definition ContainerMP4.h:439
static StscEntry readStscEntry(const uint8_t *p)
Definition ContainerMP4.h:558
void setOutput(Print &out) override
Definition ContainerMP4.h:187
uint32_t dispatched_sample_count
Definition ContainerMP4.h:499
bool isValid(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:146
bool mdat_seen
Definition ContainerMP4.h:476
void onTrak()
Definition ContainerMP4.h:696
void setOutputVideo(VideoOutput &out) override
Definition ContainerMP4.h:190
void onEsds(const MP4Parser::Box &box)
Definition ContainerMP4.h:854
Vector< uint8_t > & audioALACMagicCookie()
Definition ContainerMP4.h:244
void onAc3(const MP4Parser::Box &box)
Definition ContainerMP4.h:881
void dispatchVideo(Track &t, const uint8_t *data, size_t size, bool isFirst)
Definition ContainerMP4.h:1250
void setupParser()
Definition ContainerMP4.h:571
void onStsd(MP4Parser::Box &box)
Definition ContainerMP4.h:777
DemuxerMP4(SeekableSource &seekSource)
Definition ContainerMP4.h:107
void advanceChunkIfNeeded()
Definition ContainerMP4.h:1164
Track * current_chunk_track
Definition ContainerMP4.h:474
bool beginBoxAccum(const MP4Parser::Box &box)
Definition ContainerMP4.h:520
size_t sample_buffer_filled
Definition ContainerMP4.h:495
Vector< uint8_t > sample_buffer
Definition ContainerMP4.h:494
DemuxerMP4(VideoOutput &video_out, Print &audio_out)
Definition ContainerMP4.h:94
uint32_t samplesInChunk(Track &t, uint32_t chunkIndex0)
Definition ContainerMP4.h:1146
void flushVideo()
Definition ContainerMP4.h:1307
uint64_t box_start_file_offset
Definition ContainerMP4.h:514
SeekableSource * p_seek_source
Definition ContainerMP4.h:459
~DemuxerMP4()
Definition ContainerMP4.h:125
void configureSpoolStores(Track &t)
Definition ContainerMP4.h:760
bool is_active
Definition ContainerMP4.h:491
void setSpoolStorageFactory(SpoolStorageFactory &spoolFactory)
Definition ContainerMP4.h:133
int64_t quickstart_bytes_remaining
Definition ContainerMP4.h:468
Print * p_output_audio
Definition ContainerMP4.h:478
void dispatchSample(Track &t, const uint8_t *data, size_t size, bool isFirst)
Definition ContainerMP4.h:1241
static void writeAdtsHeader(uint8_t *adts, int aacProfile, int sampleRateIdx, int channelCfg, int frameLen)
Definition ContainerMP4.h:1328
void setSeekSource(SeekableSource &seekSource)
Definition ContainerMP4.h:129
VideoInfo getVideoInfo() override
Definition ContainerMP4.h:203
bool send_wav_header
Definition ContainerMP4.h:485
void onHdlr(MP4Parser::Box &box)
Definition ContainerMP4.h:733
AudioInfoFormat getAudioInfo() override
Definition ContainerMP4.h:236
Vector< Track * > tracks
Definition ContainerMP4.h:446
void setOutputAudio(Print &out) override
Definition ContainerMP4.h:181
uint32_t total_bytes_received
Definition ContainerMP4.h:490
bool box_accum_active
Definition ContainerMP4.h:510
void onAvc1(const MP4Parser::Box &box)
Definition ContainerMP4.h:887
void end() override
Definition ContainerMP4.h:174
AudioFormat audio_format
Definition ContainerMP4.h:484
Track * p_video_track
first video track found
Definition ContainerMP4.h:454
VideoOutput * p_video_out_video
Definition ContainerMP4.h:480
void onAlac(const MP4Parser::Box &box)
Definition ContainerMP4.h:866
void onStsz(MP4Parser::Box &box)
Definition ContainerMP4.h:1018
void onMp4a(const MP4Parser::Box &box)
Definition ContainerMP4.h:789
size_t write(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:253
void sendWavHeader()
Definition ContainerMP4.h:841
void onStsc(MP4Parser::Box &box)
Definition ContainerMP4.h:1073
static uint32_t readU32(const uint8_t *p)
Definition ContainerMP4.h:547
bool stsz_header_pending
Definition ContainerMP4.h:511
uint32_t samples_left_in_chunk
Definition ContainerMP4.h:475
bool is_wav_header_sent
Definition ContainerMP4.h:487
void onHevc(const MP4Parser::Box &box)
Definition ContainerMP4.h:902
void onMdhd(MP4Parser::Box &box)
Definition ContainerMP4.h:948
Track * current_sample_track
Definition ContainerMP4.h:497
void dispatchAudio(Track &t, const uint8_t *data, size_t size, bool isFirst)
Definition ContainerMP4.h:1312
Vector< uint8_t > nal_tmp
scratch buffer for Annex-B conversion
Definition ContainerMP4.h:545
void onMdat(MP4Parser::Box &box)
Definition ContainerMP4.h:1133
void onStts(MP4Parser::Box &box)
Definition ContainerMP4.h:993
static uint64_t readU64(const uint8_t *p)
Definition ContainerMP4.h:550
static uint64_t readU32Widened(const uint8_t *p)
Definition ContainerMP4.h:557
DemuxerMP4()
Definition ContainerMP4.h:86
Print * p_video_out
Definition ContainerMP4.h:479
const char * mime() override
Definition ContainerMP4.h:141
void writeVideo(uint8_t *data, size_t size)
Definition ContainerMP4.h:1299
DemuxerMP4(SpoolStorageFactory &spoolFactory)
Definition ContainerMP4.h:120
Track * p_audio_track
Definition ContainerMP4.h:455
void freeTracks()
Definition ContainerMP4.h:450
void feed(const uint8_t *data, size_t len)
Definition ContainerMP4.h:1197
bool current_sample_is_first_for_track
Definition ContainerMP4.h:498
Track * current_track
track currently being parsed (scoped by 'trak')
Definition ContainerMP4.h:447
void setSendWavHeader(bool flag) override
Definition ContainerMP4.h:828
SingleBuffer< uint8_t > box_accum
Definition ContainerMP4.h:503
bool begin() override
Definition ContainerMP4.h:150
static uint16_t readU16(const uint8_t *p)
Definition ContainerMP4.h:553
void setupAudioInfo(AudioFormat format, const uint8_t *entryData, size_t entrySize)
Definition ContainerMP4.h:809
uint32_t current_sample_size
Definition ContainerMP4.h:496
void onStco(MP4Parser::Box &box, bool is64)
Definition ContainerMP4.h:1098
static SttsEntry readSttsEntry(const uint8_t *p)
Definition ContainerMP4.h:564
SpoolStorageFactory * p_spool_factory
Definition ContainerMP4.h:464
void endBoxAccum()
Call once a box_accum-using handler has fully processed a complete box.
Definition ContainerMP4.h:540
const char * mimeVideo() override
Provides the container mime.
Definition ContainerMP4.h:138
DemuxerMP4(Print &video_out, Print &audio_out)
Definition ContainerMP4.h:88
bool send_wav_header_explicit
Definition ContainerMP4.h:486
bool quickStart()
Definition ContainerMP4.h:289
void setOutputVideo(Print &out) override
Definition ContainerMP4.h:189
void onAvcC(const MP4Parser::Box &box)
Definition ContainerMP4.h:912
uint64_t boxPayloadFileOffset()
Definition ContainerMP4.h:986
Codec
Definition M4ACommonDemuxer.h:27
Minimal ISO-BMFF box builder: appends bytes to an in-memory buffer, with beginBox()/endBox() taking c...
Definition ContainerMP4.h:1345
size_t size()
Definition ContainerMP4.h:1350
void u32(uint32_t v)
Definition ContainerMP4.h:1363
void zeros(size_t len)
Definition ContainerMP4.h:1375
void fourcc(const char *type)
Definition ContainerMP4.h:1369
void u8(uint8_t v)
Definition ContainerMP4.h:1353
void u24(uint32_t v)
Definition ContainerMP4.h:1358
void bytes(const uint8_t *data, size_t len)
Definition ContainerMP4.h:1370
const uint8_t * data()
Definition ContainerMP4.h:1351
void clear()
Definition ContainerMP4.h:1349
size_t beginBox(const char *type)
Definition ContainerMP4.h:1387
void u16(uint16_t v)
Definition ContainerMP4.h:1354
void cstr(const char *s)
Definition ContainerMP4.h:1380
void endBox(size_t pos)
Definition ContainerMP4.h:1395
Vector< uint8_t > buffer
Definition ContainerMP4.h:1347
MP4Parser is a class that parses MP4 container files and extracts boxes (atoms). It provides a callba...
Definition MP4Parser.h:45
void setCallback(BoxCallback cb)
Defines the generic callback for all boxes.
Definition MP4Parser.h:92
int parseString(const uint8_t *str, int len, int fileOffset=0, int level=0)
Trigger separate parsing (and callbacks) on the indicated string.
Definition MP4Parser.h:228
bool begin(uint64_t startFileOffset=0)
Initializes the parser.
Definition MP4Parser.h:129
void setReference(void *ref)
Defines an optional reference. By default it is the parser itself.
Definition MP4Parser.h:86
bool findBox(const char *name, const uint8_t *data, size_t len, Box &result)
find box in box
Definition MP4Parser.h:255
size_t write(const uint8_t *data, size_t len)
Provide the data to the parser (in chunks if needed).
Definition MP4Parser.h:160
Common interface for muxers (MuxerAVI, MuxerMP4) that combine an already-encoded video track (and opt...
Definition ContainerCommon.h:42
Video track configuration for MuxerMP4 - update before calling begin().
Definition ContainerMP4.h:1486
StreamContentType write_stream_type
Definition ContainerMP4.h:1761
bool is_open
Definition ContainerMP4.h:1757
void setOutput(Print &out) override
Defines the output: e.g. a local File or a network Client.
Definition ContainerMP4.h:1494
void writeMoofMdat(uint32_t trackId, const uint8_t *data, size_t len, uint32_t sampleDuration, bool isKeyFrame, uint32_t baseTime)
Definition ContainerMP4.h:2258
void writeAudioStbl(MP4BoxWriter &b)
Definition ContainerMP4.h:2139
static int aacSampleRateIndex(uint32_t sampleRate)
Definition ContainerMP4.h:1884
uint32_t audio_base_time
Definition ContainerMP4.h:1770
size_t addVideoFrame(const uint8_t *data, size_t len, bool isKeyFrame=true) override
Definition ContainerMP4.h:1630
AudioInfoFormat audio_info
Definition ContainerMP4.h:1753
StreamContentType streamType() override
The track write() currently targets (see setStreamType())
Definition ContainerMP4.h:1591
Vector< uint8_t > sps_data
Definition ContainerMP4.h:1750
size_t addJpegFrame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1664
uint32_t video_sample_duration
Definition ContainerMP4.h:1763
void checkVideoFormat(VideoFormat expected)
Definition ContainerMP4.h:1813
uint32_t audioFrameCount()
Number of audio frames (fragments) written so far.
Definition ContainerMP4.h:1742
void writeMdhd(MP4BoxWriter &b, uint32_t timescale)
Definition ContainerMP4.h:2015
uint32_t video_seq
Definition ContainerMP4.h:1767
void writeTkhd(MP4BoxWriter &b, uint32_t trackId, bool isVideo)
Definition ContainerMP4.h:1993
uint32_t video_timescale
Definition ContainerMP4.h:1762
void checkRawFrame(VideoFormat expected, size_t len)
Definition ContainerMP4.h:1821
void writeTrak(MP4BoxWriter &b, uint32_t trackId, bool isVideo)
Definition ContainerMP4.h:2178
bool has_audio
Definition ContainerMP4.h:1754
void setAudioProfile(int aacProfile)
Definition ContainerMP4.h:1520
void writeEsdsMjpeg(MP4BoxWriter &b)
Definition ContainerMP4.h:1914
uint32_t fragment_seq
Definition ContainerMP4.h:1771
void writeMvhd(MP4BoxWriter &b)
Definition ContainerMP4.h:1972
AudioInfoFormat & audioInfo() override
Provides read/write access to the audio track's AudioInfoFormat.
Definition ContainerMP4.h:1516
void writeHdlr(MP4BoxWriter &b, const char *handlerType, const char *name)
Definition ContainerMP4.h:2027
Vector< uint8_t > pps_data
Definition ContainerMP4.h:1751
void writeDinf(MP4BoxWriter &b)
Definition ContainerMP4.h:2037
void end() override
Closes the encoder: no trailer is required for playback.
Definition ContainerMP4.h:1579
void setVideoInfo(MuxerVideoConfig config) override
Defines the video track configuration - call before begin()
Definition ContainerMP4.h:1497
void setStreamType(StreamContentType type) override
Definition ContainerMP4.h:1587
uint32_t audio_timescale
Definition ContainerMP4.h:1764
size_t addAudioFrame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1718
size_t write(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1600
void setAudioInfo(AudioInfoFormat info) override
Definition ContainerMP4.h:1510
uint32_t videoFrameCount()
Number of video frames (fragments) written so far.
Definition ContainerMP4.h:1740
MuxerMP4()
Definition ContainerMP4.h:1488
size_t addRawFrame(const uint8_t *data, size_t len)
Definition ContainerMP4.h:1839
Vector< uint8_t > nal_tmp
scratch buffer for Annex-B -> AVCC conversion
Definition ContainerMP4.h:1775
size_t addYUV422Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1677
uint32_t audio_frame_count
Definition ContainerMP4.h:1773
static const uint32_t kAudioTrackId
Definition ContainerMP4.h:1746
void writeMvex(MP4BoxWriter &b)
Definition ContainerMP4.h:2209
size_t addI420Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1704
uint32_t audio_sample_duration
Definition ContainerMP4.h:1765
Print * p_out
Definition ContainerMP4.h:1748
MP4BoxWriter box
scratch buffer, reused for 'moov' and each 'moof'
Definition ContainerMP4.h:1776
void writeFtypMoov()
Definition ContainerMP4.h:2232
MuxerMP4(Print &out)
Definition ContainerMP4.h:1489
size_t addRGB565Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1688
void setVideoConfigData(const uint8_t *spsPpsAnnexB, size_t len)
Definition ContainerMP4.h:1783
bool moov_written
Definition ContainerMP4.h:1760
int audio_profile
Definition ContainerMP4.h:1755
static const uint32_t kVideoTrackId
Definition ContainerMP4.h:1745
bool tryWriteMoov()
Definition ContainerMP4.h:1802
bool begin() override
Definition ContainerMP4.h:1529
static void forEachAnnexBNal(const uint8_t *data, size_t len, F callback)
Definition ContainerMP4.h:1853
void writeVideoStbl(MP4BoxWriter &b)
Definition ContainerMP4.h:2049
void writeEsds(MP4BoxWriter &b)
Definition ContainerMP4.h:1936
uint32_t video_frame_count
Definition ContainerMP4.h:1772
uint32_t audio_seq
Definition ContainerMP4.h:1769
const char * mimeVideo() override
Definition ContainerMP4.h:1491
void writeAudioSampleEntryHeader(MP4BoxWriter &b)
Definition ContainerMP4.h:2128
void writeAvcC(MP4BoxWriter &b)
Definition ContainerMP4.h:1893
MuxerVideoConfig getVideoInfo() override
Provides the video track configuration.
Definition ContainerMP4.h:1500
uint32_t video_base_time
Definition ContainerMP4.h:1768
MuxerVideoConfig video_cfg
Definition ContainerMP4.h:1749
Sequential-access storage for one MP4 sample table (e.g. stsz sample sizes, stco chunk offsets,...
Definition SampleTableStore.h:61
virtual size_t size()=0
Number of entries appended so far.
virtual void append(T value)=0
Appends one entry - call in file order, once per table entry.
virtual void setNextEntryOffset(uint64_t fileOffset)
Definition SampleTableStore.h:77
virtual T get(size_t index)=0
Reads back entry 'index' - index must be < size().
virtual void setOnDiskEntrySize(size_t bytes)
Definition SampleTableStore.h:81
Minimal seek+read interface a SourceSeekSampleTableStore needs from "the original MP4 source" - kept ...
Definition SampleTableStore.h:168
virtual bool seek(size_t pos)=0
virtual size_t readBytes(uint8_t *data, size_t len)=0
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:194
int available() override
provides the number of entries that are available to read
Definition Buffers.h:255
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:223
T * data()
Provides address of actual data.
Definition Buffers.h:306
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:327
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:274
Discards every value right after appending it, keeping only the file offset of the first entry - get(...
Definition SampleTableStore.h:339
Writes every entry to a caller-provided scratch file as it's appended, instead of keeping it in RAM -...
Definition SampleTableStore.h:485
Factory DemuxerMP4 calls once per table, per track, to obtain a SpoolStorage for spool-backed sample ...
Definition SampleTableStore.h:537
virtual SpoolStorage * createSpoolStorage(TrackKind trackKind, SampleTableKind tableKind)=0
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
void push_back(T &&value)
Definition Vector.h:182
void clear()
Definition Vector.h:176
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
Abstract class for video playback. This class is used to assemble a complete video frame in memory....
Definition VideoOutput.h:107
virtual size_t write(const uint8_t *data, size_t len)=0
virtual void flush()
Definition VideoOutput.h:113
virtual bool isKeyFrame(const uint8_t *data, size_t len)
Definition VideoOutput.h:135
Parser for Wav header data for details see https://de.wikipedia.org/wiki/RIFF_WAVE.
Definition CodecWAV.h:89
void setAudioInfo(WAVAudioInfo info)
Sets the info in the header.
Definition CodecWAV.h:166
bool writeHeader(Print *out)
Just write a wav header to the indicated outputbu.
Definition CodecWAV.h:169
VideoFormat
Video codec/pixel-format identifier, shared by two unrelated uses: the (single) video stream of a con...
Definition VideoOutput.h:29
StreamContentType
Which track write() feeds, for muxers (MuxerAVI, MuxerMP4) that double as a plain,...
Definition Video.h:21
size_t videoFrameSizeBytes(VideoFormat format, uint16_t width, uint16_t height)
Fixed per-frame size (bytes) for a raw/uncompressed VideoFormat at the given resolution - 0 for compr...
Definition Video.h:27
AudioFormat
Audio format codes used by Microsoft e.g. in avi or wav files.
Definition AudioFormat.h:21
const char * toMime(AudioFormat format)
Provides the mime type for a AudioFormat wav code, or nullptr if not known/mapped.
Definition AudioFormat.h:300
bool isWavFormat(AudioFormat format)
True if the wav code is handled via the WAV decoder (i.e. toMime() maps it to "audio/wav": PCM and al...
Definition AudioFormat.h:350
TrackKind
Video vs. audio track classification - lives here (rather than nested inside DemuxerMP4::Track,...
Definition SampleTableStore.h:46
@ Audio
Definition Video.h:21
@ Video
Definition Video.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
AudioInfo extended with a WAVEFORMATEX-style codec tag (the "wav code"): identifies the codec (PCM,...
Definition AudioFormat.h:392
AudioFormat format
Definition AudioFormat.h:401
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57
virtual void logInfo(const char *source="")
Definition AudioTypes.h:122
sample-to-chunk table entry (stsc)
Definition ContainerMP4.h:356
uint32_t first_chunk
Definition ContainerMP4.h:357
uint32_t samples_per_chunk
Definition ContainerMP4.h:358
Definition ContainerMP4.h:364
uint32_t sample_count
Definition ContainerMP4.h:365
uint32_t sample_delta
Definition ContainerMP4.h:366
One track (audio or video) as found in 'moov'.
Definition ContainerMP4.h:370
Vector< uint8_t > alacMagicCookie
Definition ContainerMP4.h:382
int channelCfg
Definition ContainerMP4.h:381
uint32_t next_chunk_index
Definition ContainerMP4.h:421
uint32_t timescale
Definition ContainerMP4.h:405
SampleTableStore< SttsEntry > * stts
Definition ContainerMP4.h:406
SampleTableStore< uint64_t > * chunk_offsets
Definition ContainerMP4.h:400
bool spool_configured
Guards DemuxerMP4::configureSpoolStores() against running twice.
Definition ContainerMP4.h:377
uint16_t height
Definition ContainerMP4.h:385
~Track()
Definition ContainerMP4.h:408
uint32_t next_sample_index
Definition ContainerMP4.h:416
SampleTableStore< StscEntry > * stsc
Definition ContainerMP4.h:399
uint8_t nal_length_size
Definition ContainerMP4.h:386
uint32_t stsc_cursor
Definition ContainerMP4.h:425
int aacProfile
Definition ContainerMP4.h:381
Vector< uint8_t > sps_pps_annexb
Definition ContainerMP4.h:387
uint32_t sampleSize(uint32_t idx)
Definition ContainerMP4.h:427
Codec audio_codec
Definition ContainerMP4.h:380
uint16_t width
Definition ContainerMP4.h:385
int sampleRateIdx
Definition ContainerMP4.h:381
bool is_hevc_unsupported
Definition ContainerMP4.h:389
uint32_t fixed_sample_size
Definition ContainerMP4.h:397
TrackKind kind
Definition ContainerMP4.h:375
SampleTableStore< uint32_t > * sample_sizes
Definition ContainerMP4.h:396
uint32_t sampleCount()
Definition ContainerMP4.h:433
uint32_t fixed_sample_count
Definition ContainerMP4.h:398
bool avc_config_sent
Definition ContainerMP4.h:388
A parser for the ESDS segment to extract the relevant aac information.
Definition M4ACommonDemuxer.h:49
uint8_t audioObjectType
Definition M4ACommonDemuxer.h:50
uint8_t channelConfiguration
Definition M4ACommonDemuxer.h:52
uint8_t samplingRateIndex
Definition M4ACommonDemuxer.h:51
bool parse(const uint8_t *data, size_t size)
Definition M4ACommonDemuxer.h:56
Represents an individual box in the MP4 file.
Definition MP4Parser.h:50
const uint8_t * data
Pointer to box payload (not including header)
Definition MP4Parser.h:57
int available
Number of bytes available as data.
Definition MP4Parser.h:64
uint64_t file_offset
File offset where box starts.
Definition MP4Parser.h:63
size_t size
Size of payload including subboxes (not including header)
Definition MP4Parser.h:60
bool is_complete
True if the box data is complete.
Definition MP4Parser.h:65
int level
Nesting depth.
Definition MP4Parser.h:62
size_t data_size
Size of payload (not including header)
Definition MP4Parser.h:59
Shared video track configuration for muxers (MuxerAVI, MuxerMP4) - call before begin().
Definition ContainerCommon.h:11
float fps
Definition ContainerCommon.h:14
uint16_t height
Definition ContainerCommon.h:13
uint16_t width
Definition ContainerCommon.h:12
VideoFormat format
Definition ContainerCommon.h:15
Basic video information (width/height/codec/frame size), analogous to AudioInfo - common to both Demu...
Definition VideoOutput.h:49
uint32_t frame_size
Definition VideoOutput.h:62
uint32_t total_file_size
Definition VideoOutput.h:68
float fps
Definition VideoOutput.h:56
uint16_t height
Frame height in pixels.
Definition VideoOutput.h:53
uint16_t width
Frame width in pixels.
Definition VideoOutput.h:51
VideoFormat format
Video codec - VideoFormat::UNKNOWN if not (yet) determined.
Definition VideoOutput.h:58
Sound information which is available in the WAV header.
Definition CodecWAV.h:30
AudioFormat format
Definition CodecWAV.h:38
bool is_streamed
Definition CodecWAV.h:41
int block_align
Definition CodecWAV.h:40
int byte_rate
Definition CodecWAV.h:39