mp4_atom/
prft.rs

1use crate::*;
2
3// ProducerReferenceTimeBox, ISO/IEC 14496-12 Section 8.16.5
4// This is called out in CMAF (23000-19) and DASH (23009-1), optional.
5
6ext! {
7    name: Prft,
8    versions: [0, 1],
9    flags: {
10        output_time = 0,
11        fragment_finalised = 1,
12        fragment_written = 2,
13        consistent_offset = 3,
14        real_time = 4,
15    }
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub enum ReferenceTime {
21    /// The UTC time is the time at which the frame belonging to
22    /// the reference track in the following movie fragment and
23    /// whose presentation time is `media_time` was input to the encoder.
24    #[default]
25    Input,
26
27    /// The UTC time is the time at which the frame belonging to
28    /// the reference track in the following movie fragment and
29    /// whose presentation time is `media_time` was output from the encoder.
30    Output,
31
32    /// The UTC time is the time at which the following `MovieFragmentBox`
33    /// was finalized. `media_time` is set to the presentation of
34    /// the earliest frame of the reference track in presentation order
35    /// of the movie fragment.
36    Finalised,
37
38    /// The UTC time is the time at which the following `MovieFragmentBox`
39    /// was written to file. `media_time` is set to the presentation of
40    /// the earliest frame of the reference track in presentation order
41    /// of the movie fragment.
42    Written,
43
44    /// The association between the `media_time` and UTC time is arbitrary
45    /// but consistent between  multiple occurrences of this box in the same track.
46    Consistent,
47
48    /// The UTC time has a consistent, small (ideally zero), offset from the
49    /// real-time of the experience depicted in the media at `media_time`.
50    RealTime,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Default)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct Prft {
56    pub reference_track_id: u32,
57    pub ntp_timestamp: u64,
58    pub media_time: u64,
59    pub utc_time_semantics: ReferenceTime,
60}
61
62impl AtomExt for Prft {
63    type Ext = PrftExt;
64
65    const KIND_EXT: FourCC = FourCC::new(b"prft");
66
67    fn decode_body_ext<B: Buf>(buf: &mut B, ext: PrftExt) -> Result<Self> {
68        let reference_track_id = u32::decode(buf)?;
69        let ntp_timestamp = u64::decode(buf)?;
70        let utc_time_semantics = if ext.real_time && ext.consistent_offset {
71            ReferenceTime::RealTime
72        } else if ext.consistent_offset {
73            ReferenceTime::Consistent
74        } else if ext.fragment_written {
75            ReferenceTime::Written
76        } else if ext.fragment_finalised {
77            ReferenceTime::Finalised
78        } else if ext.output_time {
79            ReferenceTime::Output
80        } else {
81            // fallback
82            ReferenceTime::Input
83        };
84        if ext.version == PrftVersion::V0 {
85            Ok(Prft {
86                reference_track_id,
87                ntp_timestamp,
88                media_time: u32::decode(buf)?.into(),
89                utc_time_semantics,
90            })
91        } else {
92            Ok(Prft {
93                reference_track_id,
94                ntp_timestamp,
95                media_time: u64::decode(buf)?,
96                utc_time_semantics,
97            })
98        }
99    }
100
101    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<PrftExt> {
102        self.reference_track_id.encode(buf)?;
103        self.ntp_timestamp.encode(buf)?;
104        let (output_time, fragment_finalised, fragment_written, consistent_offset, real_time) =
105            match self.utc_time_semantics {
106                ReferenceTime::Input => (false, false, false, false, false),
107                ReferenceTime::Output => (true, false, false, false, false),
108                ReferenceTime::Finalised => (false, true, false, false, false),
109                ReferenceTime::Written => (false, false, true, false, false),
110                ReferenceTime::Consistent => (false, false, false, true, false),
111                ReferenceTime::RealTime => (false, false, false, true, true),
112            };
113        if self.media_time <= u32::MAX.into() {
114            (self.media_time as u32).encode(buf)?;
115            Ok(PrftExt {
116                version: PrftVersion::V0,
117                output_time,
118                fragment_finalised,
119                fragment_written,
120                consistent_offset,
121                real_time,
122            })
123        } else {
124            self.media_time.encode(buf)?;
125            Ok(PrftExt {
126                version: PrftVersion::V1,
127                output_time,
128                fragment_finalised,
129                fragment_written,
130                consistent_offset,
131                real_time,
132            })
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    // From MPEG File Format Conformance suite: 21_segment.mp4
142    const ENCODED_PRFT: &[u8] = &[
143        0x00, 0x00, 0x00, 0x20, 0x70, 0x72, 0x66, 0x74, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
144        0x01, 0xda, 0x74, 0xca, 0x46, 0x6b, 0xc6, 0xa7, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
145        0xff, 0xf8,
146    ];
147
148    // Decoded values per 21_segment_gpac.json
149    const DECODED_PRFT: Prft = Prft {
150        reference_track_id: 1,
151        ntp_timestamp: 15741429001371428847,
152        media_time: 18446744073709551608,
153        utc_time_semantics: ReferenceTime::Input,
154    };
155
156    #[test]
157    fn test_prft_v1_decode() {
158        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_PRFT);
159        let prft = Prft::decode(buf).expect("failed to decode prft");
160        assert_eq!(prft, DECODED_PRFT);
161    }
162
163    #[test]
164    fn test_prft_v1_encode() {
165        let mut buf = Vec::new();
166        DECODED_PRFT.encode(&mut buf).unwrap();
167
168        assert_eq!(buf.as_slice(), ENCODED_PRFT);
169    }
170
171    #[test]
172    fn test_prft_v0_round_trip() {
173        let mut buf = Vec::new();
174        let prft = Prft {
175            reference_track_id: 7,
176            ntp_timestamp: 15741429001371428847,
177            media_time: u32::MAX.into(),
178            utc_time_semantics: ReferenceTime::Written,
179        };
180        prft.encode(&mut buf).unwrap();
181        assert_eq!(
182            buf.as_slice(),
183            &[
184                0x00, 0x00, 0x00, 0x1C, 0x70, 0x72, 0x66, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
185                0x00, 0x07, 0xda, 0x74, 0xca, 0x46, 0x6b, 0xc6, 0xa7, 0xef, 0xff, 0xff, 0xff, 0xff
186            ]
187        );
188
189        let decoded = Prft::decode(&mut buf.as_ref()).unwrap();
190        assert_eq!(decoded, prft);
191    }
192
193    #[test]
194    fn test_prft_realtime_roundtrip() {
195        let mut buf = Vec::new();
196        let prft = Prft {
197            reference_track_id: 1,
198            ntp_timestamp: 16571585696146385000,
199            media_time: 41234604048,
200            utc_time_semantics: ReferenceTime::RealTime,
201        };
202        prft.encode(&mut buf).unwrap();
203        assert_eq!(
204            buf.as_slice(),
205            &[
206                0x00, 0x00, 0x00, 0x20, 0x70, 0x72, 0x66, 0x74, 0x01, 0x00, 0x00, 0x18, 0x00, 0x00,
207                0x00, 0x01, 0xe5, 0xfa, 0x19, 0x63, 0xff, 0xbf, 0xe8, 0x68, 0x00, 0x00, 0x00, 0x09,
208                0x99, 0xc6, 0x20, 0x10
209            ]
210        );
211
212        let decoded = Prft::decode(&mut buf.as_ref()).unwrap();
213        assert_eq!(decoded, prft);
214    }
215}