diff --git a/CLAUDE.md b/CLAUDE.md index 3cc9527..38a5a2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,9 +15,9 @@ two modules below have no own CLAUDE.md. ### Modules - **pj_base** — vocabulary types (`Timestamp`, `DatasetId`, `Expected`, `Span`, type trees), - the canonical builtin object vocabulary (`pj_base/builtin/`: 17 struct headers — Image, DepthImage, - PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame, AssetVideo, - SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame, VoxelGrid) and their 16 + the canonical builtin object vocabulary (`pj_base/builtin/`: 16 struct headers — Image, DepthImage, + PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame, + SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame, VoxelGrid) and their 15 wire codecs (RobotDescription carries source text as-is — no codec), the C-ABI protocol headers for DataSource/MessageParser/Toolbox + the C++ SDK base classes / host-view helpers built on them. - **pj_plugins** — host-side loaders + RAII handles + plugin discovery/catalog for four plugin diff --git a/docs/builtin_type.md b/docs/builtin_type.md index 6a965e6..99944c8 100644 --- a/docs/builtin_type.md +++ b/docs/builtin_type.md @@ -22,7 +22,6 @@ The public headers live under: #include #include #include -#include #include #include #include @@ -36,7 +35,6 @@ The public headers live under: #include #include #include -#include #include #include #include @@ -86,7 +84,7 @@ Builtin objects fall into two serialization families: | Family | Current types | Storage model | Codec policy | |--------|---------------|---------------|--------------| | Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `VoxelGrid`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span` plus `BufferAnchor`. | No mandatory canonical codec; preserve zero-copy views over ROS, MCAP, compressed image, point-cloud, or plugin-owned payloads. If conversion is unavoidable, allocate a new payload and anchor it. | -| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `AssetVideo`, `RobotDescription`, `CameraInfo`, `PosesInFrame`; future marker types | SDK structs own their vectors/strings/scalars directly. | Add explicit codecs when canonical bytes are needed. Codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription` carries source-format text as-is (no canonical codec) — the format hint distinguishes URDF / SDF / MJCF. | +| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `RobotDescription`, `CameraInfo`, `PosesInFrame`; future marker types | SDK structs own their vectors/strings/scalars directly. | Add explicit codecs when canonical bytes are needed. Codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription` carries source-format text as-is (no canonical codec) — the format hint distinguishes URDF / SDF / MJCF. | Canonical `.proto` files live under `pj_base/proto/pj` and act as the wire format contract. One file per top-level message, each named after its message @@ -120,7 +118,6 @@ annotations, frame transforms, or no builtin object. | `kMesh3D` | `PJ::sdk::Mesh3D` | 3D mesh asset in its native binary format (GLTF/GLB/STL/PLY/OBJ/USD/DAE). | | `kVideoFrame` | `PJ::sdk::VideoFrame` | One frame of an inter-frame-coded video stream (h264/h265/vp9/av1). | | `kSceneEntities` | `PJ::sdk::SceneEntities` | Procedural 3D scene primitives (arrows, cubes, lines, text, …). | -| `kAssetVideo` | `PJ::sdk::AssetVideo` | File-backed video reference plus typed playback metadata. | | `kRobotDescription` | `PJ::sdk::RobotDescription` | Raw URDF/SDF/MJCF text + format hint. | | `kCameraInfo` | `PJ::sdk::CameraInfo` | Pinhole camera calibration (intrinsics K, distortion D, rectification R, projection P). | | `kOccupancyGridUpdate` | `PJ::sdk::OccupancyGridUpdate` | Incremental sub-rectangle patch for a previously-published `OccupancyGrid`. | @@ -421,44 +418,6 @@ frames within a stream. `pj_base/builtin/video_frame_codec.hpp` serializes and deserializes this type using the canonical `PJ.VideoFrame` protobuf wire format. -## AssetVideo - -`AssetVideo` is the entry-point handle for video assets ingested by data -loaders that point at an external media file — LeRobot datasets, MP4 -loaders, and similar. Producers push exactly one `AssetVideo` per topic; -the ObjectStore timestamp of that entry equals `time_origin_ns` so -timeline UIs naturally see the asset's start instant. - -Unlike `VideoFrame` (a single frame of a streamed payload), `AssetVideo` -carries no pixel data — it references the file by path and surfaces -decode-routing metadata (media type, dimensions, frame rate) without -forcing the consumer to open the file just to size a playback window. - -| Field | Type | Notes | -|-------|------|-------| -| `time_origin_ns` | `std::optional` | Wall-clock instant of the first frame. Absent means the asset is not aligned to wall clock. | -| `start_ns` | `std::optional` | In-file offset (ns) where the playable window begins. Absent means "play from the start of the file". | -| `end_ns` | `std::optional` | In-file offset (ns) where the playable window ends. Absent means "play to the end of the file". | -| `file_path` | `std::string` | Absolute path or path relative to a consumer-known root. | -| `media_type` | `std::string` | MIME type hint. Empty means probe the file. | -| `width` | `uint32_t` | Pixel width. `0` means unknown. | -| `height` | `uint32_t` | Pixel height. `0` means unknown. | -| `frame_rate` | `double` | Nominal FPS. `0` or NaN means unknown. | - -When both `start_ns` and `end_ns` are absent the whole file is the playable -window. When present, consumers must clamp seek requests to -`[start_ns, end_ns]` and bound timeline UI to that range. This is how -producers expose one clip out of a file that holds many concatenated -clips — for example LeRobot v3.0, where a single MP4 per camera packs -many episodes back-to-back and `[from_timestamp, to_timestamp]` in the -episode metadata maps directly to `[start_ns, end_ns]`. - -The total file duration is *not* carried in the message — the decoder -backend reports it. - -`pj_base/builtin/asset_video_codec.hpp` serializes and deserializes this -type using the canonical `PJ.AssetVideo` protobuf wire format. - ## SceneEntities `SceneEntities` is the workhorse for marker-style 3D visualization — the @@ -640,7 +599,6 @@ using the canonical `PJ.VoxelGrid` protobuf wire format. | URDF / `visualization_msgs/Marker` mesh resource | `Mesh3D` | Embed `data` (with `format`) or point at `url`; preserve `pose` and `scale`. | | ROS `nav_msgs/Path`, marker arrays | `SceneEntities` | Map polylines to `LinePrimitive`, arrows to `ArrowPrimitive`, etc. | | H.264/H.265/VP9/AV1 stream frame | `VideoFrame` | Forward one frame's bitstream bytes plus the codec identifier. | -| MP4 / MKV / AV1 dataset file | `AssetVideo` | Push once per topic with the file path and metadata; consumers seek into the file by tracker time. | | Detection or tracking message | `ImageAnnotations` | Convert boxes, points, circles, and labels into pixel-space primitives. | | ROS `tf2_msgs/TFMessage` | `FrameTransforms` | Convert transform batches into named parent/child frame relationships. | | ROS `geometry_msgs/PoseArray` | `PosesInFrame` | Forward timestamp, frame, and the pose array; the viewer chooses how to draw them. | diff --git a/pj_base/CLAUDE.md b/pj_base/CLAUDE.md index 2d01acb..ab14c46 100644 --- a/pj_base/CLAUDE.md +++ b/pj_base/CLAUDE.md @@ -1,10 +1,10 @@ # pj_base — SDK vocabulary, builtin object schemas, and the C plugin ABI -pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `VoxelGrid`, `FrameTransforms`, … — 17 types) **and 16 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`. +pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `VoxelGrid`, `FrameTransforms`, … — 16 types) **and 15 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`. ## Layout - `include/pj_base/` — vocabulary primitives: `types.hpp`, `time.hpp` (absolute time spine: `Timepoint`/`Duration` + `fromRaw`/`toRaw`), `type_tree.hpp`, `dataset.hpp`, `expected.hpp`, `span.hpp`, `number_parse.hpp`, `assert.hpp`, `diagnostic_sink.hpp`, `buffer_anchor.hpp`. -- `include/pj_base/builtin/` — the 17 builtin object struct headers (`*.hpp`; 18 enum values in `BuiltinObjectType`, value 2 reserved) + their 16 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder. +- `include/pj_base/builtin/` — the 16 builtin object struct headers (`*.hpp`; 17 enum values in `BuiltinObjectType`, values 2 and 12 reserved) + their 15 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder. - `include/pj_base/sdk/` — C++ SDK over the ABI: DataSource + Toolbox `*_plugin_base.hpp`, `service_registry.hpp`/`service_traits.hpp`, host views, Arrow RAII holders, `testing/`. - `include/pj_base/*_protocol.h`, `plugin_data_api.h`, `builtin_object_abi.h`, `plugin_abi_export.hpp` — the stable C-ABI surface for DataSource/MessageParser/Toolbox (the Dialog protocol header lives in `pj_plugins/dialog_protocol/`). - `proto/pj/` — canonical `.proto` wire contracts for the builtin types (see its README). @@ -12,7 +12,7 @@ pj_base is the **Level 0** foundation and the **SDK boundary** for plugin author - `abi/baseline.abi` — golden libabigail dump; the ABI-stability regression baseline. ## Gotchas -- ABI numbering is **frozen**: `BuiltinObjectType` (builtin_object.hpp) and `PJ_builtin_object_type_t` (builtin_object_abi.h) share stable numeric values — never renumber; type 2 is permanently reserved. Append-only. +- ABI numbering is **frozen**: `BuiltinObjectType` (builtin_object.hpp) and `PJ_builtin_object_type_t` (builtin_object_abi.h) share stable numeric values — never renumber; types 2 and 12 are permanently reserved. Append-only. - Every vtable slot is `PJ_NOEXCEPT`; a throw across the ABI boundary calls `std::terminate`. See the header block in `plugin_data_api.h`. - `BuiltinObject` is `std::any`, deliberately not `std::variant`, for forward-compat — recover via `std::any_cast` / `sdk::typeOf`. See `builtin/builtin_object.hpp`. - ABI/struct-layout or signature changes require a Conan **MINOR** bump and a refreshed `abi/baseline.abi`; tail-appended slots are a PATCH (see submodule CLAUDE.md → Release Versioning). diff --git a/pj_base/CMakeLists.txt b/pj_base/CMakeLists.txt index dd69992..de61d2f 100644 --- a/pj_base/CMakeLists.txt +++ b/pj_base/CMakeLists.txt @@ -3,7 +3,6 @@ # --------------------------------------------------------------------------- add_library(pj_base STATIC - src/builtin/asset_video_codec.cpp src/builtin/camera_info_codec.cpp src/builtin/compressed_point_cloud_codec.cpp src/builtin/depth_image_codec.cpp @@ -103,7 +102,6 @@ if(PJ_BUILD_TESTS) tests/mesh3d_codec_test.cpp tests/video_frame_codec_test.cpp tests/scene_entities_codec_test.cpp - tests/asset_video_codec_test.cpp tests/time_spine_test.cpp tests/poses_in_frame_codec_test.cpp tests/voxel_grid_codec_test.cpp diff --git a/pj_base/include/pj_base/builtin/asset_video.hpp b/pj_base/include/pj_base/builtin/asset_video.hpp deleted file mode 100644 index e410d38..0000000 --- a/pj_base/include/pj_base/builtin/asset_video.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @file asset_video.hpp - * @brief File-backed video reference + typed playback metadata. - * - * AssetVideo is the entry-point handle for video assets ingested by data - * loaders that point at an external media file (LeRobot datasets, MP4 - * loaders, etc.). Producers push exactly one AssetVideo per topic; the - * ObjectStore timestamp of that entry equals `time_origin_ns` so timeline - * UIs naturally see the asset's start instant. - * - * Unlike VideoFrame (a single frame of a streamed payload), AssetVideo - * carries no pixel data — it references the file by path and surfaces - * decode-routing metadata (media type, dimensions, frame rate) without - * forcing the consumer to open the file just to size a playback window. - */ -// Copyright 2026 Davide Faconti -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include -#include - -#include "pj_base/types.hpp" - -namespace PJ { -namespace sdk { - -/// File-backed video reference with typed metadata. -/// -/// `time_origin_ns` carries the wall-clock instant of the first frame. -/// Consumers subtract this from a tracker time to derive a file-relative -/// seek position. An absent value means the asset is not aligned to wall -/// clock and should not advance with the tracker. -/// -/// `start_ns` / `end_ns` describe an optional playable window inside the -/// file, expressed as in-file offsets in nanoseconds (zero = first frame -/// of the file). When both are absent the whole file is playable; when -/// present, consumers must clamp seek requests to `[start_ns, end_ns]` -/// and bound timeline UI to that range. Producers use this for assets -/// that share a file with other clips (e.g. LeRobot v3.0, where a single -/// MP4 holds many concatenated episodes per camera). -/// -/// `media_type` is a MIME-type hint ("video/mp4", "video/x-matroska", -/// "video/av1"). Empty string means "probe the file". -/// -/// `width` / `height` are pixel dimensions; zero in either field means -/// "unknown — probe the file". -/// -/// `frame_rate` is nominal frames per second; zero or NaN means "unknown — -/// probe the file". For variable-frame-rate video this is an advisory -/// average; actual per-frame timestamps come from the decoder. -struct AssetVideo { - std::optional time_origin_ns; ///< Wall-clock instant of the first frame. - std::optional start_ns; ///< In-file offset where the playable window begins (ns). - std::optional end_ns; ///< In-file offset where the playable window ends (ns). - std::string file_path; ///< Absolute path or path relative to a consumer-known root. - std::string media_type; ///< MIME type. Empty string means "probe the file". - uint32_t width = 0; ///< Pixel width. 0 means unknown. - uint32_t height = 0; ///< Pixel height. 0 means unknown. - double frame_rate = 0.0; ///< Nominal frames per second. 0 or NaN means unknown. -}; - -} // namespace sdk -} // namespace PJ diff --git a/pj_base/include/pj_base/builtin/asset_video_codec.hpp b/pj_base/include/pj_base/builtin/asset_video_codec.hpp deleted file mode 100644 index 21e6936..0000000 --- a/pj_base/include/pj_base/builtin/asset_video_codec.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -// Copyright 2026 Davide Faconti -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include -#include - -#include "pj_base/builtin/asset_video.hpp" -#include "pj_base/expected.hpp" - -namespace PJ { - -inline constexpr std::string_view kSchemaAssetVideo = "PJ.AssetVideo"; - -/// Serializes sdk::AssetVideo to canonical PJ.AssetVideo wire bytes (see -/// pj_base/proto/pj/AssetVideo.proto). -[[nodiscard]] std::vector serializeAssetVideo(const sdk::AssetVideo& asset); - -/// Decodes canonical PJ.AssetVideo wire bytes into sdk::AssetVideo. -[[nodiscard]] Expected deserializeAssetVideo(const uint8_t* data, size_t size); - -} // namespace PJ diff --git a/pj_base/include/pj_base/builtin/builtin_object.hpp b/pj_base/include/pj_base/builtin/builtin_object.hpp index 23250e5..18239ed 100644 --- a/pj_base/include/pj_base/builtin/builtin_object.hpp +++ b/pj_base/include/pj_base/builtin/builtin_object.hpp @@ -26,7 +26,6 @@ #include #include -#include "pj_base/builtin/asset_video.hpp" #include "pj_base/builtin/camera_info.hpp" #include "pj_base/builtin/compressed_point_cloud.hpp" #include "pj_base/builtin/depth_image.hpp" @@ -59,7 +58,7 @@ enum class BuiltinObjectType : uint16_t { kMesh3D = 9, ///< sdk::Mesh3D — binary mesh asset (GLTF/STL/PLY/OBJ/USD/DAE). kVideoFrame = 10, ///< sdk::VideoFrame — single frame of h264/h265/vp9/av1 stream. kSceneEntities = 11, ///< sdk::SceneEntities — procedural 3D scene primitives. - kAssetVideo = 12, ///< sdk::AssetVideo — file-backed video reference + playback metadata. + // 12 reserved — was kAssetVideo (removed; video unified on kVideoFrame). Never reuse. kRobotDescription = 13, ///< sdk::RobotDescription — raw URDF/SDF/MJCF text + format hint. kCameraInfo = 14, ///< sdk::CameraInfo — pinhole camera calibration (K/D/R/P). kOccupancyGridUpdate = 15, ///< sdk::OccupancyGridUpdate — incremental sub-rectangle patch for an OccupancyGrid. @@ -100,8 +99,6 @@ struct SchemaClassification { return "kVideoFrame"; case BuiltinObjectType::kSceneEntities: return "kSceneEntities"; - case BuiltinObjectType::kAssetVideo: - return "kAssetVideo"; case BuiltinObjectType::kRobotDescription: return "kRobotDescription"; case BuiltinObjectType::kCameraInfo: @@ -154,9 +151,6 @@ struct SchemaClassification { if (s == "kSceneEntities") { return BuiltinObjectType::kSceneEntities; } - if (s == "kAssetVideo") { - return BuiltinObjectType::kAssetVideo; - } if (s == "kRobotDescription") { return BuiltinObjectType::kRobotDescription; } @@ -218,9 +212,6 @@ using BuiltinObject = std::any; if (t == typeid(SceneEntities)) { return BuiltinObjectType::kSceneEntities; } - if (t == typeid(AssetVideo)) { - return BuiltinObjectType::kAssetVideo; - } if (t == typeid(RobotDescription)) { return BuiltinObjectType::kRobotDescription; } diff --git a/pj_base/include/pj_base/builtin_object_abi.h b/pj_base/include/pj_base/builtin_object_abi.h index bf37a75..ea93521 100644 --- a/pj_base/include/pj_base/builtin_object_abi.h +++ b/pj_base/include/pj_base/builtin_object_abi.h @@ -51,7 +51,7 @@ typedef enum PJ_builtin_object_type_t { PJ_BUILTIN_OBJECT_TYPE_MESH3D = 9, PJ_BUILTIN_OBJECT_TYPE_VIDEO_FRAME = 10, PJ_BUILTIN_OBJECT_TYPE_SCENE_ENTITIES = 11, - PJ_BUILTIN_OBJECT_TYPE_ASSET_VIDEO = 12, + /* 12 reserved — was PJ_BUILTIN_OBJECT_TYPE_ASSET_VIDEO (removed; video unified on VIDEO_FRAME). */ PJ_BUILTIN_OBJECT_TYPE_ROBOT_DESCRIPTION = 13, PJ_BUILTIN_OBJECT_TYPE_CAMERA_INFO = 14, PJ_BUILTIN_OBJECT_TYPE_OCCUPANCY_GRID_UPDATE = 15, diff --git a/pj_base/proto/pj/AssetVideo.proto b/pj_base/proto/pj/AssetVideo.proto deleted file mode 100644 index f738c63..0000000 --- a/pj_base/proto/pj/AssetVideo.proto +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2026 Davide Faconti -// SPDX-License-Identifier: Apache-2.0 - -// PlotJuggler canonical asset video protobuf schema. -// File-backed video reference + playback metadata. - -syntax = "proto3"; - -import "google/protobuf/timestamp.proto"; - -package PJ; - -// A reference to a file-backed video, plus typed metadata sufficient for consumers to size playback windows and route -// to a decoder without opening the file. Producers (e.g. data_load_lerobot, data_load_mp4) push exactly one -// AssetVideo per topic, with the entry's ObjectStore timestamp equal to `time_origin` (so timeRange() of the topic -// naturally reflects the start instant). -message AssetVideo { - // Wall-clock instant of the first frame. Consumers subtract this from the global tracker time to derive the - // file-relative seek position. Required: an absent time_origin is treated as "this asset is not aligned to wall - // clock" and the asset will not advance with the tracker. - google.protobuf.Timestamp time_origin = 1; - - // Path to the video file. Resolution is consumer-side: producers should emit either an absolute path or a path - // relative to a consumer-known root (the dataset directory in LeRobot's case). Required. - string file_path = 3; - - // Codec / container hint as a MIME type — e.g. "video/mp4", "video/x-matroska", "video/av1". Open-ended like - // Image.encoding so new formats land without an SDK bump. Consumers route to the matching decoder backend. Empty - // string means "probe the file". - string media_type = 4; - - // Pixel dimensions. Zero in either field means "unknown — probe the file". Producers should populate when known - // (LeRobot dataset manifests carry these explicitly). - uint32 width = 5; - uint32 height = 6; - - // Nominal frame rate in frames per second. Zero or NaN means "unknown — probe the file". For variable-frame-rate - // video this is an advisory average; actual per-frame timestamps come from the decoder. - double frame_rate = 7; - - // Optional playable window inside the file, expressed as in-file offsets in nanoseconds (zero = first frame of - // the file). When both are absent, the whole file is playable. When present, consumers must clamp seek requests - // to `[start_ns, end_ns]` and bound the timeline UI to that range. Used by producers that share a file across - // many clips (e.g. LeRobot v3.0, where one MP4 per camera holds many concatenated episodes). - optional int64 start_ns = 8; - optional int64 end_ns = 9; -} diff --git a/pj_base/proto/pj/README.md b/pj_base/proto/pj/README.md index e9ec4d7..a6e5a82 100644 --- a/pj_base/proto/pj/README.md +++ b/pj_base/proto/pj/README.md @@ -42,8 +42,6 @@ rationale. - `Log` - **`VideoFrame.proto`** — one frame of an inter-frame-coded video stream (`h264`, `h265`, `vp9`, `av1`) when per-frame `Image` messages would be wasteful. Field layout is wire-identical to `foxglove.CompressedVideo` (timestamp=1, frame_id=2, data=3, format=4), so one decoder parses both. - `VideoFrame` -- **`AssetVideo.proto`** — reference to a file-backed video plus typed playback metadata (path, MIME type, dimensions, frame rate) so consumers can size playback windows without opening the file. - - `AssetVideo` ### Point clouds diff --git a/pj_base/src/builtin/asset_video_codec.cpp b/pj_base/src/builtin/asset_video_codec.cpp deleted file mode 100644 index 0603f61..0000000 --- a/pj_base/src/builtin/asset_video_codec.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2026 Davide Faconti -// SPDX-License-Identifier: Apache-2.0 - -#include "pj_base/builtin/asset_video_codec.hpp" - -#include -#include -#include -#include - -#include "geometry_codec.hpp" -#include "protobuf_wire.hpp" - -namespace PJ { -namespace { - -using builtin_wire::parseFields; -using builtin_wire::Reader; -using builtin_wire::Tag; -using builtin_wire::WireType; -using builtin_wire::Writer; -using sdk::AssetVideo; - -} // namespace - -std::vector serializeAssetVideo(const AssetVideo& asset) { - std::vector out; - Writer writer(out); - - // `time_origin` uses the seconds+nanos wire shape; omit the field entirely - // when the SDK optional is empty. - if (asset.time_origin_ns.has_value()) { - writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, *asset.time_origin_ns); }); - } - writer.string(3, asset.file_path); - writer.string(4, asset.media_type); - writer.varint(5, asset.width); - writer.varint(6, asset.height); - writer.doubleField(7, asset.frame_rate); - if (asset.start_ns.has_value()) { - writer.varint(8, static_cast(*asset.start_ns)); - } - if (asset.end_ns.has_value()) { - writer.varint(9, static_cast(*asset.end_ns)); - } - - return out; -} - -Expected deserializeAssetVideo(const uint8_t* data, size_t size) { - if (data == nullptr || size == 0) { - return unexpected(std::string("AssetVideo wire: empty buffer")); - } - - Reader reader(data, size); - sdk::AssetVideo asset; - - const bool ok = parseFields(reader, [&](Tag tag, Reader& r) { - switch (tag.field) { - case 1: { - if (tag.type != WireType::kLengthDelimited) { - return false; - } - Timestamp t = 0; - if (!builtin_wire::readTimestampMessage(r, t)) { - return false; - } - asset.time_origin_ns = t; - return true; - } - case 3: - return tag.type == WireType::kLengthDelimited && r.readString(asset.file_path); - case 4: - return tag.type == WireType::kLengthDelimited && r.readString(asset.media_type); - case 5: { - if (tag.type != WireType::kVarint) { - return false; - } - uint64_t v = 0; - if (!r.readVarint(v)) { - return false; - } - asset.width = static_cast(v); - return true; - } - case 6: { - if (tag.type != WireType::kVarint) { - return false; - } - uint64_t v = 0; - if (!r.readVarint(v)) { - return false; - } - asset.height = static_cast(v); - return true; - } - case 7: - return tag.type == WireType::kFixed64 && r.readDouble(asset.frame_rate); - case 8: { - if (tag.type != WireType::kVarint) { - return false; - } - uint64_t v = 0; - if (!r.readVarint(v)) { - return false; - } - asset.start_ns = static_cast(v); - return true; - } - case 9: { - if (tag.type != WireType::kVarint) { - return false; - } - uint64_t v = 0; - if (!r.readVarint(v)) { - return false; - } - asset.end_ns = static_cast(v); - return true; - } - default: - return false; - } - }); - - if (!ok) { - return unexpected(std::string("AssetVideo wire: decode failed")); - } - - return asset; -} - -} // namespace PJ diff --git a/pj_base/tests/abi_layout_sentinels_test.cpp b/pj_base/tests/abi_layout_sentinels_test.cpp index c44353a..87a6553 100644 --- a/pj_base/tests/abi_layout_sentinels_test.cpp +++ b/pj_base/tests/abi_layout_sentinels_test.cpp @@ -116,12 +116,12 @@ static_assert(PJ_BUILTIN_OBJECT_TYPE_COMPRESSED_POINTCLOUD == 8, "CompressedPoin static_assert(PJ_BUILTIN_OBJECT_TYPE_MESH3D == 9, "Mesh3D type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_VIDEO_FRAME == 10, "VideoFrame type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_SCENE_ENTITIES == 11, "SceneEntities type id pinned"); -static_assert(PJ_BUILTIN_OBJECT_TYPE_ASSET_VIDEO == 12, "AssetVideo type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_ROBOT_DESCRIPTION == 13, "RobotDescription type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_CAMERA_INFO == 14, "CameraInfo type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_OCCUPANCY_GRID_UPDATE == 15, "OccupancyGridUpdate type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_LOG == 16, "Log type id pinned"); static_assert(PJ_BUILTIN_OBJECT_TYPE_POSES_IN_FRAME == 17, "PosesInFrame type id pinned"); +static_assert(PJ_BUILTIN_OBJECT_TYPE_VOXEL_GRID == 18, "VoxelGrid type id pinned"); static_assert(sizeof(PJ_schema_classification_t) == 4, "PJ_schema_classification_t layout pinned"); static_assert(offsetof(PJ_schema_classification_t, object_type) == 0, "object_type at offset 0"); static_assert(offsetof(PJ_schema_classification_t, reserved) == 2, "reserved at offset 2"); diff --git a/pj_base/tests/asset_video_codec_test.cpp b/pj_base/tests/asset_video_codec_test.cpp deleted file mode 100644 index df38958..0000000 --- a/pj_base/tests/asset_video_codec_test.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2026 Davide Faconti -// SPDX-License-Identifier: Apache-2.0 - -#include "pj_base/builtin/asset_video_codec.hpp" - -#include - -#include -#include - -#include "protobuf_wire_test_helpers.hpp" - -namespace PJ { -namespace { - -using sdk::AssetVideo; -namespace pb = ::PJ::test_pb; - -TEST(AssetVideoCodecTest, SchemaName) { - EXPECT_EQ(kSchemaAssetVideo, "PJ.AssetVideo"); -} - -TEST(AssetVideoCodecTest, EmptyBufferProducesError) { - EXPECT_FALSE(deserializeAssetVideo(nullptr, 0).has_value()); -} - -TEST(AssetVideoCodecTest, RoundTripFullyPopulated) { - AssetVideo in; - in.time_origin_ns = 1'700'000'000'000'000'000LL; - in.start_ns = 12'000'000'000LL; // 12 s into the file - in.end_ns = 17'500'000'000LL; // 17.5 s into the file - in.file_path = "/data/2026-05-21/camera0.mp4"; - in.media_type = "video/mp4"; - in.width = 1920; - in.height = 1080; - in.frame_rate = 29.97; - - const auto bytes = serializeAssetVideo(in); - auto out = deserializeAssetVideo(bytes.data(), bytes.size()); - ASSERT_TRUE(out.has_value()); - ASSERT_TRUE(out->time_origin_ns.has_value()); - EXPECT_EQ(*out->time_origin_ns, *in.time_origin_ns); - ASSERT_TRUE(out->start_ns.has_value()); - EXPECT_EQ(*out->start_ns, *in.start_ns); - ASSERT_TRUE(out->end_ns.has_value()); - EXPECT_EQ(*out->end_ns, *in.end_ns); - EXPECT_EQ(out->file_path, in.file_path); - EXPECT_EQ(out->media_type, in.media_type); - EXPECT_EQ(out->width, in.width); - EXPECT_EQ(out->height, in.height); - EXPECT_DOUBLE_EQ(out->frame_rate, in.frame_rate); -} - -TEST(AssetVideoCodecTest, OptionalsAbsentRoundTrip) { - AssetVideo in; - in.file_path = "relative/path.mkv"; - - const auto bytes = serializeAssetVideo(in); - auto out = deserializeAssetVideo(bytes.data(), bytes.size()); - ASSERT_TRUE(out.has_value()); - EXPECT_FALSE(out->time_origin_ns.has_value()); - EXPECT_FALSE(out->start_ns.has_value()); - EXPECT_FALSE(out->end_ns.has_value()); - EXPECT_EQ(out->file_path, in.file_path); - EXPECT_TRUE(out->media_type.empty()); - EXPECT_EQ(out->width, 0u); - EXPECT_EQ(out->height, 0u); -} - -TEST(AssetVideoCodecTest, OneBoundSetOneAbsent) { - // start_ns set, end_ns absent — consumers should clamp to start_ns and let - // the decoder reveal the file's true end. - AssetVideo in_start_only; - in_start_only.file_path = "/data/file.mp4"; - in_start_only.start_ns = 5'000'000'000LL; - - const auto b1 = serializeAssetVideo(in_start_only); - auto out1 = deserializeAssetVideo(b1.data(), b1.size()); - ASSERT_TRUE(out1.has_value()); - ASSERT_TRUE(out1->start_ns.has_value()); - EXPECT_EQ(*out1->start_ns, *in_start_only.start_ns); - EXPECT_FALSE(out1->end_ns.has_value()); - - // end_ns set, start_ns absent — symmetric, lets producers cap playback - // without anchoring the start. - AssetVideo in_end_only; - in_end_only.file_path = "/data/file.mp4"; - in_end_only.end_ns = 9'000'000'000LL; - - const auto b2 = serializeAssetVideo(in_end_only); - auto out2 = deserializeAssetVideo(b2.data(), b2.size()); - ASSERT_TRUE(out2.has_value()); - EXPECT_FALSE(out2->start_ns.has_value()); - ASSERT_TRUE(out2->end_ns.has_value()); - EXPECT_EQ(*out2->end_ns, *in_end_only.end_ns); -} - -} // namespace -} // namespace PJ diff --git a/pj_base/tests/builtin_object_test.cpp b/pj_base/tests/builtin_object_test.cpp index 42eee84..5af344b 100644 --- a/pj_base/tests/builtin_object_test.cpp +++ b/pj_base/tests/builtin_object_test.cpp @@ -5,7 +5,6 @@ #include -using PJ::sdk::AssetVideo; using PJ::sdk::BuiltinObject; using PJ::sdk::BuiltinObjectType; using PJ::sdk::CameraInfo; @@ -40,7 +39,6 @@ TEST(BuiltinObjectTest, TypeOfRecognizesKnownBuiltinTypes) { EXPECT_EQ(typeOf(BuiltinObject{Mesh3D{}}), BuiltinObjectType::kMesh3D); EXPECT_EQ(typeOf(BuiltinObject{VideoFrame{}}), BuiltinObjectType::kVideoFrame); EXPECT_EQ(typeOf(BuiltinObject{SceneEntities{}}), BuiltinObjectType::kSceneEntities); - EXPECT_EQ(typeOf(BuiltinObject{AssetVideo{}}), BuiltinObjectType::kAssetVideo); EXPECT_EQ(typeOf(BuiltinObject{RobotDescription{}}), BuiltinObjectType::kRobotDescription); EXPECT_EQ(typeOf(BuiltinObject{CameraInfo{}}), BuiltinObjectType::kCameraInfo); EXPECT_EQ(typeOf(BuiltinObject{OccupancyGridUpdate{}}), BuiltinObjectType::kOccupancyGridUpdate); @@ -62,7 +60,6 @@ TEST(BuiltinObjectTest, NameAndParseRoundTripForEveryEnumEntry) { BuiltinObjectType::kMesh3D, BuiltinObjectType::kVideoFrame, BuiltinObjectType::kSceneEntities, - BuiltinObjectType::kAssetVideo, BuiltinObjectType::kRobotDescription, BuiltinObjectType::kCameraInfo, BuiltinObjectType::kOccupancyGridUpdate,