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;
159 box_accum.clear();
160 stsz_header_pending = true;
161 sample_buffer.clear();
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;
211 result.format = p_video_track->is_hevc_unsupported ? VideoFormat::UNKNOWN
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 }
227
237 AudioInfoFormat result(info);
238 result.format = audio_format;
239 return result;
240 }
241
245 static Vector<uint8_t> empty;
246 return p_audio_track ? p_audio_track->alacMagicCookie : empty;
247 }
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);
523 box_accum.clear();
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() {
541 box_accum.clear();
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
577 parser.setCallback(
578 "trak",
579 [](MP4Parser::Box& box, void* ref) {
580 static_cast<DemuxerMP4*>(ref)->onTrak();
581 },
582 false);
583
584 parser.setCallback(
585 "hdlr",
586 [](MP4Parser::Box& box, void* ref) {
587 static_cast<DemuxerMP4*>(ref)->onHdlr(box);
588 },
589 false);
590
591 parser.setCallback(
592 "stsd",
593 [](MP4Parser::Box& box, void* ref) {
594 static_cast<DemuxerMP4*>(ref)->onStsd(box);
595 },
596 false);
597
598 parser.setCallback(
599 "mp4a",
600 [](MP4Parser::Box& box, void* ref) {
601 static_cast<DemuxerMP4*>(ref)->onMp4a(box);
602 },
603 false);
604 parser.setCallback(
605 "alac",
606 [](MP4Parser::Box& box, void* ref) {
607 static_cast<DemuxerMP4*>(ref)->onAlac(box);
608 },
609 false);
610 parser.setCallback(
611 "ac-3",
612 [](MP4Parser::Box& box, void* ref) {
613 static_cast<DemuxerMP4*>(ref)->onAc3(box);
614 },
615 false);
616 parser.setCallback(
617 "esds",
618 [](MP4Parser::Box& box, void* ref) {
619 static_cast<DemuxerMP4*>(ref)->onEsds(box);
620 },
621 false);
622
623 parser.setCallback(
624 "avc1",
625 [](MP4Parser::Box& box, void* ref) {
626 static_cast<DemuxerMP4*>(ref)->onAvc1(box);
627 },
628 false);
629 parser.setCallback(
630 "hev1",
631 [](MP4Parser::Box& box, void* ref) {
632 static_cast<DemuxerMP4*>(ref)->onHevc(box);
633 },
634 false);
635 parser.setCallback(
636 "hvc1",
637 [](MP4Parser::Box& box, void* ref) {
638 static_cast<DemuxerMP4*>(ref)->onHevc(box);
639 },
640 false);
641 parser.setCallback(
642 "avcC",
643 [](MP4Parser::Box& box, void* ref) {
644 static_cast<DemuxerMP4*>(ref)->onAvcC(box);
645 },
646 false);
647
648 parser.setCallback(
649 "mdhd",
650 [](MP4Parser::Box& box, void* ref) {
651 static_cast<DemuxerMP4*>(ref)->onMdhd(box);
652 },
653 false);
654 parser.setCallback(
655 "stts",
656 [](MP4Parser::Box& box, void* ref) {
657 static_cast<DemuxerMP4*>(ref)->onStts(box);
658 },
659 false);
660
661 parser.setCallback(
662 "stsz",
663 [](MP4Parser::Box& box, void* ref) {
664 static_cast<DemuxerMP4*>(ref)->onStsz(box);
665 },
666 false);
667 parser.setCallback(
668 "stsc",
669 [](MP4Parser::Box& box, void* ref) {
670 static_cast<DemuxerMP4*>(ref)->onStsc(box);
671 },
672 false);
673 parser.setCallback(
674 "stco",
675 [](MP4Parser::Box& box, void* ref) {
676 static_cast<DemuxerMP4*>(ref)->onStco(box, false);
677 },
678 false);
679 parser.setCallback(
680 "co64",
681 [](MP4Parser::Box& box, void* ref) {
682 static_cast<DemuxerMP4*>(ref)->onStco(box, true);
683 },
684 false);
685
686 parser.setCallback(
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);
736 box_accum.writeArray(box.data, box.available);
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
761 if (t.spool_configured) return;
762 t.spool_configured = true;
763 delete t.sample_sizes;
765 *p_spool_factory->createSpoolStorage(t.kind, SampleTableKind::SampleSizes));
766 delete t.chunk_offsets;
768 *p_spool_factory->createSpoolStorage(t.kind, SampleTableKind::ChunkOffsets));
769 delete t.stsc;
771 *p_spool_factory->createSpoolStorage(t.kind, SampleTableKind::Stsc));
772 delete t.stts;
774 *p_spool_factory->createSpoolStorage(t.kind, SampleTableKind::Stts));
775 }
776
778 beginBoxAccum(box);
779 box_accum.writeArray(box.data, box.available);
780 if (box.is_complete && box_accum.available() >= 8) {
781 // sample entries (mp4a/alac/avc1/hev1/...) parsed generically, same
782 // mechanism as M4ACommonDemuxer::onStsd
783 parser.parseString(box_accum.data() + 8, box_accum.available() - 8,
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);
814 info.bits_per_sample = 16;
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;
846 winfo.block_align = info.channels * (info.bits_per_sample / 8);
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 }
861 current_track->aacProfile = esdsParser.audioObjectType;
862 current_track->sampleRateIdx = esdsParser.samplingRateIndex;
863 current_track->channelCfg = esdsParser.channelConfiguration;
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)) {
872 current_track->alacMagicCookie.resize(alac.data_size - 4);
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;
906 if (!current_track->is_hevc_unsupported) {
907 LOGE("HEVC (hev1/hvc1) video is not supported - only H.264 (avc1)");
908 current_track->is_hevc_unsupported = true;
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);
951 box_accum.writeArray(box.data, box.available);
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);
996 box_accum.writeArray(box.data, box.available);
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
1022 box_accum.writeArray(box.data, box.available);
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);
1040 box_accum.clearArray(12);
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)
1058 current_track->sample_sizes->setNextEntryOffset(boxPayloadFileOffset() + 12);
1059 current_track->sample_sizes->append(readU32(box_accum.data()));
1060 box_accum.clearArray(4);
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);
1076 box_accum.writeArray(box.data, box.available);
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);
1101 box_accum.writeArray(box.data, box.available);
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.
1113 current_track->chunk_offsets->setOnDiskEntrySize(is64 ? 8 : 4);
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
1120 current_track->chunk_offsets->setNextEntryOffset(boxPayloadFileOffset() + 8);
1121 current_track->chunk_offsets->append(off);
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 &&
1169 p_video_track->next_chunk_index < p_video_track->chunk_offsets->size()) {
1170 best_offset = p_video_track->chunk_offsets->get(p_video_track->next_chunk_index);
1171 next = p_video_track;
1172 }
1173 if (p_audio_track != nullptr &&
1174 p_audio_track->next_chunk_index < p_audio_track->chunk_offsets->size()) {
1175 uint64_t off = p_audio_track->chunk_offsets->get(p_audio_track->next_chunk_index);
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
1229 current_sample_track->next_sample_index++;
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) {
1273 nal_tmp.resize(t.sps_pps_annexb.size());
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 }
1393
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 {
1511 if (info.format == AudioFormat::UNKNOWN) info.format = audio_info.format;
1512 audio_info = info;
1513 has_audio = true;
1514 }
1515
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 }
1539 if (video_cfg.format != VideoFormat::H264 &&
1540 video_cfg.format != VideoFormat::MJPEG &&
1541 video_cfg.format != VideoFormat::YUV422 &&
1542 video_cfg.format != VideoFormat::RGB565 &&
1543 video_cfg.format != VideoFormat::I420) {
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;
1553 audio_timescale = has_audio ? audio_info.sample_rate : 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.
1558 audio_sample_duration = (audio_info.format == AudioFormat::PCM) ? 0 : 1024;
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 }
1590
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
1632 size_t addVideoFrame(const uint8_t* data, size_t len,
1633 bool isKeyFrame = true) override {
1634 if (!is_open) return 0;
1635 switch (video_cfg.format) {
1636 case VideoFormat::H264:
1637 break;
1638 case VideoFormat::MJPEG:
1639 return addJpegFrame(data, len);
1641 return addYUV422Frame(data, len);
1643 return addRGB565Frame(data, len);
1644 case VideoFormat::I420:
1645 return addI420Frame(data, len);
1646 default:
1647 return 0;
1648 }
1649 setVideoConfigData(data, len);
1650 if (!tryWriteMoov()) {
1651 LOGW("dropping video frame: SPS/PPS not seen yet");
1652 return 0;
1653 }
1654 nal_tmp.clear();
1655 forEachAnnexBNal(data, len, [this](const uint8_t* nal, size_t nalLen) {
1656 if (nalLen == 0) return;
1657 uint8_t nalType = nal[0] & 0x1F;
1658 if (nalType == 7 || nalType == 8) return; // SPS/PPS: already in avcC
1659 size_t off = nal_tmp.size();
1660 nal_tmp.resize(off + 4 + nalLen);
1661 uint8_t* p = nal_tmp.data() + off;
1662 p[0] = (uint8_t)(nalLen >> 24);
1663 p[1] = (uint8_t)(nalLen >> 16);
1664 p[2] = (uint8_t)(nalLen >> 8);
1665 p[3] = (uint8_t)nalLen;
1666 memcpy(p + 4, nal, nalLen);
1667 });
1668 if (nal_tmp.size() == 0) return 0;
1673 return len;
1674 }
1675
1680 size_t addJpegFrame(const uint8_t* data, size_t len) override {
1681 if (video_cfg.format != VideoFormat::MJPEG) return 0;
1682 return addRawFrame(data, len);
1683 }
1684
1693 size_t addYUV422Frame(const uint8_t* data, size_t len) override {
1695 return addRawFrame(data, len);
1696 }
1697
1704 size_t addRGB565Frame(const uint8_t* data, size_t len) override {
1706 return addRawFrame(data, len);
1707 }
1708
1720 size_t addI420Frame(const uint8_t* data, size_t len) override {
1722 return addRawFrame(data, len);
1723 }
1724
1734 size_t addAudioFrame(const uint8_t* data, size_t len) override {
1735 if (!is_open || !has_audio || len == 0) return 0;
1736 if (!moov_written) {
1737 LOGW(
1738 "dropping audio frame: moov not written yet (video SPS/PPS not "
1739 "seen)");
1740 return 0;
1741 }
1742 uint32_t duration = audio_sample_duration; // AAC: fixed 1024
1743 if (audio_info.format == AudioFormat::PCM) {
1744 int bytesPerFrame =
1745 audio_info.channels * (audio_info.bits_per_sample / 8);
1746 duration = bytesPerFrame > 0 ? (uint32_t)(len / bytesPerFrame) : 0;
1747 }
1748 writeMoofMdat(kAudioTrackId, data, len, duration,
1749 /*isKeyFrame*/ true, audio_base_time);
1750 audio_base_time += duration;
1752 return len;
1753 }
1754
1756 uint32_t videoFrameCount() { return video_frame_count; }
1758 uint32_t audioFrameCount() { return audio_frame_count; }
1759
1760 protected:
1761 static const uint32_t kVideoTrackId = 1;
1762 static const uint32_t kAudioTrackId = 2;
1763
1764 Print* p_out = nullptr;
1768
1770 bool has_audio = false;
1771 int audio_profile = 2; // AAC LC
1772
1773 bool is_open = false;
1776 bool moov_written = false;
1778 uint32_t video_timescale = 90000;
1780 uint32_t audio_timescale = 0;
1781 uint32_t audio_sample_duration = 1024;
1782
1783 uint32_t video_seq = 0;
1784 uint32_t video_base_time = 0;
1785 uint32_t audio_seq = 0;
1786 uint32_t audio_base_time = 0;
1787 uint32_t fragment_seq = 0;
1788 uint32_t video_frame_count = 0;
1789 uint32_t audio_frame_count = 0;
1790
1793
1799 void setVideoConfigData(const uint8_t* spsPpsAnnexB, size_t len) {
1800 forEachAnnexBNal(spsPpsAnnexB, len,
1801 [this](const uint8_t* nal, size_t nalLen) {
1802 if (nalLen == 0) return;
1803 uint8_t nalType = nal[0] & 0x1F;
1804 if (nalType == 7) {
1805 sps_data.resize(nalLen);
1806 memcpy(sps_data.data(), nal, nalLen);
1807 } else if (nalType == 8) {
1808 pps_data.resize(nalLen);
1809 memcpy(pps_data.data(), nal, nalLen);
1810 }
1811 });
1812 }
1813
1819 if (moov_written) return true;
1820 if (video_cfg.format == VideoFormat::H264 &&
1821 (sps_data.size() == 0 || pps_data.size() == 0)) {
1822 return false;
1823 }
1824 writeFtypMoov();
1825 moov_written = true;
1826 return true;
1827 }
1828
1830 if (video_cfg.format != expected) {
1831 LOGW("getVideoInfo().format does not match the addXxxFrame() called");
1832 }
1833 }
1834
1837 void checkRawFrame(VideoFormat expected, size_t len) {
1838 checkVideoFormat(expected);
1839 size_t expectedSize =
1840 videoFrameSizeBytes(expected, video_cfg.width, video_cfg.height);
1841 if (expectedSize > 0 && len != expectedSize) {
1842 LOGW("frame size %d does not match the expected %d bytes for %d x %d",
1843 (int)len, (int)expectedSize, (int)video_cfg.width,
1844 (int)video_cfg.height);
1845 }
1846 }
1847
1855 size_t addRawFrame(const uint8_t* data, size_t len) {
1856 if (!is_open || len == 0) return 0;
1857 if (!tryWriteMoov()) return 0;
1859 /*isKeyFrame*/ true, video_base_time);
1862 return len;
1863 }
1864
1868 template <typename F>
1869 static void forEachAnnexBNal(const uint8_t* data, size_t len, F callback) {
1870 size_t i = 0;
1871 while (i + 3 <= len) {
1872 size_t scLen = 0;
1873 if (i + 4 <= len && data[i] == 0 && data[i + 1] == 0 &&
1874 data[i + 2] == 0 && data[i + 3] == 1) {
1875 scLen = 4;
1876 } else if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
1877 scLen = 3;
1878 }
1879 if (scLen == 0) {
1880 i++;
1881 continue;
1882 }
1883 size_t nalStart = i + scLen;
1884 size_t j = nalStart;
1885 size_t nextStart = len;
1886 while (j + 3 <= len) {
1887 if (data[j] == 0 && data[j + 1] == 0 &&
1888 (data[j + 2] == 1 ||
1889 (j + 3 < len && data[j + 2] == 0 && data[j + 3] == 1))) {
1890 nextStart = j;
1891 break;
1892 }
1893 j++;
1894 }
1895 if (nextStart > nalStart) callback(data + nalStart, nextStart - nalStart);
1896 i = nextStart;
1897 }
1898 }
1899
1900 static int aacSampleRateIndex(uint32_t sampleRate) {
1901 static const uint32_t rates[13] = {96000, 88200, 64000, 48000, 44100,
1902 32000, 24000, 22050, 16000, 12000,
1903 11025, 8000, 7350};
1904 for (int i = 0; i < 13; i++)
1905 if (rates[i] == sampleRate) return i;
1906 return 4; // default: 44100
1907 }
1908
1910 size_t pos = b.beginBox("avcC");
1911 b.u8(1); // configurationVersion
1912 b.u8(sps_data[1]); // AVCProfileIndication
1913 b.u8(sps_data[2]); // profile_compatibility
1914 b.u8(sps_data[3]); // AVCLevelIndication
1915 b.u8(0xFF); // reserved(6) + lengthSizeMinusOne(2) = 4-byte lengths
1916 b.u8(0xE1); // reserved(3) + numOfSPS(5) = 1
1917 b.u16((uint16_t)sps_data.size());
1918 b.bytes(sps_data.data(), sps_data.size());
1919 b.u8(1); // numOfPPS
1920 b.u16((uint16_t)pps_data.size());
1921 b.bytes(pps_data.data(), pps_data.size());
1922 b.endBox(pos);
1923 }
1924
1931 size_t pos = b.beginBox("esds");
1932 b.u32(0); // version + flags
1933 b.u8(0x03);
1934 // size: ES_ID(2)+flags(1)+DecoderConfigDescr(15 incl. its own tag+size)
1935 // +SLConfigDescr(3 incl. its own tag+size) = 21
1936 b.u8(21);
1937 b.u16(kVideoTrackId); // ES_ID
1938 b.u8(0); // flags
1939 b.u8(0x04);
1940 b.u8(13); // size: objType(1)+streamType(1)+bufSize(3)+maxBr(4)+avgBr(4)
1941 b.u8(0x6C); // objectTypeIndication: JPEG (ISO/IEC 10918-1)
1942 b.u8((4 << 2) | 1); // streamType: visual(4), reserved bit set
1943 b.u24(0); // bufferSizeDB
1944 b.u32(0); // maxBitrate (0 = unspecified)
1945 b.u32(0); // avgBitrate
1946 b.u8(0x06);
1947 b.u8(1);
1948 b.u8(0x02);
1949 b.endBox(pos);
1950 }
1951
1953 int sampleRateIdx = aacSampleRateIndex(audio_info.sample_rate);
1954 uint8_t asc[2];
1955 asc[0] = (uint8_t)((audio_profile << 3) | (sampleRateIdx >> 1));
1956 asc[1] = (uint8_t)(((sampleRateIdx & 1) << 7) | (audio_info.channels << 3));
1957
1958 size_t pos = b.beginBox("esds");
1959 b.u32(0); // version + flags
1960 // ES_Descriptor
1961 b.u8(0x03);
1962 // size: ES_ID(2)+flags(1)+DecoderConfigDescr(19 incl. its own tag+size)
1963 // +SLConfigDescr(3 incl. its own tag+size) = 25
1964 b.u8(25);
1965 b.u16(kAudioTrackId); // ES_ID
1966 b.u8(0); // flags
1967 // DecoderConfigDescr
1968 b.u8(0x04);
1969 b.u8(
1970 17); // size:
1971 // objType(1)+streamType(1)+bufSize(3)+maxBr(4)+avgBr(4)+DecSpecificInfo(4)
1972 b.u8(0x40); // objectTypeIndication: AAC
1973 b.u8((5 << 2) | 1); // streamType: audio(5), upstream=0, reserved=1
1974 b.u24(0); // bufferSizeDB
1975 b.u32(128000); // maxBitrate
1976 b.u32(128000); // avgBitrate
1977 // DecSpecificInfo (the 2-byte AudioSpecificConfig)
1978 b.u8(0x05);
1979 b.u8(2);
1980 b.bytes(asc, 2);
1981 // SLConfigDescr
1982 b.u8(0x06);
1983 b.u8(1);
1984 b.u8(0x02);
1985 b.endBox(pos);
1986 }
1987
1989 size_t pos = b.beginBox("mvhd");
1990 b.u32(0); // version + flags
1991 b.u32(0); // creation_time
1992 b.u32(0); // modification_time
1993 b.u32(1000); // timescale
1994 b.u32(0); // duration (unknown - fragmented)
1995 b.u32(0x00010000); // rate 1.0
1996 b.u16(0x0100); // volume 1.0
1997 b.u16(0); // reserved
1998 b.u32(0);
1999 b.u32(0); // reserved[2]
2000 // unity matrix
2001 static const uint32_t identity[9] = {
2002 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000};
2003 for (int i = 0; i < 9; i++) b.u32(identity[i]);
2004 b.zeros(24); // pre_defined[6]
2005 b.u32(has_audio ? kAudioTrackId + 1 : kVideoTrackId + 1); // next_track_ID
2006 b.endBox(pos);
2007 }
2008
2009 void writeTkhd(MP4BoxWriter& b, uint32_t trackId, bool isVideo) {
2010 size_t pos = b.beginBox("tkhd");
2011 b.u32(0x000007); // version 0 + flags: enabled|in_movie|in_preview
2012 b.u32(0); // creation_time
2013 b.u32(0); // modification_time
2014 b.u32(trackId);
2015 b.u32(0); // reserved
2016 b.u32(0); // duration (unknown)
2017 b.u32(0);
2018 b.u32(0); // reserved[2]
2019 b.u16(0); // layer
2020 b.u16(0); // alternate_group
2021 b.u16(isVideo ? 0 : 0x0100); // volume
2022 b.u16(0); // reserved
2023 static const uint32_t identity[9] = {
2024 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000};
2025 for (int i = 0; i < 9; i++) b.u32(identity[i]);
2026 b.u32(isVideo ? ((uint32_t)video_cfg.width << 16) : 0);
2027 b.u32(isVideo ? ((uint32_t)video_cfg.height << 16) : 0);
2028 b.endBox(pos);
2029 }
2030
2031 void writeMdhd(MP4BoxWriter& b, uint32_t timescale) {
2032 size_t pos = b.beginBox("mdhd");
2033 b.u32(0); // version + flags
2034 b.u32(0); // creation_time
2035 b.u32(0); // modification_time
2036 b.u32(timescale);
2037 b.u32(0); // duration (unknown)
2038 b.u16(0x55C4); // language: und
2039 b.u16(0);
2040 b.endBox(pos);
2041 }
2042
2043 void writeHdlr(MP4BoxWriter& b, const char* handlerType, const char* name) {
2044 size_t pos = b.beginBox("hdlr");
2045 b.u32(0); // version + flags
2046 b.u32(0); // pre_defined
2047 b.fourcc(handlerType);
2048 b.zeros(12); // reserved
2049 b.cstr(name);
2050 b.endBox(pos);
2051 }
2052
2054 size_t pos = b.beginBox("dinf");
2055 size_t drefPos = b.beginBox("dref");
2056 b.u32(0); // version + flags
2057 b.u32(1); // entry_count
2058 size_t urlPos = b.beginBox("url ");
2059 b.u32(1); // version + flags: self-contained (data in this file)
2060 b.endBox(urlPos);
2061 b.endBox(drefPos);
2062 b.endBox(pos);
2063 }
2064
2066 size_t pos = b.beginBox("stbl");
2067 size_t stsdPos = b.beginBox("stsd");
2068 b.u32(0); // version + flags
2069 b.u32(1); // entry_count
2070 // VisualSampleEntry: same fixed 78-byte header for every fourCC below
2071 const char* fourcc = "avc1";
2072 uint16_t depth = 0x0018; // 24, nominal - not critical for playback
2073 switch (video_cfg.format) {
2074 case VideoFormat::MJPEG:
2075 fourcc = "mp4v";
2076 break;
2078 fourcc = "yuvs"; // matches what ffmpeg itself writes for YUY2-in-MOV
2079 break;
2081 fourcc = "L565"; // matches what ffmpeg itself writes for rgb565le
2082 depth = 16; // actual bit depth, unlike the nominal 24 above
2083 break;
2084 case VideoFormat::I420:
2085 fourcc = "I420"; // see addI420Frame()'s note: unverified in MP4
2086 break;
2087 default:
2088 break; // H264: 'avc1'
2089 }
2090 size_t entryPos = b.beginBox(fourcc);
2091 b.zeros(6); // reserved
2092 b.u16(1); // data_reference_index
2093 b.u16(0); // pre_defined
2094 b.u16(0); // reserved
2095 b.zeros(12); // pre_defined[3]
2096 b.u16(video_cfg.width);
2097 b.u16(video_cfg.height);
2098 b.u32(0x00480000); // horizresolution 72dpi
2099 b.u32(0x00480000); // vertresolution 72dpi
2100 b.u32(0); // reserved
2101 b.u16(1); // frame_count
2102 b.zeros(32); // compressorname
2103 b.u16(depth);
2104 b.u16(0xFFFF); // pre_defined
2105 switch (video_cfg.format) {
2106 case VideoFormat::MJPEG:
2107 writeEsdsMjpeg(b);
2108 break;
2111 case VideoFormat::I420:
2112 break; // raw formats: no child config box needed
2113 default:
2114 writeAvcC(b); // H264
2115 break;
2116 }
2117 b.endBox(entryPos);
2118 b.endBox(stsdPos);
2119 // empty sample tables - samples are described per-fragment instead
2120 size_t sttsPos = b.beginBox("stts");
2121 b.u32(0);
2122 b.u32(0);
2123 b.endBox(sttsPos);
2124 size_t stscPos = b.beginBox("stsc");
2125 b.u32(0);
2126 b.u32(0);
2127 b.endBox(stscPos);
2128 size_t stszPos = b.beginBox("stsz");
2129 b.u32(0);
2130 b.u32(0);
2131 b.u32(0);
2132 b.endBox(stszPos);
2133 size_t stcoPos = b.beginBox("stco");
2134 b.u32(0);
2135 b.u32(0);
2136 b.endBox(stcoPos);
2137 b.endBox(pos);
2138 }
2139
2145 b.zeros(6); // reserved
2146 b.u16(1); // data_reference_index
2147 b.zeros(8); // reserved[2]
2148 b.u16((uint16_t)audio_info.channels);
2149 b.u16((uint16_t)audio_info.bits_per_sample);
2150 b.u16(0); // pre_defined
2151 b.u16(0); // reserved
2152 b.u32((uint32_t)audio_info.sample_rate << 16);
2153 }
2154
2156 size_t pos = b.beginBox("stbl");
2157 size_t stsdPos = b.beginBox("stsd");
2158 b.u32(0); // version + flags
2159 b.u32(1); // entry_count
2160 if (audio_info.format == AudioFormat::PCM) {
2161 // 'sowt': little-endian signed PCM - the fixed AudioSampleEntry
2162 // header alone is a complete sample description, no child box
2163 // needed (unlike 'mp4a', which needs 'esds' for the AAC config).
2164 size_t sowtPos = b.beginBox("sowt");
2166 b.endBox(sowtPos);
2167 } else {
2168 size_t mp4aPos = b.beginBox("mp4a");
2170 writeEsds(b);
2171 b.endBox(mp4aPos);
2172 }
2173 b.endBox(stsdPos);
2174 size_t sttsPos = b.beginBox("stts");
2175 b.u32(0);
2176 b.u32(0);
2177 b.endBox(sttsPos);
2178 size_t stscPos = b.beginBox("stsc");
2179 b.u32(0);
2180 b.u32(0);
2181 b.endBox(stscPos);
2182 size_t stszPos = b.beginBox("stsz");
2183 b.u32(0);
2184 b.u32(0);
2185 b.u32(0);
2186 b.endBox(stszPos);
2187 size_t stcoPos = b.beginBox("stco");
2188 b.u32(0);
2189 b.u32(0);
2190 b.endBox(stcoPos);
2191 b.endBox(pos);
2192 }
2193
2194 void writeTrak(MP4BoxWriter& b, uint32_t trackId, bool isVideo) {
2195 size_t pos = b.beginBox("trak");
2196 writeTkhd(b, trackId, isVideo);
2197 size_t mdiaPos = b.beginBox("mdia");
2199 writeHdlr(b, isVideo ? "vide" : "soun",
2200 isVideo ? "VideoHandler" : "SoundHandler");
2201 size_t minfPos = b.beginBox("minf");
2202 if (isVideo) {
2203 size_t vmhdPos = b.beginBox("vmhd");
2204 b.u32(1); // version + flags
2205 b.zeros(8); // graphicsmode + opcolor
2206 b.endBox(vmhdPos);
2207 } else {
2208 size_t smhdPos = b.beginBox("smhd");
2209 b.u32(0); // version + flags
2210 b.u16(0); // balance
2211 b.u16(0); // reserved
2212 b.endBox(smhdPos);
2213 }
2214 writeDinf(b);
2215 if (isVideo) {
2216 writeVideoStbl(b);
2217 } else {
2218 writeAudioStbl(b);
2219 }
2220 b.endBox(minfPos);
2221 b.endBox(mdiaPos);
2222 b.endBox(pos);
2223 }
2224
2226 size_t pos = b.beginBox("mvex");
2227 size_t trexVideoPos = b.beginBox("trex");
2228 b.u32(0); // version + flags
2229 b.u32(kVideoTrackId);
2230 b.u32(1); // default_sample_description_index
2232 b.u32(0); // default_sample_size
2233 b.u32(0); // default_sample_flags
2234 b.endBox(trexVideoPos);
2235 if (has_audio) {
2236 size_t trexAudioPos = b.beginBox("trex");
2237 b.u32(0);
2238 b.u32(kAudioTrackId);
2239 b.u32(1);
2241 b.u32(0);
2242 b.u32(0);
2243 b.endBox(trexAudioPos);
2244 }
2245 b.endBox(pos);
2246 }
2247
2249 box.clear();
2250 size_t ftypPos = box.beginBox("ftyp");
2251 box.fourcc("isom");
2252 box.u32(0x200);
2253 box.fourcc("isom");
2254 box.fourcc("iso5");
2255 box.fourcc("iso6");
2256 box.fourcc("mp41");
2257 box.endBox(ftypPos);
2258
2259 size_t moovPos = box.beginBox("moov");
2260 writeMvhd(box);
2261 writeTrak(box, kVideoTrackId, true);
2262 if (has_audio) writeTrak(box, kAudioTrackId, false);
2263 writeMvex(box);
2264 box.endBox(moovPos);
2265
2266 p_out->write(box.data(), box.size());
2267 }
2268
2274 void writeMoofMdat(uint32_t trackId, const uint8_t* data, size_t len,
2275 uint32_t sampleDuration, bool isKeyFrame,
2276 uint32_t baseTime) {
2277 if (p_out == nullptr) return;
2278 box.clear();
2279 size_t moofPos = box.beginBox("moof");
2280 size_t mfhdPos = box.beginBox("mfhd");
2281 box.u32(0); // version + flags
2282 box.u32(fragment_seq);
2283 box.endBox(mfhdPos);
2284
2285 size_t trafPos = box.beginBox("traf");
2286 size_t tfhdPos = box.beginBox("tfhd");
2287 box.u32(0x020000); // version 0 + flags: default-base-is-moof
2288 box.u32(trackId);
2289 box.endBox(tfhdPos);
2290
2291 size_t tfdtPos = box.beginBox("tfdt");
2292 box.u32(0); // version 0 + flags
2293 box.u32(baseTime);
2294 box.endBox(tfdtPos);
2295
2296 bool isVideo = (trackId == kVideoTrackId);
2297 // data-offset | sample-duration | sample-size (always explicit per
2298 // fragment - relying on trex's constant default doesn't hold once a
2299 // track's per-sample duration can vary, as with PCM audio)
2300 uint32_t trunFlags = 0x000001 | 0x000100 | 0x000200;
2301 if (isVideo) trunFlags |= 0x000400; // + sample-flags
2302
2303 size_t trunPos = box.beginBox("trun");
2304 box.u32(trunFlags);
2305 box.u32(1); // sample_count
2306 size_t dataOffsetFieldPos = box.size();
2307 box.u32(0); // data_offset placeholder
2308 // per-sample fields, in the fixed order the spec mandates: duration,
2309 // size, flags
2310 box.u32(sampleDuration);
2311 box.u32((uint32_t)len);
2312 if (isVideo) {
2313 // sample_depends_on / sample_is_non_sync_sample
2314 box.u32(isKeyFrame ? 0x02000000 : 0x01010000);
2315 }
2316 box.endBox(trunPos);
2317 box.endBox(trafPos);
2318 box.endBox(moofPos);
2319
2320 uint32_t dataOffset = (uint32_t)(box.size() + 8); // + mdat header
2321 box.buffer[dataOffsetFieldPos] = (uint8_t)(dataOffset >> 24);
2322 box.buffer[dataOffsetFieldPos + 1] = (uint8_t)(dataOffset >> 16);
2323 box.buffer[dataOffsetFieldPos + 2] = (uint8_t)(dataOffset >> 8);
2324 box.buffer[dataOffsetFieldPos + 3] = (uint8_t)dataOffset;
2325
2326 p_out->write(box.data(), box.size());
2327
2328 uint8_t mdatHeader[8];
2329 uint32_t mdatSize = (uint32_t)(len + 8);
2330 mdatHeader[0] = (uint8_t)(mdatSize >> 24);
2331 mdatHeader[1] = (uint8_t)(mdatSize >> 16);
2332 mdatHeader[2] = (uint8_t)(mdatSize >> 8);
2333 mdatHeader[3] = (uint8_t)mdatSize;
2334 mdatHeader[4] = 'm';
2335 mdatHeader[5] = 'd';
2336 mdatHeader[6] = 'a';
2337 mdatHeader[7] = 't';
2338 p_out->write(mdatHeader, 8);
2339 p_out->write(data, len);
2340
2341 fragment_seq++;
2342 }
2343};
2344
2345} // namespace audio_tools
#define LOGW(...)
#define LOGI(...)
#define LOGD(...)
#define LOGE(...)
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:184
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
M4ACommonDemuxer::Codec Codec
Definition ContainerMP4.h:73
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
Common interface for muxers (MuxerAVI, MuxerMP4) that combine an already-encoded video track (and opt...
Definition ContainerCommon.h:42
StreamContentType write_stream_type
Definition ContainerMP4.h:1777
bool is_open
Definition ContainerMP4.h:1773
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:2274
void writeAudioStbl(MP4BoxWriter &b)
Definition ContainerMP4.h:2155
static int aacSampleRateIndex(uint32_t sampleRate)
Definition ContainerMP4.h:1900
uint32_t audio_base_time
Definition ContainerMP4.h:1786
size_t addVideoFrame(const uint8_t *data, size_t len, bool isKeyFrame=true) override
Definition ContainerMP4.h:1632
AudioInfoFormat audio_info
Definition ContainerMP4.h:1769
StreamContentType streamType() override
The track write() currently targets (see setStreamType()).
Definition ContainerMP4.h:1591
Vector< uint8_t > sps_data
Definition ContainerMP4.h:1766
size_t addJpegFrame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1680
uint32_t video_sample_duration
Definition ContainerMP4.h:1779
void checkVideoFormat(VideoFormat expected)
Definition ContainerMP4.h:1829
uint32_t audioFrameCount()
Number of audio frames (fragments) written so far.
Definition ContainerMP4.h:1758
void writeMdhd(MP4BoxWriter &b, uint32_t timescale)
Definition ContainerMP4.h:2031
uint32_t video_seq
Definition ContainerMP4.h:1783
void writeTkhd(MP4BoxWriter &b, uint32_t trackId, bool isVideo)
Definition ContainerMP4.h:2009
uint32_t video_timescale
Definition ContainerMP4.h:1778
void checkRawFrame(VideoFormat expected, size_t len)
Definition ContainerMP4.h:1837
void writeTrak(MP4BoxWriter &b, uint32_t trackId, bool isVideo)
Definition ContainerMP4.h:2194
bool has_audio
Definition ContainerMP4.h:1770
void setAudioProfile(int aacProfile)
Definition ContainerMP4.h:1520
void writeEsdsMjpeg(MP4BoxWriter &b)
Definition ContainerMP4.h:1930
uint32_t fragment_seq
Definition ContainerMP4.h:1787
void writeMvhd(MP4BoxWriter &b)
Definition ContainerMP4.h:1988
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:2043
Vector< uint8_t > pps_data
Definition ContainerMP4.h:1767
void writeDinf(MP4BoxWriter &b)
Definition ContainerMP4.h:2053
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:1780
size_t addAudioFrame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1734
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:1756
MuxerMP4()
Definition ContainerMP4.h:1488
size_t addRawFrame(const uint8_t *data, size_t len)
Definition ContainerMP4.h:1855
Vector< uint8_t > nal_tmp
scratch buffer for Annex-B -> AVCC conversion
Definition ContainerMP4.h:1791
size_t addYUV422Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1693
uint32_t audio_frame_count
Definition ContainerMP4.h:1789
static const uint32_t kAudioTrackId
Definition ContainerMP4.h:1762
void writeMvex(MP4BoxWriter &b)
Definition ContainerMP4.h:2225
size_t addI420Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1720
uint32_t audio_sample_duration
Definition ContainerMP4.h:1781
Print * p_out
Definition ContainerMP4.h:1764
MP4BoxWriter box
scratch buffer, reused for 'moov' and each 'moof'
Definition ContainerMP4.h:1792
void writeFtypMoov()
Definition ContainerMP4.h:2248
MuxerMP4(Print &out)
Definition ContainerMP4.h:1489
size_t addRGB565Frame(const uint8_t *data, size_t len) override
Definition ContainerMP4.h:1704
void setVideoConfigData(const uint8_t *spsPpsAnnexB, size_t len)
Definition ContainerMP4.h:1799
bool moov_written
Definition ContainerMP4.h:1776
int audio_profile
Definition ContainerMP4.h:1771
static const uint32_t kVideoTrackId
Definition ContainerMP4.h:1761
bool tryWriteMoov()
Definition ContainerMP4.h:1818
bool begin() override
Definition ContainerMP4.h:1529
static void forEachAnnexBNal(const uint8_t *data, size_t len, F callback)
Definition ContainerMP4.h:1869
void writeVideoStbl(MP4BoxWriter &b)
Definition ContainerMP4.h:2065
void writeEsds(MP4BoxWriter &b)
Definition ContainerMP4.h:1952
uint32_t video_frame_count
Definition ContainerMP4.h:1788
uint32_t audio_seq
Definition ContainerMP4.h:1785
const char * mimeVideo() override
Definition ContainerMP4.h:1491
void writeAudioSampleEntryHeader(MP4BoxWriter &b)
Definition ContainerMP4.h:2144
void writeAvcC(MP4BoxWriter &b)
Definition ContainerMP4.h:1909
MuxerVideoConfig getVideoInfo() override
Provides the video track configuration.
Definition ContainerMP4.h:1500
uint32_t video_base_time
Definition ContainerMP4.h:1784
MuxerVideoConfig video_cfg
Definition ContainerMP4.h:1765
Definition CodecOpusOgg.h:57
Sequential-access storage for one MP4 sample table (e.g. stsz sample sizes, stco chunk offsets,...
Definition SampleTableStore.h:61
Minimal seek+read interface a SourceSeekSampleTableStore needs from "the original MP4 source" - kept ...
Definition SampleTableStore.h:168
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:194
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
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Abstract class for video playback. This class is used to assemble a complete video frame in memory....
Definition VideoOutput.h:107
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
AudioInfo info
Definition CodecOpusOgg.h:80
void notifyAudioChange(AudioInfo info)
Definition CodecOpusOgg.h:180
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
@ MJPEG
Definition VideoOutput.h:31
@ RGB565
Definition VideoOutput.h:35
@ UNKNOWN
Definition VideoOutput.h:40
@ H264
Definition VideoOutput.h:30
@ I420
Definition VideoOutput.h:38
@ YUV422
Definition VideoOutput.h:34
@ Audio
Definition Video.h:21
@ Video
Definition Video.h:21
@ ALAC
Definition AudioFormat.h:250
@ PCM
Definition AudioFormat.h:23
@ AAC
Definition AudioFormat.h:251
@ UNKNOWN
Definition AudioFormat.h:22
@ AC3
Definition AudioFormat.h:115
@ Stts
Definition SampleTableStore.h:523
@ SampleSizes
Definition SampleTableStore.h:523
@ Stsc
Definition SampleTableStore.h:523
@ ChunkOffsets
Definition SampleTableStore.h:523
@ Unknown
Definition SampleTableStore.h:46
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 "wavcode"): identifies the codec (PCM,...
Definition AudioFormat.h:392
AudioFormat format
Definition AudioFormat.h:401
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
TrackKind Kind
Definition ContainerMP4.h:374
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
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