// // FFmpeg 风格的串口解码工具核心定义(带详细中文注释)。 // 说明:本文件使用了 C++17 引入的 std::optional(可选值容器)与 [[nodiscard]] 属性, // 以及 C++11 的 std::initializer_list、基于 enum class 的强类型枚举,用于提高类型安全。 // #pragma once #include // 固定宽度整数类型,便于协议字段与位宽一致 #include #include // 互斥锁,用于注册表线程安全 #include // C++17 可选类型,用来表示可能为空的查找结果 #include #include // C++17 字符串视图,避免不必要的拷贝 #include #include // C++11 可初始化列表,支持批量注册编解码器 namespace ffmsep { // 命名空间隔离所有与串口编解码相关的类型 // 错误码定义:参考 FFmpeg 的返回值约定,所有负数表示异常。 inline constexpr int CP_SUCCESS = 0; // 成功 inline constexpr int CP_ERROR_EOF = -1; // 流结束(End Of File) inline constexpr int CP_ERROR_EAGAIN = -2; // 数据暂不可用,稍后重试 inline constexpr int CP_ERROR_NOT_OPEN = -3; // 编解码上下文尚未打开 inline constexpr int CP_ERROR_INVALID_STATE = -4; // 当前状态不允许该操作 inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;// 传入参数不合法 // enum class 使用 C++11 强类型枚举,避免隐式转换导致的错误。 enum class CPMediaType : std::uint8_t { Unknown = 0, // 未知类型:默认值 Data, // 数据流(例如串口数据) Audio, // 音频流(预留扩展) Video // 视频流(预留扩展) }; // 编解码器 ID,用于在注册表中快速定位具体实现。 enum class CPCodecID : std::uint32_t { Unknown = 0, // 未知或未设置 Tactile = 0x54514354u // 'T','Q','C','T':触觉传感器协议标识 }; // CPPacket 表示输入的“帧包”,与 FFmpeg 中 AVPacket 的思想类似。 struct CPPacket { std::vector payload; // 有效载荷:串口原始数据 std::int64_t pts = 0; // 显示时间戳(presentation timestamp) std::int64_t dts = 0; // 解码时间戳(decode timestamp) bool end_of_stream = false; // 标记此包是否为流结尾 bool flush = false; // 是否请求刷新内部状态(例如重置缓冲) CPPacket() = default; CPPacket(std::vector data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept : payload(std::move(data)), pts(pts_value), dts(dts_value) {} [[nodiscard]] bool empty() const noexcept { return payload.empty(); } // [[nodiscard]] 防止忽略返回值 }; // CPFrame 表示解码后的数据帧,对应业务层可消费的实体。 struct CPFrame { std::vector data; // 解码后的数据内容 std::int64_t pts = 0; // 与输入包对应的时间戳 bool key_frame = false; // 是否为关键帧(例如起始帧) bool valid = false; // 是否包含有效数据 void reset() noexcept { data.clear(); pts = 0; key_frame = false; valid = false; } }; struct CPCodecContext; // CPCodec 用函数指针描述具体编解码器的行为,等价于 FFmpeg 中的 AVCodec。 struct CPCodec { using InitFn = int (*)(CPCodecContext*); // 初始化回调 using CloseFn = void (*)(CPCodecContext*); // 关闭回调 using SendPacketFn = int (*)(CPCodecContext*, const CPPacket&); // 发送输入包 using ReceiveFrameFn = int (*)(CPCodecContext*, CPFrame&); // 拉取输出帧 const char* name = nullptr; const char* long_name = nullptr; CPMediaType type = CPMediaType::Unknown; CPCodecID id = CPCodecID::Unknown; std::size_t priv_data_size = 0; InitFn init = nullptr; CloseFn close = nullptr; SendPacketFn send_packet = nullptr; ReceiveFrameFn receive_frame = nullptr; }; struct CPCodecContext { const CPCodec* codec = nullptr; // 指向当前使用的编解码器描述 void* priv_data = nullptr; // 指向编解码器私有状态(大小由 priv_data_size 控制) CPMediaType codec_type = CPMediaType::Unknown; // 保存媒体类型,便于外部查询 bool is_open = false; // 是否已经成功调用 open void clear() noexcept { codec = nullptr; priv_data = nullptr; codec_type = CPMediaType::Unknown; is_open = false; priv_storage.clear(); } void* ensure_priv_storage(std::size_t size); // 确保私有存储空间足够,不足时重新分配 void release_priv_storage() noexcept; // 释放私有存储 template [[nodiscard]] T* priv_as() noexcept { return static_cast(priv_data); } template [[nodiscard]] const T* priv_as() const noexcept { return static_cast(priv_data); } private: std::vector priv_storage; // 私有缓冲区,使用 std::vector 管理生命周期 friend CPCodecContext* cpcodec_alloc_context3(const CPCodec*); friend int cpcodec_open2(CPCodecContext*, const CPCodec*); friend int cpcodec_close(CPCodecContext*); }; // 注册接口:允许外部模块将编解码器加入全局列表。 void cpcodec_register(const CPCodec* codec); void cpcodec_register_many(std::initializer_list codecs); const CPCodec* cpcodec_find_decoder(CPCodecID id); const CPCodec* cpcodec_find_decoder_by_name(std::string_view name); std::vector cpcodec_list_codecs(); CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec); // 分配上下文,关联指定编解码器 int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec = nullptr); // 打开上下文,可在此处指定或替换编解码器 int cpcodec_close(CPCodecContext* ctx); // 关闭上下文并释放资源 void cpcodec_free_context(CPCodecContext** ctx); // 释放上下文指针 int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet); // 推送一份待解码的数据包 int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame); // 拉取解码出来的帧 } // namespace ffmsep