openipc-video 0.1.38

Low-latency platform video decoding for OpenIPC applications
Documentation
use bytes::Bytes;

use super::{DecodedSurface, VideoCodec};

/// Rational media timestamp.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct VideoTimestamp {
    /// Timestamp numerator.
    pub value: i64,
    /// Ticks per second.
    pub timescale: i32,
}

impl VideoTimestamp {
    /// RTP video clock frequency used by OpenIPC streams.
    pub const RTP_VIDEO_TIMESCALE: i32 = 90_000;

    /// Construct a timestamp, rejecting a non-positive timescale.
    pub const fn new(value: i64, timescale: i32) -> Option<Self> {
        if timescale > 0 {
            Some(Self { value, timescale })
        } else {
            None
        }
    }

    /// Construct a timestamp from a 90 kHz RTP video timestamp.
    pub const fn from_rtp(value: u32) -> Self {
        Self {
            value: value as i64,
            timescale: Self::RTP_VIDEO_TIMESCALE,
        }
    }
}

/// Complete encoded Annex-B access unit submitted to a decoder.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodedAccessUnit {
    /// Encoded codec family.
    pub codec: VideoCodec,
    /// Annex-B NAL units including start codes.
    pub data: Bytes,
    /// Presentation timestamp.
    pub timestamp: VideoTimestamp,
    /// Whether this access unit provides a random-access entry point.
    pub keyframe: bool,
    /// Source packet sequence number when available.
    pub sequence_number: Option<u16>,
    /// Whether packet loss left this access unit incomplete.
    pub damaged: bool,
}

impl EncodedAccessUnit {
    /// Create an encoded access unit.
    pub fn new(
        codec: VideoCodec,
        data: impl Into<Bytes>,
        timestamp: VideoTimestamp,
        keyframe: bool,
    ) -> Self {
        Self {
            codec,
            data: data.into(),
            timestamp,
            keyframe,
            sequence_number: None,
            damaged: false,
        }
    }

    /// Return true when this access unit can safely establish decoder state.
    pub const fn can_resynchronize(&self) -> bool {
        self.keyframe && !self.damaged
    }
}

#[cfg(test)]
mod tests {
    use super::{EncodedAccessUnit, VideoTimestamp};
    use crate::VideoCodec;

    #[test]
    fn only_clean_keyframes_can_resynchronize_a_decoder() {
        let mut frame = EncodedAccessUnit::new(
            VideoCodec::H264,
            vec![0, 0, 0, 1, 0x65],
            VideoTimestamp::from_rtp(90_000),
            true,
        );
        assert!(frame.can_resynchronize());
        frame.damaged = true;
        assert!(!frame.can_resynchronize());
        frame.damaged = false;
        frame.keyframe = false;
        assert!(!frame.can_resynchronize());
    }
}

impl From<openipc_core::DepacketizedFrame> for EncodedAccessUnit {
    fn from(value: openipc_core::DepacketizedFrame) -> Self {
        Self {
            codec: value.codec.into(),
            data: Bytes::from(value.data),
            timestamp: VideoTimestamp::from_rtp(value.timestamp),
            keyframe: value.is_keyframe,
            sequence_number: Some(value.sequence_number),
            damaged: value.damaged,
        }
    }
}

/// Decoded frame and its presentation metadata.
#[derive(Debug)]
pub struct DecodedFrame<S> {
    /// Native decoded surface.
    pub surface: S,
    /// Presentation timestamp carried by the source access unit.
    pub timestamp: VideoTimestamp,
    /// Optional frame duration.
    pub duration: Option<VideoTimestamp>,
}

impl<S: DecodedSurface> DecodedFrame<S> {
    /// Visible dimensions reported by the decoded surface.
    pub fn dimensions(&self) -> super::FrameDimensions {
        self.surface.dimensions()
    }
}