1. Live555 流媒体框架概述
Live555 是一个开源的跨平台流媒体处理框架,广泛应用于实时音视频传输领域。作为一名长期从事流媒体开发的工程师,我经常使用 Live555 构建各种流媒体应用。今天,我将从架构设计的角度,深入剖析这个框架的核心组件和工作原理。
Live555 的核心架构主要由四个层次组成:
- UsageEnvironment:提供运行环境和错误处理机制
- BasicUsageEnvironment:实现基础功能的具体环境
- liveMedia:包含 RTSP 服务器和各类流媒体编码类
- groupsock:管理网络层套接字操作
这个框架采用单线程异步 I/O 模型,以 TaskScheduler 和 UsageEnvironment 为核心基础类,通过事件驱动机制处理定时任务和网络 I/O。这种设计使得 Live555 能够在单线程中高效处理多个并发流媒体会话。
提示:Live555 虽然功能强大,但学习曲线较陡。理解其架构设计对于高效使用和二次开发至关重要。
2. 核心基础类解析
2.1 TaskScheduler 与 UsageEnvironment 的协作机制
TaskScheduler 和 UsageEnvironment 是 Live555 框架的双核心,构成了整个框架的事件驱动、单线程、异步 I/O 模型基础。
2.1.1 类关系设计
UsageEnvironment 持有一个 TaskScheduler 的引用,这种设计体现了清晰的职责划分:
cpp复制class UsageEnvironment {
protected:
UsageEnvironment(TaskScheduler& scheduler); // 构造时传入
private:
TaskScheduler& fScheduler; // 成员引用
public:
TaskScheduler& taskScheduler() const { return fScheduler; }
};
典型的使用模式如下:
cpp复制TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
这种设计实现了:
- UsageEnvironment 依赖 TaskScheduler 提供事件调度能力
- TaskScheduler 通过 UsageEnvironment 暴露给上层使用
- 两者共同构成了框架的基础设施
2.1.2 UsageEnvironment 的核心功能
UsageEnvironment 作为抽象基类,主要提供以下能力:
-
错误处理:
setResultMsg()设置错误信息getErrno()获取错误码
-
日志输出:
- 重载流操作符
operator<<()用于打印调试信息
- 重载流操作符
-
任务调度访问:
taskScheduler()获取关联的 TaskScheduler
-
扩展字段:
liveMediaPriv,groupsockPriv供内部模块存储私有数据
-
资源回收:
reclaim()配合引用计数安全删除自身
2.1.3 TaskScheduler 的核心功能
TaskScheduler 同样是抽象基类,定义了单线程事件驱动引擎的接口:
| 功能类别 | 方法 | 说明 |
|---|---|---|
| 延迟任务 | scheduleDelayedTask() |
安排未来执行的函数 |
unscheduleDelayedTask() |
取消已安排的任务 | |
| Socket事件 | setBackgroundHandling() |
注册socket可读/可写回调 |
| 事件循环 | doEventLoop() |
主事件处理循环 |
| 跨线程通信 | createEventTrigger() |
创建跨线程事件触发器 |
triggerEvent() |
触发跨线程事件 | |
| 错误处理 | internalsError() |
处理不可恢复错误 |
TaskScheduler 是 Live555 真正的"心脏",所有异步操作(网络、文件、定时器)都由它调度,保证了单线程无锁执行的高效性。
2.2 典型工作流程分析
Live555 的工作流程体现了清晰的事件驱动模型:

-
对象创建时注入环境:
- 所有 Live555 对象(如 RTPSink、FramedSource)构造时接收 UsageEnvironment&
- 这使得它们可以使用
*env << "..."打印日志 - 可以调用
env->taskScheduler()安排任务
-
I/O 事件驱动:
- 当 socket 有数据到达(如 RTSP DESCRIBE 请求)
- TaskScheduler 调用注册的 BackgroundHandlerProc
- 处理函数通过 UsageEnvironment 输出响应或报错
-
跨线程通信:
cpp复制triggerEvent(myEventId, frameData); // 线程安全!- 主线程的
doEventLoop()会调用预设的 eventHandlerProc - 在 Live555 线程上下文中处理帧数据
- 主线程的
-
资源统一管理:
- 错误信息通过
env->setResultMsg()设置 - 所有模块可以通过
env->getResultMsg()获取最近错误
- 错误信息通过
经验分享:在实际开发中,我经常遇到需要从其他线程通知 Live555 主线程的情况。
triggerEvent()机制是线程安全的,但要注意事件处理不能阻塞,否则会影响整个事件循环。
3. 视频帧生产者 FramedSource 详解
3.1 FramedSource 类体系
FramedSource 是一个关键抽象类,代表能够生产按帧组织的数据流的源。它继承自 Medium → MediaSource,形成了一套完整的媒体源体系。
典型继承链示例:
code复制Medium → MediaSource → FramedSource → FramedFileSource → ByteStreamFileSource

