VLink  2.0.0
A high-performance communication middleware
camera_frame.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file camera_frame.h
26  * @brief Zero-copy container for a single image / video frame plus pixel-format metadata.
27  *
28  * @details
29  * @c CameraFrame is the canonical conduit for camera capture, ISP outputs, and
30  * compressed video streams in the VLink autonomous-driving stack. One frame
31  * carries pixel data (raw or codec-encoded), resolution, pixel format, camera
32  * channel id, capture frequency, video stream-frame type (I/P/B), plus a
33  * 40-byte @c Header for sequencing and dual-timestamp latency measurement.
34  *
35  * @par Pixel formats
36  * | Enum | Family | Description |
37  * | ----------------------- | ----------------- | -------------------------------------- |
38  * | @c kFormatYuv420 | Planar YUV | 4:2:0 (I420) |
39  * | @c kFormatYuv422 | Planar YUV | 4:2:2 |
40  * | @c kFormatYuv444 | Planar YUV | 4:4:4 |
41  * | @c kFormatNv12 | Semi-planar YUV | Y plane + interleaved UV (4:2:0) |
42  * | @c kFormatNv21 | Semi-planar YUV | Y plane + interleaved VU (4:2:0) |
43  * | @c kFormatYuyv | Packed YUV | YUYV 4:2:2 |
44  * | @c kFormatYvyu | Packed YUV | YVYU 4:2:2 |
45  * | @c kFormatUyvy | Packed YUV | UYVY 4:2:2 |
46  * | @c kFormatVyuy | Packed YUV | VYUY 4:2:2 |
47  * | @c kFormatBgr888Packed | Packed RGB | 24-bit BGR, 3 bytes per pixel |
48  * | @c kFormatRgb888Packed | Packed RGB | 24-bit RGB, 3 bytes per pixel |
49  * | @c kFormatRgb888Planar | Planar RGB | Separate R, G, B planes |
50  * | @c kFormatJpeg | Compressed image | JPEG bitstream |
51  * | @c kFormatH264 | Compressed video | H.264 / AVC frame |
52  * | @c kFormatH265 | Compressed video | H.265 / HEVC frame |
53  *
54  * @par Image buffer layout
55  * @code
56  * Planar YUV 4:2:0 (e.g. I420, NV12 family):
57  * +---------------------------+
58  * | Y plane (width * height) |
59  * +---------------------------+
60  * | U plane (width/2 * h/2) |
61  * +---------------------------+
62  * | V plane (width/2 * h/2) |
63  * +---------------------------+
64  *
65  * Packed RGB888:
66  * [ R G B | R G B | R G B | ... ] stride = width * 3
67  *
68  * Compressed JPEG/H264/H265:
69  * opaque codec bitstream of size_ bytes
70  * @endcode
71  *
72  * @par Wire format
73  * @c CameraFrame is POD; the canonical serialiser is @c memcpy. The @c sizeof
74  * value is locked by @c static_assert and forms a permanent contract:
75  * @c vlink::zerocopy::* containers offer NO forward and NO backward binary
76  * compatibility -- every field, including reserved bytes, is wire-locked.
77  * @code
78  * static_assert(sizeof(CameraFrame) == 80, "Sizeof must be 80 bytes.");
79  * @endcode
80  *
81  * @par Memory layout
82  * @code
83  * Offset Size Field
84  * ------ ---- ----------------------
85  * 0 40 Header header
86  * 40 8 uint8_t* data_
87  * 48 8 size_t size_
88  * 56 4 uint32_t channel_
89  * 60 4 uint32_t width_
90  * 64 4 uint32_t height_
91  * 68 4 uint32_t freq_
92  * 72 1 Format format_
93  * 73 1 Stream stream_
94  * 74 1 bool is_owner_
95  * 75 1 (padding)
96  * 76 4 uint32_t reserved_buf_
97  * ------ ---- ----------------------
98  * Total 80 bytes (alignas 8)
99  *
100  * Wire envelope:
101  * [ magic_begin (4) | version (4) | CameraFrame struct (80) | pixel bytes (size_) | magic_end (4) ]
102  * @endcode
103  *
104  * @par Reserved bytes
105  * @c reserved_buf_ is exposed through @c get_reserved() and persists through both
106  * @c clear() and the copy / move helpers so application bridges can stash
107  * minor identifiers. It is part of the wire contract and MUST NOT be
108  * redefined: future library revisions may bind the slot to a real field.
109  *
110  * @par Example
111  * @code
112  * vlink::zerocopy::CameraFrame frame;
113  * frame.set_width(1920);
114  * frame.set_height(1080);
115  * frame.set_format(vlink::zerocopy::CameraFrame::kFormatNv12);
116  * frame.create(1920 * 1080 * 3 / 2);
117  *
118  * // Intra-process zero-copy publishing (no serialisation hop):
119  * VLINK_INTRA_DATA_DECLARE(vlink::zerocopy::CameraFrame, "camera/front");
120  * vlink::Bytes wire;
121  * frame >> wire;
122  * @endcode
123  */
124 
125 #pragma once
126 
127 #include <cstdint>
128 
129 #include "../base/bytes.h"
130 #include "./header.h"
131 
132 namespace vlink {
133 
134 namespace zerocopy {
135 
136 /**
137  * @struct CameraFrame
138  * @brief 80-byte POD container holding one camera / video frame plus image-format metadata.
139  *
140  * @details
141  * The struct size is locked at 80 bytes on 64-bit targets via @c static_assert;
142  * 32-bit toolchains emit a build-time warning. The struct embeds a @c Header
143  * prefix and exposes resolution, pixel format, video stream-type, capture
144  * frequency, and camera channel id.
145  */
146 struct VLINK_EXPORT_AND_ALIGNED(8) CameraFrame final {
147  /**
148  * @brief Pixel / codec encoding of the image payload.
149  */
150  enum Format : uint8_t {
151  kFormatUnknown = 0, ///< Uninitialised / unspecified format.
152 
153  kFormatYuv420 = 1, ///< Planar YUV 4:2:0 (I420).
154  kFormatYuv422 = 2, ///< Planar YUV 4:2:2.
155  kFormatYuv444 = 3, ///< Planar YUV 4:4:4.
156  kFormatNv12 = 4, ///< Semi-planar Y + interleaved UV (4:2:0).
157  kFormatNv21 = 5, ///< Semi-planar Y + interleaved VU (4:2:0).
158  kFormatYuyv = 6, ///< Packed YUYV 4:2:2.
159  kFormatYvyu = 7, ///< Packed YVYU 4:2:2.
160  kFormatUyvy = 8, ///< Packed UYVY 4:2:2.
161  kFormatVyuy = 9, ///< Packed VYUY 4:2:2.
162  kFormatBgr888Packed = 10, ///< Packed 24-bit BGR (3 bytes per pixel).
163  kFormatRgb888Packed = 11, ///< Packed 24-bit RGB (3 bytes per pixel).
164  kFormatRgb888Planar = 12, ///< Planar 24-bit RGB (separate R, G, B planes).
165 
166  kFormatJpeg = 101, ///< JPEG bitstream.
167  kFormatH264 = 102, ///< H.264 / AVC frame.
168  kFormatH265 = 103, ///< H.265 / HEVC frame.
169  };
170 
171  /**
172  * @brief Stream-frame type for compressed video payloads.
173  */
174  enum Stream : uint8_t {
175  kStreamUnknown = 0, ///< Uninitialised / unspecified frame type.
176  kStreamI, ///< Intra-coded key frame.
177  kStreamP, ///< Predicted frame referencing earlier frames.
178  kStreamB, ///< Bi-directionally predicted frame.
179  };
180 
181  /**
182  * @brief Default-constructs an empty frame and asserts the 80-byte contract.
183  */
184  CameraFrame() noexcept;
185 
186  /**
187  * @brief Frees the owned pixel buffer when @c is_owner() is @c true.
188  */
189  ~CameraFrame() noexcept;
190 
191  /**
192  * @brief Deep-copies @p target into a freshly allocated frame.
193  *
194  * @param target Source frame to clone.
195  */
196  CameraFrame(const CameraFrame& target) noexcept;
197 
198  /**
199  * @brief Steals @p target's allocation and metadata; @p target ends empty.
200  *
201  * @param target Source frame moved from.
202  */
203  CameraFrame(CameraFrame&& target) noexcept;
204 
205  /**
206  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
207  *
208  * @param target Source frame to clone.
209  * @return Reference to @c *this.
210  */
211  CameraFrame& operator=(const CameraFrame& target) noexcept;
212 
213  /**
214  * @brief Move-assigns @p target; self-assignment is a no-op.
215  *
216  * @param target Source frame moved from.
217  * @return Reference to @c *this.
218  */
219  CameraFrame& operator=(CameraFrame&& target) noexcept;
220 
221  /**
222  * @brief Deserialises a @c CameraFrame from @p bytes with zero-copy borrowing semantics.
223  *
224  * @details
225  * Validates the magic-number envelope and total length, then borrows the
226  * pixel pointer from @p bytes. Callers must keep @p bytes alive for as
227  * long as this @c CameraFrame is in use.
228  *
229  * @param bytes Wire buffer previously produced by @c operator>>.
230  * @return @c true on success; @c false on magic mismatch or size mismatch.
231  */
232  bool operator<<(const Bytes& bytes) noexcept;
233 
234  /**
235  * @brief Serialises the struct snapshot plus pixel bytes into @p bytes.
236  *
237  * @param bytes Output buffer; resized automatically when its size differs from the serialized size.
238  * @return Always @c true.
239  */
240  bool operator>>(Bytes& bytes) const noexcept;
241 
242  /**
243  * @brief Validates that @p bytes carries a well-formed @c CameraFrame envelope.
244  *
245  * @param bytes Wire buffer to inspect.
246  * @return @c true when both magic sentinels match and the minimum size holds.
247  */
248  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
249 
250  /**
251  * @brief Total bytes that @c operator>> would write for this frame.
252  *
253  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(CameraFrame) + @c size() + @c sizeof(magic_end).
254  */
255  [[nodiscard]] size_t get_serialized_size() const noexcept;
256 
257  /**
258  * @brief Whether the pixel buffer pointer is non-null and its size is positive.
259  *
260  * @return @c true when the frame holds usable pixel data.
261  */
262  [[nodiscard]] bool is_valid() const noexcept;
263 
264  /**
265  * @brief Borrows @p target's pixel buffer without copying.
266  *
267  * @param target Source frame whose buffer must outlive @c *this.
268  * @return @c false on self-borrow, otherwise @c true.
269  */
270  bool shallow_copy(const CameraFrame& target) noexcept;
271 
272  /**
273  * @brief Allocates (or reuses) an owned buffer and copies @p target's pixels.
274  *
275  * @param target Source frame to clone.
276  * @return @c false on self-copy, otherwise @c true.
277  */
278  bool deep_copy(const CameraFrame& target) noexcept;
279 
280  /**
281  * @brief Transfers ownership from @p target; @p target ends empty.
282  *
283  * @param target Source frame moved from.
284  * @return @c false on self-move, otherwise @c true.
285  */
286  bool move_copy(CameraFrame& target) noexcept;
287 
288  /**
289  * @brief Allocates an uninitialised owned pixel buffer of @p size bytes.
290  *
291  * @param size Byte count; must be non-zero.
292  * @return @c false when @p size is zero, otherwise @c true.
293  */
294  bool create(size_t size) noexcept;
295 
296  /**
297  * @brief Releases the owned buffer (if any) and resets metadata except reserved fields.
298  */
299  void clear() noexcept;
300 
301  /**
302  * @brief Borrows an externally owned pixel buffer without copying.
303  *
304  * @param data Non-null source pointer that must outlive @c *this.
305  * @param size Buffer length in bytes; must be non-zero.
306  * @return @c false on invalid arguments or unchanged pointer, otherwise @c true.
307  */
308  bool shallow_copy(uint8_t* data, size_t size) noexcept;
309 
310  /**
311  * @brief Copies @p size bytes from @p data into an owned buffer.
312  *
313  * @param data Non-null source pointer.
314  * @param size Number of bytes to copy; must be non-zero.
315  * @return @c false on invalid arguments or aliasing, otherwise @c true.
316  */
317  bool deep_copy(uint8_t* data, size_t size) noexcept;
318 
319  /**
320  * @brief Compatibility alias for @c deep_copy(uint8_t*, size_t).
321  *
322  * @param data Source pointer.
323  * @param size Number of bytes.
324  * @return Result of the delegated @c deep_copy call.
325  */
326  bool fill_data(uint8_t* data, size_t size) noexcept;
327 
328  /**
329  * @brief Camera channel / sensor index.
330  *
331  * @return Stored channel id.
332  */
333  [[nodiscard]] uint32_t channel() const noexcept;
334 
335  /**
336  * @brief Image width in pixels.
337  *
338  * @return Stored width.
339  */
340  [[nodiscard]] uint32_t width() const noexcept;
341 
342  /**
343  * @brief Image height in pixels.
344  *
345  * @return Stored height.
346  */
347  [[nodiscard]] uint32_t height() const noexcept;
348 
349  /**
350  * @brief Capture frequency in frames per second.
351  *
352  * @return Stored frequency.
353  */
354  [[nodiscard]] uint32_t freq() const noexcept;
355 
356  /**
357  * @brief Pixel / codec encoding tag.
358  *
359  * @return @c Format enum value.
360  */
361  [[nodiscard]] Format format() const noexcept;
362 
363  /**
364  * @brief Video stream-frame type tag.
365  *
366  * @return @c Stream enum value.
367  */
368  [[nodiscard]] Stream stream() const noexcept;
369 
370  /**
371  * @brief Read-only pointer to the pixel bytes.
372  *
373  * @return Pointer to payload start; may be non-null with @c size() == 0 for empty deserialised frames.
374  */
375  [[nodiscard]] const uint8_t* data() const noexcept;
376 
377  /**
378  * @brief Pixel buffer size in bytes.
379  *
380  * @return Byte count, or 0 when empty.
381  */
382  [[nodiscard]] size_t size() const noexcept;
383 
384  /**
385  * @brief Whether this frame owns its pixel buffer.
386  *
387  * @return @c true when the destructor would free the buffer.
388  */
389  [[nodiscard]] bool is_owner() const noexcept;
390 
391  /**
392  * @brief Stores the camera channel / sensor index.
393  *
394  * @param channel Channel id.
395  */
396  void set_channel(uint32_t channel) noexcept;
397 
398  /**
399  * @brief Stores the image width in pixels.
400  *
401  * @param width Pixel width.
402  */
403  void set_width(uint32_t width) noexcept;
404 
405  /**
406  * @brief Stores the image height in pixels.
407  *
408  * @param height Pixel height.
409  */
410  void set_height(uint32_t height) noexcept;
411 
412  /**
413  * @brief Stores the capture frequency.
414  *
415  * @param freq Capture rate in Hz.
416  */
417  void set_freq(uint32_t freq) noexcept;
418 
419  /**
420  * @brief Stores the pixel / codec encoding tag.
421  *
422  * @param format @c Format enum value.
423  */
424  void set_format(Format format) noexcept;
425 
426  /**
427  * @brief Stores the video stream-frame type tag.
428  *
429  * @param stream @c Stream enum value.
430  */
431  void set_stream(Stream stream) noexcept;
432 
433  /**
434  * @brief Mutable accessor for the 32-bit reserved slot in the wire format.
435  *
436  * @return Reference to @c reserved_buf_.
437  */
438  uint32_t& get_reserved() noexcept { return reserved_buf_; }
439 
440  Header header; ///< Sequencing and timestamp metadata prefix.
441 
442  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
443 
444  private:
445  uint8_t* data_{nullptr};
446  size_t size_{0};
447  uint32_t channel_{0};
448  uint32_t width_{0};
449  uint32_t height_{0};
450  uint32_t freq_{0};
451  Format format_{kFormatUnknown};
452  Stream stream_{kStreamUnknown};
453  bool is_owner_{false};
454  uint32_t reserved_buf_{0};
455 
456  static constexpr uint32_t kMagicNumberBegin{0x98B7F15A};
457  static constexpr uint32_t kMagicNumberEnd{0x98B7F15F};
458 };
459 
460 } // namespace zerocopy
461 
462 } // namespace vlink
Fixed-size timestamp and sequencing prefix embedded by every VLink zero-copy container.
80-byte POD container holding one camera / video frame plus image-format metadata.
40-byte timestamp / sequencing metadata prefix shared by all zero-copy containers.