==2023.1.1 追加,归纳总结一下,作为复习的提要:
- 实现新的MediaSubsession,覆盖实现其中的虚函数createNewStreamSource() ,用来创建source . 覆盖 实现 其中的虚函数createNewRTPSink() 用来创建sink
- 实现自己的 FrameSource, 实现其中的虚函数 doGetNextFrame(),在这个里面把自己的h264数据拷贝到 fTo 缓冲区,并且赋值成员变量fFrameSize 、时间戳、最大帧数据大小等等。接着 添加下一次获取数据的任务到事件队列。
- 利用装饰者模式。。 ”包裹装饰“一下自己的FrameSource, 优化: 在装饰自己的FrameSource的时候不要用H264VideoStreamFramer,而改用DiscreteFramer,避免重复的流分成帧的解析任务。(DiscreteFramer要求输入的数据不带 NAL头。不然一直在抱怨)
=======================================================================
================20191212追加,以下方式从内存中读取h264帧的实现方式,有冗余操作,改进说明-《live555-从buffer读取h264推流》===========================================
(2019-03-25 纠正下,下面所有h264帧概念,其实是h264slice, 并非一帧图像,h264中每一个slice可以单独解码,所以有时候会看到 花屏,因为一帧数据被分成很多单独的slice,如果网络帧丢了部分slice,则一帧图像会出现只解码一部分的情况)
现有本地已知的h264数据帧缓存,想对接到live555 使用rtsp发送。
live555源码中是支持直接读h264文件的demo, 想改为从自己的dev获取数据,官方也有相关FAQ:http://www.live555.com/liveMedia/faq.html :3. The "test*Streamer" test programs read from a file. Can I modify them so that they take input from a H.264, H.265 or MPEG encoder instead, so I can stream live (rather than prerecorded) video and/or audio?
原意还是自行查看原语,有一句:(Even simpler, if your operating system represents the encoder device as a file, then you can just use the name of this file (instead of "test.*")
先贴上最终成果:(具体分析流程后续贴上)
先说明:(以下方式对接,修改是比较少的,但是效率可是有折扣的,数据在传到发送模块前先在framepaser中走了一圈,而这个framepaser中很大的一部分工作在于从这一串流数据中解析出每一帧的起始和结束,当然也有sps,pps等信息,而我们已知的本地缓冲区已经是知道帧开头结束的,这一部分的工作重复,还多了framepaser内部使用的内存空间 ,要解决这些问题,那就需要自行实现H264VideoStreamFramer这一层了)
参照testOnDemandRTSPServer.cpp, 在其中添加自己的一个模块(尽量保持原代码的完整性,建议所有的修改另行添加一个目录)。
main函数添加自己的模块:
#if 1
// A H.264 live elementary stream:
{//这里是自己的ServerMediaSubssion
//这个是输出缓冲区的大小,要设置到比任意一帧h264 大
OutPacketBuffer::maxSize = 2000000;
char const* streamName = "h264Live";
char const* inputFileName = "live";
ServerMediaSession* sms
= ServerMediaSession::createNew(*env, streamName, streamName,
descriptionString);
sms->addSubsession(H264LiveVideoServerMediaSubssion
::createNew(*env,reuseFirstSource));
rtspServer->addServerMediaSession(sms);
announceStream(rtspServer, sms, streamName, inputFileName);
}
#endif
#if 1
// H.264原demo对比
// A H.264 video elementary stream:
{
char const* streamName = "h264ESVideoTest";
char const* inputFileName = "test.264";
ServerMediaSession* sms
= ServerMediaSession::createNew(*env, streamName, streamName,
descriptionString);
sms->addSubsession(H264VideoFileServerMediaSubsession
::createNew(*env, inputFileName, reuseFirstSource));
rtspServer->addServerMediaSession(sms);
announceStream(rtspServer, sms, streamName, inputFileName);
}
#endif
下面要实现自己的
class H264LiveVideoServerMediaSubssion
H264FramedLiveSource.hh
#ifndef _H264_LIVE_VIDEO_SERVER_MEDIA_SUBSESSION_HH
#define _H264_LIVE_VIDEO_SERVER_MEDIA_SUBSESSION_HH
#include "OnDemandServerMediaSubsession.hh"
class H264LiveVideoServerMediaSubssion : public OnDemandServerMediaSubsession
{
public:
static H264LiveVideoServerMediaSubssion* createNew(UsageEnvironment& env, Boolean reuseFirstSource);
protected:
H264LiveVideoServerMediaSubssion(UsageEnvironment& env,Boolean reuseFirstSource);
~H264LiveVideoServerMediaSubssion();
protected: // redefined virtual functions
FramedSource* createNewStreamSource(unsigned clientSessionId,unsigned& estBitrate);
RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
FramedSource* inputSource);
public:
};
#endif
H264FramedLiveSource.cpp
#include "H264LiveVideoServerMediaSubssion.hh"
#include "H264FramedLiveSource.hh"
#include "H264VideoStreamFramer.hh"
#include "H264VideoRTPSink.hh"
H264LiveVideoServerMediaSubssion* H264LiveVideoServerMediaSubssion::createNew(UsageEnvironment& env, Boolean reuseFirstSource)
{
return new H264LiveVideoServerMediaSubssion(env, reuseFirstSource);
}
H264LiveVideoServerMediaSubssion::H264LiveVideoServerMediaSubssion(UsageEnvironment& env,Boolean reuseFirstSource)
: OnDemandServerMediaSubsession(env,reuseFirstSource)
{
}
H264LiveVideoServerMediaSubssion::~H264LiveVideoServerMediaSubssion()
{
}
FramedSource* H264LiveVideoServerMediaSubssion::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
{
//创建视频源,参照H264VideoFileServerMediaSubsession
H264FramedLiveSource* liveSource = H264FramedLiveSource::createNew(envir());
if (liveSource == NULL)
{
return NULL;
}
// Create a framer for the Video Elementary Stream:
return H264VideoStreamFramer::createNew(envir(), liveSource);
}
RTPSink* H264LiveVideoServerMediaSubssion
::createNewRTPSink(Groupsock* rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
FramedSource* /*inputSource*/) {
return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}
下面是关键,创建H264FramedLiveSource,自己的输入源。也是对比H264VideoFileServerMediaSubsession
里面用的ByteStreamFileSource 来分析。
H264FramedLiveSource
class H264FramedLiveSource : public FramedSource
{
public:
static H264FramedLiveSource* createNew(UsageEnvironment& env);
// redefined virtual functions
virtual unsigned maxFrameSize() const;
protected:
H264FramedLiveSource(UsageEnvironment& env);
virtual ~H264FramedLiveSource();
private:
virtual void doGetNextFrame();
protected:
static TestFromFile * pTest;
};
H264FramedLiveSource.cpp
H264FramedLiveSource::H264FramedLiveSource(UsageEnvironment& env)
: FramedSource(env)
{
}
H264FramedLiveSource* H264FramedLiveSource::createNew(UsageEnvironment& env)
{
H264FramedLiveSource* newSource = new H264FramedLiveSource(env);
return newSource;
}
H264FramedLiveSource::~H264FramedLiveSource()
{
}
unsigned H264FramedLiveSource::maxFrameSize() const
{
//printf("wangmaxframesize %d %s\n",__LINE__,__FUNCTION__);
//这里返回本地h264帧数据的最大长度
return 1024*120;
}
void H264FramedLiveSource::doGetNextFrame()
{
//这里读取本地的帧数据,就是一个memcpy(fTo,XX,fMaxSize),要确保你的数据不丢失,即fMaxSize要大于等于本地帧缓存的大小,关键在于上面的maxFrameSize() 虚函数的实现
fFrameSize = XXXgeth264Frame(fTo, fMaxSize);
printf("read dat befor %d %s fMaxSize %d ,fFrameSize %d \n",__LINE__,__FUNCTION__,fMaxSize,fFrameSize);
if (fFrameSize == 0) {
handleClosure();
return;
}
//设置时间戳
gettimeofday(&fPresentationTime, NULL);
// Inform the reader that he has data:
// To avoid possible infinite recursion, we need to return to the event loop to do this:
nextTask() = envir().taskScheduler().scheduleDelayedTask(0,
(TaskFunc*)FramedSource::afterGetting, this);
}
return;
}
以上,关键在于doGetNextFrame 虚函数(用java里面的 接口 称谓,感觉更为妥当)的实现,明确该接口必须要做哪些工作:
参照ByteStreamFileSource类中的实现,可以列出这个虚函数需要做的工作:
1.0 读数据到 fto
2.0 实际读到的数据个数设置到 fFrameSize
3.0 设置时间戳到 fPresentationTime
4.0 读完数据,通知调用者 Inform the reader that he has data
不过有一点问题,你会发现 读数据时用到的 表示能读多少数据的输入参数 fMaxSize 是不得而知的,这个就只能去查看调用者怎么设置的了,就是要明确它的调用逻辑,分析过程后续补充。 要控制这个参数,实现上面的H264FramedLiveSource::maxFrameSize(),返回一个帧的最大长度。这个虚函数在ByteStreamFileSource是没有覆盖实现的,ByteStreamFileSource 中是一个文件流,上面想读多少数据,就能直接中这个文件中读多少数据,下次还可以从上一次的读取位置接着读取,回想官方的FAQ:if your operating system represents the encoder device as a file, 而我这个实现的是,我有一帧数据在缓冲区,要求调用者一次性读走,不然下次可就指不定去哪里了,整个要实现的功能就是这样。
(其实这样对接,修改是比较少的,但是效率可是有折扣的,数据在传到发送模块前先在framepaser中走了一圈,而这个framepaser中很大的一部分工作在于从这一串流数据中解析出每一帧的起始和结束,当然也有sps,pps等信息,而我们已知的本地缓冲区已经是知道帧开头结束的,这一部分的工作重复,还多了framepaser内部使用的内存空间 ,要解决这些问题,那就需要自行实现H264VideoStreamFramer这一层了) 补充:这一部分工作live555源码中已经支持了,参考 <三:使用,推流端>live555-从buffer读取h264推流_王二の黄金时代的博客-优快云博客_h264videostreamdiscreteframer
图解:https://blog.youkuaiyun.com/u012459903/article/details/86597475 图四