自动驾驶的一种数据采集(mcap格式)

本文介绍了MCAP存储格式,一种针对机器人应用的标准化容器格式,支持多种序列化格式,如Protobuf。文中详细展示了如何使用Protobuf进行数据存储,并通过示例说明了如何将数据写入和从MCAP文件中读取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MCAP存储格式是一种用于机器人数据的标准化容器格式。MCAP(发音为“em-cap”),可以用于异构时间戳数据的模块化容器文件格式。它非常适用于机器人应用,因为它可以在单个文件中记录多个流的结构化和非结构化数据(例如ROS、Protobuf、JSON Schema等)。

使用MCAP存储格式优点:

  1. 用于在多种序列化格式(Protobuf、MessagePack、JSON等)中存储数据。
  2. 高性能写入。(采用追加结构,数据可以流式传输到磁盘或网络,无需回溯)
  3. 解码不需要额外的依赖项。(自包含消息模式)
  4. 高效的读取和定位。
  5. 能够轻松集成现有的第三方工具(如Foxglove Studio、Foxglove Data Platform等)。

这里以Protobuf数据保存作为案例:

1. 先给出一个简单的结构体, speed代表车速, acc代表加速度

#include <cstdint>

struct Head{
    int id;    
    int index;     
    uint64_t timestamp;  
};

struct Data{
    Head head;
    int speed;   
    float acc;
};

2. 接下来用写一个proto存储

syntax = "proto3";

message Head {
  int32 id = 1;
  int32 index = 2;
  uint64 timestamp = 3;
}

message Data {
  Head head = 1;
  int32 speed = 2;
  float acc = 3;
}

3. 用protobuf序列化, 这里序列化为cpp的

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: mcapTest/data.proto

#ifndef PROTOBUF_mcapTest_2fdata_2eproto__INCLUDED
#define PROTOBUF_mcapTest_2fdata_2eproto__INCLUDED

#include <string>

#include <google/protobuf/stubs/common.h>

#if GOOGLE_PROTOBUF_VERSION < 3003000
#error This file was generated by a newer version of protoc which is
#error incompatible with your Protocol Buffer headers.  Please update
#error your headers.
#endif
#if 3003000 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers.  Please
#error regenerate this file with a newer version of protoc.
#endif

#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/arena.h>
#include <google/protobuf/arenastring.h>
#include <google/protobuf/generated_message_table_driven.h>
#include <google/protobuf/generated_message_util.h>
#include <google/protobuf/metadata.h>
#include <google/protobuf/message.h>
#include <google/protobuf/repeated_field.h>  // IWYU pragma: export
#include <google/protobuf/extension_set.h>  // IWYU pragma: export
#include <google/protobuf/unknown_field_set.h>
// @@protoc_insertion_point(includes)
class Data;
class DataDefaultTypeInternal;
extern DataDefaultTypeInternal _Data_default_instance_;
class Head;
class HeadDefaultTypeInternal;
extern HeadDefaultTypeInternal _Head_default_instance_;

namespace protobuf_mcapTest_2fdata_2eproto {
// Internal implementation detail -- do not call these.
struct TableStruct {
  static const ::google::protobuf::internal::ParseTableField entries[];
  static const ::google::protobuf::internal::AuxillaryParseTableField aux[];
  static const ::google::protobuf::internal::ParseTable schema[];
  static const ::google::protobuf::uint32 offsets[];
  static void InitDefaultsImpl();
  static void Shutdown();
};
void AddDescriptors();
void InitDefaults();
}  // namespace protobuf_mcapTest_2fdata_2eproto

// ===================================================================

class Head : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:Head) */ {
 public:
  Head();
  virtual ~Head();

  Head(const Head& from);

  inline Head& operator=(const Head& from) {
    CopyFrom(from);
    return *this;
  }

  static const ::google::protobuf::Descriptor* descriptor();
  static const Head& default_instance();

