如何使用live555提取H264帧?求完整实现示例
终于找到一个懂行的需求了!确实,live555自带的testRTSPClient.cpp用DummySink只是打酱油,根本没处理实际的H264数据,而且全网确实缺一套用官方H264专用Sink提取帧的完整示例。我给你整理一套可直接落地的方案:
替换DummySink实现H264帧提取的完整方案
核心思路
live555针对H264专门做了H264VideoRTPSink和配套的接收端解析逻辑,咱们不用自己啃原始RTP包,直接基于官方的H264处理组件,自定义一个MediaSink子类替换掉DummySink,就能优雅地解析出完整的H264 NAL单元(也就是帧数据)。
具体实现步骤
1. 自定义H264FrameExtractorSink类
先创建一个继承自MediaSink的子类,把H264的解析逻辑集成进去:
#include "H264VideoRTPSink.hh" #include "H264VideoStreamFramer.hh" class H264FrameExtractorSink : public MediaSink { public: static H264FrameExtractorSink* createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId = NULL); protected: H264FrameExtractorSink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId); virtual ~H264FrameExtractorSink(); private: // 重写MediaSink的核心播放控制方法 virtual Boolean continuePlaying(); // 回调函数:收到帧数据后触发 static void afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds); // 实际处理帧数据的方法 void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds); private: MediaSubsession& fSubsession; char* fStreamId; unsigned char* fReceiveBuffer; H264VideoStreamFramer* fH264Framer; // 官方自带的H264 NAL解析器 };
2. 实现Sink的核心方法
重点是在afterGettingFrame里处理解析后的H264数据——H264VideoStreamFramer已经帮我们把RTP分片拼好了,直接拿完整NAL就行:
H264FrameExtractorSink* H264FrameExtractorSink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) { return new H264FrameExtractorSink(env, subsession, streamId); } H264FrameExtractorSink::H264FrameExtractorSink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) : MediaSink(env), fSubsession(subsession), fStreamId(strDup(streamId) ? strDup(streamId) : NULL) { // 初始化接收缓冲区,按MTU设置大小(1500足够应付大部分场景) fReceiveBuffer = new unsigned char[1500]; // 创建官方的H264帧解析器,自动处理RTP分片 fH264Framer = H264VideoStreamFramer::createNew(env, subsession.source()); } H264FrameExtractorSink::~H264FrameExtractorSink() { delete[] fReceiveBuffer; delete[] fStreamId; Medium::close(fH264Framer); } Boolean H264FrameExtractorSink::continuePlaying() { if (fH264Framer == NULL) return False; // 向数据源请求下一个帧 fH264Framer->getNextFrame(fReceiveBuffer, 1500, afterGettingFrame, this, onSourceClosure, this); return True; } void H264FrameExtractorSink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) { H264FrameExtractorSink* sink = (H264FrameExtractorSink*)clientData; sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds); } void H264FrameExtractorSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) { // 这里就是你要的完整H264 NAL单元! // fReceiveBuffer存的是NAL数据,frameSize是NAL长度 // 你可以在这里把数据写入.h264文件、传给解码器,或者做其他处理 printf("拿到H264 NAL单元,大小:%u 字节\n", frameSize); // 别忘了继续请求下一个帧,不然流就断了 continuePlaying(); }
3. 替换testRTSPClient里的DummySink
找到testRTSPClient.cpp里的createNewMediaSink函数,把原来创建DummySink的逻辑改成我们的自定义Sink:
MediaSink* createNewMediaSink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) { // 判断当前流是不是H264,是的话用我们的自定义Sink if (subsession.codecName() != NULL && strcmp(subsession.codecName(), "H264") == 0) { return H264FrameExtractorSink::createNew(env, subsession, streamId); } else { // 非H264流还是用原来的DummySink兜底 return DummySink::createNew(env, subsession, streamId); } }
4. 编译测试
确保你的项目链接了live555的库文件,编译后运行修改后的testRTSPClient,输入RTSP地址就能看到NAL单元的输出了。如果要生成可播放的H264文件,就在afterGettingFrame里给每个NAL单元加上0x00 0x00 0x00 0x01的起始码,然后写入文件就行。
关键注意点
- 一定要确认RTSP流是H264编码的,不然会自动回退到DummySink
H264VideoStreamFramer已经帮你处理了RTP分片的拼接,不用自己写分片逻辑- 接收缓冲区大小建议设为MTU的1.5倍,避免数据截断
- 如果需要获取SPS/PPS等关键参数,可以在Sink初始化时从
subsession的SDP信息里提取
内容的提问来源于stack exchange,提问作者PPP
相关产品推荐
相关产品推荐

