1use std::io::Write;
30
31const TIMESCALE: u32 = 90_000;
33
34const DEFAULT_LAST_DURATION: u32 = TIMESCALE / 30;
37
38pub fn split_annexb(data: &[u8]) -> Vec<&[u8]> {
41 let mut nals = Vec::new();
42 let mut i = 0usize;
43 let mut nal_start: Option<usize> = None;
44 while i + 2 < data.len() {
45 if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
46 let code_start = if i > 0 && data[i - 1] == 0 { i - 1 } else { i };
47 if let Some(s) = nal_start
48 && code_start > s {
49 nals.push(&data[s..code_start]);
50 }
51 i += 3;
52 nal_start = Some(i);
53 } else if data[i + 2] == 0 {
54 i += 1;
56 } else {
57 i += 3;
58 }
59 }
60 if let Some(s) = nal_start
61 && data.len() > s {
62 nals.push(&data[s..]);
63 }
64 nals
65}
66
67fn unescape_rbsp(data: &[u8]) -> Vec<u8> {
69 let mut out = Vec::with_capacity(data.len());
70 let mut zeros = 0u32;
71 for &b in data {
72 if zeros >= 2 && b == 3 {
73 zeros = 0;
74 continue;
75 }
76 if b == 0 {
77 zeros += 1;
78 } else {
79 zeros = 0;
80 }
81 out.push(b);
82 }
83 out
84}
85
86struct BitReader<'a> {
88 data: &'a [u8],
89 pos: usize,
90}
91
92impl<'a> BitReader<'a> {
93 fn new(data: &'a [u8]) -> Self {
94 Self { data, pos: 0 }
95 }
96
97 fn bit(&mut self) -> Option<u32> {
98 let byte = *self.data.get(self.pos / 8)?;
99 let bit = (byte >> (7 - (self.pos % 8))) & 1;
100 self.pos += 1;
101 Some(bit as u32)
102 }
103
104 fn bits(&mut self, n: u32) -> Option<u32> {
105 let mut v = 0u32;
106 for _ in 0..n {
107 v = (v << 1) | self.bit()?;
108 }
109 Some(v)
110 }
111
112 fn ue(&mut self) -> Option<u32> {
114 let mut zeros = 0u32;
115 while self.bit()? == 0 {
116 zeros += 1;
117 if zeros > 31 {
118 return None;
119 }
120 }
121 let rest = self.bits(zeros)?;
122 Some((1u32 << zeros) - 1 + rest)
123 }
124
125 fn se(&mut self) -> Option<i32> {
126 let k = self.ue()? as i64;
127 Some(if k % 2 == 0 { -(k / 2) as i32 } else { ((k + 1) / 2) as i32 })
128 }
129}
130
131pub fn parse_sps_dimensions(sps_nal: &[u8]) -> Option<(u32, u32)> {
138 if sps_nal.len() < 4 || sps_nal[0] & 0x1f != 7 {
139 return None;
140 }
141 let profile_idc = sps_nal[1];
142 let rbsp = unescape_rbsp(&sps_nal[4..]);
143 let mut r = BitReader::new(&rbsp);
144 r.ue()?;
146
147 let mut chroma_format_idc = 1u32;
148 if matches!(
149 profile_idc,
150 100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
151 ) {
152 chroma_format_idc = r.ue()?;
153 if chroma_format_idc == 3 {
154 r.bit()?;
156 }
157 r.ue()?;
159 r.ue()?;
160 r.bit()?;
161 if r.bit()? == 1 {
162 let lists = if chroma_format_idc == 3 { 12 } else { 8 };
164 for i in 0..lists {
165 if r.bit()? == 1 {
166 let size = if i < 6 { 16 } else { 64 };
167 let mut next_scale = 8i32;
168 let mut last_scale = 8i32;
169 for _ in 0..size {
170 if next_scale != 0 {
171 let delta = r.se()?;
172 next_scale = (last_scale + delta + 256) % 256;
173 }
174 if next_scale != 0 {
175 last_scale = next_scale;
176 }
177 }
178 }
179 }
180 }
181 }
182
183 r.ue()?;
185 let pic_order_cnt_type = r.ue()?;
186 if pic_order_cnt_type == 0 {
187 r.ue()?;
189 } else if pic_order_cnt_type == 1 {
190 r.bit()?;
193 r.se()?;
194 r.se()?;
195 let n = r.ue()?;
196 for _ in 0..n {
197 r.se()?;
198 }
199 }
200 r.ue()?;
202 r.bit()?;
203 let pic_width_in_mbs = r.ue()? + 1;
204 let pic_height_in_map_units = r.ue()? + 1;
205 let frame_mbs_only = r.bit()?;
206 if frame_mbs_only == 0 {
207 r.bit()?;
209 }
210 r.bit()?;
212
213 let (mut crop_l, mut crop_r, mut crop_t, mut crop_b) = (0u32, 0u32, 0u32, 0u32);
214 if r.bit()? == 1 {
215 crop_l = r.ue()?;
216 crop_r = r.ue()?;
217 crop_t = r.ue()?;
218 crop_b = r.ue()?;
219 }
220
221 let (sub_w, sub_h) = match chroma_format_idc {
222 0 | 3 => (1u32, 1u32),
223 2 => (2, 1),
224 _ => (2, 2),
225 };
226 let crop_unit_x = sub_w;
227 let crop_unit_y = sub_h * (2 - frame_mbs_only);
228 let width = pic_width_in_mbs * 16 - crop_unit_x * (crop_l + crop_r);
229 let height = (2 - frame_mbs_only) * pic_height_in_map_units * 16 - crop_unit_y * (crop_t + crop_b);
230 Some((width, height))
231}
232
233fn mk_box(fourcc: &[u8; 4], payload: &[u8]) -> Vec<u8> {
234 let mut b = Vec::with_capacity(8 + payload.len());
235 b.extend_from_slice(&((8 + payload.len()) as u32).to_be_bytes());
236 b.extend_from_slice(fourcc);
237 b.extend_from_slice(payload);
238 b
239}
240
241fn mk_full_box(fourcc: &[u8; 4], version: u8, flags: u32, payload: &[u8]) -> Vec<u8> {
242 let mut p = Vec::with_capacity(4 + payload.len());
243 p.push(version);
244 p.extend_from_slice(&flags.to_be_bytes()[1..]);
245 p.extend_from_slice(payload);
246 mk_box(fourcc, &p)
247}
248
249const MATRIX_IDENTITY: [u8; 36] = {
252 let mut m = [0u8; 36];
253 m[1] = 0x01;
254 m[17] = 0x01;
255 m[32] = 0x40;
256 m
257};
258
259pub struct TrackConfig {
262 pub sample_entry: Vec<u8>,
263 pub width: u32,
264 pub height: u32,
265}
266
267struct PendingSample {
269 data: Vec<u8>,
270 sync: bool,
271 dts: u64,
272}
273
274#[derive(Clone, Copy, Debug, Default)]
276pub struct Mp4Stats {
277 pub samples: u64,
278 pub sync_samples: u64,
279 pub bytes: u64,
280 pub duration_us: u64,
281}
282
283pub struct FragmentWriter<W: Write> {
286 out: W,
287 seq: u32,
288 wrote_init: bool,
289 pending: Option<PendingSample>,
290 last_dts: Option<u64>,
291 durations: Vec<u32>,
295 stats: Mp4Stats,
296}
297
298impl<W: Write> FragmentWriter<W> {
299 pub fn new(out: W) -> Self {
300 Self {
301 out,
302 seq: 0,
303 wrote_init: false,
304 pending: None,
305 last_dts: None,
306 durations: Vec::new(),
307 stats: Mp4Stats::default(),
308 }
309 }
310
311 pub fn init_written(&self) -> bool {
312 self.wrote_init
313 }
314
315 pub fn stats(&self) -> Mp4Stats {
318 self.stats
319 }
320
321 pub fn write_init(&mut self, cfg: &TrackConfig) -> std::io::Result<()> {
324 let mut ftyp_p = Vec::new();
325 ftyp_p.extend_from_slice(b"isom");
326 ftyp_p.extend_from_slice(&0x200u32.to_be_bytes());
327 for brand in [b"isom", b"iso5", b"iso6", b"avc1", b"mp41"] {
328 ftyp_p.extend_from_slice(brand);
329 }
330 let ftyp = mk_box(b"ftyp", &ftyp_p);
331
332 let mut mvhd_p = Vec::new();
336 mvhd_p.extend_from_slice(&[0u8; 8]);
337 mvhd_p.extend_from_slice(&TIMESCALE.to_be_bytes());
338 mvhd_p.extend_from_slice(&0u32.to_be_bytes());
339 mvhd_p.extend_from_slice(&0x00010000u32.to_be_bytes());
340 mvhd_p.extend_from_slice(&0x0100u16.to_be_bytes());
341 mvhd_p.extend_from_slice(&[0u8; 10]);
342 mvhd_p.extend_from_slice(&MATRIX_IDENTITY);
343 mvhd_p.extend_from_slice(&[0u8; 24]);
344 mvhd_p.extend_from_slice(&2u32.to_be_bytes());
345 let mvhd = mk_full_box(b"mvhd", 0, 0, &mvhd_p);
346
347 let mut tkhd_p = Vec::new();
351 tkhd_p.extend_from_slice(&[0u8; 8]);
352 tkhd_p.extend_from_slice(&1u32.to_be_bytes());
353 tkhd_p.extend_from_slice(&[0u8; 4]);
354 tkhd_p.extend_from_slice(&0u32.to_be_bytes());
355 tkhd_p.extend_from_slice(&[0u8; 16]);
356 tkhd_p.extend_from_slice(&MATRIX_IDENTITY);
357 tkhd_p.extend_from_slice(&(cfg.width << 16).to_be_bytes());
358 tkhd_p.extend_from_slice(&(cfg.height << 16).to_be_bytes());
359 let tkhd = mk_full_box(b"tkhd", 0, 3, &tkhd_p);
360
361 let mut mdhd_p = Vec::new();
364 mdhd_p.extend_from_slice(&[0u8; 8]);
365 mdhd_p.extend_from_slice(&TIMESCALE.to_be_bytes());
366 mdhd_p.extend_from_slice(&0u32.to_be_bytes());
367 mdhd_p.extend_from_slice(&0x55c4u16.to_be_bytes());
368 mdhd_p.extend_from_slice(&[0u8; 2]);
369 let mdhd = mk_full_box(b"mdhd", 0, 0, &mdhd_p);
370
371 let mut hdlr_p = Vec::new();
374 hdlr_p.extend_from_slice(&[0u8; 4]);
375 hdlr_p.extend_from_slice(b"vide");
376 hdlr_p.extend_from_slice(&[0u8; 12]);
377 hdlr_p.extend_from_slice(b"pixelflux\0");
378 let hdlr = mk_full_box(b"hdlr", 0, 0, &hdlr_p);
379
380 let vmhd = mk_full_box(b"vmhd", 0, 1, &[0u8; 8]);
381 let url = mk_full_box(b"url ", 0, 1, &[]);
383 let mut dref_p = 1u32.to_be_bytes().to_vec();
384 dref_p.extend_from_slice(&url);
385 let dref = mk_full_box(b"dref", 0, 0, &dref_p);
386 let dinf = mk_box(b"dinf", &dref);
387
388 let mut stsd_p = 1u32.to_be_bytes().to_vec();
389 stsd_p.extend_from_slice(&cfg.sample_entry);
390 let stsd = mk_full_box(b"stsd", 0, 0, &stsd_p);
391 let stts = mk_full_box(b"stts", 0, 0, &0u32.to_be_bytes());
392 let stsc = mk_full_box(b"stsc", 0, 0, &0u32.to_be_bytes());
393 let stsz = mk_full_box(b"stsz", 0, 0, &[0u8; 8]);
394 let stco = mk_full_box(b"stco", 0, 0, &0u32.to_be_bytes());
395 let stbl = mk_box(
396 b"stbl",
397 &[stsd, stts, stsc, stsz, stco].concat(),
398 );
399
400 let minf = mk_box(b"minf", &[vmhd, dinf, stbl].concat());
401 let mdia = mk_box(b"mdia", &[mdhd, hdlr, minf].concat());
402 let trak = mk_box(b"trak", &[tkhd, mdia].concat());
403
404 let mut trex_p = Vec::new();
407 trex_p.extend_from_slice(&1u32.to_be_bytes());
408 trex_p.extend_from_slice(&1u32.to_be_bytes());
409 trex_p.extend_from_slice(&[0u8; 12]);
410 let trex = mk_full_box(b"trex", 0, 0, &trex_p);
411 let mvex = mk_box(b"mvex", &trex);
412
413 let moov = mk_box(b"moov", &[mvhd, trak, mvex].concat());
414
415 self.out.write_all(&ftyp)?;
416 self.out.write_all(&moov)?;
417 self.stats.bytes += (ftyp.len() + moov.len()) as u64;
418 self.wrote_init = true;
419 Ok(())
420 }
421
422 pub fn push_sample(&mut self, data: Vec<u8>, sync: bool, pts_us: u64) -> std::io::Result<()> {
427 let mut dts = pts_us * (TIMESCALE as u64 / 1000) / 1000;
428 if let Some(last) = self.last_dts
429 && dts <= last {
430 dts = last + 1;
431 }
432 self.last_dts = Some(dts);
433 if let Some(prev) = self.pending.take() {
434 let duration = (dts - prev.dts).min(u32::MAX as u64) as u32;
435 self.durations.push(duration);
436 self.write_fragment(&prev, duration)?;
437 }
438 self.pending = Some(PendingSample { data, sync, dts });
439 Ok(())
440 }
441
442 fn median_duration(&self) -> u32 {
445 if self.durations.is_empty() {
446 return DEFAULT_LAST_DURATION;
447 }
448 let mut sorted = self.durations.clone();
449 sorted.sort_unstable();
450 sorted[sorted.len() / 2]
451 }
452
453 fn write_fragment(&mut self, s: &PendingSample, duration: u32) -> std::io::Result<()> {
454 self.seq += 1;
455
456 let mut tfhd_p = Vec::new();
459 tfhd_p.extend_from_slice(&1u32.to_be_bytes());
460 let tfhd = mk_full_box(b"tfhd", 0, 0x020000, &tfhd_p);
461
462 let mut tfdt_p = Vec::new();
463 tfdt_p.extend_from_slice(&s.dts.to_be_bytes());
464 let tfdt = mk_full_box(b"tfdt", 1, 0, &tfdt_p);
465
466 let sample_flags: u32 = if s.sync { 0x0200_0000 } else { 0x0101_0000 };
468 let mut trun_p = Vec::new();
472 trun_p.extend_from_slice(&1u32.to_be_bytes());
473 trun_p.extend_from_slice(&0i32.to_be_bytes());
474 trun_p.extend_from_slice(&duration.to_be_bytes());
475 trun_p.extend_from_slice(&(s.data.len() as u32).to_be_bytes());
476 trun_p.extend_from_slice(&sample_flags.to_be_bytes());
477 let mut trun = mk_full_box(b"trun", 0, 0x000701, &trun_p);
478
479 let traf_len = 8 + tfhd.len() + tfdt.len() + trun.len();
480 let moof_len = 8 + 16 + traf_len;
482 let data_offset = (moof_len + 8) as i32;
484 let off_pos = trun.len() - trun_p.len() + 4;
486 trun[off_pos..off_pos + 4].copy_from_slice(&data_offset.to_be_bytes());
487
488 let mfhd = mk_full_box(b"mfhd", 0, 0, &self.seq.to_be_bytes());
489 let traf = mk_box(b"traf", &[tfhd, tfdt, trun].concat());
490 let moof = mk_box(b"moof", &[mfhd, traf].concat());
491 debug_assert_eq!(moof.len(), moof_len);
492
493 self.out.write_all(&moof)?;
494 self.out.write_all(&((8 + s.data.len()) as u32).to_be_bytes())?;
495 self.out.write_all(b"mdat")?;
496 self.out.write_all(&s.data)?;
497 self.out.flush()?;
498
499 self.stats.samples += 1;
500 if s.sync {
501 self.stats.sync_samples += 1;
502 }
503 self.stats.bytes += (moof.len() + 8 + s.data.len()) as u64;
504 self.stats.duration_us = (s.dts + duration as u64) * 1000 / (TIMESCALE as u64 / 1000);
505 Ok(())
506 }
507
508 pub fn finish(mut self) -> std::io::Result<Mp4Stats> {
511 if let Some(prev) = self.pending.take() {
512 let d = self.median_duration();
513 self.write_fragment(&prev, d)?;
514 }
515 self.out.flush()?;
516 Ok(self.stats)
517 }
518}
519
520pub struct H264SampleBuilder {
523 sps: Option<Vec<u8>>,
524 pps: Option<Vec<u8>>,
525}
526
527pub struct BuiltSample {
529 pub data: Vec<u8>,
530 pub sync: bool,
531}
532
533impl Default for H264SampleBuilder {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539impl H264SampleBuilder {
540 pub fn new() -> Self {
541 Self { sps: None, pps: None }
542 }
543
544 pub fn have_parameter_sets(&self) -> bool {
545 self.sps.is_some() && self.pps.is_some()
546 }
547
548 pub fn build_sample(&mut self, annexb: &[u8]) -> Option<BuiltSample> {
552 let nals = split_annexb(annexb);
553 let mut data = Vec::with_capacity(annexb.len() + 8);
554 let mut sync = false;
555 let mut has_slice = false;
556 for nal in nals {
557 if nal.is_empty() {
558 continue;
559 }
560 match nal[0] & 0x1f {
561 7 => {
562 if self.sps.as_deref() != Some(nal) {
563 self.sps = Some(nal.to_vec());
564 }
565 }
566 8 => {
567 if self.pps.as_deref() != Some(nal) {
568 self.pps = Some(nal.to_vec());
569 }
570 }
571 5 => {
572 sync = true;
573 has_slice = true;
574 }
575 1 => has_slice = true,
576 _ => {}
577 }
578 data.extend_from_slice(&(nal.len() as u32).to_be_bytes());
579 data.extend_from_slice(nal);
580 }
581 if !has_slice {
582 return None;
583 }
584 Some(BuiltSample { data, sync })
585 }
586
587 pub fn track_config(&self) -> Option<TrackConfig> {
590 let sps = self.sps.as_deref()?;
591 let pps = self.pps.as_deref()?;
592 let (width, height) = parse_sps_dimensions(sps)?;
593
594 let mut avcc_p = vec![1, sps[1], sps[2], sps[3], 0xff, 0xe1];
599 avcc_p.extend_from_slice(&(sps.len() as u16).to_be_bytes());
600 avcc_p.extend_from_slice(sps);
601 avcc_p.push(1);
603 avcc_p.extend_from_slice(&(pps.len() as u16).to_be_bytes());
604 avcc_p.extend_from_slice(pps);
605 let avcc = mk_box(b"avcC", &avcc_p);
606
607 let mut entry_p = Vec::new();
611 entry_p.extend_from_slice(&[0u8; 6]);
612 entry_p.extend_from_slice(&1u16.to_be_bytes());
613 entry_p.extend_from_slice(&[0u8; 16]);
614 entry_p.extend_from_slice(&(width as u16).to_be_bytes());
615 entry_p.extend_from_slice(&(height as u16).to_be_bytes());
616 entry_p.extend_from_slice(&0x0048_0000u32.to_be_bytes());
617 entry_p.extend_from_slice(&0x0048_0000u32.to_be_bytes());
618 entry_p.extend_from_slice(&[0u8; 4]);
619 entry_p.extend_from_slice(&1u16.to_be_bytes());
620 entry_p.extend_from_slice(&[0u8; 32]);
621 entry_p.extend_from_slice(&0x0018u16.to_be_bytes());
622 entry_p.extend_from_slice(&(-1i16).to_be_bytes());
623 entry_p.extend_from_slice(&avcc);
624 let entry = mk_box(b"avc1", &entry_p);
625
626 Some(TrackConfig { sample_entry: entry, width, height })
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 const SPS_HIGH_1284X722: &str = "67640020acd9405105de788c0440000003004000000f03c60c6580";
636 const SPS_BASE_640X360: &str = "6742c01ed900a02ff970110000030001000003003c0f162e48";
638 const SPS_444_1920X1080: &str = "67f40028919b280f0044fc4e0220000003002000000781e30632c0";
640
641 fn hex(s: &str) -> Vec<u8> {
642 (0..s.len())
643 .step_by(2)
644 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
645 .collect()
646 }
647
648 #[test]
650 fn split_annexb_handles_mixed_start_codes() {
651 let mut data = Vec::new();
652 data.extend_from_slice(&[0, 0, 0, 1, 0x67, 0xAA, 0xBB]);
653 data.extend_from_slice(&[0, 0, 1, 0x68, 0xCC]);
654 data.extend_from_slice(&[0, 0, 0, 1, 0x65, 0x00, 0x00, 0x03, 0x01, 0xDD]);
655 let nals = split_annexb(&data);
656 assert_eq!(nals.len(), 3);
657 assert_eq!(nals[0], &[0x67, 0xAA, 0xBB]);
658 assert_eq!(nals[1], &[0x68, 0xCC]);
659 assert_eq!(nals[2], &[0x65, 0x00, 0x00, 0x03, 0x01, 0xDD]);
660 }
661
662 #[test]
665 fn sps_dimensions_across_profiles() {
666 assert_eq!(parse_sps_dimensions(&hex(SPS_HIGH_1284X722)), Some((1284, 722)));
667 assert_eq!(parse_sps_dimensions(&hex(SPS_BASE_640X360)), Some((640, 360)));
668 assert_eq!(parse_sps_dimensions(&hex(SPS_444_1920X1080)), Some((1920, 1080)));
669 }
670
671 #[test]
674 fn annexb_to_avcc_sample() {
675 let sps = hex(SPS_BASE_640X360);
676 let mut au = Vec::new();
677 au.extend_from_slice(&[0, 0, 0, 1]);
678 au.extend_from_slice(&sps);
679 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
680 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1, 2, 3, 4]);
681
682 let mut b = H264SampleBuilder::new();
683 assert!(b.build_sample(&[0u8, 0, 0, 1, 0x67, 0x42, 0xc0, 0x1e, 0xd9]).is_none());
684 let s = b.build_sample(&au).expect("IDR AU builds a sample");
685 assert!(s.sync);
686 assert!(b.have_parameter_sets());
687 let mut off = 0usize;
689 let mut sizes = Vec::new();
690 while off < s.data.len() {
691 let n = u32::from_be_bytes(s.data[off..off + 4].try_into().unwrap()) as usize;
692 sizes.push(n);
693 off += 4 + n;
694 }
695 assert_eq!(off, s.data.len());
696 assert_eq!(sizes, vec![sps.len(), 4, 5]);
697
698 let p = b.build_sample(&[0u8, 0, 0, 1, 0x41, 9, 9]).unwrap();
699 assert!(!p.sync);
700 }
701
702 #[test]
705 fn fragment_stream_box_layout() {
706 let mut b = H264SampleBuilder::new();
707 let mut au = Vec::new();
708 au.extend_from_slice(&[0, 0, 0, 1]);
709 au.extend_from_slice(&hex(SPS_BASE_640X360));
710 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
711 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1, 2, 3, 4]);
712 let s1 = b.build_sample(&au).unwrap();
713 let s2 = b.build_sample(&[0u8, 0, 0, 1, 0x41, 5, 6, 7]).unwrap();
714
715 let mut buf = Vec::new();
716 let mut w = FragmentWriter::new(&mut buf);
717 w.write_init(&b.track_config().unwrap()).unwrap();
718 w.push_sample(s1.data, s1.sync, 0).unwrap();
719 w.push_sample(s2.data, s2.sync, 33_000).unwrap();
720 let stats = w.finish().unwrap();
721 assert_eq!(stats.samples, 2);
722 assert_eq!(stats.sync_samples, 1);
723 assert_eq!(stats.bytes as usize, buf.len());
724
725 let mut kinds = Vec::new();
726 let mut off = 0usize;
727 while off < buf.len() {
728 let size = u32::from_be_bytes(buf[off..off + 4].try_into().unwrap()) as usize;
729 kinds.push(buf[off + 4..off + 8].to_vec());
730 assert!(size >= 8 && off + size <= buf.len());
731 off += size;
732 }
733 assert_eq!(off, buf.len());
734 let names: Vec<&str> = kinds.iter().map(|k| std::str::from_utf8(k).unwrap()).collect();
735 assert_eq!(names, vec!["ftyp", "moov", "moof", "mdat", "moof", "mdat"]);
736 }
737
738 fn trun_durations(buf: &[u8]) -> Vec<u32> {
740 let mut out = Vec::new();
741 let mut off = 0usize;
742 while off + 24 <= buf.len() {
743 if &buf[off + 4..off + 8] == b"trun" {
744 out.push(u32::from_be_bytes(buf[off + 20..off + 24].try_into().unwrap()));
746 }
747 off += 1;
748 }
749 out
750 }
751
752 #[test]
756 fn sample_durations_are_pts_deltas_with_median_tail() {
757 let mut buf = Vec::new();
758 let mut w = FragmentWriter::new(&mut buf);
759 let cfg = {
760 let mut b = H264SampleBuilder::new();
761 let mut au = vec![0, 0, 0, 1];
762 au.extend_from_slice(&hex(SPS_BASE_640X360));
763 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
764 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
765 b.build_sample(&au);
766 b.track_config().unwrap()
767 };
768 w.write_init(&cfg).unwrap();
769 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 0).unwrap();
771 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 33_000).unwrap();
772 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 66_000).unwrap();
773 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 366_000).unwrap();
774 let stats = w.finish().unwrap();
775 assert_eq!(stats.samples, 4);
776 assert_eq!(trun_durations(&buf), vec![2970, 2970, 27_000, 2970]);
778 }
779
780 #[test]
782 fn single_sample_uses_default_duration() {
783 let mut buf = Vec::new();
784 let mut w = FragmentWriter::new(&mut buf);
785 let cfg = {
786 let mut b = H264SampleBuilder::new();
787 let mut au = vec![0, 0, 0, 1];
788 au.extend_from_slice(&hex(SPS_BASE_640X360));
789 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
790 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
791 b.build_sample(&au);
792 b.track_config().unwrap()
793 };
794 w.write_init(&cfg).unwrap();
795 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 0).unwrap();
796 let stats = w.finish().unwrap();
797 assert_eq!(stats.samples, 1);
798 assert_eq!(trun_durations(&buf), vec![DEFAULT_LAST_DURATION]);
799 }
800
801 #[test]
804 fn pts_clamped_strictly_monotonic() {
805 let mut buf = Vec::new();
806 let mut w = FragmentWriter::new(&mut buf);
807 let cfg = {
808 let mut b = H264SampleBuilder::new();
809 let mut au = vec![0, 0, 0, 1];
810 au.extend_from_slice(&hex(SPS_BASE_640X360));
811 au.extend_from_slice(&[0, 0, 0, 1, 0x68, 0xCE, 0x38, 0x80]);
812 au.extend_from_slice(&[0, 0, 0, 1, 0x65, 1]);
813 b.build_sample(&au);
814 b.track_config().unwrap()
815 };
816 w.write_init(&cfg).unwrap();
817 w.push_sample(vec![0, 0, 0, 1, 0x65], true, 1000).unwrap();
818 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 1000).unwrap();
820 w.push_sample(vec![0, 0, 0, 1, 0x41], false, 500).unwrap();
821 let stats = w.finish().unwrap();
822 assert_eq!(stats.samples, 3);
823
824 let mut times = Vec::new();
826 let mut off = 0usize;
827 while off + 8 <= buf.len() {
828 if &buf[off + 4..off + 8] == b"tfdt" {
829 let t = u64::from_be_bytes(buf[off + 12..off + 20].try_into().unwrap());
830 times.push(t);
831 }
832 off += 1;
833 }
834 assert_eq!(times.len(), 3);
835 assert!(times.windows(2).all(|w| w[1] > w[0]), "tfdt times: {times:?}");
836 }
837}