如何让Live555 RTSP服务器新客户端从H264 I帧开始接收流?
确保Live555 RTSP新客户端从I帧开始接收的解决方案
核心思路
通过在自定义LiveSource中维护最近I帧缓存和客户端等待状态,让新连接的客户端先等待I帧(或直接发送缓存的I帧),再开始接收后续所有帧,保证客户端能正常解码。
具体实现步骤
1. 在自定义LiveSource类中添加状态与缓存
在你基于FramedSource实现的LiveSource子类中,添加以下成员变量:
#include <map> class MyLiveH264Source : public FramedSource { private: // 缓存最近的完整I帧(IDR帧)数据 unsigned char* fLastIFrameBuffer = nullptr; unsigned fLastIFrameSize = 0; struct timeval fLastIFramePresentationTime; // I帧的时间戳 // 记录每个客户端(Sink)是否在等待I帧 std::map<FramedSource*, bool> fSinkWaitingForIFrame; // 辅助函数:处理等待I帧的客户端 static void continueWaitingForIFrame(void* clientData) { ((MyLiveH264Source*)clientData)->doGetNextFrame(); } // ... 其他已有成员 };
2. 捕获并缓存I帧
在你接收编码器输出H.264帧的逻辑中,识别I帧(IDR帧,NALU Type=5)并更新缓存:
// 假设你从编码器拿到了单NALU数据 void MyLiveH264Source::handleEncodedFrame(unsigned char* naluData, unsigned naluSize, struct timeval presentationTime) { unsigned char naluType = naluData[0] & 0x1F; // 提取NALU类型 if (naluType == 5) { // IDR帧(关键I帧) // 释放旧缓存,更新新的I帧数据 delete[] fLastIFrameBuffer; fLastIFrameBuffer = new unsigned char[naluSize]; memcpy(fLastIFrameBuffer, naluData, naluSize); fLastIFrameSize = naluSize; fLastIFramePresentationTime = presentationTime; // 通知所有等待I帧的客户端,触发数据发送 for (auto& entry : fSinkWaitingForIFrame) { if (entry.second) { entry.second = false; entry.first->resume(); // 唤醒客户端的帧获取逻辑 } } } // 非I帧的正常处理逻辑(仅发送给已完成I帧同步的客户端) // ... }
3. 修改帧获取逻辑,处理客户端等待状态
重写doGetNextFrame方法,优先处理新客户端的I帧等待需求:
void MyLiveH264Source::doGetNextFrame() { // 检查当前客户端是否在等待I帧 auto it = fSinkWaitingForIFrame.find(this); if (it != fSinkWaitingForIFrame.end() && it->second) { // 如果有缓存的I帧,直接发送 if (fLastIFrameBuffer != nullptr && fLastIFrameSize > 0) { // 确保不超过客户端缓冲区大小 unsigned copySize = fLastIFrameSize; if (copySize > fMaxSize) copySize = fMaxSize; memcpy(fTo, fLastIFrameBuffer, copySize); fFrameSize = copySize; fPresentationTime = fLastIFramePresentationTime; fDurationInMicroseconds = 0; // 标记该客户端已完成I帧同步 it->second = false; afterGetting(this); // 通知客户端已获取到帧 } else { // 无缓存I帧,延迟10ms后再尝试(避免阻塞事件循环) nextTask() = envir().taskScheduler().scheduleDelayedTask(10000, continueWaitingForIFrame, this); } return; } // 正常发送后续帧的逻辑(仅执行给已同步I帧的客户端) // ... 你的原有帧发送代码 }
4. 初始化新客户端的等待状态
当有新客户端连接时(通常在RTSPServer的客户端处理逻辑中),将该客户端对应的Sink标记为等待I帧:
// 在你创建LiveSource并关联到Sink的地方 MyLiveH264Source* source = MyLiveH264Source::createNew(envir()); // 标记该Sink需要等待I帧 source->fSinkWaitingForIFrame[source] = true;
5. 内存清理
在LiveSource的析构函数中释放I帧缓存,避免内存泄漏:
MyLiveH264Source::~MyLiveH264Source() { delete[] fLastIFrameBuffer; }
关键注意事项
- NALU类型判断:H.264中IDR帧的NALU Type是5,是最可靠的关键帧,优先选择缓存IDR帧而非普通I帧。
- 时间戳同步:必须保存I帧的
presentationTime,发送给客户端时保证时间戳正确,避免播放时序混乱。 - 事件循环不阻塞:等待I帧时使用Live555的延迟任务机制,不能直接阻塞线程,否则会影响整个RTSP服务器的响应。
- 多客户端隔离:每个客户端的等待状态独立维护,避免一个客户端的等待影响其他已连接的客户端。
内容的提问来源于stack exchange,提问作者zerpico
相关产品推荐
相关产品推荐

