@@ -9,6 +9,7 @@ pub mod mp4;
9
9
10
10
use std:: { collections:: BTreeMap , ops:: Range } ;
11
11
12
+ use bit_vec:: BitVec ;
12
13
use itertools:: Itertools as _;
13
14
14
15
use super :: { Time , Timescale } ;
@@ -89,6 +90,11 @@ pub struct SamplesStatistics {
89
90
///
90
91
/// If true, the video typically has no B-frames as those require frame reordering.
91
92
pub dts_always_equal_pts : bool ,
93
+
94
+ /// If `dts_always_equal_pts` is false, then this gives for each sample whether its PTS is the highest seen so far.
95
+ /// If `dts_always_equal_pts` is true, then this is left as `None`.
96
+ /// This is used for optimizing PTS search.
97
+ pub has_sample_highest_pts_so_far : Option < BitVec > ,
92
98
}
93
99
94
100
impl SamplesStatistics {
@@ -104,9 +110,25 @@ impl SamplesStatistics {
104
110
. iter ( )
105
111
. all ( |s| s. decode_timestamp == s. presentation_timestamp ) ;
106
112
113
+ let mut biggest_pts_so_far = Time :: MIN ;
114
+ let has_sample_highest_pts_so_far = ( !dts_always_equal_pts) . then ( || {
115
+ samples
116
+ . iter ( )
117
+ . map ( move |sample| {
118
+ if sample. presentation_timestamp > biggest_pts_so_far {
119
+ biggest_pts_so_far = sample. presentation_timestamp ;
120
+ true
121
+ } else {
122
+ false
123
+ }
124
+ } )
125
+ . collect ( )
126
+ } ) ;
127
+
107
128
Self {
108
129
minimum_presentation_timestamp,
109
130
dts_always_equal_pts,
131
+ has_sample_highest_pts_so_far,
110
132
}
111
133
}
112
134
}
@@ -296,34 +318,83 @@ impl VideoData {
296
318
} )
297
319
}
298
320
299
- /// For a given decode (!) timestamp, returns the index of the latest sample whose
321
+ /// For a given decode (!) timestamp, returns the index of the first sample whose
300
322
/// decode timestamp is lesser than or equal to the given timestamp.
301
- pub fn latest_sample_index_at_decode_timestamp ( & self , decode_time : Time ) -> Option < usize > {
302
- latest_at_idx (
303
- & self . samples ,
304
- |sample| sample. decode_timestamp ,
305
- & decode_time,
306
- )
323
+ fn latest_sample_index_at_decode_timestamp (
324
+ samples : & [ Sample ] ,
325
+ decode_time : Time ,
326
+ ) -> Option < usize > {
327
+ latest_at_idx ( samples, |sample| sample. decode_timestamp , & decode_time)
307
328
}
308
329
309
- /// For a given presentation timestamp, return the index of the latest sample
310
- /// whose presentation timestamp is lesser than or equal to the given timestamp.
311
- pub fn latest_sample_index_at_presentation_timestamp (
312
- & self ,
330
+ /// See [`Self::latest_sample_index_at_presentation_timestamp`], split out for testing purposes.
331
+ fn latest_sample_index_at_presentation_timestamp_internal (
332
+ samples : & [ Sample ] ,
333
+ sample_statistics : & SamplesStatistics ,
313
334
presentation_timestamp : Time ,
314
335
) -> Option < usize > {
315
336
// Find the latest sample where `decode_timestamp <= presentation_timestamp`.
316
337
// Because `decode <= presentation`, we never have to look further backwards in the
317
338
// video than this.
318
339
let decode_sample_idx =
319
- self . latest_sample_index_at_decode_timestamp ( presentation_timestamp) ?;
340
+ Self :: latest_sample_index_at_decode_timestamp ( samples, presentation_timestamp) ?;
341
+
342
+ // It's very common that dts==pts in which case we're done!
343
+ let Some ( has_sample_highest_pts_so_far) =
344
+ sample_statistics. has_sample_highest_pts_so_far . as_ref ( )
345
+ else {
346
+ debug_assert ! ( !sample_statistics. dts_always_equal_pts) ;
347
+ return Some ( decode_sample_idx) ;
348
+ } ;
349
+ debug_assert ! ( has_sample_highest_pts_so_far. len( ) == samples. len( ) ) ;
320
350
321
351
// Search backwards, starting at `decode_sample_idx`, looking for
322
352
// the first sample where `sample.presentation_timestamp <= presentation_timestamp`.
323
- // this is the sample which when decoded will be presented at the timestamp the user requested.
324
- self . samples [ ..=decode_sample_idx]
325
- . iter ( )
326
- . rposition ( |sample| sample. presentation_timestamp <= presentation_timestamp)
353
+ // I.e. the sample with the biggest PTS that is smaller or equal to the requested PTS.
354
+ //
355
+ // The tricky part is that we can't just take the first sample with a presentation timestamp that matches
356
+ // since smaller presentation timestamps may still show up further back!
357
+ let mut best_index = usize:: MAX ;
358
+ let mut best_pts = Time :: MIN ;
359
+ for sample_idx in ( 0 ..=decode_sample_idx) . rev ( ) {
360
+ let sample = & samples[ sample_idx] ;
361
+
362
+ if sample. presentation_timestamp == presentation_timestamp {
363
+ // Clean hit. Take this one, no questions asked :)
364
+ // (assuming that each PTS is unique!)
365
+ return Some ( sample_idx) ;
366
+ }
367
+
368
+ if sample. presentation_timestamp < presentation_timestamp
369
+ && sample. presentation_timestamp > best_pts
370
+ {
371
+ best_pts = sample. presentation_timestamp ;
372
+ best_index = sample_idx;
373
+ }
374
+
375
+ if best_pts != Time :: MIN && has_sample_highest_pts_so_far[ sample_idx] {
376
+ // We won't see any bigger PTS values anymore, meaning we're as close as we can get to the requested PTS!
377
+ return Some ( best_index) ;
378
+ }
379
+ }
380
+
381
+ None
382
+ }
383
+
384
+ /// For a given presentation timestamp, return the index of the first sample
385
+ /// whose presentation timestamp is lesser than or equal to the given timestamp.
386
+ ///
387
+ /// Remember that samples after (i.e. with higher index) may have a *lower* presentation time
388
+ /// if the stream has sample reordering!
389
+ pub fn latest_sample_index_at_presentation_timestamp (
390
+ & self ,
391
+ presentation_timestamp : Time ,
392
+ ) -> Option < usize > {
393
+ Self :: latest_sample_index_at_presentation_timestamp_internal (
394
+ & self . samples ,
395
+ & self . samples_statistics ,
396
+ presentation_timestamp,
397
+ )
327
398
}
328
399
329
400
/// For a given decode (!) timestamp, return the index of the group of pictures (GOP) index containing the given timestamp.
@@ -553,4 +624,102 @@ mod tests {
553
624
assert_eq ! ( latest_at_idx( & v, |v| * v, & 11 ) , Some ( 9 ) ) ;
554
625
assert_eq ! ( latest_at_idx( & v, |v| * v, & 1000 ) , Some ( 9 ) ) ;
555
626
}
627
+
628
+ #[ test]
629
+ fn test_latest_sample_index_at_presentation_timestamp ( ) {
630
+ // This is a snippet of real world data!
631
+ let pts = [
632
+ 512 , 1536 , 1024 , 768 , 1280 , 2560 , 2048 , 1792 , 2304 , 3584 , 3072 , 2816 , 3328 , 4608 , 4096 ,
633
+ 3840 , 4352 , 5376 , 4864 , 5120 , 6400 , 5888 , 5632 , 6144 , 7424 , 6912 , 6656 , 7168 , 8448 ,
634
+ 7936 , 7680 , 8192 , 9472 , 8960 , 8704 , 9216 , 10496 , 9984 , 9728 , 10240 , 11520 , 11008 ,
635
+ 10752 , 11264 , 12544 , 12032 , 11776 , 12288 , 13568 , 13056 ,
636
+ ] ;
637
+ let dts = [
638
+ 0 , 256 , 512 , 768 , 1024 , 1280 , 1536 , 1792 , 2048 , 2304 , 2560 , 2816 , 3072 , 3328 , 3584 ,
639
+ 3840 , 4096 , 4352 , 4608 , 4864 , 5120 , 5376 , 5632 , 5888 , 6144 , 6400 , 6656 , 6912 , 7168 ,
640
+ 7424 , 7680 , 7936 , 8192 , 8448 , 8704 , 8960 , 9216 , 9472 , 9728 , 9984 , 10240 , 10496 , 10752 ,
641
+ 11008 , 11264 , 11520 , 11776 , 12032 , 12288 , 12544 ,
642
+ ] ;
643
+
644
+ // Checking our basic assumptions about this data:
645
+ assert_eq ! ( pts. len( ) , dts. len( ) ) ;
646
+ assert ! ( pts. iter( ) . zip( dts. iter( ) ) . all( |( pts, dts) | dts <= pts) ) ;
647
+
648
+ // Create fake samples from this.
649
+ let samples = pts
650
+ . into_iter ( )
651
+ . zip ( dts)
652
+ . map ( |( pts, dts) | Sample {
653
+ is_sync : false ,
654
+ decode_timestamp : Time ( dts) ,
655
+ presentation_timestamp : Time ( pts) ,
656
+ duration : Time ( 1 ) ,
657
+ byte_offset : 0 ,
658
+ byte_length : 0 ,
659
+ } )
660
+ . collect :: < Vec < _ > > ( ) ;
661
+
662
+ let sample_statistics = SamplesStatistics :: new ( & samples) ;
663
+ assert_eq ! ( sample_statistics. minimum_presentation_timestamp, Time ( 512 ) ) ;
664
+ assert ! ( !sample_statistics. dts_always_equal_pts) ;
665
+
666
+ // Test queries on the samples.
667
+ let query_pts = |pts| {
668
+ VideoData :: latest_sample_index_at_presentation_timestamp_internal (
669
+ & samples,
670
+ & sample_statistics,
671
+ pts,
672
+ )
673
+ } ;
674
+
675
+ // Check that query for all exact positions works as expected using brute force search as the reference.
676
+ for ( idx, sample) in samples. iter ( ) . enumerate ( ) {
677
+ assert_eq ! ( Some ( idx) , query_pts( sample. presentation_timestamp) ) ;
678
+ }
679
+
680
+ // Check that for slightly offsetted positions the query is still correct.
681
+ // This works because for this dataset we know the minimum presentation timesetampe distance is always 256.
682
+ for ( idx, sample) in samples. iter ( ) . enumerate ( ) {
683
+ assert_eq ! (
684
+ Some ( idx) ,
685
+ query_pts( sample. presentation_timestamp + Time ( 1 ) )
686
+ ) ;
687
+ assert_eq ! (
688
+ Some ( idx) ,
689
+ query_pts( sample. presentation_timestamp + Time ( 255 ) )
690
+ ) ;
691
+ }
692
+
693
+ // A few hardcoded cases - both for illustrative purposes and to make sure the generic tests above are correct.
694
+
695
+ // Querying before the first sample.
696
+ assert_eq ! ( None , query_pts( Time ( 0 ) ) ) ;
697
+ assert_eq ! ( None , query_pts( Time ( 123 ) ) ) ;
698
+
699
+ // Querying for the first sample
700
+ assert_eq ! ( Some ( 0 ) , query_pts( Time ( 512 ) ) ) ;
701
+ assert_eq ! ( Some ( 0 ) , query_pts( Time ( 513 ) ) ) ;
702
+ assert_eq ! ( Some ( 0 ) , query_pts( Time ( 600 ) ) ) ;
703
+ assert_eq ! ( Some ( 0 ) , query_pts( Time ( 767 ) ) ) ;
704
+
705
+ // The next sample is a jump in index!
706
+ assert_eq ! ( Some ( 3 ) , query_pts( Time ( 768 ) ) ) ;
707
+ assert_eq ! ( Some ( 3 ) , query_pts( Time ( 769 ) ) ) ;
708
+ assert_eq ! ( Some ( 3 ) , query_pts( Time ( 800 ) ) ) ;
709
+ assert_eq ! ( Some ( 3 ) , query_pts( Time ( 1023 ) ) ) ;
710
+
711
+ // And the one after that should jump back again.
712
+ assert_eq ! ( Some ( 2 ) , query_pts( Time ( 1024 ) ) ) ;
713
+ assert_eq ! ( Some ( 2 ) , query_pts( Time ( 1025 ) ) ) ;
714
+ assert_eq ! ( Some ( 2 ) , query_pts( Time ( 1100 ) ) ) ;
715
+ assert_eq ! ( Some ( 2 ) , query_pts( Time ( 1279 ) ) ) ;
716
+
717
+ // And another one!
718
+ assert_eq ! ( Some ( 4 ) , query_pts( Time ( 1280 ) ) ) ;
719
+ assert_eq ! ( Some ( 4 ) , query_pts( Time ( 1281 ) ) ) ;
720
+
721
+ // Test way outside of the range.
722
+ // (this is not the last element in the list since that one doesn't have the highest PTS)
723
+ assert_eq ! ( Some ( 48 ) , query_pts( Time ( 123123123123123123 ) ) ) ;
724
+ }
556
725
}
0 commit comments