Skip to main content

yscv_video/
frame.rs

1use bytes::Bytes;
2use yscv_tensor::Tensor;
3
4use crate::VideoError;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum PixelFormat {
8    GrayF32,
9    RgbF32,
10}
11
12/// Raw RGB8 frame payload that can be consumed without f32 conversion.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Rgb8Frame {
15    index: u64,
16    timestamp_us: u64,
17    width: usize,
18    height: usize,
19    data: Bytes,
20}
21
22impl Rgb8Frame {
23    pub fn new(
24        index: u64,
25        timestamp_us: u64,
26        width: usize,
27        height: usize,
28        data: Vec<u8>,
29    ) -> Result<Self, VideoError> {
30        Self::from_bytes(index, timestamp_us, width, height, data.into())
31    }
32
33    pub fn from_bytes(
34        index: u64,
35        timestamp_us: u64,
36        width: usize,
37        height: usize,
38        data: Bytes,
39    ) -> Result<Self, VideoError> {
40        let expected = width
41            .checked_mul(height)
42            .and_then(|pixels| pixels.checked_mul(3))
43            .ok_or_else(|| {
44                VideoError::Source(format!(
45                    "raw frame dimensions overflow for width={width}, height={height}"
46                ))
47            })?;
48        if data.len() != expected {
49            return Err(VideoError::RawFrameSizeMismatch {
50                expected,
51                got: data.len(),
52            });
53        }
54        Ok(Self {
55            index,
56            timestamp_us,
57            width,
58            height,
59            data,
60        })
61    }
62
63    pub fn index(&self) -> u64 {
64        self.index
65    }
66
67    pub fn timestamp_us(&self) -> u64 {
68        self.timestamp_us
69    }
70
71    pub fn width(&self) -> usize {
72        self.width
73    }
74
75    pub fn height(&self) -> usize {
76        self.height
77    }
78
79    pub fn data(&self) -> &[u8] {
80        &self.data
81    }
82
83    pub fn into_data(self) -> Vec<u8> {
84        self.data.to_vec()
85    }
86
87    pub fn into_bytes(self) -> Bytes {
88        self.data
89    }
90}
91
92/// One decoded/produced frame in HWC tensor layout.
93#[derive(Debug, Clone, PartialEq)]
94pub struct Frame {
95    index: u64,
96    timestamp_us: u64,
97    pixel_format: PixelFormat,
98    image: Tensor,
99}
100
101impl Frame {
102    pub fn new(index: u64, timestamp_us: u64, image: Tensor) -> Result<Self, VideoError> {
103        if image.rank() != 3 {
104            return Err(VideoError::InvalidFrameShape {
105                got: image.shape().to_vec(),
106            });
107        }
108        let channels = image.shape()[2];
109        let pixel_format = match channels {
110            1 => PixelFormat::GrayF32,
111            3 => PixelFormat::RgbF32,
112            _ => return Err(VideoError::UnsupportedChannelCount { channels }),
113        };
114        Ok(Self {
115            index,
116            timestamp_us,
117            pixel_format,
118            image,
119        })
120    }
121
122    pub fn index(&self) -> u64 {
123        self.index
124    }
125
126    pub fn timestamp_us(&self) -> u64 {
127        self.timestamp_us
128    }
129
130    pub fn pixel_format(&self) -> PixelFormat {
131        self.pixel_format
132    }
133
134    pub fn image(&self) -> &Tensor {
135        &self.image
136    }
137}