3.1.1 各层级职责
-
MediaSource - 媒体源抽象基类:
- 提供 MIME 类型查询(如 "video/H264")
- 支持运行时类型识别(RTTI)
- 提供静态查找功能
lookupByName()
-
FramedSource - 帧式数据源抽象基类:
- 定义
getNextFrame()方法请求下一帧数据 - 实现基于回调的异步 I/O 模型
- 支持非阻塞等待下一帧数据
- 定义
-
FramedFileSource - 文件源抽象子类:
- 持有一个打开的文件指针 (FILE*)
- 为具体文件源实现提供基础
-
ByteStreamFileSource - 文件字节流源:
- 实现从普通文件读取原始字节流
- 可控制帧大小和每帧播放时间
3.2 FramedSource 核心机制
3.2.1 异步帧获取机制
FramedSource 定义了基于回调的异步 I/O 模型:
cpp复制void FramedSource::getNextFrame(
unsigned char* to, // 输出缓冲区
unsigned maxSize, // 缓冲区大小
afterGettingFunc* afterGettingFunc, // 帧就绪回调
void* clientData, // 回调上下文数据
onCloseFunc* onClose, // 源关闭回调
void* onCloseClientData // 关闭回调上下文
) {
if (fIsCurrentlyAwaitingData) {
envir() << "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
envir().internalError();
}
fTo = to;
fMaxSize = maxSize;
fNumTruncatedBytes = 0;
fDurationInMicroseconds = 0;
fAfterGettingFunc = afterGettingFunc;
fAfterGettingClientData = clientData;
fOnCloseFunc = onClose;
fOnCloseClientData = onCloseClientData;
fIsCurrentlyAwaitingData = True;
doGetNextFrame();
}
工作流程:
- 上游 RTPSink 调用
getNextFrame()请求一帧 - FramedSource 保存回调和缓冲区信息
- 调用纯虚函数
doGetNextFrame()由子类实现 - 子类完成读取后调用
FramedSource::afterGetting(this) - 触发
afterGettingFunc回调将帧数据交还给上游
关键约束:
- 同一时间只能有一个
getNextFrame()请求在处理 - 子类不能直接调用用户回调,必须通过
afterGetting()
3.2.2 帧元数据输出
子类在 doGetNextFrame() 中需设置以下成员变量:
| 变量名 | 作用 |
|---|---|
| fFrameSize | 实际读取的字节数 |
| fNumTruncatedBytes | 缓冲区不足被截断的字节数 |
| fPresentationTime | 帧的显示时间戳(PTS) |
| fDurationInMicroseconds | 帧持续时间 |
这些值会在 afterGetting() 中作为参数传给用户回调。
3.2.3 生命周期管理
FramedSource 提供了两种停止机制:
-
主动停止 -
stopGettingFrames():cpp复制void FramedSource::stopGettingFrames() { fIsCurrentlyAwaitingData = False; fAfterGettingFunc = NULL; fOnCloseFunc = NULL; doStopGettingFrames(); }- 由消费者(如 RTPSink)发起
- 典型场景:RTSP 客户端发送 TEARDOWN
-
被动关闭 -
handleClosure():cpp复制void FramedSource::handleClosure() { fIsCurrentlyAwaitingData = False; if (fOnCloseFunc != NULL) { (*fOnCloseFunc)(fOnCloseClientData); } }- 由数据源内部触发(如文件读到 EOF)
- 仅通知上游"我挂了",不清理整个会话
状态机关系:

避坑指南:我曾遇到过因未正确处理
stopGettingFrames()导致资源泄漏的问题。务必确保在析构时调用stopGettingFrames(),并正确实现子类的doStopGettingFrames()来释放资源。
3.3 典型使用场景
流化原始 H.264 文件的典型代码:
cpp复制// 1. 创建字节流源
ByteStreamFileSource* fileSource =
ByteStreamFileSource::createNew(*env, "test.264");
// 2. 包装为 H.264 帧解析器
H264VideoStreamFramer* framer =
H264VideoStreamFramer::createNew(*env, fileSource);
// 3. 创建 RTP Sink 并启动
H264VideoRTPSink* sink =
H264VideoRTPSink::createNew(*env, &rtpGroupsock, 96);
sink->startPlaying(*framer, afterPlaying, NULL);
这个管道中:
ByteStreamFileSource提供原始字节H264VideoStreamFramer解析 NAL 单元H264VideoRTPSink将帧打包为 RTP
4. 视频流的过滤和解析
4.1 FramedFilter 设计解析
FramedFilter 是一个重要的基类,用于处理和转换媒体流数据。它继承自 FramedSource,本身就是一个能够提供帧级数据的来源,但作为"中间件",它可以接收来自另一个 FramedSource 的数据,并在转发给下游之前进行处理或转换。
核心实现:
cpp复制FramedFilter::FramedFilter(UsageEnvironment& env,
FramedSource* inputSource)
: FramedSource(env),
fInputSource(inputSource) {
}
FramedFilter::~FramedFilter() {
Medium::close(fInputSource);
}
void FramedFilter::doStopGettingFrames() {
FramedSource::doStopGettingFrames();
if (fInputSource != NULL) fInputSource->stopGettingFrames();
}
关键设计特点:
- 包装一个上游的 FramedSource
- 可以处理数据后再输出
- 析构时自动关闭上游源
- 停止时级联停止上游源
4.2 视频相关过滤器类
4.2.1 类继承关系