  static inline const Head* internal_default_instance() {
    return reinterpret_cast<const Head*>(
               &_Head_default_instance_);
  }
  static PROTOBUF_CONSTEXPR int const kIndexInFileMessages =
    0;

  void Swap(Head* other);

  // implements Message ----------------------------------------------

  inline Head* New() const PROTOBUF_FINAL { return New(NULL); }

  Head* New(::google::protobuf::Arena* arena) const PROTOBUF_FINAL;
  void CopyFrom(const ::google::protobuf::Message& from) PROTOBUF_FINAL;
  void MergeFrom(const ::google::protobuf::Message& from) PROTOBUF_FINAL;
  void CopyFrom(const Head& from);
  void MergeFrom(const Head& from);
  void Clear() PROTOBUF_FINAL;
  bool IsInitialized() const PROTOBUF_FINAL;

  size_t ByteSizeLong() const PROTOBUF_FINAL;
  bool MergePartialFromCodedStream(
      ::google::protobuf::io::CodedInputStream* input) PROTOBUF_FINAL;
  void SerializeWithCachedSizes(
      ::google::protobuf::io::CodedOutputStream* output) const PROTOBUF_FINAL;
  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
      bool deterministic, ::google::protobuf::uint8* target) const PROTOBUF_FINAL;
  int GetCachedSize() const PROTOBUF_FINAL { return _cached_size_; }
  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const PROTOBUF_FINAL;
  void InternalSwap(Head* other);
  private:
  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
    return NULL;
  }
  inline void* MaybeArenaPtr() const {
    return NULL;
  }
  public:

  ::google::protobuf::Metadata GetMetadata() const PROTOBUF_FINAL;

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // int32 id = 1;
  void clear_id();
  static const int kIdFieldNumber = 1;
  ::google::protobuf::int32 id() const;
  void set_id(::google::protobuf::int32 value);

  // int32 index = 2;
  void clear_index();
  static const int kIndexFieldNumber = 2;
  ::google::protobuf::int32 index() const;
  void set_index(::google::protobuf::int32 value);

  // uint64 timestamp = 3;
  void clear_timestamp();
  static const int kTimestampFieldNumber = 3;
  ::google::protobuf::uint64 timestamp() const;
  void set_timestamp(::google::protobuf::uint64 value);

  // @@protoc_insertion_point(class_scope:Head)
 private:

  ::google::protobuf::internal::InternalMetadataWithArena _internal_metadata_;
  ::google::protobuf::int32 id_;
  ::google::protobuf::int32 index_;
  ::google::protobuf::uint64 timestamp_;
  mutable int _cached_size_;
  friend struct protobuf_mcapTest_2fdata_2eproto::TableStruct;
};
// -------------------------------------------------------------------

class Data : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:Data) */ {
 public:
  Data();
  virtual ~Data();

  Data(const Data& from);

  inline Data& operator=(const Data& from) {
    CopyFrom(from);
    return *this;
  }

  static const ::google::protobuf::Descriptor* descriptor();
  static const Data& default_instance();

  static inline const Data* internal_default_instance() {
    return reinterpret_cast<const Data*>(
               &_Data_default_instance_);
  }
  static PROTOBUF_CONSTEXPR int const kIndexInFileMessages =
    1;

  void Swap(Data* other);

  // implements Message ----------------------------------------------

  inline Data* New() const PROTOBUF_FINAL { return New(NULL); }

  Data* New(::google::protobuf::Arena* arena) const PROTOBUF_FINAL;
  void CopyFrom(const ::google::protobuf::Message& from) PROTOBUF_FINAL;
  void MergeFrom(const ::google::protobuf::Message& from) PROTOBUF_FINAL;
  void CopyFrom(const Data& from);
  void MergeFrom(const Data& from);
  void Clear() PROTOBUF_FINAL;
  bool IsInitialized() const PROTOBUF_FINAL;

