1use std::{
5 io::{BufReader, Read, Seek},
6 path::Path,
7};
8
9use chrono::{DateTime, FixedOffset, Utc};
10use h264_reader::{
11 Context as H264ParsingContext,
12 nal::{
13 Nal, RefNal, UnitType,
14 sei::{HeaderType, SeiMessage, SeiReader},
15 },
16 rbsp::BitReaderError,
17};
18use serde::{Deserialize, Serialize};
19
20use strand_cam_remote_control::{H264_METADATA_UUID, H264_METADATA_VERSION, H264Metadata};
21
22#[cfg(feature = "openh264")]
23use machine_vision_formats::owned::OImage;
24
25use crate::{
26 EncodedH264, Error, FrameData, FrameDataSource, H264EncodingVariant, ImageData, MyAsStr,
27 Result, Timestamp, TimestampSource,
28 h264_poc::{self, PocStrategy},
29 ntp_timestamp::NtpTimestamp,
30 srt_reader::{self, Stanza},
31};
32
33struct SrtData {
34 stanzas: Vec<Stanza>,
35 frame0_time: DateTime<FixedOffset>,
36 idx: usize,
37 truncated_at_line: Option<usize>,
41}
42
43#[derive(Debug, Clone)]
49pub struct SrtTruncation {
50 pub kept_frames: usize,
53 pub total_frames: usize,
55 pub usable_stanzas: usize,
57 pub malformed_at_line: Option<usize>,
61}
62
63#[derive(serde::Deserialize)]
64struct SrtMsg {
65 timestamp: DateTime<chrono::FixedOffset>,
66}
67
68impl SrtData {
69 fn parse_time(stanza: &Stanza) -> DateTime<FixedOffset> {
70 let msg: SrtMsg = serde_json::from_str(&stanza.lines).unwrap();
71 msg.timestamp
72 }
73 fn next_pts(&mut self) -> Result<std::time::Duration> {
74 let stanza = &self.stanzas[self.idx];
75 self.idx += 1;
76 let tnow = Self::parse_time(stanza);
77 Ok(tnow.signed_duration_since(self.frame0_time).to_std()?)
78 }
79 fn time_at(&self, idx: usize) -> Result<std::time::Duration> {
85 let stanza = &self.stanzas[idx];
86 let tnow = Self::parse_time(stanza);
87 Ok(tnow.signed_duration_since(self.frame0_time).to_std()?)
88 }
89 fn frame0_time(&self) -> DateTime<FixedOffset> {
90 self.frame0_time
91 }
92 fn span(&self) -> Result<std::time::Duration> {
94 let first = Self::parse_time(&self.stanzas[0]);
95 let last = Self::parse_time(&self.stanzas[self.stanzas.len() - 1]);
96 Ok(last.signed_duration_since(first).to_std()?)
97 }
98}
99
100#[derive(Debug, Clone, Copy)]
105pub struct Mp4SampleTiming {
106 pub decode_duration: std::time::Duration,
108 pub composition_offset: chrono::Duration,
111}
112
113pub trait H264Preparser {
114 fn put_seq_param_set(&mut self, nalu: &RefNal<'_>) -> eyre::Result<()>;
115 fn put_pic_param_set(&mut self, nalu: &RefNal<'_>) -> eyre::Result<()>;
116 fn put_sei_nalu(&mut self, nalu: &RefNal<'_>) -> eyre::Result<()>;
117 fn put_slice_layer_nalu(&mut self, nalu: &RefNal<'_>, is_i_frame: bool) -> eyre::Result<()>;
118 fn set_num_positions(&mut self, num_positions: usize) -> eyre::Result<()>;
119 fn set_position(&mut self, pos: usize) -> eyre::Result<()>;
120 fn close(self) -> eyre::Result<()>;
121}
122
123const X264_UUID: &[u8; 16] = uuid::uuid!("dc45e9bd-e6d9-48b7-962c-d820d923eeef").as_bytes();
126
127const VIDEOTOOLBOX_UUID: &[u8; 16] = uuid::uuid!("47564adc-5c4c-433f-94ef-c5113cd143a8").as_bytes();
129
130pub struct H264Source<H: SeekableH264Source> {
171 seekable_h264_source: H,
172 nal_locations: Vec<H::NalLocation>,
174 mp4_pts: Option<Vec<std::time::Duration>>,
176 frame_time_info: Vec<FrameTimeInfo>,
177 pub h264_metadata: Option<H264Metadata>,
178 frame0_precision_time: Option<chrono::DateTime<chrono::FixedOffset>>,
179 frame0_frameinfo: Option<FrameInfo>,
180 width: u32,
181 height: u32,
182 do_decode_h264: bool,
183 timestamp_source: Option<crate::TimestampSource>,
184 has_timestamps: bool,
185 srt_data: Option<SrtData>,
186 mp4_sample_timing: Option<Vec<Mp4SampleTiming>>,
189 srt_display_rank: Option<Vec<usize>>,
195 presentation_rank: Option<Vec<usize>>,
202 is_idr: Vec<bool>,
206 srt_truncation: Option<SrtTruncation>,
210 average_fps: Option<f64>,
211 #[cfg_attr(not(feature = "openh264"), allow(dead_code))]
217 chroma_format: h264_reader::nal::sps::ChromaFormat,
218 #[cfg_attr(not(feature = "openh264"), allow(dead_code))]
221 profile: String,
222}
223
224impl<H: SeekableH264Source> H264Source<H> {
225 pub fn as_seekable_h264_source(&self) -> &H {
226 &self.seekable_h264_source
227 }
228
229 pub fn mp4_sample_timing(&self) -> Option<&[Mp4SampleTiming]> {
233 self.mp4_sample_timing.as_deref()
234 }
235
236 pub fn srt_truncation(&self) -> Option<&SrtTruncation> {
239 self.srt_truncation.as_ref()
240 }
241
242 fn create_iter_unchecked<'a>(
243 &'a mut self,
244 frame_idx: usize,
245 ) -> Box<dyn Iterator<Item = Result<FrameData>> + 'a> {
246 let openh264_decoder_state = if self.do_decode_h264 {
247 Some(crate::opt_openh264_decoder::new_stream_decoder().unwrap())
248 } else {
249 None
250 };
251 let display_order = self.presentation_rank.as_ref().map(|rank| {
255 let mut order = vec![0usize; rank.len()];
256 for (decode_idx, &r) in rank.iter().enumerate() {
257 order[r] = decode_idx;
258 }
259 order
260 });
261 Box::new(RawH264Iter {
262 parent: self,
263 frame_idx,
264 openh264_decoder_state,
265 display_order,
266 n_pictures_out: 0,
267 pending_meta: std::collections::HashMap::new(),
268 decoded_ready: std::collections::BTreeMap::new(),
269 next_emit: frame_idx,
270 flushed: false,
271 poisoned: false,
272 })
273 }
274}
275
276pub struct FrameTimeInfo {
278 nal_location_index: usize,
284 precise_timestamp: Option<DateTime<Utc>>,
285 frameinfo: Option<FrameInfo>,
286 poc: Option<i64>,
289 is_idr: bool,
291}
292
293impl<H: SeekableH264Source> FrameDataSource for H264Source<H> {
294 fn width(&self) -> u32 {
295 self.width
296 }
297 fn height(&self) -> u32 {
298 self.height
299 }
300 fn camera_name(&self) -> Option<&str> {
301 self.h264_metadata
302 .as_ref()
303 .and_then(|x| x.camera_name.as_deref())
304 }
305 fn gamma(&self) -> Option<f32> {
306 self.h264_metadata.as_ref().and_then(|x| x.gamma)
307 }
308 fn frame0_time(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
309 match &self.timestamp_source {
310 Some(TimestampSource::BestGuess) => unreachable!(),
311 Some(TimestampSource::FixedFramerate) => {
312 if let Some(t) = &self.frame0_precision_time {
313 Some(*t)
314 } else {
315 self.frame0_frameinfo.as_ref().map(|fi| fi.recv.into())
316 }
317 }
318 Some(TimestampSource::MispMicrosectime) => self.frame0_precision_time,
319 Some(TimestampSource::FrameInfoRecvTime) | Some(TimestampSource::FrameInfoRtp) => {
320 Some(self.frame0_frameinfo.as_ref().unwrap().recv.into())
321 }
322 Some(TimestampSource::Mp4Pts) | None => None,
323 Some(TimestampSource::SrtFile) => self.srt_data.as_ref().map(|x| x.frame0_time()),
324 }
325 }
326 fn average_framerate(&self) -> Option<f64> {
327 self.average_fps
328 }
329 fn skip_n_frames(&mut self, n_frames: usize) -> Result<()> {
330 if n_frames > 0 {
331 return Err(Error::SkippingFramesNotSupported);
332 }
338 Ok(())
339 }
340 fn estimate_luminance_range(&mut self) -> Result<(u16, u16)> {
341 Err(Error::NotImplemented("h264 luminance scanning"))
342 }
343 fn decode_order_iter<'a>(&'a mut self) -> Box<dyn Iterator<Item = Result<FrameData>> + 'a> {
344 self.create_iter_unchecked(0)
345 }
346 fn presentation_order_iter<'a>(
347 &'a mut self,
348 ) -> Result<Box<dyn Iterator<Item = Result<FrameData>> + 'a>> {
349 let rank = self.presentation_rank.clone().ok_or_else(|| {
350 Error::H264Poc(
351 "cannot recover presentation order: source has neither per-sample \
352 timestamps nor a decodable picture order count"
353 .to_string(),
354 )
355 })?;
356 let is_idr = self.is_idr.clone();
357 let total = rank.len();
358 let inner = self.create_iter_unchecked(0);
361 Ok(Box::new(PresentationReorderIter {
362 inner,
363 rank,
364 is_idr,
365 total,
366 pending: Vec::new(),
367 ready: std::collections::VecDeque::new(),
368 done: false,
369 }))
370 }
371 fn timestamp_source(&self) -> &str {
372 self.timestamp_source.as_str()
373 }
374 fn has_timestamps(&self) -> bool {
375 self.has_timestamps
376 }
377 fn srt_truncation(&self) -> Option<SrtTruncation> {
378 self.srt_truncation.clone()
379 }
380}
381
382pub(crate) struct FromMp4Track {
383 pub(crate) sequence_parameter_set: Vec<u8>,
384 pub(crate) picture_parameter_set: Vec<u8>,
385}
386
387pub trait SeekRead: Seek + Read {}
388impl<T> SeekRead for T where T: Seek + Read {}
389
390pub trait SeekableH264Source {
391 type NalLocation;
392 fn nal_boundaries(&mut self) -> &[Self::NalLocation];
393 fn read_nal_units_at_location(&mut self, location: &Self::NalLocation) -> Result<Vec<Vec<u8>>>;
395 fn read_nal_units_at_locations(
397 &mut self,
398 locations: &[Self::NalLocation],
399 ) -> Result<Vec<Vec<u8>>> {
400 let mut result = Vec::with_capacity(locations.len() * 3);
401 for location in locations.iter() {
402 let nal_units = self.read_nal_units_at_location(location)?;
403 result.extend(nal_units);
404 }
405 Ok(result)
406 }
407
408 fn first_sps(&self) -> Option<Vec<u8>>;
410 fn first_pps(&self) -> Option<Vec<u8>>;
412}
413
414#[derive(Debug, PartialEq, Clone)]
415pub struct AnnexBLocation {
416 pub(crate) start: u64,
417 pub(crate) sz: usize,
418}
419
420pub struct H264AnnexBSource {
421 inner: Box<dyn SeekRead + Send>,
422 my_nal_boundaries: Vec<AnnexBLocation>,
423}
424
425impl H264AnnexBSource {
426 pub fn from_file(fd: std::fs::File) -> Result<Self> {
427 let inner = Box::new(BufReader::new(fd));
428 Self::from_readseek(inner)
429 }
430 pub fn from_readseek(mut inner: Box<dyn SeekRead + Send>) -> Result<Self> {
431 inner.seek(std::io::SeekFrom::Start(0))?;
432 let my_nal_boundaries = crate::h264_annexb_splitter::find_nals(&mut inner)?;
433 inner.seek(std::io::SeekFrom::Start(0))?;
434 Ok(Self {
435 inner,
436 my_nal_boundaries,
437 })
438 }
439}
440
441impl SeekableH264Source for H264AnnexBSource {
442 type NalLocation = AnnexBLocation;
443 fn nal_boundaries(&mut self) -> &[Self::NalLocation] {
444 &self.my_nal_boundaries
445 }
446 fn read_nal_units_at_location(&mut self, location: &Self::NalLocation) -> Result<Vec<Vec<u8>>> {
447 self.inner.seek(std::io::SeekFrom::Start(location.start))?;
448 let mut buf = vec![0u8; location.sz];
449 self.inner.read_exact(&mut buf)?;
450 Ok(vec![buf])
451 }
452
453 fn first_sps(&self) -> Option<Vec<u8>> {
454 None
455 }
456 fn first_pps(&self) -> Option<Vec<u8>> {
457 None
458 }
459}
460
461impl<H> H264Source<H>
462where
463 H: SeekableH264Source,
464 <H as SeekableH264Source>::NalLocation: Clone,
465{
466 #[expect(
467 clippy::too_many_arguments,
468 reason = "we grudgingly accept this ugliness"
469 )]
470 pub(crate) fn from_seekable_h264_source_with_timestamp_source(
471 mut seekable_h264_source: H,
472 do_decode_h264: bool,
473 mut mp4_pts: Option<Vec<std::time::Duration>>,
474 mut mp4_sample_timing: Option<Vec<Mp4SampleTiming>>,
475 data_from_mp4_track: Option<FromMp4Track>,
476 timestamp_source: crate::TimestampSource,
477 srt_file_path: Option<std::path::PathBuf>,
478 show_progress: bool,
479 mut preparser: Option<Box<dyn H264Preparser>>,
480 ) -> Result<Self> {
481 let nal_locations: Vec<H::NalLocation> = seekable_h264_source.nal_boundaries().to_vec();
482
483 let mut parsing_ctx = H264ParsingContext::default();
484
485 if timestamp_source == crate::TimestampSource::SrtFile && srt_file_path.is_none() {
487 return Err(Error::NoSrtPathGiven);
488 }
489
490 let mut srt_data = if let Some(srt_file_path) = srt_file_path {
491 let outcome = srt_reader::read_srt_file(&srt_file_path)?;
492 if outcome.stanzas.is_empty() {
493 return Err(Error::SrtParseError {
494 path: srt_file_path,
495 line: outcome.truncated_at_line.unwrap_or(1),
496 });
497 }
498 let frame0_time = SrtData::parse_time(&outcome.stanzas[0]);
499 Some(SrtData {
500 stanzas: outcome.stanzas,
501 idx: 0,
502 frame0_time,
503 truncated_at_line: outcome.truncated_at_line,
504 })
505 } else {
506 None
507 };
508
509 if let Some(dfc) = data_from_mp4_track {
511 tracing::debug!("Using SPS and PPS data from mp4 track.");
512 {
513 let sps_nal = RefNal::new(&dfc.sequence_parameter_set, &[], true);
515 if sps_nal.header().unwrap().nal_unit_type() != UnitType::SeqParameterSet {
516 return Err(Error::ExpectedSpsNotFound);
517 }
518
519 let isps =
520 h264_reader::nal::sps::SeqParameterSet::from_bits(sps_nal.rbsp_bits()).unwrap();
521 if let Some(preparser) = preparser.as_mut() {
522 preparser
523 .put_seq_param_set(&sps_nal)
524 .map_err(Error::PreParserError)?;
525 }
526 parsing_ctx.put_seq_param_set(isps);
527 }
528
529 {
530 let pps_nal = RefNal::new(&dfc.picture_parameter_set, &[], true);
532 if pps_nal.header().unwrap().nal_unit_type() != UnitType::PicParameterSet {
533 return Err(Error::ExpectedPpsNotFound);
534 }
535
536 let ipps = h264_reader::nal::pps::PicParameterSet::from_bits(
537 &parsing_ctx,
538 pps_nal.rbsp_bits(),
539 )
540 .unwrap();
541 if let Some(preparser) = preparser.as_mut() {
542 preparser
543 .put_pic_param_set(&pps_nal)
544 .map_err(Error::PreParserError)?;
545 }
546 parsing_ctx.put_pic_param_set(ipps);
547 }
548 }
549
550 let timing_data = load_timing_data(
552 &nal_locations,
553 &mut seekable_h264_source,
554 &mut parsing_ctx,
555 show_progress,
556 preparser,
557 )?;
558
559 let TimingData {
560 mut frame_time_info,
561 frame0_precision_time,
562 frame0_frameinfo,
563 h264_metadata,
564 tz_offset,
565 } = timing_data;
566
567 let mut widthheight = None;
568 let mut chroma_format = h264_reader::nal::sps::ChromaFormat::YUV420;
569 let mut profile = "Unknown".to_string();
570 for sps in parsing_ctx.sps() {
571 if let Ok(wh) = sps.pixel_dimensions() {
572 widthheight = Some(wh);
573 }
574 chroma_format = sps.chroma_info.chroma_format;
575 profile = format!("{:?}", sps.profile());
576 }
577
578 let (width, height) = widthheight.ok_or_else(|| crate::Error::ExpectedSpsNotFound)?;
579
580 let timezone = tz_offset.unwrap_or_else(|| chrono::FixedOffset::east_opt(0).unwrap());
581
582 let frame0_precision_time = frame0_precision_time
583 .as_ref()
584 .map(|dt| dt.with_timezone(&timezone));
585
586 let (timestamp_source, has_timestamps) = match timestamp_source {
587 crate::TimestampSource::BestGuess => {
588 if frame0_precision_time.is_some() {
589 (Some(crate::TimestampSource::MispMicrosectime), true)
590 } else if frame0_frameinfo.is_some() {
591 (Some(crate::TimestampSource::FrameInfoRtp), true)
592 } else if mp4_pts.is_some() {
593 (Some(crate::TimestampSource::Mp4Pts), true)
594 } else {
595 (None, false)
596 }
597 }
598 crate::TimestampSource::FixedFramerate => (Some(timestamp_source), true),
599 crate::TimestampSource::FrameInfoRecvTime | crate::TimestampSource::FrameInfoRtp => {
600 if frame0_frameinfo.is_none() {
601 return Err(Error::H264TimestampError(
602 "Requested timestamp that requires FrameInfo, but this information is not present."
603 .into(),
604 ));
605 }
606 (Some(timestamp_source), true)
607 }
608 crate::TimestampSource::MispMicrosectime => {
609 if frame0_precision_time.is_none() {
610 return Err(Error::H264TimestampError(
611 "Requested timestamp source MispMicrosectime, but frame0_precision_time not present."
612 .into(),
613 ));
614 }
615 (Some(timestamp_source), true)
616 }
617 crate::TimestampSource::Mp4Pts => {
618 if mp4_pts.is_none() {
619 return Err(Error::H264TimestampError(
620 "Requested timestamp source Mp4Pts, but MP4 PTS not present.".into(),
621 ));
622 }
623 (Some(timestamp_source), true)
624 }
625 crate::TimestampSource::SrtFile => (Some(timestamp_source), true),
626 };
627
628 if let Some(mp4_pts) = mp4_pts.as_ref()
629 && mp4_pts.len() != frame_time_info.len()
630 {
631 return Err(Error::H264TimestampError(format!(
632 "We have {} frames of MP4 PTS timing, but computed {} frames of video.",
633 mp4_pts.len(),
634 frame_time_info.len()
635 )));
636 }
637 let mut presentation_rank = compute_presentation_rank(mp4_pts.as_deref(), &frame_time_info);
643
644 let mut is_idr: Vec<bool> = frame_time_info.iter().map(|fti| fti.is_idr).collect();
645
646 let mut srt_truncation = None;
655 if let Some(srt) = srt_data.as_mut() {
656 let total_frames = frame_time_info.len();
657 let usable_stanzas = srt.stanzas.len();
658 if usable_stanzas < total_frames {
659 let rank = presentation_rank.as_ref().ok_or_else(|| {
660 Error::H264TimestampError(
661 "SRT file has fewer entries than the video has frames, and \
662 presentation order cannot be recovered to safely truncate \
663 (no per-sample PTS and no decodable picture order count)."
664 .to_string(),
665 )
666 })?;
667 let mut gop_starts: Vec<usize> = std::iter::once(0)
668 .chain((0..total_frames).filter(|&i| is_idr[i]).map(|i| rank[i]))
669 .collect();
670 gop_starts.sort_unstable();
671 gop_starts.dedup();
672 let kept_frames = gop_starts
673 .into_iter()
674 .filter(|&g| g <= usable_stanzas)
675 .max()
676 .unwrap_or(0);
677
678 if kept_frames == 0 {
679 return Err(Error::H264TimestampError(format!(
680 "SRT file only has usable timestamps for {usable_stanzas} of \
681 {total_frames} frames, not even one complete group of pictures."
682 )));
683 }
684
685 let keep: Vec<bool> = (0..total_frames).map(|i| rank[i] < kept_frames).collect();
686 retain_by_mask(&mut frame_time_info, &keep);
687 if let Some(pts) = mp4_pts.as_mut() {
688 retain_by_mask(pts, &keep);
689 }
690 if let Some(timing) = mp4_sample_timing.as_mut() {
691 retain_by_mask(timing, &keep);
692 }
693 retain_by_mask(&mut is_idr, &keep);
694 if let Some(rank) = presentation_rank.as_mut() {
695 retain_by_mask(rank, &keep);
696 }
697 srt.stanzas.truncate(kept_frames);
698
699 srt_truncation = Some(SrtTruncation {
700 kept_frames,
701 total_frames,
702 usable_stanzas,
703 malformed_at_line: srt.truncated_at_line,
704 });
705 }
706 }
707
708 let average_fps = calc_avg_fps(&frame_time_info[..]);
709
710 let mp4_sample_timing = match (mp4_sample_timing, srt_data.as_ref()) {
719 (Some(mut timing), Some(srt)) if timing.len() >= 2 => {
720 let source_span: f64 = timing.iter().map(|t| t.decode_duration.as_secs_f64()).sum();
721 let real_span = srt.span().map(|d| d.as_secs_f64()).unwrap_or(0.0);
722 if source_span > 0.0 && real_span > 0.0 {
723 let scale = real_span / source_span;
724 for t in timing.iter_mut() {
725 t.decode_duration = t.decode_duration.mul_f64(scale);
726 let off_ns = t.composition_offset.num_nanoseconds().unwrap_or(0) as f64;
727 t.composition_offset =
728 chrono::Duration::nanoseconds((off_ns * scale) as i64);
729 }
730 }
731 Some(timing)
732 }
733 (other, _) => other,
734 };
735
736 let srt_display_rank = if srt_data.is_some() {
740 presentation_rank.clone()
741 } else {
742 None
743 };
744
745 Ok(Self {
746 seekable_h264_source,
747 nal_locations,
748 mp4_pts,
749 mp4_sample_timing,
750 srt_display_rank,
751 presentation_rank,
752 is_idr,
753 frame_time_info,
754 h264_metadata,
755 frame0_precision_time,
756 frame0_frameinfo,
757 width,
758 height,
759 do_decode_h264,
760 timestamp_source,
761 has_timestamps,
762 srt_data,
763 srt_truncation,
764 average_fps,
765 chroma_format,
766 profile,
767 })
768 }
769}
770
771fn retain_by_mask<T>(items: &mut Vec<T>, keep: &[bool]) {
774 let mut keep = keep.iter();
775 items.retain(|_| *keep.next().unwrap());
776}
777
778fn compute_presentation_rank(
786 mp4_pts: Option<&[std::time::Duration]>,
787 frame_time_info: &[FrameTimeInfo],
788) -> Option<Vec<usize>> {
789 let n = frame_time_info.len();
790 if n == 0 {
791 return Some(Vec::new());
792 }
793 let keys: Vec<(i64, i64)> = if let Some(pts) = mp4_pts {
798 pts.iter().map(|d| (0i64, d.as_nanos() as i64)).collect()
799 } else {
800 if !frame_time_info.iter().all(|fti| fti.poc.is_some()) {
801 return None;
802 }
803 let mut cvs = 0i64;
804 let mut keys = Vec::with_capacity(n);
805 for (i, fti) in frame_time_info.iter().enumerate() {
806 if fti.is_idr && i != 0 {
807 cvs += 1;
808 }
809 keys.push((cvs, fti.poc.unwrap()));
810 }
811 keys
812 };
813 let mut order: Vec<usize> = (0..n).collect();
814 order.sort_by_key(|&i| keys[i]);
815 let mut rank = vec![0usize; n];
816 for (display_rank, &decode_index) in order.iter().enumerate() {
817 rank[decode_index] = display_rank;
818 }
819 Some(rank)
820}
821
822fn calc_avg_fps(fti: &[FrameTimeInfo]) -> Option<f64> {
823 if fti.len() <= 1 {
824 return None;
825 }
826 let frames = (fti.len() - 1) as f64;
827 if let Some(t0) = fti[0].precise_timestamp {
828 let tend = fti[fti.len() - 1].precise_timestamp.unwrap();
830 let secs = (tend - t0).to_std().unwrap().as_secs_f64();
831 Some(frames / secs)
832 } else if let Some(fi) = &fti[0].frameinfo {
833 let t0: chrono::DateTime<chrono::Utc> = fi.recv.into();
835 let tend: chrono::DateTime<chrono::Utc> =
836 fti[fti.len() - 1].frameinfo.as_ref().unwrap().recv.into();
837 let secs = (tend - t0).to_std().unwrap().as_secs_f64();
838 Some(frames / secs)
839 } else {
840 None
842 }
843}
844
845struct TimingData {
846 frame_time_info: Vec<FrameTimeInfo>,
847 frame0_precision_time: Option<DateTime<Utc>>,
848 frame0_frameinfo: Option<FrameInfo>,
849 h264_metadata: Option<H264Metadata>,
850 tz_offset: Option<FixedOffset>,
851}
852
853fn load_timing_data<H>(
854 nal_locations: &[H::NalLocation],
855 seekable_h264_source: &mut H,
856 parsing_ctx: &mut H264ParsingContext,
857 show_progress: bool,
858 mut preparser: Option<Box<dyn H264Preparser>>,
859) -> Result<TimingData>
860where
861 H: SeekableH264Source,
862 <H as SeekableH264Source>::NalLocation: Clone,
863{
864 let mut scratch = Vec::new();
865
866 let mut tz_offset = None;
867
868 let mut h264_metadata = None;
869
870 let mut frame_time_info = Vec::new();
874
875 let mut frame0_precision_time = None;
876 let mut frame0_frameinfo = None;
877
878 tracing::debug!(
879 "Iterating through NAL units at {} locations to load timing data.",
880 nal_locations.len()
881 );
882
883 let mut pb = if show_progress {
884 let style = indicatif::ProgressStyle::with_template(
887 "Iterating NAL units in h264 source {wide_bar} {pos}/{len} ETA: {eta} ",
888 )
889 .unwrap();
890 Some(indicatif::ProgressBar::new(nal_locations.len().try_into().unwrap()).with_style(style))
891 } else {
892 None
893 };
894
895 if let Some(preparser) = preparser.as_mut() {
896 preparser
897 .set_num_positions(nal_locations.len())
898 .map_err(Error::PreParserError)?;
899 }
900 let mut precise_timestamp = None;
902 let mut next_frame_num = 0;
904
905 let mut poc_strategy: Option<PocStrategy> = None;
909
910 let mut frameinfo = None;
913
914 for (nal_location_index, nal_location) in nal_locations.iter().enumerate() {
915 if let Some(preparser) = preparser.as_mut() {
916 preparser
917 .set_position(nal_location_index)
918 .map_err(Error::PreParserError)?;
919 }
920
921 if let Some(pb) = pb.as_mut() {
922 pb.set_position(nal_location_index.try_into().unwrap());
923 }
924
925 let nal_units = seekable_h264_source.read_nal_units_at_location(nal_location)?;
929 for nal_unit in nal_units.iter() {
930 let nal = RefNal::new(nal_unit.as_slice(), &[], true);
934 let nal_unit_type = nal.header().unwrap().nal_unit_type();
935 tracing::trace!("NAL unit location index {nal_location_index}, {nal_unit_type:?}");
936 match nal_unit_type {
937 UnitType::SEI => {
938 if let Some(preparser) = preparser.as_mut() {
939 preparser
940 .put_sei_nalu(&nal)
941 .map_err(Error::PreParserError)?;
942 }
943 let mut sei_reader = SeiReader::from_rbsp_bytes(nal.rbsp_bytes(), &mut scratch);
944 loop {
945 match sei_reader.next() {
946 Ok(Some(sei_message)) => {
947 tracing::trace!("SEI payload type: {:?}", sei_message.payload_type);
948 match &sei_message.payload_type {
949 HeaderType::UserDataUnregistered => {
950 let udu = UserDataUnregistered::read(&sei_message)?;
951 match udu.uuid {
952 &H264_METADATA_UUID => {
953 let md: H264Metadata =
954 serde_json::from_slice(udu.payload)?;
955 if md.version != H264_METADATA_VERSION {
956 return Err(Error::H264Error(
957 "unexpected version in h264 metadata",
958 ));
959 }
960 if h264_metadata.is_some() {
961 return Err(Error::H264Error(
962 "multiple SEI messages, but expected exactly one",
963 ));
964 }
965
966 tracing::debug!("Found H264_METADATA_UUID: {md:?}");
967 tz_offset = Some(*md.creation_time.offset());
968 h264_metadata = Some(md);
969 }
970 X264_UUID => {
971 let payload_str =
972 String::from_utf8_lossy(udu.payload);
973 tracing::trace!(
974 "Ignoring SEI UserDataUnregistered x264 payload: {}",
975 payload_str,
976 );
977 }
978 VIDEOTOOLBOX_UUID => {
979 tracing::trace!(
980 "Ignoring SEI UserDataUnregistered from videotoolbox."
981 );
982 }
983 b"MISPmicrosectime" => {
984 let ts = parse_precision_time(udu.payload)?;
985 tracing::trace!("Found MISPmicrosectime: {ts:?}");
986 precise_timestamp = Some(ts);
987 if next_frame_num == 0 {
988 frame0_precision_time = Some(ts);
989 }
990 }
991 b"strawlab.org/89H" => {
992 let fi: FrameInfo =
993 serde_json::from_slice(udu.payload)?;
994 tracing::trace!("Found 89H FrameInfo: {fi:?}");
995 frameinfo = Some(fi.clone());
996 if next_frame_num == 0 {
997 frame0_frameinfo = Some(fi);
998 }
999 }
1000 _uuid => {
1001 tracing::trace!(
1002 "Ignoring SEI UserDataUnregistered uuid: {}",
1003 uuid::Uuid::from_bytes(*udu.uuid).to_string(),
1004 );
1005 }
1006 }
1007 }
1008 _ => {
1009 }
1011 }
1012 }
1013 Ok(None) => {
1014 break;
1015 }
1016 Err(BitReaderError::ReaderErrorFor(what, io_err)) => {
1017 tracing::error!(
1018 "Ignoring error when reading SEI NAL unit {what}: {io_err:?}"
1019 );
1020 }
1026 Err(e) => {
1027 return Err(Error::H264Nal {
1028 nal_location_index,
1029 e,
1030 });
1031 }
1032 }
1033 }
1034 }
1035 UnitType::SeqParameterSet => {
1036 let isps =
1037 h264_reader::nal::sps::SeqParameterSet::from_bits(nal.rbsp_bits()).unwrap();
1038 if let Some(preparser) = preparser.as_mut() {
1039 preparser
1040 .put_seq_param_set(&nal)
1041 .map_err(Error::PreParserError)?;
1042 }
1043 if poc_strategy.is_none() {
1047 poc_strategy = h264_poc::strategy_from_sps(&isps).ok();
1048 }
1049 parsing_ctx.put_seq_param_set(isps);
1050 }
1051 UnitType::PicParameterSet => {
1052 match h264_reader::nal::pps::PicParameterSet::from_bits(
1053 parsing_ctx,
1054 nal.rbsp_bits(),
1055 ) {
1056 Ok(ipps) => {
1057 if let Some(preparser) = preparser.as_mut() {
1058 preparser
1059 .put_pic_param_set(&nal)
1060 .map_err(Error::PreParserError)?;
1061 }
1062 parsing_ctx.put_pic_param_set(ipps);
1063 }
1064 Err(h264_reader::nal::pps::PpsError::BadPicParamSetId(
1065 h264_reader::nal::pps::PicParamSetIdError::IdTooLarge(_id),
1066 )) => {
1067 }
1070 Err(e) => {
1071 return Err(Error::H264Pps(format!("reading PPS: {e:?}")));
1072 }
1073 }
1074 }
1075 UnitType::SliceLayerWithoutPartitioningIdr
1076 | UnitType::SliceLayerWithoutPartitioningNonIdr => {
1077 let is_i_frame = nal_unit_type == UnitType::SliceLayerWithoutPartitioningIdr;
1078 if let Some(preparser) = preparser.as_mut() {
1079 preparser
1080 .put_slice_layer_nalu(&nal, is_i_frame)
1081 .map_err(Error::PreParserError)?;
1082 }
1083 let poc = poc_strategy.as_mut().and_then(|strategy| {
1087 h264_poc::advance_poc(strategy, parsing_ctx, std::slice::from_ref(nal_unit))
1088 .ok()
1089 });
1090 frame_time_info.push(FrameTimeInfo {
1093 nal_location_index,
1094 precise_timestamp,
1095 frameinfo,
1096 poc,
1097 is_idr: is_i_frame,
1098 });
1099 precise_timestamp = None;
1101 frameinfo = None;
1102 next_frame_num += 1;
1103 }
1104 _nal_unit_type => {}
1105 }
1106 }
1107 }
1108
1109 if let Some(pb) = pb.as_mut() {
1110 pb.finish_and_clear();
1111 }
1112
1113 tracing::debug!("Done iterating through all NAL units.");
1114
1115 Ok(TimingData {
1116 frame_time_info,
1117 frame0_precision_time,
1118 frame0_frameinfo,
1119 h264_metadata,
1120 tz_offset,
1121 })
1122}
1123
1124struct RawH264Iter<'parent, H: SeekableH264Source> {
1125 parent: &'parent mut H264Source<H>,
1126 frame_idx: usize,
1128 openh264_decoder_state: Option<crate::opt_openh264_decoder::DecoderType>,
1129 display_order: Option<Vec<usize>>,
1136 n_pictures_out: usize,
1139 pending_meta: std::collections::HashMap<usize, PendingMeta>,
1144 decoded_ready: std::collections::BTreeMap<usize, FrameData>,
1147 next_emit: usize,
1149 flushed: bool,
1151 poisoned: bool,
1154}
1155
1156#[cfg_attr(not(feature = "openh264"), allow(dead_code))]
1159struct PendingMeta {
1160 timestamp: Timestamp,
1161 poc: Option<i64>,
1162 buf_len: usize,
1163}
1164
1165struct InputFrame {
1168 nal_units: Vec<Vec<u8>>,
1169 timestamp: Timestamp,
1170 poc: Option<i64>,
1171 is_idr: bool,
1172}
1173
1174impl<H: SeekableH264Source> RawH264Iter<'_, H> {
1175 fn read_input_frame(&mut self, frame_number: usize) -> Result<InputFrame> {
1177 let nti = &self.parent.frame_time_info[frame_number];
1178 let start = if frame_number == 0 {
1186 0
1187 } else {
1188 self.parent.frame_time_info[frame_number - 1].nal_location_index + 1
1189 };
1190 let nal_locations = &self.parent.nal_locations[start..=(nti.nal_location_index)];
1191 let mp4_pts = self.parent.mp4_pts.as_ref().map(|x| x[frame_number]); let fraction_done = frame_number as f32 / self.parent.nal_locations.len() as f32;
1193
1194 let frame_timestamp = match self.parent.timestamp_source {
1195 Some(TimestampSource::BestGuess) => unreachable!(),
1196 Some(TimestampSource::MispMicrosectime) => {
1197 let f0 = self.parent.frame0_precision_time.as_ref().unwrap();
1198 Timestamp::Duration(
1199 nti.precise_timestamp
1200 .unwrap()
1201 .signed_duration_since(*f0)
1202 .to_std()
1203 .unwrap(),
1204 )
1205 }
1206 Some(TimestampSource::FrameInfoRecvTime) => {
1207 let t0 = self.parent.frame0_frameinfo.as_ref().unwrap().recv;
1208 let t0: chrono::DateTime<chrono::Utc> = t0.into();
1209 let this_frame: chrono::DateTime<chrono::Utc> =
1210 nti.frameinfo.as_ref().unwrap().recv.into();
1211 Timestamp::Duration(this_frame.signed_duration_since(t0).to_std().unwrap())
1212 }
1213 Some(TimestampSource::FrameInfoRtp) => {
1214 let fi0 = self.parent.frame0_frameinfo.as_ref().unwrap();
1215 let rtp0 = fi0.rtp;
1216 let rtp_now = nti.frameinfo.as_ref().unwrap().rtp;
1217 let rtp_dur = rtp_now.wrapping_sub(rtp0);
1218 let rtp_dur_secs = rtp_dur as f64 / 90000.0; Timestamp::Duration(std::time::Duration::from_secs_f64(rtp_dur_secs))
1220 }
1221 Some(TimestampSource::FixedFramerate) => {
1222 let dur_secs = nti.nal_location_index as f64 / self.parent.average_fps.unwrap();
1223 Timestamp::Duration(std::time::Duration::from_secs_f64(dur_secs))
1224 }
1225 Some(TimestampSource::Mp4Pts) => Timestamp::Duration(mp4_pts.unwrap()),
1226 Some(TimestampSource::SrtFile) => {
1227 let rank_idx = self
1232 .parent
1233 .srt_display_rank
1234 .as_ref()
1235 .map(|rank| rank[frame_number]);
1236 let srt_data = self.parent.srt_data.as_mut().unwrap();
1237 let pts = match rank_idx {
1238 Some(idx) => srt_data.time_at(idx).unwrap(),
1239 None => srt_data.next_pts().unwrap(),
1240 };
1241 Timestamp::Duration(pts)
1242 }
1243 None => Timestamp::Fraction(fraction_done),
1244 };
1245
1246 let nal_units = self
1247 .parent
1248 .seekable_h264_source
1249 .read_nal_units_at_locations(nal_locations)?;
1250
1251 Ok(InputFrame {
1252 nal_units,
1253 timestamp: frame_timestamp,
1254 poc: nti.poc,
1255 is_idr: nti.is_idr,
1256 })
1257 }
1258
1259 fn feed_decoder(&mut self, frame_number: usize) -> Result<()> {
1262 let InputFrame {
1263 nal_units,
1264 timestamp,
1265 poc,
1266 is_idr,
1267 } = self.read_input_frame(frame_number)?;
1268
1269 let annex_b = if is_idr {
1277 let mut prefix = Vec::with_capacity(nal_units.len() + 2);
1278 prefix.extend(self.parent.seekable_h264_source.first_sps());
1279 prefix.extend(self.parent.seekable_h264_source.first_pps());
1280 prefix.extend_from_slice(nal_units.as_slice());
1281 copy_nalus_to_annex_b(&prefix)
1282 } else {
1283 copy_nalus_to_annex_b(nal_units.as_slice())
1284 };
1285
1286 let buf_len = nal_units.iter().map(|x| x.len()).sum();
1287 self.pending_meta.insert(
1288 frame_number,
1289 PendingMeta {
1290 timestamp,
1291 poc,
1292 buf_len,
1293 },
1294 );
1295
1296 let decoder = self.openh264_decoder_state.as_mut().unwrap();
1302 let decode_result = decoder.decode(&annex_b[..]);
1303 #[cfg(feature = "openh264")]
1304 let decode_result = decode_result.map_err(|source| Error::H264DecodeFailed {
1305 frame: frame_number,
1306 hint: decode_failure_hint(self.parent.chroma_format, &self.parent.profile),
1307 source,
1308 });
1309
1310 if let Some(decoded_yuv) = decode_result? {
1315 accept_decoded_picture(
1316 decoded_yuv,
1317 self.display_order.as_deref(),
1318 &mut self.n_pictures_out,
1319 &mut self.pending_meta,
1320 &mut self.decoded_ready,
1321 )?;
1322 }
1323 Ok(())
1324 }
1325
1326 fn drain_decoder(&mut self) -> Result<()> {
1329 let decoder = self.openh264_decoder_state.as_mut().unwrap();
1330 let pictures = decoder.flush_remaining();
1331 #[cfg(feature = "openh264")]
1332 let pictures = pictures.map_err(|source| Error::H264DecodeFailed {
1333 frame: self.next_emit,
1334 hint: decode_failure_hint(self.parent.chroma_format, &self.parent.profile),
1335 source,
1336 });
1337 for decoded_yuv in pictures? {
1338 accept_decoded_picture(
1339 decoded_yuv,
1340 self.display_order.as_deref(),
1341 &mut self.n_pictures_out,
1342 &mut self.pending_meta,
1343 &mut self.decoded_ready,
1344 )?;
1345 }
1346 Ok(())
1347 }
1348
1349 fn next_decoded(&mut self) -> Option<Result<FrameData>> {
1353 loop {
1354 if let Some(frame_data) = self.decoded_ready.remove(&self.next_emit) {
1355 self.next_emit += 1;
1356 return Some(Ok(frame_data));
1357 }
1358 if self.poisoned {
1359 return None;
1360 }
1361 if self.frame_idx < self.parent.frame_time_info.len() {
1362 let frame_number = self.frame_idx;
1363 self.frame_idx += 1;
1364 if let Err(e) = self.feed_decoder(frame_number) {
1365 self.poisoned = true;
1366 return Some(Err(e));
1367 }
1368 } else if !self.flushed {
1369 self.flushed = true;
1370 if let Err(e) = self.drain_decoder() {
1371 self.poisoned = true;
1372 return Some(Err(e));
1373 }
1374 } else {
1375 if !self.pending_meta.is_empty() {
1376 self.poisoned = true;
1377 return Some(Err(Error::H264Error(
1378 "decoder produced fewer pictures than input frames",
1379 )));
1380 }
1381 return None;
1382 }
1383 }
1384 }
1385}
1386
1387impl<H: SeekableH264Source> Iterator for RawH264Iter<'_, H> {
1388 type Item = Result<FrameData>;
1389 fn next(&mut self) -> Option<Self::Item> {
1390 if self.openh264_decoder_state.is_some() {
1391 return self.next_decoded();
1392 }
1393 let frame_number = self.frame_idx;
1394 if frame_number >= self.parent.frame_time_info.len() {
1395 return None;
1396 }
1397 self.frame_idx += 1;
1398
1399 Some(self.read_input_frame(frame_number).map(|input| {
1400 let buf_len = input.nal_units.iter().map(|x| x.len()).sum();
1401 let buf = EncodedH264 {
1402 data: H264EncodingVariant::RawEbsp(input.nal_units),
1403 has_precision_timestamp: self.parent.frame0_precision_time.is_some(),
1404 };
1405 FrameData {
1406 timestamp: input.timestamp,
1407 image: ImageData::EncodedH264(buf),
1408 buf_len,
1409 idx: frame_number,
1410 poc: input.poc,
1411 }
1412 }))
1413 }
1414
1415 fn size_hint(&self) -> (usize, Option<usize>) {
1416 let total = self.parent.frame_time_info.len();
1417 let remaining = if self.openh264_decoder_state.is_some() {
1422 if self.poisoned {
1423 0
1424 } else {
1425 total.saturating_sub(self.next_emit)
1426 }
1427 } else {
1428 total.saturating_sub(self.frame_idx)
1429 };
1430 (remaining, Some(remaining))
1431 }
1432}
1433
1434#[cfg(feature = "openh264")]
1441fn accept_decoded_picture(
1442 decoded_yuv: openh264::decoder::DecodedYUV<'_>,
1443 display_order: Option<&[usize]>,
1444 n_pictures_out: &mut usize,
1445 pending_meta: &mut std::collections::HashMap<usize, PendingMeta>,
1446 decoded_ready: &mut std::collections::BTreeMap<usize, FrameData>,
1447) -> Result<()> {
1448 let display_rank = *n_pictures_out;
1449 *n_pictures_out += 1;
1450 let decode_idx = match display_order {
1451 Some(order) => *order.get(display_rank).ok_or(Error::H264Error(
1452 "decoder produced more pictures than input frames",
1453 ))?,
1454 None => display_rank,
1455 };
1456 let meta = pending_meta.remove(&decode_idx).ok_or(Error::H264Error(
1457 "decoder output picture does not correspond to a pending input frame",
1458 ))?;
1459 let frame_data = yuv2rgb(
1460 decoded_yuv,
1461 decode_idx,
1462 meta.poc,
1463 meta.buf_len,
1464 meta.timestamp,
1465 )?;
1466 decoded_ready.insert(decode_idx, frame_data);
1467 Ok(())
1468}
1469
1470#[cfg(not(feature = "openh264"))]
1471fn accept_decoded_picture(
1472 _decoded_yuv: (),
1473 _display_order: Option<&[usize]>,
1474 _n_pictures_out: &mut usize,
1475 _pending_meta: &mut std::collections::HashMap<usize, PendingMeta>,
1476 _decoded_ready: &mut std::collections::BTreeMap<usize, FrameData>,
1477) -> Result<()> {
1478 Err(Error::H264Error("No H264 decoder support at compile time"))
1479}
1480
1481struct PresentationReorderIter<'a> {
1495 inner: Box<dyn Iterator<Item = Result<FrameData>> + 'a>,
1496 rank: Vec<usize>,
1498 is_idr: Vec<bool>,
1500 total: usize,
1502 pending: Vec<FrameData>,
1504 ready: std::collections::VecDeque<FrameData>,
1506 done: bool,
1508}
1509
1510impl PresentationReorderIter<'_> {
1511 fn flush(&mut self) {
1515 let rank = &self.rank;
1516 self.pending
1517 .sort_by_key(|f| rank.get(f.idx()).copied().unwrap_or(usize::MAX));
1518 let denom = self.total.max(1) as f32;
1519 for mut frame in self.pending.drain(..).collect::<Vec<_>>() {
1520 if let Timestamp::Fraction(_) = frame.timestamp {
1521 let pos = self.rank.get(frame.idx()).copied().unwrap_or(0);
1522 frame.timestamp = Timestamp::Fraction(pos as f32 / denom);
1523 }
1524 self.ready.push_back(frame);
1525 }
1526 }
1527}
1528
1529impl Iterator for PresentationReorderIter<'_> {
1530 type Item = Result<FrameData>;
1531 fn next(&mut self) -> Option<Self::Item> {
1532 loop {
1533 if let Some(frame) = self.ready.pop_front() {
1534 return Some(Ok(frame));
1535 }
1536 if self.done {
1537 return None;
1538 }
1539 match self.inner.next() {
1540 Some(Ok(frame)) => {
1541 if self.is_idr.get(frame.idx()).copied().unwrap_or(false)
1544 && !self.pending.is_empty()
1545 {
1546 self.flush();
1547 }
1548 self.pending.push(frame);
1549 }
1550 Some(Err(e)) => {
1551 self.done = true;
1552 self.pending.clear();
1553 return Some(Err(e));
1554 }
1555 None => {
1556 self.done = true;
1557 self.flush();
1558 }
1559 }
1560 }
1561 }
1562
1563 fn size_hint(&self) -> (usize, Option<usize>) {
1564 let (lo, hi) = self.inner.size_hint();
1567 let buffered = self.ready.len() + self.pending.len();
1568 (lo + buffered, hi.map(|h| h + buffered))
1569 }
1570}
1571
1572#[cfg(feature = "openh264")]
1578fn decode_failure_hint(chroma: h264_reader::nal::sps::ChromaFormat, profile: &str) -> String {
1579 use h264_reader::nal::sps::ChromaFormat::*;
1580 let feature = match chroma {
1581 YUV420 => return String::new(),
1582 Monochrome => "4:0:0 (monochrome) chroma subsampling".to_string(),
1583 YUV422 => "4:2:2 chroma subsampling".to_string(),
1584 YUV444 => "4:4:4 chroma subsampling".to_string(),
1585 Invalid(idc) => format!("unknown chroma subsampling (idc={idc})"),
1586 };
1587 format!(
1588 " This stream uses {feature} (profile {profile}), which the built-in OpenH264 decoder \
1589 does not support — it decodes only 4:2:0 (YUV420). Re-encode first, \
1590 e.g. `ffmpeg -i INPUT -c:v libx264 -pix_fmt yuv420p OUTPUT.mp4`."
1591 )
1592}
1593
1594#[cfg(feature = "openh264")]
1595fn yuv2rgb(
1596 decoded_yuv: openh264::decoder::DecodedYUV<'_>,
1597 frame_number: usize,
1598 poc: Option<i64>,
1599 buf_len: usize,
1600 timestamp: Timestamp,
1601) -> Result<FrameData> {
1602 use openh264::formats::YUVSource;
1603 let dim = decoded_yuv.dimensions();
1604
1605 let stride = dim.0 * 3;
1606 let mut image_data = vec![0u8; stride * dim.1];
1607 decoded_yuv.write_rgb8(&mut image_data);
1608
1609 let dynamic_frame = strand_dynamic_frame::DynamicFrameOwned::from_static(
1610 OImage::<machine_vision_formats::pixel_format::RGB8>::new(
1611 dim.0.try_into().unwrap(),
1612 dim.1.try_into().unwrap(),
1613 stride,
1614 image_data,
1615 )
1616 .unwrap(),
1617 );
1618
1619 let idx = frame_number;
1620 let image = ImageData::Decoded(dynamic_frame);
1621 Ok(FrameData {
1622 timestamp,
1623 image,
1624 buf_len,
1625 idx,
1626 poc,
1627 })
1628}
1629
1630pub(crate) fn from_annexb_path_with_timestamp_source<P: AsRef<Path>>(
1631 path: P,
1632 do_decode_h264: bool,
1633 timestamp_source: crate::TimestampSource,
1634 srt_file_path: Option<std::path::PathBuf>,
1635 show_progress: bool,
1636) -> Result<H264Source<H264AnnexBSource>> {
1637 let rdr = std::fs::File::open(path.as_ref())?;
1638 let seekable_h264_source = H264AnnexBSource::from_file(rdr)?;
1639 from_annexb_reader_with_timestamp_source(
1640 seekable_h264_source,
1641 do_decode_h264,
1642 timestamp_source,
1643 srt_file_path,
1644 show_progress,
1645 )
1646}
1647
1648fn from_annexb_reader_with_timestamp_source(
1649 annex_b_source: H264AnnexBSource,
1650 do_decode_h264: bool,
1651 timestamp_source: crate::TimestampSource,
1652 srt_file_path: Option<std::path::PathBuf>,
1653 show_progress: bool,
1654) -> Result<H264Source<H264AnnexBSource>> {
1655 H264Source::from_seekable_h264_source_with_timestamp_source(
1656 annex_b_source,
1657 do_decode_h264,
1658 None, None, None, timestamp_source,
1662 srt_file_path,
1663 show_progress,
1664 None,
1665 )
1666}
1667
1668pub(crate) struct UserDataUnregistered<'a> {
1669 pub uuid: &'a [u8; 16],
1670 pub payload: &'a [u8],
1671}
1672
1673impl<'a> UserDataUnregistered<'a> {
1674 pub fn read(msg: &SeiMessage<'a>) -> Result<UserDataUnregistered<'a>> {
1675 if msg.payload_type != HeaderType::UserDataUnregistered {
1676 return Err(Error::UduError(format!(
1677 "expected UserDataUnregistered message, found {:?}",
1678 msg.payload_type
1679 )));
1680 }
1681 if msg.payload.len() < 16 {
1682 return Err(Error::UduError(
1683 "SEI payload too short to contain UserDataUnregistered message".to_string(),
1684 ));
1685 }
1686 let uuid = (&msg.payload[0..16]).try_into().unwrap();
1687
1688 let payload = &msg.payload[16..];
1689 Ok(UserDataUnregistered { uuid, payload })
1690 }
1691}
1692
1693pub(crate) fn parse_precision_time(payload: &[u8]) -> Result<chrono::DateTime<chrono::Utc>> {
1694 if payload.len() != 12 {
1695 return Err(Error::UnexpectedPayloadLength);
1696 }
1697
1698 let mut precision_time_stamp_bytes = [0u8; 8];
1711 for i in &[3, 6, 9] {
1712 if payload[*i] != 0xFF {
1713 return Err(Error::UnexpectedStartCodeByte);
1714 }
1715 }
1716 precision_time_stamp_bytes[0..2].copy_from_slice(&payload[1..3]);
1717 precision_time_stamp_bytes[2..4].copy_from_slice(&payload[4..6]);
1718 precision_time_stamp_bytes[4..6].copy_from_slice(&payload[7..9]);
1719 precision_time_stamp_bytes[6..8].copy_from_slice(&payload[10..12]);
1720 let precision_time_stamp: i64 = i64::from_be_bytes(precision_time_stamp_bytes);
1721 let dur = chrono::Duration::microseconds(precision_time_stamp);
1722
1723 let epoch_start = chrono::NaiveDate::from_ymd_opt(1970, 1, 1)
1724 .unwrap()
1725 .and_hms_micro_opt(0, 0, 0, 0)
1726 .unwrap()
1727 .and_local_timezone(chrono::Utc)
1728 .unwrap();
1729
1730 Ok(epoch_start + dur)
1731}
1732
1733fn copy_nalus_to_annex_b(nalus: &[Vec<u8>]) -> Vec<u8> {
1735 let sz = nalus.iter().fold(0, |acc, x| acc + x.len() + 4);
1736 let mut result = vec![0u8; sz];
1737 let mut start_idx = 0;
1738 for src in nalus.iter() {
1739 let dest = &mut result[start_idx..start_idx + 4 + src.len()];
1740 dest[3] = 0x01;
1741 dest[4..].copy_from_slice(src);
1742 start_idx += src.len() + 4;
1743 }
1744 result
1745}
1746
1747#[derive(Debug, Clone, Serialize, Deserialize)]
1751struct FrameInfo {
1752 recv: NtpTimestamp,
1754 rtp: u32,
1756}
1757
1758#[cfg(test)]
1761mod test {
1762 #[cfg(feature = "openh264")]
1766 #[test]
1767 fn decode_failure_hint_flags_features() {
1768 use super::decode_failure_hint;
1769 use h264_reader::nal::sps::ChromaFormat::*;
1770
1771 fn features(chroma: h264_reader::nal::sps::ChromaFormat) -> String {
1773 let h = decode_failure_hint(chroma, "P");
1774 if h.is_empty() {
1775 return String::new();
1776 }
1777 let start = h.find("uses ").unwrap() + "uses ".len();
1778 let end = h.find(" (profile").unwrap();
1779 h[start..end].to_string()
1780 }
1781
1782 assert!(decode_failure_hint(YUV420, "High").is_empty());
1784
1785 assert_eq!(features(YUV444), "4:4:4 chroma subsampling");
1787 assert_eq!(features(YUV422), "4:2:2 chroma subsampling");
1788 }
1789
1790 #[cfg(feature = "openh264")]
1791 #[test]
1792 fn parse_h264() -> crate::Result<()> {
1793 use super::*;
1794
1795 {
1796 let file_buf = include_bytes!("test-data/test_less-avc_mono8_15x14.h264");
1797 let cursor = std::io::Cursor::new(file_buf);
1798 let seekable_h264_source = H264AnnexBSource::from_readseek(Box::new(cursor))?;
1799
1800 let do_decode_h264 = true;
1801 let mut h264_src = from_annexb_reader_with_timestamp_source(
1802 seekable_h264_source,
1803 do_decode_h264,
1804 TimestampSource::BestGuess,
1805 None,
1806 false,
1807 )?;
1808 assert_eq!(h264_src.width(), 15);
1809 assert_eq!(h264_src.height(), 14);
1810 let frames: Vec<_> = h264_src.decode_order_iter().collect();
1811 assert_eq!(frames.len(), 1);
1812 }
1813
1814 {
1815 let file_buf = include_bytes!("test-data/test_less-avc_rgb8_16x16.h264");
1816 let cursor = std::io::Cursor::new(file_buf);
1817 let seekable_h264_source = H264AnnexBSource::from_readseek(Box::new(cursor))?;
1818 let do_decode_h264 = true;
1819 let mut h264_src = from_annexb_reader_with_timestamp_source(
1820 seekable_h264_source,
1821 do_decode_h264,
1822 TimestampSource::BestGuess,
1823 None,
1824 false,
1825 )?;
1826 assert_eq!(h264_src.width(), 16);
1827 assert_eq!(h264_src.height(), 16);
1828 let frames: Vec<_> = h264_src.decode_order_iter().collect();
1829 assert_eq!(frames.len(), 1);
1830 }
1831 Ok(())
1832 }
1833}