# Frame 模块设计与实现 ## 1. 模块概述 `Frame.h`/`Frame.cpp` 是 ZLMediaKit 中处理媒体帧的核心模块,提供统一的帧数据抽象、时间戳管理、编解码信息转换和帧合并功能。该模块通过面向对象设计实现协议无关的媒体帧处理,支撑 RTMP/RTSP/WebRTC 等多种协议的媒体传输。 ## 2. 核心数据结构 ### 2.1 媒体类型定义 #### 2.1.1 轨道类型([TrackType](src/Extension/Frame.h#L20-L28)) ```cpp enum { TrackInvalid = -1, TrackVideo = 0, // 视频轨道 TrackAudio, // 音频轨道 TrackTitle, // 字幕轨道 TrackApplication // 应用数据轨道 }; ``` - **功能**:区分媒体数据的逻辑类型 - **使用场景**:流媒体会话建立时标识数据类型 #### 2.1.2 编解码标识([CodecId](src/Extension/Frame.h#L54-L78)) ```cpp #define CODEC_MAP(XX) \ XX(CodecH264, TrackVideo, 0, "H264", PSI_STREAM_H264, MOV_OBJECT_H264) \ // ... 其他编解码定义 typedef enum { CodecInvalid = -1, CODEC_MAP(XX) CodecMax } CodecId; ``` - **特点**: - 通过宏定义统一管理编解码参数 - 包含协议标识(mpeg_id)、容器标识(mp4_id)等扩展信息 - **关键函数**: - `[getTrackType(CodecId)](src/Extension/Frame.cpp#L15-L23)`:获取轨道类型 - `[getMovIdByCodec()](src/Extension/Frame.cpp#L35-L43)`:获取 MP4 容器标识 - `[getMpegIdByCodec()](src/Extension/Frame.cpp#L70-L78)`:获取 MPEG-TS 标识 ### 2.2 帧抽象基类([Frame](src/Extension/Frame.h#L137-L177)) ```cpp class Frame : public toolkit::Buffer, public CodecInfo { virtual uint64_t dts() const = 0; // 解码时间戳 virtual uint64_t pts() const; // 显示时间戳 virtual size_t prefixSize() const = 0; // 帧前缀长度 virtual bool keyFrame() const = 0; // 是否关键帧 virtual bool configFrame() const = 0; // 是否配置帧(sps/pps) virtual bool cacheAble() const; // 是否可缓存 }; ``` - **设计要点**: - 继承自 `Buffer` 实现内存管理 - 通过 `CodecInfo` 关联编解码信息 - 所有方法均为虚函数支持多态操作 - **典型派生类**: | 类名 | 作用 | 实现文件 | |------|------|----------| | `[FrameImp](src/Extension/Frame.h#L191-L220)` | 基础帧实现 | Frame.h | | `[FrameFromPtr](src/Extension/Frame.h#L233-L263)` | 指针包装帧(不可缓存) | Frame.h | | `[FrameCacheAble](src/Extension/Frame.h#L336-L367)` | 可缓存帧包装 | Frame.h | | `[FrameStamp](src/Extension/Frame.h#L381-L407)` | 时间戳覆盖实现 | Frame.h | ## 3. 核心处理工具 ### 3.1 帧合并器([FrameMerger](src/Extension/Frame.cpp#L235-L338)) ```cpp class FrameMerger { enum { none, h264_prefix, mp4_nal_size }; bool inputFrame(const Frame::Ptr &frame, onOutput cb, BufferLikeString *buffer = nullptr); void flush(); }; ``` - **核心功能**: - 合并时间戳相同的帧(如 AAC 复合帧) - 支持三种输出模式: - `none`:原始数据拼接(用于 PS 流) - `h264_prefix`:添加 `00 00 00 01` 前缀 - `mp4_nal_size`:添加 4 字节 NALU 长度 - **工作流程**: ```mermaid graph TD A[输入帧] --> B{是否需要合并?} B -->|是| C[缓存帧] B -->|否| D[直接输出] C --> E{是否触发flush?} E -->|是| F[合并输出] E -->|否| C ``` ### 3.2 帧分发器([FrameDispatcher](src/Extension/Frame.h#L465-L526)) ```cpp class FrameDispatcher : public FrameWriterInterface { FrameWriterInterface* addDelegate(FrameWriterInterface::Ptr delegate); bool inputFrame(const Frame::Ptr &frame) override; }; ``` - **设计特点**: - 代理模式实现多路帧分发 - 线程安全的递归锁保护 - 自动统计 GOP 信息(`_gop_size`, `_gop_interval_ms`) - **典型应用场景**: - 同时推送到 RTMP 服务器和 HLS 切片 - 媒体流录制与实时分析并行处理 ## 4. 关键方法解析 ### 4.1 帧缓存转换([getCacheAbleFrame](src/Extension/Frame.cpp#L9-L13)) ```cpp Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){ if(frame->cacheAble()) return frame; return std::make_shared(frame); } ``` - **解决痛点**:原始帧可能指向临时内存(如网络包缓冲区),通过 `FrameCacheAble` 复制数据确保生命周期 - **性能优化**:仅当 `cacheAble()=false` 时进行深拷贝 ### 4.2 时间戳修正([FrameStamp](src/Extension/Frame.cpp#L27-L33)) ```cpp FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp) { stamp.revise(frame->dts(), frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem); } ``` - **时间戳模式**: - `kModifyStampSystem`:使用系统绝对时间戳 - `kModifyStampRelative`:保持相对时间戳 - **应用场景**: - 播放器同步控制 - WebRTC 中的 RTP 时间戳转换 ## 5. 使用示例 ### 5.1 创建可缓存帧 ```cpp // 从网络包创建原始帧(不可缓存) auto raw_frame = std::make_shared( CodecH264, ptr, size, dts, pts, 4, is_key); // 转换为可缓存帧(自动深拷贝) Frame::Ptr cache_frame = Frame::getCacheAbleFrame(raw_frame); ``` ### 5.2 合并 H.264 帧 ```cpp FrameMerger merger(FrameMerger::h264_prefix); merger.inputFrame(frame1, [](uint64_t dts, uint64_t pts, Buffer::Ptr buffer, bool key) { // 处理合并后的完整帧 rtmp_muxer->inputFrame(buffer, dts, pts, key); }); merger.inputFrame(frame2); merger.flush(); // 强制输出剩余缓存 ``` ## 6. 设计哲学 1. **零拷贝优先**:通过 `FrameInternal` 等类避免不必要的内存复制 2. **协议无关性**:帧操作不依赖具体传输协议 3. **资源精确控制**:通过 `cacheAble()` 明确内存生命周期 4. **扩展性设计**:宏定义 `CODEC_MAP` 支持便捷添加新编解码器