  size_t ByteSizeLong() const PROTOBUF_FINAL;
  bool MergePartialFromCodedStream(
      ::google::protobuf::io::CodedInputStream* input) PROTOBUF_FINAL;
  void SerializeWithCachedSizes(
      ::google::protobuf::io::CodedOutputStream* output) const PROTOBUF_FINAL;
  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
      bool deterministic, ::google::protobuf::uint8* target) const PROTOBUF_FINAL;
  int GetCachedSize() const PROTOBUF_FINAL { return _cached_size_; }
  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const PROTOBUF_FINAL;
  void InternalSwap(Data* other);
  private:
  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
    return NULL;
  }
  inline void* MaybeArenaPtr() const {
    return NULL;
  }
  public:

  ::google::protobuf::Metadata GetMetadata() const PROTOBUF_FINAL;

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // .Head head = 1;
  bool has_head() const;
  void clear_head();
  static const int kHeadFieldNumber = 1;
  const ::Head& head() const;
  ::Head* mutable_head();
  ::Head* release_head();
  void set_allocated_head(::Head* head);

  // int32 speed = 2;
  void clear_speed();
  static const int kSpeedFieldNumber = 2;
  ::google::protobuf::int32 speed() const;
  void set_speed(::google::protobuf::int32 value);

  // float acc = 3;
  void clear_acc();
  static const int kAccFieldNumber = 3;
  float acc() const;
  void set_acc(float value);

  // @@protoc_insertion_point(class_scope:Data)
 private:

  ::google::protobuf::internal::InternalMetadataWithArena _internal_metadata_;
  ::Head* head_;
  ::google::protobuf::int32 speed_;
  float acc_;
  mutable int _cached_size_;
  friend struct protobuf_mcapTest_2fdata_2eproto::TableStruct;
};
// ===================================================================


// ===================================================================

#if !PROTOBUF_INLINE_NOT_IN_HEADERS
// Head

// int32 id = 1;
inline void Head::clear_id() {
  id_ = 0;
}
inline ::google::protobuf::int32 Head::id() const {
  // @@protoc_insertion_point(field_get:Head.id)
  return id_;
}
inline void Head::set_id(::google::protobuf::int32 value) {
  
  id_ = value;
  // @@protoc_insertion_point(field_set:Head.id)
}

// int32 index = 2;
inline void Head::clear_index() {
  index_ = 0;
}
inline ::google::protobuf::int32 Head::index() const {
  // @@protoc_insertion_point(field_get:Head.index)
  return index_;
}
inline void Head::set_index(::google::protobuf::int32 value) {
  
  index_ = value;
  // @@protoc_insertion_point(field_set:Head.index)
}

// uint64 timestamp = 3;
inline void Head::clear_timestamp() {
  timestamp_ = GOOGLE_ULONGLONG(0);
}
inline ::google::protobuf::uint64 Head::timestamp() const {
  // @@protoc_insertion_point(field_get:Head.timestamp)
  return timestamp_;
}
inline void Head::set_timestamp(::google::protobuf::uint64 value) {
  
  timestamp_ = value;
  // @@protoc_insertion_point(field_set:Head.timestamp)
}

// -------------------------------------------------------------------

// Data

// .Head head = 1;
inline bool Data::has_head() const {
  return this != internal_default_instance() && head_ != NULL;
}
inline void Data::clear_head() {
  if (GetArenaNoVirtual() == NULL && head_ != NULL) delete head_;
  head_ = NULL;
}
inline const ::Head& Data::head() const {
  // @@protoc_insertion_point(field_get:Data.head)
  return head_ != NULL ? *head_
                         : *::Head::internal_default_instance();
}
inline ::Head* Data::mutable_head() {
  
  if (head_ == NULL) {
    head_ = new ::Head;
  }
  // @@protoc_insertion_point(field_mutable:Data.head)
  return head_;
}
inline ::Head* Data::release_head() {
  // @@protoc_insertion_point(field_release:Data.head)
  
  ::Head* temp = head_;
  head_ = NULL;
  return temp;
}
inline void Data::set_allocated_head(::Head* head) {
  delete head_;
  head_ = head;
  if (head) {
    
  } else {
    
  }
  // @@protoc_insertion_point(field_set_allocated:Data.head)
}

