VLink  2.1.0
A high-performance communication middleware
camera_frame.h
浏览该文件的文档.
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 kFormatMono8, @c kFormatMono16 | Grayscale | 8/16-bit mono image |
51  * | @c kFormatRgba8888Packed, @c kFormatBgra8888Packed | Packed RGB | 32-bit RGBA / BGRA |
52  * | @c kFormatUint8C1 .. @c kFormatFloat64C4 | OpenCV/ROS | Generic numeric images |
53  * | @c kFormatBayerRggb8 .. @c kFormatBayerGrbg16 | Bayer | RGGB/BGGR/GBRG/GRBG, 8/16-bit |
54  * | @c kFormatJpeg, @c kFormatMjpeg | Compressed image | JPEG / Motion-JPEG |
55  * | @c kFormatPng, @c kFormatWebp | Compressed image | PNG / WebP |
56  * | @c kFormatH264, @c kFormatH265 | Compressed video | H.264 / H.265 |
57  * | @c kFormatH266, @c kFormatAv1 | Compressed video | H.266 / AV1 |
58  *
59  * @par Image buffer layout
60  * @code
61  * Planar YUV 4:2:0 (I420):
62  * +---------------------------+
63  * | Y plane (width * height) |
64  * +---------------------------+
65  * | U plane (width/2 * h/2) |
66  * +---------------------------+
67  * | V plane (width/2 * h/2) |
68  * +---------------------------+
69  *
70  * Semi-planar NV12/NV21:
71  * +---------------------------+
72  * | Y plane (width * height)|
73  * +---------------------------+
74  * | UV or VU (width * h / 2) |
75  * +---------------------------+
76  *
77  * Packed RGB888:
78  * [ R G B | R G B | R G B | ... ] stride = width * 3
79  *
80  * Compressed JPEG/PNG/WebP/MJPEG/H.26x/AV1:
81  * opaque codec bitstream of size_ bytes
82  * @endcode
83  *
84  * @par Wire format
85  * @c CameraFrame is POD; the canonical serialiser is @c memcpy. The @c sizeof
86  * value is locked by @c static_assert and forms a permanent contract:
87  * @c vlink::zerocopy::* containers offer NO forward and NO backward binary
88  * compatibility -- every field, including reserved bytes, is wire-locked.
89  * @code
90  * static_assert(sizeof(CameraFrame) == 80, "Sizeof must be 80 bytes.");
91  * @endcode
92  *
93  * @par Memory layout
94  * @code
95  * Offset Size Field
96  * ------ ---- ----------------------
97  * 0 40 Header header
98  * 40 8 uint8_t* data_
99  * 48 8 size_t size_
100  * 56 4 uint32_t channel_
101  * 60 4 uint32_t width_
102  * 64 4 uint32_t height_
103  * 68 4 uint32_t freq_
104  * 72 1 Format format_
105  * 73 1 Stream stream_
106  * 74 1 bool is_owner_
107  * 75 1 (padding)
108  * 76 4 uint32_t reserved_buf_
109  * ------ ---- ----------------------
110  * Total 80 bytes (alignas 8)
111  *
112  * Wire envelope:
113  * [ magic_begin (4) | version (4) | CameraFrame struct (80) | pixel bytes (size_) | magic_end (4) ]
114  * @endcode
115  *
116  * @par Reserved bytes
117  * @c reserved_buf_ is exposed through @c get_reserved() and persists through both
118  * @c clear() and the copy / move helpers so application bridges can stash
119  * minor identifiers. It is part of the wire contract and MUST NOT be
120  * redefined: future library revisions may bind the slot to a real field.
121  *
122  * @par Example
123  * @code
124  * vlink::zerocopy::CameraFrame frame;
125  * frame.set_width(1920);
126  * frame.set_height(1080);
127  * frame.set_format(vlink::zerocopy::CameraFrame::kFormatNv12);
128  * frame.create(1920 * 1080 * 3 / 2);
129  *
130  * // Intra-process zero-copy publishing (no serialisation hop):
131  * VLINK_INTRA_DATA_DECLARE(vlink::zerocopy::CameraFrame, "camera/front");
132  * vlink::Bytes wire;
133  * frame >> wire;
134  * @endcode
135  */
136 
137 #pragma once
138 
139 #include <cstdint>
140 #include <string_view>
141 
142 #include "../base/bytes.h"
143 #include "./header.h"
144 
145 namespace vlink {
146 
147 namespace zerocopy {
148 
149 /**
150  * @struct CameraFrame
151  * @brief 80-byte POD container holding one camera / video frame plus image-format metadata.
152  *
153  * @details
154  * The struct size is locked at 80 bytes on 64-bit targets via @c static_assert;
155  * 32-bit toolchains emit a build-time warning. The struct embeds a @c Header
156  * prefix and exposes resolution, pixel format, video stream-type, capture
157  * frequency, and camera channel id.
158  */
159 struct VLINK_EXPORT_AND_ALIGNED(8) CameraFrame final {
160  /**
161  * @brief Pixel / codec encoding of the image payload.
162  */
163  enum Format : uint8_t {
164  kFormatUnknown = 0, ///< Uninitialised / unspecified format.
165 
166  kFormatYuv420 = 1, ///< Planar YUV 4:2:0 (I420).
167  kFormatYuv422 = 2, ///< Planar YUV 4:2:2.
168  kFormatYuv444 = 3, ///< Planar YUV 4:4:4.
169  kFormatNv12 = 4, ///< Semi-planar Y + interleaved UV (4:2:0).
170  kFormatNv21 = 5, ///< Semi-planar Y + interleaved VU (4:2:0).
171  kFormatYuyv = 6, ///< Packed YUYV 4:2:2.
172  kFormatYvyu = 7, ///< Packed YVYU 4:2:2.
173  kFormatUyvy = 8, ///< Packed UYVY 4:2:2.
174  kFormatVyuy = 9, ///< Packed VYUY 4:2:2.
175  kFormatBgr888Packed = 10, ///< Packed 24-bit BGR (3 bytes per pixel).
176  kFormatRgb888Packed = 11, ///< Packed 24-bit RGB (3 bytes per pixel).
177  kFormatRgb888Planar = 12, ///< Planar 24-bit RGB (separate R, G, B planes).
178 
179  kFormatMono8 = 13, ///< 8-bit grayscale / luminance.
180  kFormatMono16 = 14, ///< 16-bit grayscale / luminance.
181  kFormatRgba8888Packed = 15, ///< Packed 32-bit RGBA (4 bytes per pixel).
182  kFormatBgra8888Packed = 16, ///< Packed 32-bit BGRA (4 bytes per pixel).
183 
184  kFormatUint8C1 = 20, ///< Generic unsigned 8-bit, 1 channel.
185  kFormatUint8C2 = 21, ///< Generic unsigned 8-bit, 2 channels.
186  kFormatUint8C3 = 22, ///< Generic unsigned 8-bit, 3 channels.
187  kFormatUint8C4 = 23, ///< Generic unsigned 8-bit, 4 channels.
188  kFormatInt8C1 = 24, ///< Generic signed 8-bit, 1 channel.
189  kFormatInt8C2 = 25, ///< Generic signed 8-bit, 2 channels.
190  kFormatInt8C3 = 26, ///< Generic signed 8-bit, 3 channels.
191  kFormatInt8C4 = 27, ///< Generic signed 8-bit, 4 channels.
192  kFormatUint16C1 = 28, ///< Generic unsigned 16-bit, 1 channel.
193  kFormatUint16C2 = 29, ///< Generic unsigned 16-bit, 2 channels.
194  kFormatUint16C3 = 30, ///< Generic unsigned 16-bit, 3 channels.
195  kFormatUint16C4 = 31, ///< Generic unsigned 16-bit, 4 channels.
196  kFormatInt16C1 = 32, ///< Generic signed 16-bit, 1 channel.
197  kFormatInt16C2 = 33, ///< Generic signed 16-bit, 2 channels.
198  kFormatInt16C3 = 34, ///< Generic signed 16-bit, 3 channels.
199  kFormatInt16C4 = 35, ///< Generic signed 16-bit, 4 channels.
200  kFormatInt32C1 = 36, ///< Generic signed 32-bit, 1 channel.
201  kFormatInt32C2 = 37, ///< Generic signed 32-bit, 2 channels.
202  kFormatInt32C3 = 38, ///< Generic signed 32-bit, 3 channels.
203  kFormatInt32C4 = 39, ///< Generic signed 32-bit, 4 channels.
204  kFormatFloat32C1 = 40, ///< Generic 32-bit float, 1 channel.
205  kFormatFloat32C2 = 41, ///< Generic 32-bit float, 2 channels.
206  kFormatFloat32C3 = 42, ///< Generic 32-bit float, 3 channels.
207  kFormatFloat32C4 = 43, ///< Generic 32-bit float, 4 channels.
208  kFormatFloat64C1 = 44, ///< Generic 64-bit float, 1 channel.
209  kFormatFloat64C2 = 45, ///< Generic 64-bit float, 2 channels.
210  kFormatFloat64C3 = 46, ///< Generic 64-bit float, 3 channels.
211  kFormatFloat64C4 = 47, ///< Generic 64-bit float, 4 channels.
212 
213  kFormatBayerRggb8 = 60, ///< Bayer RGGB, 8-bit samples.
214  kFormatBayerBggr8 = 61, ///< Bayer BGGR, 8-bit samples.
215  kFormatBayerGbrg8 = 62, ///< Bayer GBRG, 8-bit samples.
216  kFormatBayerGrbg8 = 63, ///< Bayer GRBG, 8-bit samples.
217  kFormatBayerRggb16 = 64, ///< Bayer RGGB, 16-bit samples.
218  kFormatBayerBggr16 = 65, ///< Bayer BGGR, 16-bit samples.
219  kFormatBayerGbrg16 = 66, ///< Bayer GBRG, 16-bit samples.
220  kFormatBayerGrbg16 = 67, ///< Bayer GRBG, 16-bit samples.
221 
222  kFormatJpeg = 101, ///< JPEG bitstream.
223  kFormatH264 = 102, ///< H.264 / AVC frame.
224  kFormatH265 = 103, ///< H.265 / HEVC frame.
225  kFormatMjpeg = 104, ///< Motion-JPEG frame.
226  kFormatPng = 105, ///< PNG bitstream.
227  kFormatWebp = 106, ///< WebP bitstream.
228  kFormatH266 = 107, ///< H.266 / VVC frame.
229  kFormatAv1 = 108, ///< AV1 frame.
230  };
231 
232  /**
233  * @brief Stream-frame type for compressed video payloads.
234  */
235  enum Stream : uint8_t {
236  kStreamUnknown = 0, ///< Uninitialised / unspecified frame type.
237  kStreamI, ///< Intra-coded key frame.
238  kStreamP, ///< Predicted frame referencing earlier frames.
239  kStreamB, ///< Bi-directionally predicted frame.
240  };
241 
242  /**
243  * @brief Default-constructs an empty frame and asserts the 80-byte contract.
244  */
245  CameraFrame() noexcept;
246 
247  /**
248  * @brief Frees the owned pixel buffer when @c is_owner() is @c true.
249  */
250  ~CameraFrame() noexcept;
251 
252  /**
253  * @brief Deep-copies @p target into a freshly allocated frame.
254  *
255  * @param target Source frame to clone.
256  */
257  CameraFrame(const CameraFrame& target) noexcept;
258 
259  /**
260  * @brief Steals @p target's allocation and metadata; @p target ends empty.
261  *
262  * @param target Source frame moved from.
263  */
264  CameraFrame(CameraFrame&& target) noexcept;
265 
266  /**
267  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
268  *
269  * @param target Source frame to clone.
270  * @return Reference to @c *this.
271  */
272  CameraFrame& operator=(const CameraFrame& target) noexcept;
273 
274  /**
275  * @brief Move-assigns @p target; self-assignment is a no-op.
276  *
277  * @param target Source frame moved from.
278  * @return Reference to @c *this.
279  */
280  CameraFrame& operator=(CameraFrame&& target) noexcept;
281 
282  /**
283  * @brief Deserialises a @c CameraFrame from @p bytes with zero-copy borrowing semantics.
284  *
285  * @details
286  * Validates the magic-number envelope and total length, then borrows the
287  * pixel pointer from @p bytes. Callers must keep @p bytes alive for as
288  * long as this @c CameraFrame is in use.
289  *
290  * @param bytes Wire buffer previously produced by @c operator>>.
291  * @return @c true on success; @c false on magic mismatch or size mismatch.
292  */
293  bool operator<<(const Bytes& bytes) noexcept;
294 
295  /**
296  * @brief Serialises the struct snapshot plus pixel bytes into @p bytes.
297  *
298  * @param bytes Output buffer; resized automatically when its size differs from the serialized size.
299  * @return @c true on success; @c false when output allocation fails.
300  */
301  bool operator>>(Bytes& bytes) const noexcept;
302 
303  /**
304  * @brief Validates that @p bytes carries a well-formed @c CameraFrame envelope.
305  *
306  * @param bytes Wire buffer to inspect.
307  * @return @c true when both magic sentinels match and the minimum size holds.
308  */
309  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
310 
311  /**
312  * @brief Maps common ROS/OpenCV/codec encoding names to @c Format.
313  *
314  * @details Matching is case-insensitive. Examples include @c rgb8, @c bgr8,
315  * @c mono16, @c 32FC1, @c bayer_rggb8, @c jpeg and @c h264.
316  */
317  [[nodiscard]] static Format format_from_encoding(std::string_view encoding) noexcept;
318 
319  /**
320  * @brief Returns the canonical ROS/OpenCV/codec encoding name for @p format.
321  */
322  [[nodiscard]] static std::string_view encoding_from_format(Format format) noexcept;
323 
324  /**
325  * @brief Total bytes that @c operator>> would write for this frame.
326  *
327  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(CameraFrame) + @c size() + @c sizeof(magic_end).
328  */
329  [[nodiscard]] size_t get_serialized_size() const noexcept;
330 
331  /**
332  * @brief Whether the pixel buffer pointer is non-null and its size is positive.
333  *
334  * @return @c true when the frame holds usable pixel data.
335  */
336  [[nodiscard]] bool is_valid() const noexcept;
337 
338  /**
339  * @brief Borrows @p target's pixel buffer without copying.
340  *
341  * @param target Source frame whose buffer must outlive @c *this.
342  * @return @c false on self-borrow or owned-buffer aliasing, otherwise @c true.
343  */
344  bool shallow_copy(const CameraFrame& target) noexcept;
345 
346  /**
347  * @brief Allocates (or reuses) an owned buffer and copies @p target's pixels.
348  *
349  * @param target Source frame to clone.
350  * @return @c false on self-copy, owned-buffer aliasing, or allocation failure.
351  */
352  bool deep_copy(const CameraFrame& target) noexcept;
353 
354  /**
355  * @brief Transfers ownership from @p target; @p target ends empty.
356  *
357  * @param target Source frame moved from.
358  * @return @c false on self-move, otherwise @c true.
359  */
360  bool move_copy(CameraFrame& target) noexcept;
361 
362  /**
363  * @brief Allocates an uninitialised owned pixel buffer of @p size bytes.
364  *
365  * @param size Byte count; must be non-zero.
366  * @return @c false when @p size is zero or allocation fails, otherwise @c true.
367  */
368  bool create(size_t size) noexcept;
369 
370  /**
371  * @brief Releases the owned buffer (if any) and resets metadata except reserved fields.
372  */
373  void clear() noexcept;
374 
375  /**
376  * @brief Borrows an externally owned pixel buffer without copying.
377  *
378  * @param data Non-null source pointer that must outlive @c *this.
379  * @param size Buffer length in bytes; must be non-zero.
380  * @return @c false on invalid arguments, unchanged pointer, or owned-buffer aliasing, otherwise @c true.
381  */
382  bool shallow_copy(uint8_t* data, size_t size) noexcept;
383 
384  /**
385  * @brief Copies @p size bytes from @p data into an owned buffer.
386  *
387  * @param data Non-null source pointer.
388  * @param size Number of bytes to copy; must be non-zero.
389  * @return @c false on invalid arguments, aliasing, or allocation failure, otherwise @c true.
390  */
391  bool deep_copy(uint8_t* data, size_t size) noexcept;
392 
393  /**
394  * @brief Compatibility alias for @c deep_copy(uint8_t*, size_t).
395  *
396  * @param data Source pointer.
397  * @param size Number of bytes.
398  * @return Result of the delegated @c deep_copy call.
399  */
400  bool fill_data(uint8_t* data, size_t size) noexcept;
401 
402  /**
403  * @brief Camera channel / sensor index.
404  *
405  * @return Stored channel id.
406  */
407  [[nodiscard]] uint32_t channel() const noexcept;
408 
409  /**
410  * @brief Image width in pixels.
411  *
412  * @return Stored width.
413  */
414  [[nodiscard]] uint32_t width() const noexcept;
415 
416  /**
417  * @brief Image height in pixels.
418  *
419  * @return Stored height.
420  */
421  [[nodiscard]] uint32_t height() const noexcept;
422 
423  /**
424  * @brief Capture frequency in frames per second.
425  *
426  * @return Stored frequency.
427  */
428  [[nodiscard]] uint32_t freq() const noexcept;
429 
430  /**
431  * @brief Pixel / codec encoding tag.
432  *
433  * @return @c Format enum value.
434  */
435  [[nodiscard]] Format format() const noexcept;
436 
437  /**
438  * @brief Video stream-frame type tag.
439  *
440  * @return @c Stream enum value.
441  */
442  [[nodiscard]] Stream stream() const noexcept;
443 
444  /**
445  * @brief Read-only pointer to the pixel bytes.
446  *
447  * @return Pointer to payload start; may be non-null with @c size() == 0 for empty deserialised frames.
448  */
449  [[nodiscard]] const uint8_t* data() const noexcept;
450 
451  /**
452  * @brief Pixel buffer size in bytes.
453  *
454  * @return Byte count, or 0 when empty.
455  */
456  [[nodiscard]] size_t size() const noexcept;
457 
458  /**
459  * @brief Whether this frame owns its pixel buffer.
460  *
461  * @return @c true when the destructor would free the buffer.
462  */
463  [[nodiscard]] bool is_owner() const noexcept;
464 
465  /**
466  * @brief Stores the camera channel / sensor index.
467  *
468  * @param channel Channel id.
469  */
470  void set_channel(uint32_t channel) noexcept;
471 
472  /**
473  * @brief Stores the image width in pixels.
474  *
475  * @param width Pixel width.
476  */
477  void set_width(uint32_t width) noexcept;
478 
479  /**
480  * @brief Stores the image height in pixels.
481  *
482  * @param height Pixel height.
483  */
484  void set_height(uint32_t height) noexcept;
485 
486  /**
487  * @brief Stores the capture frequency.
488  *
489  * @param freq Capture rate in Hz.
490  */
491  void set_freq(uint32_t freq) noexcept;
492 
493  /**
494  * @brief Stores the pixel / codec encoding tag.
495  *
496  * @param format @c Format enum value.
497  */
498  void set_format(Format format) noexcept;
499 
500  /**
501  * @brief Stores the video stream-frame type tag.
502  *
503  * @param stream @c Stream enum value.
504  */
505  void set_stream(Stream stream) noexcept;
506 
507  /**
508  * @brief Mutable accessor for the 32-bit reserved slot in the wire format.
509  *
510  * @return Reference to @c reserved_buf_.
511  */
512  uint32_t& get_reserved() noexcept { return reserved_buf_; }
513 
514  Header header; ///< Sequencing and timestamp metadata prefix.
515 
516  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
517 
518  private:
519  uint8_t* data_{nullptr};
520  size_t size_{0};
521  uint32_t channel_{0};
522  uint32_t width_{0};
523  uint32_t height_{0};
524  uint32_t freq_{0};
525  Format format_{kFormatUnknown};
526  Stream stream_{kStreamUnknown};
527  bool is_owner_{false};
528  uint32_t reserved_buf_{0};
529 
530  static constexpr uint32_t kMagicNumberBegin{0x98B7F15A};
531  static constexpr uint32_t kMagicNumberEnd{0x98B7F15F};
532 };
533 
534 } // namespace zerocopy
535 
536 } // 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.