Skip to main content

openipc_video/api/
frame.rs

1use bytes::Bytes;
2
3use super::{DecodedSurface, VideoCodec};
4
5/// Rational media timestamp.
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
7pub struct VideoTimestamp {
8    /// Timestamp numerator.
9    pub value: i64,
10    /// Ticks per second.
11    pub timescale: i32,
12}
13
14impl VideoTimestamp {
15    /// RTP video clock frequency used by OpenIPC streams.
16    pub const RTP_VIDEO_TIMESCALE: i32 = 90_000;
17
18    /// Construct a timestamp, rejecting a non-positive timescale.
19    pub const fn new(value: i64, timescale: i32) -> Option<Self> {
20        if timescale > 0 {
21            Some(Self { value, timescale })
22        } else {
23            None
24        }
25    }
26
27    /// Construct a timestamp from a 90 kHz RTP video timestamp.
28    pub const fn from_rtp(value: u32) -> Self {
29        Self {
30            value: value as i64,
31            timescale: Self::RTP_VIDEO_TIMESCALE,
32        }
33    }
34}
35
36/// Complete encoded Annex-B access unit submitted to a decoder.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct EncodedAccessUnit {
39    /// Encoded codec family.
40    pub codec: VideoCodec,
41    /// Annex-B NAL units including start codes.
42    pub data: Bytes,
43    /// Presentation timestamp.
44    pub timestamp: VideoTimestamp,
45    /// Whether this access unit provides a random-access entry point.
46    pub keyframe: bool,
47    /// Source packet sequence number when available.
48    pub sequence_number: Option<u16>,
49    /// Whether packet loss left this access unit incomplete.
50    pub damaged: bool,
51}
52
53impl EncodedAccessUnit {
54    /// Create an encoded access unit.
55    pub fn new(
56        codec: VideoCodec,
57        data: impl Into<Bytes>,
58        timestamp: VideoTimestamp,
59        keyframe: bool,
60    ) -> Self {
61        Self {
62            codec,
63            data: data.into(),
64            timestamp,
65            keyframe,
66            sequence_number: None,
67            damaged: false,
68        }
69    }
70
71    /// Return true when this access unit can safely establish decoder state.
72    pub const fn can_resynchronize(&self) -> bool {
73        self.keyframe && !self.damaged
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::{EncodedAccessUnit, VideoTimestamp};
80    use crate::VideoCodec;
81
82    #[test]
83    fn only_clean_keyframes_can_resynchronize_a_decoder() {
84        let mut frame = EncodedAccessUnit::new(
85            VideoCodec::H264,
86            vec![0, 0, 0, 1, 0x65],
87            VideoTimestamp::from_rtp(90_000),
88            true,
89        );
90        assert!(frame.can_resynchronize());
91        frame.damaged = true;
92        assert!(!frame.can_resynchronize());
93        frame.damaged = false;
94        frame.keyframe = false;
95        assert!(!frame.can_resynchronize());
96    }
97}
98
99impl From<openipc_core::DepacketizedFrame> for EncodedAccessUnit {
100    fn from(value: openipc_core::DepacketizedFrame) -> Self {
101        Self {
102            codec: value.codec.into(),
103            data: Bytes::from(value.data),
104            timestamp: VideoTimestamp::from_rtp(value.timestamp),
105            keyframe: value.is_keyframe,
106            sequence_number: Some(value.sequence_number),
107            damaged: value.damaged,
108        }
109    }
110}
111
112/// Decoded frame and its presentation metadata.
113#[derive(Debug)]
114pub struct DecodedFrame<S> {
115    /// Native decoded surface.
116    pub surface: S,
117    /// Presentation timestamp carried by the source access unit.
118    pub timestamp: VideoTimestamp,
119    /// Optional frame duration.
120    pub duration: Option<VideoTimestamp>,
121}
122
123impl<S: DecodedSurface> DecodedFrame<S> {
124    /// Visible dimensions reported by the decoded surface.
125    pub fn dimensions(&self) -> super::FrameDimensions {
126        self.surface.dimensions()
127    }
128}