Skip to main content

vmb_core/
frame.rs

1//! Safe view over a Vimba X frame.
2//!
3//! A [`Frame`] is handed to user callbacks and borrows into the adapter's
4//! internal buffer for the duration of the call. It is intentionally
5//! **not** `'static` — the caller MUST either consume the data
6//! synchronously (e.g. copy it out via [`Frame::to_vec`]) or forward it
7//! through a bounded channel. Once the callback returns, the adapter
8//! re-queues the buffer and the underlying bytes may be overwritten at
9//! any moment.
10
11/// Borrowed view of a single received frame.
12///
13/// The lifetime `'a` is tied to the adapter-owned buffer that fed this
14/// invocation; callers must not escape `Frame` past the end of their
15/// callback.
16pub struct Frame<'a> {
17    pub(crate) data: &'a [u8],
18    /// Frame width in pixels.
19    pub width: u32,
20    /// Frame height in pixels.
21    pub height: u32,
22    /// Decoded pixel format.
23    pub pixel_format: PixelFormat,
24    /// Wall-clock timestamp captured by the adapter at frame arrival,
25    /// expressed in nanoseconds since the Unix epoch.
26    ///
27    /// This is intentionally NOT the GenICam `Timestamp` register on
28    /// `VmbFrame_t`, which is camera-clock-relative (counts from the
29    /// camera's last power-on for most Allied Vision USB models) and
30    /// therefore unsuitable for wall-clock-aware downstream consumers.
31    pub timestamp_ns: u64,
32    /// Monotonically increasing frame identifier assigned by the SDK.
33    pub frame_id: u64,
34}
35
36impl<'a> Frame<'a> {
37    /// Build a borrowed frame view. Intended to be called by a
38    /// [`VmbRuntime`] adapter (either the real FFI adapter or an
39    /// in-memory fake) after it has decoded the SDK's frame descriptor
40    /// into plain Rust types.
41    ///
42    /// [`VmbRuntime`]: crate::types
43    pub fn new(
44        data: &'a [u8],
45        width: u32,
46        height: u32,
47        pixel_format: PixelFormat,
48        timestamp_ns: u64,
49        frame_id: u64,
50    ) -> Self {
51        Self {
52            data,
53            width,
54            height,
55            pixel_format,
56            timestamp_ns,
57            frame_id,
58        }
59    }
60
61    /// Raw byte view of the frame.
62    pub fn data(&self) -> &[u8] {
63        self.data
64    }
65
66    /// Copy the frame bytes into a new [`Vec<u8>`]. This is the typical
67    /// callback path: copy out before returning so the adapter can
68    /// re-queue.
69    pub fn to_vec(&self) -> Vec<u8> {
70        self.data.to_vec()
71    }
72
73    /// Length of the borrowed buffer in bytes.
74    pub fn len(&self) -> usize {
75        self.data.len()
76    }
77
78    /// Returns `true` if the borrowed buffer is empty.
79    pub fn is_empty(&self) -> bool {
80        self.data.is_empty()
81    }
82}
83
84/// Minimal pixel format representation.
85///
86/// Vimba's pixel format space is enormous (PFNC codes); we explicitly map
87/// only the formats our pipeline currently cares about and pass everything
88/// else through as [`PixelFormat::Other`] so the consumer can interpret it.
89#[derive(Copy, Clone, Debug, PartialEq, Eq)]
90pub enum PixelFormat {
91    /// 8-bit mono, one byte per pixel (`VmbPixelFormatMono8` = 0x0108_0001).
92    Mono8,
93    /// 8-bit BGR, three bytes per pixel (`VmbPixelFormatBgr8` = 0x0218_0015).
94    Bgr8,
95    /// Anything else — the consumer must interpret the raw PFNC code.
96    Other(u32),
97}
98
99impl PixelFormat {
100    /// Map a raw `VmbPixelFormat_t` / PFNC code.
101    pub fn from_raw(raw: u32) -> Self {
102        // Values are in `vmb_sys::bindings::VmbPixelFormatType`:
103        //   VmbPixelFormatMono8 = 17_301_505  (0x0108_0001)
104        //   VmbPixelFormatBgr8  = 35_127_317  (0x0218_0015)
105        match raw {
106            0x0108_0001 => PixelFormat::Mono8,
107            0x0218_0015 => PixelFormat::Bgr8,
108            other => PixelFormat::Other(other),
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn pixel_format_maps_known_codes() {
119        assert_eq!(PixelFormat::from_raw(0x0108_0001), PixelFormat::Mono8);
120        assert_eq!(PixelFormat::from_raw(0x0218_0015), PixelFormat::Bgr8);
121    }
122
123    #[test]
124    fn pixel_format_preserves_unknown_codes() {
125        assert_eq!(
126            PixelFormat::from_raw(0xdead_beef),
127            PixelFormat::Other(0xdead_beef)
128        );
129    }
130
131    #[test]
132    fn frame_basic_accessors() {
133        let data = [1u8, 2, 3, 4, 5, 6, 7, 8];
134        let frame = Frame::new(&data, 2, 4, PixelFormat::Mono8, 123, 7);
135        assert_eq!(frame.len(), 8);
136        assert!(!frame.is_empty());
137        assert_eq!(frame.to_vec(), vec![1, 2, 3, 4, 5, 6, 7, 8]);
138        assert_eq!(frame.data(), &data);
139        assert_eq!(frame.width, 2);
140        assert_eq!(frame.height, 4);
141        assert_eq!(frame.pixel_format, PixelFormat::Mono8);
142        assert_eq!(frame.timestamp_ns, 123);
143        assert_eq!(frame.frame_id, 7);
144    }
145
146    #[test]
147    fn frame_is_empty_when_no_bytes() {
148        let frame = Frame::new(&[], 0, 0, PixelFormat::Mono8, 0, 0);
149        assert_eq!(frame.len(), 0);
150        assert!(frame.is_empty());
151        assert_eq!(frame.to_vec(), Vec::<u8>::new());
152    }
153}