1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(docsrs, allow(unused_attributes))]
5#![deny(missing_docs)]
6#![forbid(unsafe_code)]
7
8#[cfg(feature = "quickcheck")]
13#[allow(unused_extern_crates)]
14extern crate std;
15
16use core::{
17 cmp::Ordering,
18 hash::{Hash, Hasher},
19 num::NonZeroU32,
20 time::Duration,
21};
22
23#[cfg(feature = "serde")]
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, Copy, Eq)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40#[cfg_attr(
41 feature = "quickcheck",
42 derive(::quickcheck_richderive::Arbitrary),
43 quickcheck(arbitrary = "crate::quickcheck_impls::timebase")
44)]
45pub struct Timebase {
46 #[cfg_attr(feature = "serde", serde(rename = "numerator"))]
47 num: u32,
48 #[cfg_attr(feature = "serde", serde(rename = "denominator"))]
49 den: NonZeroU32,
50}
51
52impl Default for Timebase {
53 #[cfg_attr(not(tarpaulin), inline(always))]
54 fn default() -> Self {
55 Self::new(1, NonZeroU32::new(1).unwrap())
56 }
57}
58
59impl Timebase {
60 #[cfg_attr(not(tarpaulin), inline(always))]
62 pub const fn new(num: u32, den: NonZeroU32) -> Self {
63 Self { num, den }
64 }
65
66 #[cfg_attr(not(tarpaulin), inline(always))]
68 pub const fn num(&self) -> u32 {
69 self.num
70 }
71
72 #[cfg_attr(not(tarpaulin), inline(always))]
74 pub const fn den(&self) -> NonZeroU32 {
75 self.den
76 }
77
78 #[cfg_attr(not(tarpaulin), inline(always))]
80 pub const fn with_num(mut self, num: u32) -> Self {
81 self.set_num(num);
82 self
83 }
84
85 #[cfg_attr(not(tarpaulin), inline(always))]
87 pub const fn with_den(mut self, den: NonZeroU32) -> Self {
88 self.set_den(den);
89 self
90 }
91
92 #[cfg_attr(not(tarpaulin), inline(always))]
94 pub const fn set_num(&mut self, num: u32) -> &mut Self {
95 self.num = num;
96 self
97 }
98
99 #[cfg_attr(not(tarpaulin), inline(always))]
101 pub const fn set_den(&mut self, den: NonZeroU32) -> &mut Self {
102 self.den = den;
103 self
104 }
105
106 #[cfg_attr(not(tarpaulin), inline(always))]
118 pub const fn rescale_pts(pts: i64, from: Self, to: Self) -> i64 {
119 assert!(to.num != 0, "target timebase numerator must be non-zero");
120 let numerator = (pts as i128) * (from.num as i128) * (to.den.get() as i128);
123 let denominator = (from.den.get() as i128) * (to.num as i128);
124 let q = numerator / denominator;
125 if q > i64::MAX as i128 {
126 i64::MAX
127 } else if q < i64::MIN as i128 {
128 i64::MIN
129 } else {
130 q as i64
131 }
132 }
133
134 #[cfg_attr(not(tarpaulin), inline(always))]
142 pub const fn rescale(&self, pts: i64, to: Self) -> i64 {
143 Self::rescale_pts(pts, *self, to)
144 }
145
146 #[cfg_attr(not(tarpaulin), inline(always))]
162 pub const fn frames_to_duration(&self, frames: u32) -> Duration {
163 let num = self.num as u128;
165 let den = self.den.get() as u128;
166 assert!(num != 0, "frame rate numerator must be non-zero");
167 let total_ns = (frames as u128) * den * 1_000_000_000 / num;
168 let secs = (total_ns / 1_000_000_000) as u64;
169 let nanos = (total_ns % 1_000_000_000) as u32;
170 Duration::new(secs, nanos)
171 }
172
173 #[cfg_attr(not(tarpaulin), inline(always))]
180 pub const fn duration_to_pts(&self, d: Duration) -> i64 {
181 let num = self.num as u128;
182 if num == 0 {
183 return 0;
184 }
185 let den = self.den.get() as u128;
186 let ns = d.as_nanos();
188 let pts = ns * den / (num * 1_000_000_000);
189 if pts > i64::MAX as u128 {
190 i64::MAX
191 } else {
192 pts as i64
193 }
194 }
195}
196
197impl PartialEq for Timebase {
198 #[cfg_attr(not(tarpaulin), inline(always))]
199 fn eq(&self, other: &Self) -> bool {
200 (self.num as u64) * (other.den.get() as u64) == (other.num as u64) * (self.den.get() as u64)
202 }
203}
204
205impl Hash for Timebase {
206 #[cfg_attr(not(tarpaulin), inline(always))]
207 fn hash<H: Hasher>(&self, state: &mut H) {
208 let d = self.den.get();
209 let g = gcd_u32(self.num, d);
211 (self.num / g).hash(state);
212 (d / g).hash(state);
213 }
214}
215
216impl Ord for Timebase {
217 #[cfg_attr(not(tarpaulin), inline(always))]
218 fn cmp(&self, other: &Self) -> Ordering {
219 let lhs = (self.num as u64) * (other.den.get() as u64);
220 let rhs = (other.num as u64) * (self.den.get() as u64);
221 lhs.cmp(&rhs)
222 }
223}
224
225impl PartialOrd for Timebase {
226 #[cfg_attr(not(tarpaulin), inline(always))]
227 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
228 Some(self.cmp(other))
229 }
230}
231
232#[derive(Debug, Default, Clone, Copy)]
244#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
245#[cfg_attr(
246 feature = "quickcheck",
247 derive(::quickcheck_richderive::Arbitrary),
248 quickcheck(arbitrary = "crate::quickcheck_impls::timestamp")
249)]
250pub struct Timestamp {
251 pts: i64,
252 timebase: Timebase,
253}
254
255impl Timestamp {
256 #[cfg_attr(not(tarpaulin), inline(always))]
258 pub const fn new(pts: i64, timebase: Timebase) -> Self {
259 Self { pts, timebase }
260 }
261
262 #[cfg_attr(not(tarpaulin), inline(always))]
267 pub const fn pts(&self) -> i64 {
268 self.pts
269 }
270
271 #[cfg_attr(not(tarpaulin), inline(always))]
273 pub const fn timebase(&self) -> Timebase {
274 self.timebase
275 }
276
277 #[cfg_attr(not(tarpaulin), inline(always))]
279 pub const fn with_pts(mut self, pts: i64) -> Self {
280 self.set_pts(pts);
281 self
282 }
283
284 #[cfg_attr(not(tarpaulin), inline(always))]
286 pub const fn set_pts(&mut self, pts: i64) -> &mut Self {
287 self.pts = pts;
288 self
289 }
290
291 #[cfg_attr(not(tarpaulin), inline(always))]
296 pub const fn rescale_to(self, target: Timebase) -> Self {
297 Self {
298 pts: self.timebase.rescale(self.pts, target),
299 timebase: target,
300 }
301 }
302
303 #[cfg_attr(not(tarpaulin), inline(always))]
311 pub const fn saturating_sub_duration(self, d: Duration) -> Self {
312 let units = self.timebase.duration_to_pts(d);
313 Self::new(self.pts.saturating_sub(units), self.timebase)
314 }
315
316 #[cfg_attr(not(tarpaulin), inline(always))]
322 pub const fn cmp_semantic(&self, other: &Self) -> Ordering {
323 if self.timebase.num == other.timebase.num
324 && self.timebase.den.get() == other.timebase.den.get()
325 {
326 return if self.pts < other.pts {
327 Ordering::Less
328 } else if self.pts > other.pts {
329 Ordering::Greater
330 } else {
331 Ordering::Equal
332 };
333 }
334 let lhs = (self.pts as i128) * (self.timebase.num as i128) * (other.timebase.den.get() as i128);
337 let rhs =
338 (other.pts as i128) * (other.timebase.num as i128) * (self.timebase.den.get() as i128);
339 if lhs < rhs {
340 Ordering::Less
341 } else if lhs > rhs {
342 Ordering::Greater
343 } else {
344 Ordering::Equal
345 }
346 }
347
348 #[cfg_attr(not(tarpaulin), inline(always))]
354 pub const fn duration(&self) -> Option<Duration> {
355 self.duration_since(&Self::new(0, self.timebase))
356 }
357
358 #[cfg_attr(not(tarpaulin), inline(always))]
367 pub const fn duration_since(&self, earlier: &Self) -> Option<Duration> {
368 const NS_PER_SEC: i128 = 1_000_000_000;
369
370 let self_den = self.timebase.den.get();
373 let earlier_den = earlier.timebase.den.get();
374
375 let mut a = self_den;
376 let mut b = earlier_den;
377 while b != 0 {
378 let r = a % b;
379 a = b;
380 b = r;
381 }
382 let gcd = a as i128;
383
384 let self_scale = (earlier_den as i128) / gcd;
385 let earlier_scale = (self_den as i128) / gcd;
386 let common_den = (self_den as i128) * self_scale; let diff_num = (self.pts as i128) * (self.timebase.num as i128) * self_scale
390 - (earlier.pts as i128) * (earlier.timebase.num as i128) * earlier_scale;
391 if diff_num < 0 {
392 return None;
393 }
394
395 let secs_i128 = diff_num / common_den;
397 if secs_i128 > u64::MAX as i128 {
398 return Some(Duration::MAX);
399 }
400 let rem = diff_num % common_den;
401 let nanos = (rem * NS_PER_SEC / common_den) as u32;
402 Some(Duration::new(secs_i128 as u64, nanos))
403 }
404}
405
406impl PartialEq for Timestamp {
407 #[cfg_attr(not(tarpaulin), inline(always))]
408 fn eq(&self, other: &Self) -> bool {
409 self.cmp_semantic(other).is_eq()
410 }
411}
412impl Eq for Timestamp {}
413
414impl Hash for Timestamp {
415 #[cfg_attr(not(tarpaulin), inline(always))]
416 fn hash<H: Hasher>(&self, state: &mut H) {
417 let n: i128 = (self.pts as i128) * (self.timebase.num as i128);
419 let d: u128 = self.timebase.den.get() as u128;
420 let g = gcd_u128(n.unsigned_abs(), d) as i128;
422 let rn = n / g;
423 let rd = (d as i128) / g;
424 rn.hash(state);
425 rd.hash(state);
426 }
427}
428
429impl Ord for Timestamp {
430 #[cfg_attr(not(tarpaulin), inline(always))]
431 fn cmp(&self, other: &Self) -> Ordering {
432 self.cmp_semantic(other)
433 }
434}
435
436impl PartialOrd for Timestamp {
437 #[cfg_attr(not(tarpaulin), inline(always))]
438 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
439 Some(self.cmp(other))
440 }
441}
442
443#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
453#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
454#[cfg_attr(
455 feature = "quickcheck",
456 derive(::quickcheck_richderive::Arbitrary),
457 quickcheck(arbitrary = "crate::quickcheck_impls::time_range")
458)]
459pub struct TimeRange {
460 start: i64,
461 end: i64,
462 timebase: Timebase,
463}
464
465impl TimeRange {
466 #[cfg_attr(not(tarpaulin), inline(always))]
472 pub const fn new(start: i64, end: i64, timebase: Timebase) -> Self {
473 assert!(start <= end, "end must not be greater or equal to start");
474
475 Self {
476 start,
477 end,
478 timebase,
479 }
480 }
481
482 #[cfg(feature = "buffa")]
491 #[inline(always)]
492 pub(crate) const fn new_for_decode(start: i64, end: i64, timebase: Timebase) -> Self {
493 Self {
494 start,
495 end,
496 timebase,
497 }
498 }
499
500 #[cfg_attr(not(tarpaulin), inline(always))]
503 pub const fn try_new(start: i64, end: i64, timebase: Timebase) -> Option<Self> {
504 if start <= end {
505 Some(Self {
506 start,
507 end,
508 timebase,
509 })
510 } else {
511 None
512 }
513 }
514
515 #[cfg_attr(not(tarpaulin), inline(always))]
517 pub const fn instant(ts: Timestamp) -> Self {
518 Self {
519 start: ts.pts(),
520 end: ts.pts(),
521 timebase: ts.timebase(),
522 }
523 }
524
525 #[cfg_attr(not(tarpaulin), inline(always))]
527 pub const fn start_pts(&self) -> i64 {
528 self.start
529 }
530
531 #[cfg_attr(not(tarpaulin), inline(always))]
533 pub const fn end_pts(&self) -> i64 {
534 self.end
535 }
536
537 #[cfg_attr(not(tarpaulin), inline(always))]
539 pub const fn timebase(&self) -> Timebase {
540 self.timebase
541 }
542
543 #[cfg_attr(not(tarpaulin), inline(always))]
545 pub const fn start(&self) -> Timestamp {
546 Timestamp::new(self.start, self.timebase)
547 }
548
549 #[cfg_attr(not(tarpaulin), inline(always))]
551 pub const fn end(&self) -> Timestamp {
552 Timestamp::new(self.end, self.timebase)
553 }
554
555 #[cfg_attr(not(tarpaulin), inline(always))]
557 pub const fn with_start(mut self, val: i64) -> Self {
558 self.start = val;
559 self
560 }
561
562 #[cfg_attr(not(tarpaulin), inline(always))]
564 pub const fn set_start(&mut self, val: i64) -> &mut Self {
565 self.start = val;
566 self
567 }
568
569 #[cfg_attr(not(tarpaulin), inline(always))]
571 pub const fn with_end(mut self, val: i64) -> Self {
572 self.end = val;
573 self
574 }
575
576 #[cfg_attr(not(tarpaulin), inline(always))]
578 pub const fn set_end(&mut self, val: i64) -> &mut Self {
579 self.end = val;
580 self
581 }
582
583 #[cfg_attr(not(tarpaulin), inline(always))]
585 pub const fn with_timebase(mut self, timebase: Timebase) -> Self {
586 self.set_timebase(timebase);
587 self
588 }
589
590 #[cfg_attr(not(tarpaulin), inline(always))]
592 pub const fn set_timebase(&mut self, timebase: Timebase) -> &mut Self {
593 self.timebase = timebase;
594 self
595 }
596
597 #[cfg_attr(not(tarpaulin), inline(always))]
599 pub const fn is_instant(&self) -> bool {
600 self.start == self.end
601 }
602
603 #[cfg_attr(not(tarpaulin), inline(always))]
609 pub const fn total_pts(&self) -> i64 {
610 self.end.saturating_sub(self.start)
611 }
612
613 #[cfg_attr(not(tarpaulin), inline(always))]
616 pub const fn duration(&self) -> Duration {
617 self
618 .end()
619 .duration_since(&self.start())
620 .expect("end must greater than or equal to start")
621 }
622
623 #[cfg_attr(not(tarpaulin), inline(always))]
629 pub const fn rescale_to(self, target: Timebase) -> Self {
630 Self {
631 start: self.timebase.rescale(self.start, target),
632 end: self.timebase.rescale(self.end, target),
633 timebase: target,
634 }
635 }
636
637 #[cfg_attr(not(tarpaulin), inline(always))]
644 pub const fn interpolate(&self, t: f64) -> Timestamp {
645 let t = t.clamp(0.0, 1.0);
646 let delta = self.end.saturating_sub(self.start);
647 let offset = (delta as f64 * t) as i64;
648 Timestamp::new(self.start.saturating_add(offset), self.timebase)
649 }
650}
651
652#[cfg_attr(not(tarpaulin), inline(always))]
653const fn gcd_u32(mut a: u32, mut b: u32) -> u32 {
654 while b != 0 {
655 let t = b;
656 b = a % b;
657 a = t;
658 }
659 a
660}
661
662#[cfg_attr(not(tarpaulin), inline(always))]
663const fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
664 while b != 0 {
665 let t = b;
666 b = a % b;
667 a = t;
668 }
669 a
670}
671
672#[cfg(feature = "quickcheck")]
679#[cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))]
680pub mod quickcheck_impls {
681 use crate::{TimeRange, Timebase, Timestamp};
682 use core::num::NonZeroU32;
683 use quickcheck::{Arbitrary, Gen};
684
685 pub fn timebase(g: &mut Gen) -> Timebase {
689 Timebase::new(u32::arbitrary(g), NonZeroU32::arbitrary(g))
690 }
691
692 pub fn timestamp(g: &mut Gen) -> Timestamp {
694 Timestamp::new(non_negative_i64(g), timebase(g))
695 }
696
697 pub fn time_range(g: &mut Gen) -> TimeRange {
702 let a = non_negative_i64(g);
703 let b = non_negative_i64(g);
704 let start = a.min(b);
705 let mut end = a.max(b);
706 if start == end {
707 end = end.saturating_add(1);
708 }
709 TimeRange::new(start, end, timebase(g))
710 }
711
712 fn non_negative_i64(g: &mut Gen) -> i64 {
713 loop {
714 let d = i64::arbitrary(g);
715 if d >= 0 {
716 return d;
717 }
718 }
719 }
720}
721
722#[cfg(feature = "arbitrary")]
723#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
724const _: () = {
725 use arbitrary::Arbitrary;
726
727 impl<'a> Arbitrary<'a> for Timebase {
728 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
729 let d = u.arbitrary::<core::num::NonZeroU32>()?;
731 let num = u.arbitrary::<u32>()?;
732 Ok(Timebase::new(num, d))
733 }
734 }
735
736 impl<'a> Arbitrary<'a> for Timestamp {
737 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
738 non_negative_i64(u).and_then(|i| u.arbitrary().map(|tb| Self::new(i, tb)))
739 }
740 }
741
742 impl<'a> Arbitrary<'a> for TimeRange {
743 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
744 let a = non_negative_i64(u)?;
745 let b = non_negative_i64(u)?;
746 let start = a.min(b);
747 let mut end = a.max(b);
748
749 if start == end {
750 end = end.saturating_add(1);
751 }
752
753 Ok(TimeRange::new(start, end, u.arbitrary()?))
754 }
755 }
756
757 fn non_negative_i64(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<i64> {
758 loop {
759 let val = u.arbitrary::<i64>()?;
760 if val >= 0 {
761 return Ok(val);
762 }
763 }
764 }
765};
766
767#[cfg(test)]
768mod tests {
769 use super::*;
770
771 const fn nz(n: u32) -> NonZeroU32 {
772 match NonZeroU32::new(n) {
773 Some(v) => v,
774 None => panic!("zero"),
775 }
776 }
777
778 fn hash_of<T: Hash>(v: &T) -> u64 {
779 use std::collections::hash_map::DefaultHasher;
780 let mut h = DefaultHasher::new();
781 v.hash(&mut h);
782 h.finish()
783 }
784
785 #[test]
786 fn rescale_identity() {
787 let tb = Timebase::new(1, nz(1000));
788 assert_eq!(Timebase::rescale_pts(42, tb, tb), 42);
789 assert_eq!(tb.rescale(42, tb), 42);
790 }
791
792 #[test]
793 fn rescale_between_timebases() {
794 let ms = Timebase::new(1, nz(1000));
795 let mpeg = Timebase::new(1, nz(90_000));
796 assert_eq!(Timebase::rescale_pts(1000, ms, mpeg), 90_000);
797 assert_eq!(ms.rescale(1000, mpeg), 90_000);
798 assert_eq!(mpeg.rescale(90_000, ms), 1000);
799 }
800
801 #[test]
802 fn rescale_rounds_toward_zero() {
803 let from = Timebase::new(1, nz(1000));
804 let to = Timebase::new(1, nz(3));
805 assert_eq!(from.rescale(1, to), 0);
806 assert_eq!(from.rescale(-1, to), 0);
807 }
808
809 #[test]
810 fn rescale_saturates_on_i64_overflow() {
811 let from = Timebase::new(u32::MAX, nz(1));
816 let to = Timebase::new(1, nz(u32::MAX));
817 assert_eq!(from.rescale(1_000_000, to), i64::MAX);
818 assert_eq!(from.rescale(-1_000_000, to), i64::MIN);
819 }
820
821 #[test]
822 fn timebase_eq_is_semantic() {
823 let a = Timebase::new(1, nz(2));
825 let b = Timebase::new(2, nz(4));
826 let c = Timebase::new(3, nz(6));
827 assert_eq!(a, b);
828 assert_eq!(b, c);
829 assert_eq!(a, c);
830 let d = Timebase::new(1, nz(3));
832 assert_ne!(a, d);
833 }
834
835 #[test]
836 fn timebase_hash_matches_eq() {
837 let a = Timebase::new(1, nz(2));
838 let b = Timebase::new(2, nz(4));
839 let c = Timebase::new(3, nz(6));
840 assert_eq!(hash_of(&a), hash_of(&b));
841 assert_eq!(hash_of(&b), hash_of(&c));
842 }
843
844 #[test]
845 fn timebase_ord_is_numeric() {
846 let third = Timebase::new(1, nz(3));
847 let half = Timebase::new(1, nz(2));
848 let two_thirds = Timebase::new(2, nz(3));
849 let one = Timebase::new(1, nz(1));
850 assert!(third < half);
851 assert!(half < two_thirds);
852 assert!(two_thirds < one);
853 assert!(one > third);
855 }
856
857 #[test]
858 fn timebase_num_zero() {
859 let a = Timebase::new(0, nz(3));
861 let b = Timebase::new(0, nz(5));
862 assert_eq!(a, b);
863 assert_eq!(hash_of(&a), hash_of(&b));
864 assert!(a < Timebase::new(1, nz(1_000_000)));
865 }
866
867 #[test]
868 fn timestamp_cmp_same_timebase() {
869 let tb = Timebase::new(1, nz(1000));
870 let a = Timestamp::new(100, tb);
871 let b = Timestamp::new(200, tb);
872 assert!(a < b);
873 assert!(b > a);
874 assert_eq!(a, a);
875 assert_eq!(a.cmp(&b), Ordering::Less);
876 }
877
878 #[test]
879 fn timestamp_cmp_cross_timebase() {
880 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
881 let b = Timestamp::new(90_000, Timebase::new(1, nz(90_000)));
882 assert_eq!(a, b);
883 assert_eq!(a.cmp(&b), Ordering::Equal);
884
885 let c = Timestamp::new(500, Timebase::new(1, nz(1000)));
886 assert!(c < a);
887 assert!(a > c);
888 }
889
890 #[test]
891 fn timestamp_hash_matches_semantic_eq() {
892 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
893 let b = Timestamp::new(90_000, Timebase::new(1, nz(90_000)));
894 let c = Timestamp::new(2000, Timebase::new(1, nz(2000))); assert_eq!(a, b);
896 assert_eq!(hash_of(&a), hash_of(&b));
897 assert_eq!(hash_of(&a), hash_of(&c));
898 }
899
900 #[test]
901 fn timestamp_hash_negative_pts() {
902 let a = Timestamp::new(-500, Timebase::new(1, nz(1000)));
904 let b = Timestamp::new(-45_000, Timebase::new(1, nz(90_000)));
905 assert_eq!(a, b);
906 assert_eq!(hash_of(&a), hash_of(&b));
907 }
908
909 #[test]
910 fn rescale_to_preserves_instant() {
911 let ms = Timebase::new(1, nz(1000));
912 let mpeg = Timebase::new(1, nz(90_000));
913 let a = Timestamp::new(1000, ms);
914 let b = a.rescale_to(mpeg);
915 assert_eq!(b.pts(), 90_000);
916 assert_eq!(b.timebase(), mpeg);
917 assert_eq!(a, b);
918 }
919
920 #[test]
921 fn timestamp_duration_from_zero() {
922 let ms = Timebase::new(1, nz(1000));
923 let ts = Timestamp::new(1500, ms);
924 assert_eq!(ts.duration(), Some(Duration::from_millis(1500)));
925 assert_eq!(Timestamp::new(0, ms).duration(), Some(Duration::ZERO));
926
927 let mpeg = Timebase::new(1, nz(90_000));
929 assert_eq!(
930 Timestamp::new(90_000, mpeg).duration(),
931 Some(Duration::from_secs(1))
932 );
933
934 assert_eq!(Timestamp::new(-1, ms).duration(), None);
936 }
937
938 #[test]
939 fn duration_since_same_timebase() {
940 let tb = Timebase::new(1, nz(1000));
941 let a = Timestamp::new(1500, tb);
942 let b = Timestamp::new(500, tb);
943 assert_eq!(a.duration_since(&b), Some(Duration::from_millis(1000)));
944 assert_eq!(b.duration_since(&a), None);
945 }
946
947 #[test]
948 fn duration_since_cross_timebase() {
949 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
950 let b = Timestamp::new(45_000, Timebase::new(1, nz(90_000)));
951 assert_eq!(a.duration_since(&b), Some(Duration::from_millis(500)));
952 }
953
954 #[test]
955 fn duration_since_saturates_to_duration_max_on_overflow() {
956 let tb = Timebase::new(u32::MAX, nz(1));
960 let huge = Timestamp::new(i64::MAX, tb);
961 let zero = Timestamp::new(0, tb);
962 assert_eq!(huge.duration_since(&zero), Some(Duration::MAX));
963 }
964
965 #[test]
966 fn frames_to_duration_integer_fps() {
967 let fps30 = Timebase::new(30, nz(1));
968 assert_eq!(fps30.frames_to_duration(15), Duration::from_millis(500));
969 assert_eq!(fps30.frames_to_duration(30), Duration::from_secs(1));
970 assert_eq!(fps30.frames_to_duration(0), Duration::ZERO);
971 }
972
973 #[test]
974 fn frames_to_duration_ntsc() {
975 let ntsc = Timebase::new(30_000, nz(1001));
977 assert_eq!(ntsc.frames_to_duration(30_000), Duration::from_secs(1001));
978 assert_eq!(
980 ntsc.frames_to_duration(15),
981 Duration::from_nanos(500_500_000),
982 );
983 }
984
985 #[test]
986 fn time_range_basic() {
987 let tb = Timebase::default().with_den(nz(1000)).with_num(1);
988 let r = TimeRange::new(100, 500, tb);
989 assert_eq!(r.start_pts(), 100);
990 assert_eq!(r.end_pts(), 500);
991 assert_eq!(r.timebase(), tb);
992 assert_eq!(r.start(), Timestamp::new(100, tb));
993 assert_eq!(r.end(), Timestamp::new(500, tb));
994 assert!(!r.is_instant());
995 assert_eq!(r.duration(), Duration::from_millis(400));
996 assert_eq!(r.interpolate(0.0).pts(), 100);
998 assert_eq!(r.interpolate(1.0).pts(), 500);
999 assert_eq!(r.interpolate(0.5).pts(), 300);
1000 assert_eq!(r.interpolate(-1.0).pts(), 100);
1002 assert_eq!(r.interpolate(2.0).pts(), 500);
1003
1004 let nr = r.with_timebase(Timebase::new(1, nz(2000)));
1005 assert_eq!(nr.timebase().den().get(), 2000);
1006 assert_eq!(nr.timebase().num(), 1);
1007 }
1008
1009 #[test]
1010 fn time_range_instant() {
1011 let tb = Timebase::new(1, nz(1000));
1012 let ts = Timestamp::new(123, tb);
1013 let r = TimeRange::instant(ts);
1014 assert!(r.is_instant());
1015 assert_eq!(r.start_pts(), 123);
1016 assert_eq!(r.end_pts(), 123);
1017 assert_eq!(r.duration(), Duration::ZERO);
1018 }
1019
1020 #[test]
1026 fn timebase_accessors_and_builders() {
1027 let tb = Timebase::new(30_000, nz(1001));
1028 assert_eq!(tb.num(), 30_000);
1029 assert_eq!(tb.den(), nz(1001));
1030
1031 let tb2 = tb.with_num(48_000).with_den(nz(1));
1033 assert_eq!(tb2.num(), 48_000);
1034 assert_eq!(tb2.den(), nz(1));
1035
1036 let mut tb3 = Timebase::new(1, nz(1000));
1038 tb3.set_num(25).set_den(nz(2));
1039 assert_eq!(tb3.num(), 25);
1040 assert_eq!(tb3.den(), nz(2));
1041 }
1042
1043 #[test]
1044 fn duration_to_pts_happy_path_and_edge_cases() {
1045 let ms = Timebase::new(1, nz(1000));
1047 assert_eq!(ms.duration_to_pts(Duration::from_millis(1500)), 1500);
1048 assert_eq!(ms.duration_to_pts(Duration::ZERO), 0);
1049
1050 let mpegts = Timebase::new(1, nz(90_000));
1052 assert_eq!(mpegts.duration_to_pts(Duration::from_secs(2)), 180_000,);
1053
1054 let degenerate = Timebase::new(0, nz(1));
1056 assert_eq!(degenerate.duration_to_pts(Duration::from_secs(1)), 0,);
1057
1058 let fps1 = Timebase::new(1, nz(1));
1063 let huge = Duration::new(u64::MAX, 0);
1064 assert_eq!(fps1.duration_to_pts(huge), i64::MAX);
1065 }
1066
1067 #[test]
1068 fn timestamp_accessors_and_builders() {
1069 let tb = Timebase::new(1, nz(1000));
1070 let mut ts = Timestamp::new(42, tb);
1071 assert_eq!(ts.pts(), 42);
1072 assert_eq!(ts.timebase(), tb);
1073
1074 let ts2 = ts.with_pts(777);
1076 assert_eq!(ts2.pts(), 777);
1077
1078 ts.set_pts(-5).set_pts(-6);
1080 assert_eq!(ts.pts(), -6);
1081 }
1082
1083 #[test]
1084 fn cmp_semantic_exercises_all_branches() {
1085 let tb_a = Timebase::new(1, nz(1000)); let tb_b = Timebase::new(1, nz(90_000)); let a = Timestamp::new(100, tb_a);
1090 let b = Timestamp::new(200, tb_a);
1091 assert_eq!(a.cmp_semantic(&b), Ordering::Less);
1092 assert_eq!(b.cmp_semantic(&a), Ordering::Greater);
1093 assert_eq!(a.cmp_semantic(&a), Ordering::Equal);
1094
1095 let one_second_ms = Timestamp::new(1000, tb_a);
1097 let one_second_mpg = Timestamp::new(90_000, tb_b);
1098 let half_second_ms = Timestamp::new(500, tb_a);
1099 let two_seconds_mpg = Timestamp::new(180_000, tb_b);
1100 assert_eq!(half_second_ms.cmp_semantic(&one_second_mpg), Ordering::Less,);
1101 assert_eq!(
1102 two_seconds_mpg.cmp_semantic(&one_second_ms),
1103 Ordering::Greater,
1104 );
1105 assert_eq!(one_second_ms.cmp_semantic(&one_second_mpg), Ordering::Equal,);
1106 }
1107
1108 #[test]
1109 fn saturating_sub_duration_saturates() {
1110 let tb = Timebase::new(1, nz(1000));
1111 let near_floor = Timestamp::new(i64::MIN + 10, tb);
1114 let shifted = near_floor.saturating_sub_duration(Duration::from_secs(1));
1115 assert_eq!(shifted.pts(), i64::MIN);
1116
1117 let ts = Timestamp::new(1500, tb);
1119 let shifted = ts.saturating_sub_duration(Duration::from_millis(500));
1120 assert_eq!(shifted.pts(), 1000);
1121 }
1122
1123 #[test]
1124 fn time_range_builders_and_setters() {
1125 let tb = Timebase::new(1, nz(1000));
1126 let r = TimeRange::new(0, 0, tb);
1127
1128 let r2 = r.with_start(100).with_end(500);
1130 assert_eq!(r2.start_pts(), 100);
1131 assert_eq!(r2.end_pts(), 500);
1132
1133 let mut r3 = TimeRange::new(0, 0, tb);
1135 r3.set_start(10).set_end(20);
1136 assert_eq!(r3.start_pts(), 10);
1137 assert_eq!(r3.end_pts(), 20);
1138 }
1139
1140 #[test]
1141 fn time_range_total_pts() {
1142 let tb = Timebase::new(1, nz(1000));
1143 assert_eq!(TimeRange::new(100, 500, tb).total_pts(), 400);
1144 assert_eq!(TimeRange::new(0, 0, tb).total_pts(), 0);
1145 assert_eq!(TimeRange::new(i64::MIN, i64::MAX, tb).total_pts(), i64::MAX);
1147 }
1148
1149 #[test]
1150 fn time_range_rescale_to() {
1151 let ms = Timebase::new(1, nz(1000));
1152 let mpeg = Timebase::new(1, nz(90_000));
1153 let r = TimeRange::new(1000, 2000, ms);
1154 let r2 = r.rescale_to(mpeg);
1155 assert_eq!(r2.start_pts(), 90_000);
1156 assert_eq!(r2.end_pts(), 180_000);
1157 assert_eq!(r2.timebase(), mpeg);
1158 assert_eq!(r.duration(), r2.duration());
1160 let inst = TimeRange::instant(Timestamp::new(500, ms));
1162 assert!(inst.rescale_to(mpeg).is_instant());
1163 }
1164
1165 #[test]
1166 fn time_range_try_new() {
1167 let tb = Timebase::new(1, nz(1000));
1168 let r = TimeRange::try_new(100, 500, tb).unwrap();
1170 assert_eq!(r.start_pts(), 100);
1171 assert_eq!(r.end_pts(), 500);
1172 assert!(TimeRange::try_new(42, 42, tb).is_some());
1174 assert!(TimeRange::try_new(500, 100, tb).is_none());
1176 }
1177
1178 #[test]
1179 #[should_panic]
1180 fn time_range_new_panics_on_negative_duration() {
1181 let tb = Timebase::new(1, nz(1000));
1182 TimeRange::new(500, 100, tb);
1183 }
1184}
1185
1186#[cfg(all(test, feature = "quickcheck"))]
1187mod quickcheck_arbitrary_tests {
1188 use super::*;
1189 use quickcheck::{Arbitrary, Gen};
1190
1191 const ITERATIONS: usize = 1000;
1192 const SIZE: usize = 512;
1193
1194 #[test]
1195 fn timebase_denominator_is_nonzero() {
1196 let mut g = Gen::new(SIZE);
1197 for _ in 0..ITERATIONS {
1198 let tb = Timebase::arbitrary(&mut g);
1199 assert!(tb.den().get() != 0);
1200 }
1201 }
1202
1203 #[test]
1204 fn timestamp_pts_is_non_negative() {
1205 let mut g = Gen::new(SIZE);
1206 for _ in 0..ITERATIONS {
1207 let ts = Timestamp::arbitrary(&mut g);
1208 assert!(ts.pts() >= 0, "pts was {}", ts.pts());
1209 assert!(ts.timebase().den().get() != 0);
1210 }
1211 }
1212
1213 #[test]
1214 fn timerange_is_well_formed() {
1215 let mut g = Gen::new(SIZE);
1216 for _ in 0..ITERATIONS {
1217 let r = TimeRange::arbitrary(&mut g);
1218 assert!(r.start_pts() >= 0, "start was {}", r.start_pts());
1219 assert!(r.end_pts() >= 0, "end was {}", r.end_pts());
1220 assert!(
1221 r.start_pts() <= r.end_pts(),
1222 "start {} > end {}",
1223 r.start_pts(),
1224 r.end_pts()
1225 );
1226 assert!(r.timebase().den().get() != 0);
1227 }
1228 }
1229}
1230
1231#[cfg(all(test, feature = "arbitrary"))]
1232mod arbitrary_impl_tests {
1233 use super::*;
1234 use arbitrary::{Arbitrary, Unstructured};
1235
1236 fn pseudo_random_bytes(seed: u64) -> [u8; 4096] {
1237 let mut out = [0u8; 4096];
1239 let mut s = seed.wrapping_add(0x9E3779B97F4A7C15);
1240 for chunk in out.chunks_mut(8) {
1241 s = s.wrapping_mul(0xBF58476D1CE4E5B9).wrapping_add(1);
1242 let mut z = s;
1243 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
1244 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
1245 z ^= z >> 31;
1246 chunk.copy_from_slice(&z.to_le_bytes()[..chunk.len()]);
1247 }
1248 out
1249 }
1250
1251 #[test]
1252 fn timebase_denominator_is_nonzero() {
1253 for seed in 0..200 {
1254 let data = pseudo_random_bytes(seed);
1255 let mut u = Unstructured::new(&data);
1256 let tb = Timebase::arbitrary(&mut u).expect("enough bytes to build a Timebase");
1257 assert!(tb.den().get() != 0);
1258 }
1259 }
1260
1261 #[test]
1262 fn timestamp_pts_is_non_negative() {
1263 for seed in 0..200 {
1264 let data = pseudo_random_bytes(seed);
1265 let mut u = Unstructured::new(&data);
1266 let ts = Timestamp::arbitrary(&mut u).expect("enough bytes to build a Timestamp");
1267 assert!(ts.pts() >= 0, "pts was {}", ts.pts());
1268 assert!(ts.timebase().den().get() != 0);
1269 }
1270 }
1271
1272 #[test]
1273 fn timerange_is_well_formed() {
1274 for seed in 0..200 {
1275 let data = pseudo_random_bytes(seed);
1276 let mut u = Unstructured::new(&data);
1277 let r = TimeRange::arbitrary(&mut u).expect("enough bytes to build a TimeRange");
1278 assert!(r.start_pts() >= 0);
1279 assert!(r.end_pts() >= 0);
1280 assert!(r.start_pts() <= r.end_pts());
1281 assert!(r.timebase().den().get() != 0);
1282 }
1283 }
1284
1285 #[test]
1286 fn arbitrary_take_rest_produces_valid_values() {
1287 let data = pseudo_random_bytes(42);
1290 let u = Unstructured::new(&data);
1291 let r = TimeRange::arbitrary_take_rest(u).expect("should consume the buffer");
1292 assert!(r.start_pts() >= 0);
1293 assert!(r.end_pts() >= 0);
1294 assert!(r.start_pts() <= r.end_pts());
1295 assert!(r.timebase().den().get() != 0);
1296 }
1297}
1298
1299#[cfg(feature = "buffa")]
1300mod buffa;
1301
1302#[cfg(feature = "buffa")]
1306#[doc(hidden)]
1307pub mod __buffa {
1308 pub mod view {
1309 pub type TimebaseView<'a> = crate::Timebase;
1312 pub type TimeRangeView<'a> = crate::TimeRange;
1313 pub type TimestampView<'a> = crate::Timestamp;
1314 }
1315}