ZLMediaKit/doc/FRAME_DESIGN.md

5.8 KiB
Raw Permalink Blame History

Frame 模块设计与实现

1. 模块概述

Frame.h/Frame.cpp 是 ZLMediaKit 中处理媒体帧的核心模块,提供统一的帧数据抽象、时间戳管理、编解码信息转换和帧合并功能。该模块通过面向对象设计实现协议无关的媒体帧处理,支撑 RTMP/RTSP/WebRTC 等多种协议的媒体传输。

2. 核心数据结构

2.1 媒体类型定义

2.1.1 轨道类型(TrackType

enum {
    TrackInvalid = -1,
    TrackVideo = 0,  // 视频轨道
    TrackAudio,      // 音频轨道
    TrackTitle,      // 字幕轨道
    TrackApplication // 应用数据轨道
};
  • 功能:区分媒体数据的逻辑类型
  • 使用场景:流媒体会话建立时标识数据类型

2.1.2 编解码标识(CodecId

#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

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

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 长度
  • 工作流程
    graph TD
      A[输入帧] --> B{是否需要合并?}
      B -->|是| C[缓存帧]
      B -->|否| D[直接输出]
      C --> E{是否触发flush?}
      E -->|是| F[合并输出]
      E -->|否| C
    

3.2 帧分发器(FrameDispatcher

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

Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
    if(frame->cacheAble()) return frame;
    return std::make_shared<FrameCacheAble>(frame);
}
  • 解决痛点:原始帧可能指向临时内存(如网络包缓冲区),通过 FrameCacheAble 复制数据确保生命周期
  • 性能优化:仅当 cacheAble()=false 时进行深拷贝

4.2 时间戳修正(FrameStamp

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 创建可缓存帧

// 从网络包创建原始帧(不可缓存)
auto raw_frame = std::make_shared<FrameFromPtr>(
    CodecH264, ptr, size, dts, pts, 4, is_key);

// 转换为可缓存帧(自动深拷贝)
Frame::Ptr cache_frame = Frame::getCacheAbleFrame(raw_frame);

5.2 合并 H.264 帧

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 支持便捷添加新编解码器