4.2.2 各类职责
-
MPEGVideoStreamFramer:
- 处理视频流通用逻辑(时间戳计算等)
- 作为 H.264/H.265 解析器的父类
- 实现与编解码无关的通用功能
-
H264or5VideoStreamFramer:
- 识别 NAL 单元
- 提取 SPS/PPS/VPS 参数集
- 为具体格式解析器提供基础设施
-
H264VideoStreamFramer:
- 专门针对 H.264 格式的解析器
- 从字节流中组装完整的 Access Unit
- 提供已解析的 H.264 视频帧
-
H264or5Fragmenter:
- 将大视频帧分割成适合 RTP 传输的小包
- 支持 FU-A/FU-B 分片模式
- 解决大帧网络传输问题
4.2.3 正常解析流程

- 原始字节流进入 ByteStreamFileSource
- H264VideoStreamFramer 解析出 NAL 单元
- H264or5Fragmenter 根据需要分片
- H264VideoRTPSink 打包为 RTP 包
性能提示:在实际项目中,我发现过多的过滤器层级会影响性能。对于简单场景,可以考虑合并部分过滤器功能,减少数据拷贝和上下文切换。
5. RTP 调度与发送机制
5.1 关键类解析
5.1.1 RTPSink 类
RTPSink 是可实例化的具备完整 RTP 会话能力的基类,但不处理帧数据打包逻辑:
| 功能 | 实现 | 说明 |
|---|---|---|
| RTP 包头管理 | fSeqNo, fCurrentTimestamp, fSSRC | 自动维护序列号、时间戳等 |
| 网络发送 | RTPInterface fRTPInterface | 封装 UDP/TCP/SRTP 发送 |
| 时间戳转换 | convertToRTPTimestamp() | 转换 timeval 为 RTP 时间戳 |
| RTCP 统计 | fTransmissionStatsDB | 记录客户端丢包、抖动等 |
| SRTP 支持 | fCrypto | 集成 MIKEY 密钥协商 |
| SDP 生成 | rtpmapLine(), auxSDPLine() | 提供 SDP 描述字段 |
RTPSink 不关心媒体帧内容,只负责 RTP 传输层功能。
5.1.2 MultiFramedRTPSink 类
MultiFramedRTPSink 是 RTP 媒体打包的调度引擎,为多帧聚合、大帧分片提供通用框架:

打包发送逻辑核心:
cpp复制void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {
// 设置 RTP 头部
unsigned rtpHdr = 0x80000000; // Version=2, P=0, X=0, CC=0
rtpHdr |= (fRTPPayloadType<<16);
rtpHdr |= fSeqNo;
fOutBuf->enqueueWord(rtpHdr);
// 预留时间戳位置
fTimestampPosition = fOutBuf->curPacketSize();
fOutBuf->skipBytes(4);
fOutBuf->enqueueWord(SSRC());
// 预留特殊头部空间
fSpecialHeaderPosition = fOutBuf->curPacketSize();
fSpecialHeaderSize = specialHeaderSize();
fOutBuf->skipBytes(fSpecialHeaderSize);
// 开始填充帧
packFrame();
}
5.1.3 H264VideoRTPSink 与 H264or5Fragmenter
H264VideoRTPSink 继承层次:

协作机制:
cpp复制Boolean H264or5VideoRTPSink::continuePlaying() {
if (fOurFragmenter == NULL) {
fOurFragmenter = new H264or5Fragmenter(...);
}
fSource = fOurFragmenter; // 关键!将 Fragmenter 设为自己的 source
return MultiFramedRTPSink::continuePlaying();
}
数据流:
- Fragmenter 的输出 = RTPSink 的输入
- 每次 packFrame() → Fragmenter::doGetNextFrame()
Marker Bit 设置逻辑:
cpp复制void H264or5VideoRTPSink::doSpecialFrameHandling(...) {
if (((H264or5Fragmenter*)fOurFragmenter)->lastFragmentCompletedNALUnit() &&
framerSource != NULL && framerSource->pictureEndMarker()) {
setMarkerBit(); // 标记一帧图像结束
framerSource->pictureEndMarker() = False;
}
setTimestamp(framePresentationTime);
}
5.2 典型工作流程
- 启动:
H264VideoRTPSink::startPlaying(source) - 首次拉帧:
packFrame() → source->getNextFrame() - 收到帧:
afterGettingFrame() → afterGettingFrame1() - 判断大小:
- ≤ MTU:直接发送
-
MTU:分片发送
- 后续分片:
buildAndSendPacket(False) - 最后一片:设置 Marker Bit
完整类关系:

数据流与控制流:

在实际项目中,理解这些核心类的交互关系对于调试复杂的流媒体问题至关重要。我曾遇到过一个性能问题,最终发现是因为不恰当的分片策略导致过多的 RTP 包。通过调整 Fragmenter 的配置,显著提高了传输效率。