feat: bundle synced multi-camera ZED SVO streams into MCAP
Extend zed_svo_to_mcap to export multiple SVO inputs or a kindergarten segment directory into a single MCAP with per-camera namespaced topics. Add a MultiMcapRecordSink so one writer can manage independent video, depth, calibration, pose, and body channels for multiple cameras while reusing the existing protobuf schemas and encoded access-unit flow. Implement strict timestamp synchronization for bundled exports by computing the common time window, seeking each SVO to the shared start, dropping unsynced frames, and only emitting groups that fall within the configured tolerance. Load per-camera positional tracking settings from a cv-mmap style TOML pose config, preserve single-camera behavior, and add graceful SIGINT or SIGTERM shutdown so interrupted exports flush encoders and finalize readable MCAP files. Add mcap_multi_record_tester coverage and switch zed_svo_to_mcap defaults to H.265 plus QUALITY depth mode.
This commit is contained in:
@@ -57,6 +57,15 @@ struct RawBodyTrackingMessageView {
|
||||
std::span<const std::uint8_t> bytes{};
|
||||
};
|
||||
|
||||
struct McapRecordStreamConfig {
|
||||
std::string topic{"/camera/video"};
|
||||
std::string depth_topic{"/camera/depth"};
|
||||
std::string calibration_topic{"/camera/calibration"};
|
||||
std::string pose_topic{"/camera/pose"};
|
||||
std::string body_topic{"/camera/body"};
|
||||
std::string frame_id{"camera"};
|
||||
};
|
||||
|
||||
class McapRecordSink {
|
||||
public:
|
||||
McapRecordSink() = default;
|
||||
@@ -107,4 +116,75 @@ private:
|
||||
State *state_{nullptr};
|
||||
};
|
||||
|
||||
class MultiMcapRecordSink {
|
||||
public:
|
||||
using StreamId = std::size_t;
|
||||
struct State;
|
||||
|
||||
MultiMcapRecordSink() = default;
|
||||
~MultiMcapRecordSink();
|
||||
|
||||
MultiMcapRecordSink(const MultiMcapRecordSink &) = delete;
|
||||
MultiMcapRecordSink &operator=(const MultiMcapRecordSink &) = delete;
|
||||
|
||||
MultiMcapRecordSink(MultiMcapRecordSink &&other) noexcept;
|
||||
MultiMcapRecordSink &operator=(MultiMcapRecordSink &&other) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static std::expected<MultiMcapRecordSink, std::string> create(
|
||||
std::string path,
|
||||
McapCompression compression);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<StreamId, std::string> add_stream(
|
||||
const McapRecordStreamConfig &config,
|
||||
const encode::EncodedStreamInfo &stream_info);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> update_stream_info(
|
||||
StreamId stream_id,
|
||||
const encode::EncodedStreamInfo &stream_info);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_access_unit(
|
||||
StreamId stream_id,
|
||||
const encode::EncodedAccessUnit &access_unit);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_depth_map(
|
||||
StreamId stream_id,
|
||||
const RawDepthMapView &depth_map);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_depth_map_u16(
|
||||
StreamId stream_id,
|
||||
const RawDepthMapU16View &depth_map);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_camera_calibration(
|
||||
StreamId stream_id,
|
||||
const RawCameraCalibrationView &calibration);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_pose(
|
||||
StreamId stream_id,
|
||||
const RawPoseView &pose);
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_body_tracking_message(
|
||||
StreamId stream_id,
|
||||
const RawBodyTrackingMessageView &body_message);
|
||||
|
||||
[[nodiscard]]
|
||||
bool is_open() const;
|
||||
|
||||
[[nodiscard]]
|
||||
std::string_view path() const;
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
State *state_{nullptr};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user