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}