You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 19:33:12