@@ -137,6 +137,17 @@ impl Mp4 {
137137 let mut ctts_run_index = -1i64 ;
138138 let mut dts_shift = 0 ;
139139
140+ // The smallest presentation timestamp observed in this stream.
141+ //
142+ // This is typically 0, but in the presence of sample reordering (caused by AVC/HVC b-frames), it may be non-zero.
143+ // In fact, many formats don't require this to be zero, but video players typically
144+ // normalize the shown time to start at zero.
145+ // This is roughly equivalent to FFmpeg's internal `min_corrected_pts`
146+ // https://github.com/FFmpeg/FFmpeg/blob/4047b887fc44b110bccb1da09bcb79d6e454b88b/libavformat/isom.h#L202
147+ // To learn more about this I recommend reading the patch that introduced this in FFmpeg:
148+ // https://patchwork.ffmpeg.org/project/ffmpeg/patch/[email protected] /#12592 149+ let mut min_composition_timestamp = i64:: MAX ;
150+
140151 let mut samples = Vec :: < Sample > :: new ( ) ;
141152
142153 fn get_sample_chunk_offset ( stbl : & StblBox , chunk_index : u64 ) -> u64 {
@@ -231,6 +242,7 @@ impl Mp4 {
231242 } else {
232243 decode_timestamp
233244 } ;
245+ min_composition_timestamp = min_composition_timestamp. min ( composition_timestamp) ;
234246
235247 let is_sync = if let Some ( stss) = & stbl. stss {
236248 if last_stss_index < stss. entries . len ( )
@@ -271,6 +283,15 @@ impl Mp4 {
271283 }
272284 }
273285
286+ // Shift both DTS & CTS by the smallest CTS.
287+ // For details, see declaration of `min_composition_timestamp` above.
288+ if min_composition_timestamp != 0 {
289+ for sample in & mut samples {
290+ sample. decode_timestamp -= min_composition_timestamp;
291+ sample. composition_timestamp -= min_composition_timestamp;
292+ }
293+ }
294+
274295 tracks. insert (
275296 trak. tkhd . track_id ,
276297 Track {
@@ -524,10 +545,16 @@ pub struct Sample {
524545
525546 /// Timestamp of the sample at which it should be decoded,
526547 /// in time units.
548+ ///
549+ /// This is offsetted:
550+ /// * with decode timestamp shift determined from negative sample offsets
551+ /// * such that the first [`Self::composition_timestamp`] is zero.
527552 pub decode_timestamp : i64 ,
528553
529554 /// Timestamp of the sample at which the sample should be displayed,
530555 /// in time units.
556+ ///
557+ /// This is offsetted such that the first composition timestamp is zero.
531558 pub composition_timestamp : i64 ,
532559
533560 /// Duration of the sample in time units.
0 commit comments