1use std::io::Write;
33
34const TIMESCALE: u32 = 90_000;
36
37const DEFAULT_LAST_DURATION: u32 = TIMESCALE / 30;
40
41pub const OPUS_TIMESCALE: u32 = 48_000;
43const AUDIO_FLUSH_SAMPLES: usize = 25;
46
47pub fn opus_packet_samples(packet: &[u8]) -> u32 {
50 let Some(&toc) = packet.first() else { return 0 };
51 let config = toc >> 3;
52 let tenths_ms = match config {
53 0..=11 => [100, 200, 400, 600][(config % 4) as usize],
54 12..=15 => [100, 200][(config % 2) as usize],
55 _ => [25, 50, 100, 200][(config % 4) as usize],
56 };
57 let frames = match toc & 3 {
58 0 => 1,
59 1 | 2 => 2,
60 _ => packet.get(1).map_or(0, |&b| (b & 0x3f) as u32),
61 };
62 frames * tenths_ms * OPUS_TIMESCALE / 10_000
63}
64
65pub fn split_annexb(data: &[u8]) -> Vec<&[u8]> {
68 let mut nals = Vec::new();
69 let mut i = 0usize;
70 let mut nal_start: Option<usize> = None;
71 while i + 2 < data.len() {
72 if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
73 let code_start = if i > 0 && data[i - 1] == 0 { i - 1 } else { i };
74 if let Some(s) = nal_start
75 && code_start > s
76 {
77 nals.push(&data[s..code_start]);
78 }
79 i += 3;
80 nal_start = Some(i);
81 } else if data[i + 2] == 0 {
82 i += 1;
84 } else {
85 i += 3;
86 }
87 }
88 if let Some(s) = nal_start
89 && data.len() > s
90 {
91 nals.push(&data[s..]);
92 }
93 nals
94}
95
96fn unescape_rbsp(data: &[u8]) -> Vec<u8> {
98 let mut out = Vec::with_capacity(data.len());
99 let mut zeros = 0u32;
100 for &b in data {
101 if zeros >= 2 && b == 3 {
102 zeros = 0;
103 continue;
104 }
105 if b == 0 {
106 zeros += 1;
107 } else {
108 zeros = 0;
109 }
110 out.push(b);
111 }
112 out
113}
114
115struct BitReader<'a> {
117 data: &'a [u8],
118 pos: usize,
119}
120
121impl<'a> BitReader<'a> {
122 fn new(data: &'a [u8]) -> Self {
123 Self { data, pos: 0 }
124 }
125
126 fn bit(&mut self) -> Option<u32> {
127 let byte = *self.data.get(self.pos / 8)?;
128 let bit = (byte >> (7 - (self.pos % 8))) & 1;
129 self.pos += 1;
130 Some(bit as u32)
131 }
132
133 fn bits(&mut self, n: u32) -> Option<u32> {
134 let mut v = 0u32;
135 for _ in 0..n {
136 v = (v << 1) | self.bit()?;
137 }
138 Some(v)
139 }
140
141 fn ue(&mut self) -> Option<u32> {
143 let mut zeros = 0u32;
144 while self.bit()? == 0 {
145 zeros += 1;
146 if zeros > 31 {
147 return None;
148 }
149 }
150 let rest = self.bits(zeros)?;
151 Some((1u32 << zeros) - 1 + rest)
152 }
153
154 fn se(&mut self) -> Option<i32> {
155 let k = self.ue()? as i64;
156 Some(if k % 2 == 0 {
157 -(k / 2) as i32
158 } else {
159 ((k + 1) / 2) as i32
160 })
161 }
162}
163
164pub fn parse_sps_dimensions(sps_nal: &[u8]) -> Option<(u32, u32)> {
171 if sps_nal.len() < 4 || sps_nal[0] & 0x1f != 7 {
172 return None;
173 }
174 let profile_idc = sps_nal[1];
175 let rbsp = unescape_rbsp(&sps_nal[4..]);
176 let mut r = BitReader::new(&rbsp);
177 r.ue()?;
179
180 let mut chroma_format_idc = 1u32;
181 if matches!(
182 profile_idc,
183 100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
184 ) {
185 chroma_format_idc = r.ue()?;
186 if chroma_format_idc == 3 {
187 r.bit()?;
189 }
190 r.ue()?;
192 r.ue()?;
193 r.bit()?;
194 if r.bit()? == 1 {
195 let lists = if chroma_format_idc == 3 { 12 } else { 8 };
197 for i in 0..lists {
198 if r.bit()? == 1 {
199 let size = if i < 6 { 16 } else { 64 };
200 let mut next_scale = 8i32;
201 let mut last_scale = 8i32;
202 for _ in 0..size {
203 if next_scale != 0 {
204 let delta = r.se()?;
205 next_scale = (last_scale + delta + 256) % 256;
206 }
207 if next_scale != 0 {
208 last_scale = next_scale;
209 }
210 }
211 }
212 }
213 }
214 }
215
216 r.ue()?;
218 let pic_order_cnt_type = r.ue()?;
219 if pic_order_cnt_type == 0 {
220 r.ue()?;
222 } else if pic_order_cnt_type == 1 {
223 r.bit()?;
226 r.se()?;
227 r.se()?;
228 let n = r.ue()?;
229 for _ in 0..n {
230 r.se()?;
231 }
232 }
233 r.ue()?;
235 r.bit()?;
236 let pic_width_in_mbs = r.ue()? + 1;
237 let pic_height_in_map_units = r.ue()? + 1;
238 let frame_mbs_only = r.bit()?;
239 if frame_mbs_only == 0 {
240 r.bit()?;
242 }
243 r.bit()?;
245
246 let (mut crop_l, mut crop_r, mut crop_t, mut crop_b) = (0u32, 0u32, 0u32, 0u32);
247 if r.bit()? == 1 {
248 crop_l = r.ue()?;
249 crop_r = r.ue()?;
250 crop_t = r.ue()?;
251 crop_b = r.ue()?;
252 }
253
254 let (sub_w, sub_h) = match chroma_format_idc {
255 0 | 3 => (1u32, 1u32),
256 2 => (2, 1),
257 _ => (2, 2),
258 };
259 let crop_unit_x = sub_w;
260 let crop_unit_y = sub_h * (2 - frame_mbs_only);
261 let width = pic_width_in_mbs * 16 - crop_unit_x * (crop_l + crop_r);
262 let height =
263 (2 - frame_mbs_only) * pic_height_in_map_units * 16 - crop_unit_y * (crop_t + crop_b);
264 Some((width, height))
265}
266
267fn mk_box(fourcc: &[u8; 4], payload: &[u8]) -> Vec<u8> {
268 let mut b = Vec::with_capacity(8 + payload.len());
269 b.extend_from_slice(&((8 + payload.len()) as u32).to_be_bytes());
270 b.extend_from_slice(fourcc);
271 b.extend_from_slice(payload);
272 b
273}
274
275fn mk_full_box(fourcc: &[u8; 4], version: u8, flags: u32, payload: &[u8]) -> Vec<u8> {
276 let mut p = Vec::with_capacity(4 + payload.len());
277 p.push(version);
278 p.extend_from_slice(&flags.to_be_bytes()[1..]);
279 p.extend_from_slice(payload);
280 mk_box(fourcc, &p)
281}
282
283const MATRIX_IDENTITY: [u8; 36] = {
286 let mut m = [0u8; 36];
287 m[1] = 0x01;
288 m[17] = 0x01;
289 m[32] = 0x40;
290 m
291};
292
293pub struct TrackConfig {
296 pub sample_entry: Vec<u8>,
297 pub width: u32,
298 pub height: u32,
299}
300
301pub struct AudioTrackConfig {
303 pub sample_entry: Vec<u8>,
304}
305
306pub fn opus_sample_entry(head: &super::ogg::OpusHead) -> Vec<u8> {
308 let mut entry_p = Vec::new();
311 entry_p.extend_from_slice(&[0u8; 6]);
312 entry_p.extend_from_slice(&1u16.to_be_bytes());
313 entry_p.extend_from_slice(&[0u8; 8]);
314 entry_p.extend_from_slice(&(head.channels as u16).to_be_bytes());
315 entry_p.extend_from_slice(&16u16.to_be_bytes());
316 entry_p.extend_from_slice(&[0u8; 4]);
317 entry_p.extend_from_slice(&(OPUS_TIMESCALE << 16).to_be_bytes());
318 let mut dops_p = vec![0u8, head.channels];
322 dops_p.extend_from_slice(&head.pre_skip.to_be_bytes());
323 dops_p.extend_from_slice(&head.input_sample_rate.to_be_bytes());
324 dops_p.extend_from_slice(&head.output_gain.to_be_bytes());
325 dops_p.push(head.mapping_family);
326 dops_p.extend_from_slice(&head.mapping);
327 entry_p.extend_from_slice(&mk_box(b"dOps", &dops_p));
328 mk_box(b"Opus", &entry_p)
329}
330
331struct PendingSample {
333 data: Vec<u8>,
334 sync: bool,
335 dts: u64,
336}
337
338#[derive(Clone, Copy, Debug, Default)]
340pub struct Mp4Stats {
341 pub samples: u64,
342 pub sync_samples: u64,
343 pub audio_samples: u64,
344 pub bytes: u64,
345 pub duration_us: u64,
347}
348
349pub struct FragmentWriter<W: Write> {
353 out: W,
354 seq: u32,
355 wrote_init: bool,
356 pending: Option<PendingSample>,
357 last_dts: Option<u64>,
358 durations: Vec<u32>,
362 audio: Option<AudioTrackConfig>,
363 audio_pending: Vec<(Vec<u8>, u32)>,
365 audio_batch_dts: u64,
366 audio_next_dts: u64,
367 stats: Mp4Stats,
368}
369
370fn dinf() -> Vec<u8> {
372 let url = mk_full_box(b"url ", 0, 1, &[]);
373 let mut dref_p = 1u32.to_be_bytes().to_vec();
374 dref_p.extend_from_slice(&url);
375 mk_box(b"dinf", &mk_full_box(b"dref", 0, 0, &dref_p))
376}
377
378fn stbl(sample_entry: &[u8]) -> Vec<u8> {
380 let mut stsd_p = 1u32.to_be_bytes().to_vec();
381 stsd_p.extend_from_slice(sample_entry);
382 let stsd = mk_full_box(b"stsd", 0, 0, &stsd_p);
383 let stts = mk_full_box(b"stts", 0, 0, &0u32.to_be_bytes());
384 let stsc = mk_full_box(b"stsc", 0, 0, &0u32.to_be_bytes());
385 let stsz = mk_full_box(b"stsz", 0, 0, &[0u8; 8]);
386 let stco = mk_full_box(b"stco", 0, 0, &0u32.to_be_bytes());
387 mk_box(b"stbl", &[stsd, stts, stsc, stsz, stco].concat())
388}
389
390enum TrakMedia {
393 Video { width: u32, height: u32 },
394 Audio { volume: u16 },
395}
396
397fn trak(
400 track_id: u32,
401 timescale: u32,
402 handler: &[u8; 4],
403 media_header: Vec<u8>,
404 sample_entry: &[u8],
405 media: TrakMedia,
406) -> Vec<u8> {
407 let (width, height, volume) = match media {
408 TrakMedia::Video { width, height } => (width, height, 0),
409 TrakMedia::Audio { volume } => (0, 0, volume),
410 };
411 let mut tkhd_p = Vec::new();
415 tkhd_p.extend_from_slice(&[0u8; 8]);
416 tkhd_p.extend_from_slice(&track_id.to_be_bytes());
417 tkhd_p.extend_from_slice(&[0u8; 4]);
418 tkhd_p.extend_from_slice(&0u32.to_be_bytes());
419 tkhd_p.extend_from_slice(&[0u8; 12]);
420 tkhd_p.extend_from_slice(&volume.to_be_bytes());
421 tkhd_p.extend_from_slice(&[0u8; 2]);
422 tkhd_p.extend_from_slice(&MATRIX_IDENTITY);
423 tkhd_p.extend_from_slice(&(width << 16).to_be_bytes());
424 tkhd_p.extend_from_slice(&(height << 16).to_be_bytes());
425 let tkhd = mk_full_box(b"tkhd", 0, 3, &tkhd_p);
426
427 let mut mdhd_p = Vec::new();
430 mdhd_p.extend_from_slice(&[0u8; 8]);
431 mdhd_p.extend_from_slice(×cale.to_be_bytes());
432 mdhd_p.extend_from_slice(&0u32.to_be_bytes());
433 mdhd_p.extend_from_slice(&0x55c4u16.to_be_bytes());
434 mdhd_p.extend_from_slice(&[0u8; 2]);
435 let mdhd = mk_full_box(b"mdhd", 0, 0, &mdhd_p);
436
437 let mut hdlr_p = Vec::new();
439 hdlr_p.extend_from_slice(&[0u8; 4]);
440 hdlr_p.extend_from_slice(handler);
441 hdlr_p.extend_from_slice(&[0u8; 12]);
442 hdlr_p.extend_from_slice(b"pixelflux\0");
443 let hdlr = mk_full_box(b"hdlr", 0, 0, &hdlr_p);
444
445 let minf = mk_box(
446 b"minf",
447 &[media_header, dinf(), stbl(sample_entry)].concat(),
448 );
449 let mdia = mk_box(b"mdia", &[mdhd, hdlr, minf].concat());
450 mk_box(b"trak", &[tkhd, mdia].concat())
451}
452
453fn trex(track_id: u32, default_duration: u32) -> Vec<u8> {
456 let mut trex_p = Vec::new();
457 trex_p.extend_from_slice(&track_id.to_be_bytes());
458 trex_p.extend_from_slice(&1u32.to_be_bytes());
459 trex_p.extend_from_slice(&default_duration.to_be_bytes());
460 trex_p.extend_from_slice(&[0u8; 8]);
461 mk_full_box(b"trex", 0, 0, &trex_p)
462}
463
464impl<W: Write> FragmentWriter<W> {
465 pub fn new(out: W) -> Self {
466 Self {
467 out,
468 seq: 0,
469 wrote_init: false,
470 pending: None,
471 last_dts: None,
472 durations: Vec::new(),
473 audio: None,
474 audio_pending: Vec::new(),
475 audio_batch_dts: 0,
476 audio_next_dts: 0,
477 stats: Mp4Stats::default(),
478 }
479 }
480
481 pub fn with_audio(mut self, cfg: AudioTrackConfig) -> Self {
483 self.audio = Some(cfg);
484 self
485 }
486
487 pub fn init_written(&self) -> bool {
488 self.wrote_init
489 }
490
491 pub fn stats(&self) -> Mp4Stats {
494 self.stats
495 }
496
497 pub fn write_init(&mut self, cfg: &TrackConfig) -> std::io::Result<()> {
501 let mut ftyp_p = Vec::new();
502 ftyp_p.extend_from_slice(b"isom");
503 ftyp_p.extend_from_slice(&0x200u32.to_be_bytes());
504 for brand in [b"isom", b"iso5", b"iso6", b"avc1", b"mp41"] {
505 ftyp_p.extend_from_slice(brand);
506 }
507 let ftyp = mk_box(b"ftyp", &ftyp_p);
508
509 let mut mvhd_p = Vec::new();
513 mvhd_p.extend_from_slice(&[0u8; 8]);
514 mvhd_p.extend_from_slice(&TIMESCALE.to_be_bytes());
515 mvhd_p.extend_from_slice(&0u32.to_be_bytes());
516 mvhd_p.extend_from_slice(&0x00010000u32.to_be_bytes());
517 mvhd_p.extend_from_slice(&0x0100u16.to_be_bytes());
518 mvhd_p.extend_from_slice(&[0u8; 10]);
519 mvhd_p.extend_from_slice(&MATRIX_IDENTITY);
520 mvhd_p.extend_from_slice(&[0u8; 24]);
521 mvhd_p.extend_from_slice(&(if self.audio.is_some() { 3u32 } else { 2u32 }).to_be_bytes());
522 let mvhd = mk_full_box(b"mvhd", 0, 0, &mvhd_p);
523
524 let vmhd = mk_full_box(b"vmhd", 0, 1, &[0u8; 8]);
525 let mut traks = trak(
526 1,
527 TIMESCALE,
528 b"vide",
529 vmhd,
530 &cfg.sample_entry,
531 TrakMedia::Video {
532 width: cfg.width,
533 height: cfg.height,
534 },
535 );
536 let mut mvex_p = trex(1, 0);
537 if let Some(audio) = &self.audio {
538 let smhd = mk_full_box(b"smhd", 0, 0, &[0u8; 4]);
539 traks.extend(trak(
540 2,
541 OPUS_TIMESCALE,
542 b"soun",
543 smhd,
544 &audio.sample_entry,
545 TrakMedia::Audio { volume: 0x0100 },
546 ));
547 mvex_p.extend(trex(2, 0));
548 }
549 let mvex = mk_box(b"mvex", &mvex_p);
550
551 let moov = mk_box(b"moov", &[mvhd, traks, mvex].concat());
552
553 self.out.write_all(&ftyp)?;
554 self.out.write_all(&moov)?;
555 self.stats.bytes += (ftyp.len() + moov.len()) as u64;
556 self.wrote_init = true;
557 Ok(())
558 }
559
560 pub fn push_sample(&mut self, data: Vec<u8>, sync: bool, pts_us: u64) -> std::io::Result<()> {
565 let mut dts = pts_us * (TIMESCALE as u64 / 1000) / 1000;
566 if let Some(last) = self.last_dts
567 && dts <= last
568 {
569 dts = last + 1;
570 }
571 self.last_dts = Some(dts);
572 if let Some(prev) = self.pending.take() {
573 let duration = (dts - prev.dts).min(u32::MAX as u64) as u32;
574 self.durations.push(duration);
575 self.write_fragment(&prev, duration)?;
576 }
577 self.pending = Some(PendingSample { data, sync, dts });
578 Ok(())
579 }
580
581 fn median_duration(&self) -> u32 {
584 if self.durations.is_empty() {
585 return DEFAULT_LAST_DURATION;
586 }
587 let mut sorted = self.durations.clone();
588 sorted.sort_unstable();
589 sorted[sorted.len() / 2]
590 }
591
592 fn write_fragment(&mut self, s: &PendingSample, duration: u32) -> std::io::Result<()> {
593 self.seq += 1;
594
595 let mut tfhd_p = Vec::new();
598 tfhd_p.extend_from_slice(&1u32.to_be_bytes());
599 let tfhd = mk_full_box(b"tfhd", 0, 0x020000, &tfhd_p);
600
601 let mut tfdt_p = Vec::new();
602 tfdt_p.extend_from_slice(&s.dts.to_be_bytes());
603 let tfdt = mk_full_box(b"tfdt", 1, 0, &tfdt_p);
604
605 let sample_flags: u32 = if s.sync { 0x0200_0000 } else { 0x0101_0000 };
607 let mut trun_p = Vec::new();
611 trun_p.extend_from_slice(&1u32.to_be_bytes());
612 trun_p.extend_from_slice(&0i32.to_be_bytes());
613 trun_p.extend_from_slice(&duration.to_be_bytes());
614 trun_p.extend_from_slice(&(s.data.len() as u32).to_be_bytes());
615 trun_p.extend_from_slice(&sample_flags.to_be_bytes());
616 let mut trun = mk_full_box(b"trun", 0, 0x000701, &trun_p);
617
618 let traf_len = 8 + tfhd.len() + tfdt.len() + trun.len();
619 let moof_len = 8 + 16 + traf_len;
621 let data_offset = (moof_len + 8) as i32;
623 let off_pos = trun.len() - trun_p.len() + 4;
625 trun[off_pos..off_pos + 4].copy_from_slice(&data_offset.to_be_bytes());
626
627 let mfhd = mk_full_box(b"mfhd", 0, 0, &self.seq.to_be_bytes());
628 let traf = mk_box(b"traf", &[tfhd, tfdt, trun].concat());
629 let moof = mk_box(b"moof", &[mfhd, traf].concat());
630 debug_assert_eq!(moof.len(), moof_len);
631
632 self.out.write_all(&moof)?;
633 self.out
634 .write_all(&((8 + s.data.len()) as u32).to_be_bytes())?;
635 self.out.write_all(b"mdat")?;
636 self.out.write_all(&s.data)?;
637 self.out.flush()?;
638
639 self.stats.samples += 1;
640 if s.sync {
641 self.stats.sync_samples += 1;
642 }
643 self.stats.bytes += (moof.len() + 8 + s.data.len()) as u64;
644 self.stats.duration_us = self
645 .stats
646 .duration_us
647 .max((s.dts + duration as u64) * 1000 / (TIMESCALE as u64 / 1000));
648 Ok(())
649 }
650
651 pub fn push_audio(&mut self, data: Vec<u8>, dts: u64) -> std::io::Result<()> {
655 if self.audio.is_none() {
656 return Ok(());
657 }
658 let samples = opus_packet_samples(&data);
659 if !self.audio_pending.is_empty() && dts != self.audio_next_dts {
660 self.flush_audio()?;
661 }
662 if self.audio_pending.is_empty() {
663 self.audio_batch_dts = dts;
664 }
665 self.audio_pending.push((data, samples));
666 self.audio_next_dts = dts + samples as u64;
667 if self.audio_pending.len() >= AUDIO_FLUSH_SAMPLES {
668 self.flush_audio()?;
669 }
670 Ok(())
671 }
672
673 fn flush_audio(&mut self) -> std::io::Result<()> {
676 if self.audio_pending.is_empty() {
677 return Ok(());
678 }
679 self.seq += 1;
680 let samples = std::mem::take(&mut self.audio_pending);
681
682 let mut tfhd_p = 2u32.to_be_bytes().to_vec();
685 tfhd_p.extend_from_slice(&0x0200_0000u32.to_be_bytes());
686 let tfhd = mk_full_box(b"tfhd", 0, 0x020020, &tfhd_p);
687 let tfdt = mk_full_box(b"tfdt", 1, 0, &self.audio_batch_dts.to_be_bytes());
688 let mut trun_p = Vec::new();
692 trun_p.extend_from_slice(&(samples.len() as u32).to_be_bytes());
693 trun_p.extend_from_slice(&0i32.to_be_bytes());
694 for (s, duration) in &samples {
695 trun_p.extend_from_slice(&duration.to_be_bytes());
696 trun_p.extend_from_slice(&(s.len() as u32).to_be_bytes());
697 }
698 let mut trun = mk_full_box(b"trun", 0, 0x000301, &trun_p);
699
700 let traf_len = 8 + tfhd.len() + tfdt.len() + trun.len();
701 let moof_len = 8 + 16 + traf_len;
702 let data_offset = (moof_len + 8) as i32;
703 let off_pos = trun.len() - trun_p.len() + 4;
704 trun[off_pos..off_pos + 4].copy_from_slice(&data_offset.to_be_bytes());
705
706 let mfhd = mk_full_box(b"mfhd", 0, 0, &self.seq.to_be_bytes());
707 let traf = mk_box(b"traf", &[tfhd, tfdt, trun].concat());
708 let moof = mk_box(b"moof", &[mfhd, traf].concat());
709 debug_assert_eq!(moof.len(), moof_len);
710
711 let data_len: usize = samples.iter().map(|(s, _)| s.len()).sum();
712 self.out.write_all(&moof)?;
713 self.out.write_all(&((8 + data_len) as u32).to_be_bytes())?;
714 self.out.write_all(b"mdat")?;
715 for (s, _) in &samples {
716 self.out.write_all(s)?;
717 }
718 self.out.flush()?;
719 self.stats.audio_samples += samples.len() as u64;
720 self.stats.bytes += (moof.len() + 8 + data_len) as u64;
721 let end = self.audio_batch_dts + samples.iter().map(|(_, d)| *d as u64).sum::<u64>();
722 self.stats.duration_us = self
723 .stats
724 .duration_us
725 .max(end * 1_000_000 / OPUS_TIMESCALE as u64);
726 Ok(())
727 }
728
729 pub fn finish(mut self) -> std::io::Result<Mp4Stats> {
733 self.flush_audio()?;
734 if let Some(prev) = self.pending.take() {
735 let d = self.median_duration();
736 self.write_fragment(&prev, d)?;
737 }
738 self.out.flush()?;
739 Ok(self.stats)
740 }
741}
742
743pub struct H264SampleBuilder {
746 sps: Option<Vec<u8>>,
747 pps: Option<Vec<u8>>,
748}
749
750pub struct BuiltSample {
752 pub data: Vec<u8>,
753 pub sync: bool,
754}
755
756impl Default for H264SampleBuilder {
757 fn default() -> Self {
758 Self::new()
759 }
760}
761
762impl H264SampleBuilder {
763 pub fn new() -> Self {
764 Self {
765 sps: None,
766 pps: None,
767 }
768 }
769
770 pub fn have_parameter_sets(&self) -> bool {
771 self.sps.is_some() && self.pps.is_some()
772 }
773
774 pub fn build_sample(&mut self, annexb: &[u8]) -> Option<BuiltSample> {
778 let nals = split_annexb(annexb);
779 let mut data = Vec::with_capacity(annexb.len() + 8);
780 let mut sync = false;
781 let mut has_slice = false;
782 for nal in nals {
783 if nal.is_empty() {
784 continue;
785 }
786 match nal[0] & 0x1f {
787 7 => {
788 if self.sps.as_deref() != Some(nal) {
789 self.sps = Some(nal.to_vec());
790 }
791 }
792 8 => {
793 if self.pps.as_deref() != Some(nal) {
794 self.pps = Some(nal.to_vec());
795 }
796 }
797 5 => {
798 sync = true;
799 has_slice = true;
800 }
801 1 => has_slice = true,
802 _ => {}
803 }
804 data.extend_from_slice(&(nal.len() as u32).to_be_bytes());
805 data.extend_from_slice(nal);
806 }
807 if !has_slice {
808 return None;
809 }
810 Some(BuiltSample { data, sync })
811 }
812
813 pub fn track_config(&self) -> Option<TrackConfig> {
816 let sps = self.sps.as_deref()?;
817 let pps = self.pps.as_deref()?;
818 let (width, height) = parse_sps_dimensions(sps)?;
819
820 let mut avcc_p = vec![1, sps[1], sps[2], sps[3], 0xff, 0xe1];
825 avcc_p.extend_from_slice(&(sps.len() as u16).to_be_bytes());
826 avcc_p.extend_from_slice(sps);
827 avcc_p.push(1);
829 avcc_p.extend_from_slice(&(pps.len() as u16).to_be_bytes());
830 avcc_p.extend_from_slice(pps);
831 let avcc = mk_box(b"avcC", &avcc_p);
832
833 let mut entry_p = Vec::new();
837 entry_p.extend_from_slice(&[0u8; 6]);
838 entry_p.extend_from_slice(&1u16.to_be_bytes());
839 entry_p.extend_from_slice(&[0u8; 16]);
840 entry_p.extend_from_slice(&(width as u16).to_be_bytes());
841 entry_p.extend_from_slice(&(height as u16).to_be_bytes());
842 entry_p.extend_from_slice(&0x0048_0000u32.to_be_bytes());
843 entry_p.extend_from_slice(&0x0048_0000u32.to_be_bytes());
844 entry_p.extend_from_slice(&[0u8; 4]);
845 entry_p.extend_from_slice(&1u16.to_be_bytes());
846 entry_p.extend_from_slice(&[0u8; 32]);
847 entry_p.extend_from_slice(&0x0018u16.to_be_bytes());
848 entry_p.extend_from_slice(&(-1i16).to_be_bytes());
849 entry_p.extend_from_slice(&avcc);
850 let entry = mk_box(b"avc1", &entry_p);
851
852 Some(TrackConfig {
853 sample_entry: entry,
854 width,
855 height,
856 })
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863
864 const SPS_HIGH_1284X722: &str = "67640020acd9405105de788c0440000003004000000f03c60c6580";
866 const SPS_BASE_640X360: &str = "6742c01ed900a02ff970110000030001000003003c0f162e48";
868 const SPS_444_1920X1080: &str = "67f40028919b280f0044fc4e0220000003002000000781e30632c0";
870
871 fn hex(s: &str) -> Vec<u8> {
872 (0..s.len())
873 .step_by(2)
874 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
875 .collect()
876 }
877
878 #[test]
880 fn split_annexb_handles_mixed_start_codes() {
881 let mut data = Vec::new();
882 data.extend_from_slice(&[0, 0, 0, 1, 0x67, 0xAA, 0xBB]);
883 data.extend_from_slice(&[0, 0, 1, 0x68, 0xCC]);
884 data.extend_from_slice(&[0, 0, 0, 1, 0x65, 0x00, 0x00, 0x03, 0x01, 0xDD]);
885 let nals = split_annexb(&data);
886 assert_eq!(nals.len(), 3);
887 assert_eq!(nals[0], &[0x67, 0xAA, 0xBB]);
888 assert_eq!(nals[1], &[0x68, 0xCC]);
889 assert_eq!(nals[2], &[0x65, 0x00, 0x00, 0x03, 0x01, 0xDD]);
890 }
891
892 #[test]
896 fn split_annexb_emits_no_empty_nals() {
897 assert!(split_annexb(&[]).is_empty());
898 assert!(split_annexb(&[0x67, 0xAA, 0xBB]).is_empty());
899 assert!(split_annexb(&[0, 0, 0, 1]).is_empty());
900 assert_eq!(
901 split_annexb(&[0, 0, 1, 0x68, 0, 0, 0, 1]),
902 vec![&[0x68u8][..]]
903 );
904 }
905
906 #[test]
909 fn sps_dimensions_across_profiles() {
910 assert_eq!(
911 parse_sps_dimensions(&hex(SPS_HIGH_1284X722)),
912 Some((1284, 722))
913 );
914 assert_eq!(
915 parse_sps_dimensions(&hex(SPS_BASE_640X360)),
916 Some((640, 360))
917 );
918 assert_eq!(
919 parse_sps_dimensions(&hex(SPS_444_1920X1080)),
920 Some((1920, 1080))
921 );
922 }
923
924 #[test]
927 fn annexb_to_avcc_sample() {
928 let sps = hex(SPS_BASE_640X360);
929 let mut au = Vec::new();
930 au.extend_from_slice(&[0, 0, 0, 1]);
931 au.extend_from_slice(&sps);
932 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
933 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1, 2, 3, 4]);
934
935 let mut b = H264SampleBuilder::new();
936 assert!(
937 b.build_sample(&[0u8, 0, 0, 1, 0x67, 0x42, 0xc0, 0x1e, 0xd9])
938 .is_none()
939 );
940 let s = b.build_sample(&au).expect("IDR AU builds a sample");
941 assert!(s.sync);
942 assert!(b.have_parameter_sets());
943 let mut off = 0usize;
945 let mut sizes = Vec::new();
946 while off < s.data.len() {
947 let n = u32::from_be_bytes(s.data[off..off + 4].try_into().unwrap()) as usize;
948 sizes.push(n);
949 off += 4 + n;
950 }
951 assert_eq!(off, s.data.len());
952 assert_eq!(sizes, vec![sps.len(), 4, 5]);
953
954 let p = b.build_sample(&[0u8, 0, 0, 1, 0x41, 9, 9]).unwrap();
955 assert!(!p.sync);
956 }
957
958 fn top_level(buf: &[u8]) -> Vec<(String, Vec<u8>)> {
959 let mut out = Vec::new();
960 let mut at = 0;
961 while at + 8 <= buf.len() {
962 let size = u32::from_be_bytes(buf[at..at + 4].try_into().unwrap()) as usize;
963 out.push((
964 String::from_utf8_lossy(&buf[at + 4..at + 8]).into_owned(),
965 buf[at + 8..at + size].to_vec(),
966 ));
967 at += size;
968 }
969 assert_eq!(at, buf.len(), "boxes tile the buffer");
970 out
971 }
972
973 fn count(haystack: &[u8], needle: &[u8]) -> usize {
974 haystack
975 .windows(needle.len())
976 .filter(|w| *w == needle)
977 .count()
978 }
979
980 fn idr_au() -> Vec<u8> {
981 let mut au = vec![0, 0, 0, 1];
982 au.extend_from_slice(&hex(SPS_BASE_640X360));
983 au.extend_from_slice(&[
984 0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80, 0, 0, 0, 1, 0x65, 1, 2, 3, 4,
985 ]);
986 au
987 }
988
989 #[test]
992 fn opus_packet_durations() {
993 assert_eq!(opus_packet_samples(&[0xfc]), 960);
994 assert_eq!(opus_packet_samples(&[0xe0]), 120);
995 assert_eq!(opus_packet_samples(&[0x18]), 2880);
996 assert_eq!(opus_packet_samples(&[0xfd]), 1920);
997 assert_eq!(opus_packet_samples(&[0xff, 0x03]), 2880);
998 assert_eq!(opus_packet_samples(&[]), 0);
999 }
1000
1001 #[test]
1006 fn audio_track_fragments() {
1007 let head = super::super::ogg::OpusHead {
1008 channels: 2,
1009 pre_skip: 312,
1010 input_sample_rate: 48000,
1011 output_gain: 0,
1012 mapping_family: 0,
1013 mapping: vec![],
1014 };
1015 let entry = opus_sample_entry(&head);
1016 assert_eq!(&entry[4..8], b"Opus");
1017 assert_eq!(count(&entry, b"dOps"), 1);
1018 let dops = entry.windows(4).position(|w| w == b"dOps").unwrap() + 4;
1019 assert_eq!(&entry[dops..dops + 4], &[0, 2, 0x01, 0x38]);
1020
1021 let mut b = H264SampleBuilder::new();
1022 let sample = b.build_sample(&idr_au()).unwrap();
1023 let mut out = Vec::new();
1024 {
1025 let mut w = FragmentWriter::new(&mut out).with_audio(AudioTrackConfig {
1026 sample_entry: entry,
1027 });
1028 w.write_init(&b.track_config().unwrap()).unwrap();
1029 for i in 0..AUDIO_FLUSH_SAMPLES as u64 + 2 {
1030 w.push_audio(vec![0xfc, i as u8], 1000 + i * 960).unwrap();
1031 }
1032 w.push_audio(vec![0xfc, 0xee], 1000 + 28 * 960 + 48_000)
1034 .unwrap();
1035 w.push_sample(sample.data.clone(), true, 0).unwrap();
1036 w.push_sample(sample.data, true, 33_000).unwrap();
1037 let st = w.finish().unwrap();
1038 assert_eq!(st.audio_samples, AUDIO_FLUSH_SAMPLES as u64 + 3);
1039 assert_eq!(st.samples, 2);
1040 assert_eq!(
1041 st.duration_us,
1042 (1000 + 28 * 960 + 48_000 + 960) * 1_000_000 / 48_000
1043 );
1044 }
1045 let boxes = top_level(&out);
1046 let kinds: Vec<&str> = boxes.iter().map(|(k, _)| k.as_str()).collect();
1047 assert_eq!(kinds[..2], ["ftyp", "moov"]);
1048 assert_eq!(count(&boxes[1].1, b"trak"), 2);
1049 assert_eq!(count(&boxes[1].1, b"trex"), 2);
1050 assert_eq!(count(&boxes[1].1, b"soun"), 1);
1051 assert_eq!(
1052 kinds.iter().filter(|k| **k == "moof").count(),
1053 5,
1054 "{kinds:?}"
1055 );
1056 let tfdts: Vec<u64> = boxes
1057 .iter()
1058 .filter(|(k, _)| k == "moof")
1059 .map(|(_, m)| {
1060 let at = m.windows(4).position(|w| w == b"tfdt").unwrap() + 8;
1061 u64::from_be_bytes(m[at..at + 8].try_into().unwrap())
1062 })
1063 .collect();
1064 assert_eq!(
1068 tfdts,
1069 vec![
1070 1000,
1071 1000 + AUDIO_FLUSH_SAMPLES as u64 * 960,
1072 0,
1073 1000 + 28 * 960 + 48_000,
1074 2970
1075 ]
1076 );
1077 let first_audio = &boxes[2].1;
1078 let trun = first_audio.windows(4).position(|w| w == b"trun").unwrap() + 8;
1079 assert_eq!(
1080 u32::from_be_bytes(first_audio[trun..trun + 4].try_into().unwrap()),
1081 AUDIO_FLUSH_SAMPLES as u32
1082 );
1083 assert_eq!(
1084 u32::from_be_bytes(first_audio[trun + 8..trun + 12].try_into().unwrap()),
1085 960
1086 );
1087 }
1088
1089 #[test]
1092 fn fragment_stream_box_layout() {
1093 let mut b = H264SampleBuilder::new();
1094 let mut au = Vec::new();
1095 au.extend_from_slice(&[0, 0, 0, 1]);
1096 au.extend_from_slice(&hex(SPS_BASE_640X360));
1097 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
1098 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1, 2, 3, 4]);
1099 let s1 = b.build_sample(&au).unwrap();
1100 let s2 = b.build_sample(&[0u8, 0, 0, 1, 0x41, 5, 6, 7]).unwrap();
1101
1102 let mut buf = Vec::new();
1103 let mut w = FragmentWriter::new(&mut buf);
1104 w.write_init(&b.track_config().unwrap()).unwrap();
1105 w.push_sample(s1.data, s1.sync, 0).unwrap();
1106 w.push_sample(s2.data, s2.sync, 33_000).unwrap();
1107 let stats = w.finish().unwrap();
1108 assert_eq!(stats.samples, 2);
1109 assert_eq!(stats.sync_samples, 1);
1110 assert_eq!(stats.bytes as usize, buf.len());
1111
1112 let mut kinds = Vec::new();
1113 let mut off = 0usize;
1114 while off < buf.len() {
1115 let size = u32::from_be_bytes(buf[off..off + 4].try_into().unwrap()) as usize;
1116 kinds.push(buf[off + 4..off + 8].to_vec());
1117 assert!(size >= 8 && off + size <= buf.len());
1118 off += size;
1119 }
1120 assert_eq!(off, buf.len());
1121 let names: Vec<&str> = kinds
1122 .iter()
1123 .map(|k| std::str::from_utf8(k).unwrap())
1124 .collect();
1125 assert_eq!(names, vec!["ftyp", "moov", "moof", "mdat", "moof", "mdat"]);
1126 }
1127
1128 fn trun_durations(buf: &[u8]) -> Vec<u32> {
1130 let mut out = Vec::new();
1131 let mut off = 0usize;
1132 while off + 24 <= buf.len() {
1133 if &buf[off + 4..off + 8] == b"trun" {
1134 out.push(u32::from_be_bytes(
1136 buf[off + 20..off + 24].try_into().unwrap(),
1137 ));
1138 }
1139 off += 1;
1140 }
1141 out
1142 }
1143
1144 #[test]
1148 fn sample_durations_are_pts_deltas_with_median_tail() {
1149 let mut buf = Vec::new();
1150 let mut w = FragmentWriter::new(&mut buf);
1151 let cfg = {
1152 let mut b = H264SampleBuilder::new();
1153 let mut au = vec![0, 0, 0, 1];
1154 au.extend_from_slice(&hex(SPS_BASE_640X360));
1155 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
1156 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
1157 b.build_sample(&au);
1158 b.track_config().unwrap()
1159 };
1160 w.write_init(&cfg).unwrap();
1161 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 0).unwrap();
1163 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 33_000)
1164 .unwrap();
1165 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 66_000)
1166 .unwrap();
1167 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 366_000)
1168 .unwrap();
1169 let stats = w.finish().unwrap();
1170 assert_eq!(stats.samples, 4);
1171 assert_eq!(trun_durations(&buf), vec![2970, 2970, 27_000, 2970]);
1173 }
1174
1175 #[test]
1177 fn single_sample_uses_default_duration() {
1178 let mut buf = Vec::new();
1179 let mut w = FragmentWriter::new(&mut buf);
1180 let cfg = {
1181 let mut b = H264SampleBuilder::new();
1182 let mut au = vec![0, 0, 0, 1];
1183 au.extend_from_slice(&hex(SPS_BASE_640X360));
1184 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
1185 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
1186 b.build_sample(&au);
1187 b.track_config().unwrap()
1188 };
1189 w.write_init(&cfg).unwrap();
1190 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 0).unwrap();
1191 let stats = w.finish().unwrap();
1192 assert_eq!(stats.samples, 1);
1193 assert_eq!(trun_durations(&buf), vec![DEFAULT_LAST_DURATION]);
1194 }
1195
1196 #[test]
1199 fn pts_clamped_strictly_monotonic() {
1200 let mut buf = Vec::new();
1201 let mut w = FragmentWriter::new(&mut buf);
1202 let cfg = {
1203 let mut b = H264SampleBuilder::new();
1204 let mut au = vec![0, 0, 0, 1];
1205 au.extend_from_slice(&hex(SPS_BASE_640X360));
1206 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
1207 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
1208 b.build_sample(&au);
1209 b.track_config().unwrap()
1210 };
1211 w.write_init(&cfg).unwrap();
1212 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 1000).unwrap();
1213 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 1000).unwrap();
1215 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 500).unwrap();
1216 let stats = w.finish().unwrap();
1217 assert_eq!(stats.samples, 3);
1218
1219 let mut times = Vec::new();
1221 let mut off = 0usize;
1222 while off + 8 <= buf.len() {
1223 if &buf[off + 4..off + 8] == b"tfdt" {
1224 let t = u64::from_be_bytes(buf[off + 12..off + 20].try_into().unwrap());
1225 times.push(t);
1226 }
1227 off += 1;
1228 }
1229 assert_eq!(times.len(), 3);
1230 assert!(
1231 times.windows(2).all(|w| w[1] > w[0]),
1232 "tfdt times: {times:?}"
1233 );
1234 }
1235}