unbundle/packet_iterator.rs
1//! Raw packet-level iteration.
2//!
3//! This module provides [`PacketIterator`] for iterating over the
4//! demuxed packets of a media file without decoding. Each yielded
5//! [`PacketInfo`] carries the stream index, PTS, DTS, size and keyframe
6//! flag of a single packet.
7//!
8//! # Example
9//!
10//! ```no_run
11//! use unbundle::{MediaFile, UnbundleError};
12//!
13//! let mut unbundler = MediaFile::open("input.mp4")?;
14//! let iter = unbundler.packet_iter()?;
15//! for info in iter {
16//! let pkt = info?;
17//! if pkt.is_keyframe {
18//! println!("Keyframe at PTS {:?} in stream {}", pkt.pts, pkt.stream_index);
19//! }
20//! }
21//! # Ok::<(), UnbundleError>(())
22//! ```
23
24use std::time::Duration;
25
26use ffmpeg_next::{Error as FfmpegError, Packet, Rational};
27
28use crate::error::UnbundleError;
29use crate::unbundle::MediaFile;
30
31/// Metadata for a single demuxed packet.
32#[derive(Debug, Clone)]
33pub struct PacketInfo {
34 /// The stream index this packet belongs to.
35 pub stream_index: usize,
36 /// Presentation timestamp (if available).
37 pub pts: Option<i64>,
38 /// Decoding timestamp (if available).
39 pub dts: Option<i64>,
40 /// Presentation timestamp converted to [`Duration`] using the stream's
41 /// time base. `None` if no PTS is present.
42 pub pts_duration: Option<Duration>,
43 /// Packet payload size in bytes.
44 pub size: usize,
45 /// Whether this packet is a keyframe / sync point.
46 pub is_keyframe: bool,
47 /// The stream's time base numerator / denominator.
48 pub time_base: Rational,
49}
50
51/// A lazy iterator over demuxed packets.
52///
53/// Packets are read one at a time without decoding. The iterator
54/// borrows the underlying [`MediaFile`] mutably.
55pub struct PacketIterator<'a> {
56 unbundler: &'a mut MediaFile,
57 /// Per-stream time bases, indexed by stream index.
58 time_bases: Vec<Rational>,
59 done: bool,
60}
61
62impl<'a> PacketIterator<'a> {
63 /// Create a new packet iterator over all streams.
64 pub(crate) fn new(unbundler: &'a mut MediaFile) -> Self {
65 log::debug!("Creating PacketIterator");
66 let time_bases: Vec<Rational> = unbundler
67 .input_context
68 .streams()
69 .map(|s| s.time_base())
70 .collect();
71
72 Self {
73 unbundler,
74 time_bases,
75 done: false,
76 }
77 }
78}
79
80impl<'a> Iterator for PacketIterator<'a> {
81 type Item = Result<PacketInfo, UnbundleError>;
82
83 fn next(&mut self) -> Option<Self::Item> {
84 if self.done {
85 return None;
86 }
87
88 let mut packet = Packet::empty();
89 match packet.read(&mut self.unbundler.input_context) {
90 Ok(()) => {
91 let stream_index = packet.stream() as usize;
92 let time_base = self
93 .time_bases
94 .get(stream_index)
95 .copied()
96 .unwrap_or(Rational::new(1, 90_000));
97
98 let pts = packet.pts();
99 let dts = packet.dts();
100 let pts_duration = pts.map(|p| {
101 let seconds = p as f64 * time_base.numerator() as f64
102 / time_base.denominator().max(1) as f64;
103 Duration::from_secs_f64(seconds.max(0.0))
104 });
105
106 let is_keyframe = packet.is_key();
107 let size = packet.size();
108
109 Some(Ok(PacketInfo {
110 stream_index,
111 pts,
112 dts,
113 pts_duration,
114 size,
115 is_keyframe,
116 time_base,
117 }))
118 }
119 Err(FfmpegError::Eof) => {
120 self.done = true;
121 None
122 }
123 Err(e) => {
124 self.done = true;
125 Some(Err(UnbundleError::from(e)))
126 }
127 }
128 }
129}