// int32 speed = 2;
inline void Data::clear_speed() {
  speed_ = 0;
}
inline ::google::protobuf::int32 Data::speed() const {
  // @@protoc_insertion_point(field_get:Data.speed)
  return speed_;
}
inline void Data::set_speed(::google::protobuf::int32 value) {
  
  speed_ = value;
  // @@protoc_insertion_point(field_set:Data.speed)
}

// float acc = 3;
inline void Data::clear_acc() {
  acc_ = 0;
}
inline float Data::acc() const {
  // @@protoc_insertion_point(field_get:Data.acc)
  return acc_;
}
inline void Data::set_acc(float value) {
  
  acc_ = value;
  // @@protoc_insertion_point(field_set:Data.acc)
}

#endif  // !PROTOBUF_INLINE_NOT_IN_HEADERS
// -------------------------------------------------------------------


// @@protoc_insertion_point(namespace_scope)


// @@protoc_insertion_point(global_scope)

#endif  // PROTOBUF_mcapTest_2fdata_2eproto__INCLUDED

4. 创建mcap文件并写入数据

const char* outputFilename = "aaa.mcap";
  mcap::McapWriter writer;
  {
    auto options = mcap::McapWriterOptions("");
    const auto res = writer.open(outputFilename, options);
    if (!res.ok())
    {
      std::cerr << "Failed to open " << outputFilename << " for writing: " << res.message
                << std::endl;
      return 1;
    }
  }

mcap::ChannelId channelId;
  {
    mcap::Schema schema(
      "Data", "protobuf",
      foxglove::BuildFileDescriptorSet(Data::descriptor()).SerializeAsString());
    writer.addSchema(schema);

    // choose an arbitrary topic name.
    mcap::Channel channel("Data", "protobuf", schema.id);
    writer.addChannel(channel);
    channelId = channel.id;
  }

5. 写入mcap数据到文件

// 创建一个ExampleMessage实例并填充数据。
  Data message;
  Head head;
  head.set_id(1);
  head.set_index(3);
  head.set_timestamp(2);

  message.set_allocated_head(&head);
  message.set_speed(80);
  message.set_acc(5);

  mcap::Timestamp startTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
                                std::chrono::system_clock::now().time_since_epoch())
                                .count();
  for (uint32_t frameIndex = 0; frameIndex < 100; ++frameIndex) {
    mcap::Timestamp cloudTime = startTime + (static_cast<uint64_t>(frameIndex) * 100 * 1000000);
    head.set_index(head.index() + 1);
    message.set_speed(message.speed() + 1);
    message.set_acc(message.acc() + 1);
    std::string serialized = message.SerializeAsString();
    mcap::Message msg;
    msg.channelId = channelId;
    msg.sequence = frameIndex;
    msg.publishTime = cloudTime;
    msg.logTime = cloudTime;
    msg.data = reinterpret_cast<const std::byte*>(serialized.data());
    msg.dataSize = serialized.size();
    const auto res = writer.write(msg);
    if (!res.ok()) {
      std::cerr << "Failed to write message: " << res.message << "\n";
      writer.terminate();
      writer.close();
      std::ignore = std::remove(outputFilename);
      return 1;
    }
  }

  
  // Write the index and footer to the file, then close it.
  writer.close();

6. 编译代码并运行

cmake ..
make
./Demo

最后用fox glove打开文件查看里面的数据:

 项目代码:GitHub - yk785496878/mcapTest: 使用mcap存储protobuf数据

conan库构建项目: c++项目起始(五)-CSDN博客

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值