ZLMediaKit/doc/FRAME_DESIGN.md

162 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<FrameCacheAble>(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<FrameFromPtr>(
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` 支持便捷添加新编解码器