live555 rtsp服务器实战之doGetNextFrame
live555 rtsp服务器实战之createNewStreamSource
live555 rtsp服务器实战之doGetNextFrame
概要
live555用于实际项目开发时,createNewStreamSource和doGetNextFrame是必须要实现的两个虚函数,一般会创建两个类来实现这两个函数:假如这两个类为H264LiveVideoServerMediaSubssion和H264FramedLiveSource;H264LiveVideoServerMediaSubssion为实时视频会话类,用于实现createNewStreamSource虚函数;
H264FramedLiveSource为实时视频帧资源类,用户实现doGetNextFrame函数;
那么这两个函数是什么时候被调用以及他们的作用是什么呢?本节将详细介绍;
声明:该文章基于H264视频源为基础分析,其他源类似;
由于这两个类主要是自定义虚函数,所以存在一定的继承关系,首先需要明确这两个类的继承关系(本章只介绍H264FramedLiveSource):
H264FramedLiveSource:FramedSource:MediaSource:Medium
doGetNextFrame调用流程
doGetNextFrame函数声明于FrameSource类:
virtual void doGetNextFrame() = 0;
该声明为纯虚函数,所以必须有子类对该函数进行实现,H264FramedLiveSource类就是用于实现该函数;doGetNextFrame函数只有一个功能:获取视频帧;
void H264FramedLiveSource::doGetNextFrame() { uint8_t *frame = new uint8_t[1024*1024]; int len = 0; //获取一帧视频帧 get_frame(&frame, len); //将帧长度赋值给父类的成员变量fFrameSize fFrameSize = len; //将帧数据赋值给父类的成员变量fTo memcpy(fTo, frame, len); delete [] frame; // nextTask() = envir().taskScheduler().scheduleDelayedTask(0,(TaskFunc*)FramedSource::afterGetting, this);//表示延迟0秒后再执行 afterGetting 函数 afterGetting(this); return; }
fFrameSize和fTo的两个父类变量在流传输时会用到;那么问题来了:doGetNextFrame函数在整个流程中在哪里被调用?什么时候调用?
首先doGetNextFrame函数是在PLAY信令交互的时候被调用;调用流程为:
handleCmd_PLAY->startStream->startPlaying(OnDemandServerMediaSubsession)->startPlaying(MediaSink)->continuePlaying(虚函数,子类H264or5VideoRTPSink实现)->continuePlaying(虚函数,子类MultiFramedRTPSink实现)->buildAndSendPacket->packFrame(MultiFramedRTPSink)->getNextFrame->doGetNextFrame
下面详解介绍下这个流程,看过我的另一篇文章:"live555 rtsp服务器实战之createNewStreamSource" 的都知道handleRequestBytes函数调用了handleCmd_SETUP;同样handleCmd_PLAY函数也是在这里被调用的;handleRequestBytes函数就是用来处理各种客户端信令交互信息的;handleRequestBytes函数如下:
void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) { . . . if (urlIsRTSPS != fOurRTSPServer.fOurConnectionsUseTLS) { #ifdef DEBUG fprintf(stderr, "Calling handleCmd_redirect()\n"); #endif handleCmd_redirect(urlSuffix); } else if (strcmp(cmdName, "OPTIONS") == 0) { // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist, // then treat this as an error: if (requestIncludedSessionId && clientSession == NULL) { #ifdef DEBUG fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n"); #endif handleCmd_sessionNotFound(); } else { // Normal case: handleCmd_OPTIONS(); } } else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') { // The special "*" URL means: an operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER: if (strcmp(cmdName, "GET_PARAMETER") == 0) { handleCmd_GET_PARAMETER((char const *)fRequestBuffer); } else if (strcmp(cmdName, "SET_PARAMETER") == 0) { handleCmd_SET_PARAMETER((char const *)fRequestBuffer); } else { handleCmd_notSupported(); } } else if (strcmp(cmdName, "DESCRIBE") == 0) { handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const *)fRequestBuffer); } else if (strcmp(cmdName, "SETUP") == 0) { Boolean areAuthenticated = True; if (!requestIncludedSessionId) { // No session id was present in the request. // So create a new "RTSPClientSession" object for this request. // But first, make sure that we're authenticated to perform this command: char urlTotalSuffix[2 * RTSP_PARAM_STRING_MAX]; // enough space for urlPreSuffix/urlSuffix'\0' urlTotalSuffix[0] = '\0'; if (urlPreSuffix[0] != '\0') { strcat(urlTotalSuffix, urlPreSuffix); strcat(urlTotalSuffix, "/"); } strcat(urlTotalSuffix, urlSuffix); if (authenticationOK("SETUP", urlTotalSuffix, (char const *)fRequestBuffer)) { clientSession = (RTSPServer::RTSPClientSession *)fOurRTSPServer.createNewClientSessionWithId(); } else { areAuthenticated = False; } } if (clientSession != NULL) { clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer); playAfterSetup = clientSession->fStreamAfterSETUP; } else if (areAuthenticated) { #ifdef DEBUG fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n"); #endif handleCmd_sessionNotFound(); } } else if (strcmp(cmdName, "TEARDOWN") == 0 || strcmp(cmdName, "PLAY") == 0 || strcmp(cmdName, "PAUSE") == 0 || strcmp(cmdName, "GET_PARAMETER") == 0 || strcmp(cmdName, "SET_PARAMETER") == 0) { if (clientSession != NULL) { clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer); } else { #ifdef DEBUG fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n"); #endif handleCmd_sessionNotFound(); } } else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 0) { // Because - unlike other commands - an implementation of this command needs // the entire URL, we re-parse the command to get it: char *url = strDupSize((char *)fRequestBuffer); if (sscanf((char *)fRequestBuffer, "%*s %s", url) == 1) { // Check for special command-specific parameters in a "Transport:" header: Boolean reuseConnection, deliverViaTCP; char *proxyURLSuffix; parseTransportHeaderForREGISTER((const char *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix); handleCmd_REGISTER(cmdName, url, urlSuffix, (char const *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix); delete[] proxyURLSuffix; } else { handleCmd_bad(); } delete[] url; } else { // The command is one that we don't handle: handleCmd_notSupported(); } . . . }
handleCmd_withinSession函数内部就调用了handleCmd_PLAY函数;仅整理流程,调用途中的函数这里不做解释;直接跳到packFrame(MultiFramedRTPSink)函数:
void MultiFramedRTPSink::packFrame() { // Get the next frame. // First, skip over the space we'll use for any frame-specific header: fCurFrameSpecificHeaderPosition = fOutBuf->curPacketSize(); fCurFrameSpecificHeaderSize = frameSpecificHeaderSize(); fOutBuf->skipBytes(fCurFrameSpecificHeaderSize); fTotalFrameSpecificHeaderSizes += fCurFrameSpecificHeaderSize; // See if we have an overflow frame that was too big for the last pkt if (fOutBuf->haveOverflowData()) { // Use this frame before reading a new one from the source unsigned frameSize = fOutBuf->overflowDataSize(); struct timeval presentationTime = fOutBuf->overflowPresentationTime(); unsigned durationInMicroseconds = fOutBuf->overflowDurationInMicroseconds(); fOutBuf->useOverflowData(); afterGettingFrame1(frameSize, 0, presentationTime, durationInMicroseconds); } else { // Normal case: we need to read a new frame from the source if (fSource == NULL) return; fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(), afterGettingFrame, this, ourHandleClosure, this); } }
这里调用了getNextFrame函数,来看下getNextFrame函数的实现:
void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize, afterGettingFunc* afterGettingFunc, void* afterGettingClientData, onCloseFunc* onCloseFunc, void* onCloseClientData) { // Make sure we're not already being read: 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; // by default; could be changed by doGetNextFrame() fDurationInMicroseconds = 0; // by default; could be changed by doGetNextFrame() fAfterGettingFunc = afterGettingFunc; fAfterGettingClientData = afterGettingClientData; fOnCloseFunc = onCloseFunc; fOnCloseClientData = onCloseClientData; fIsCurrentlyAwaitingData = True; doGetNextFrame(); }
发现啦!发现doGetNextFrame函数啦!所以doGetNextFrame函数就是在getNextFrame内被调用的;但是问题来了:搜索可以发现在live555中doGetNextFrame函数很多;怎么就确定这里的doGetNextFrame和我们自定义的doGetNextFrame有关呢?
问的好!那这的关键点就在于fSource->getNextFrame的fSource变量到底是什么类的对象;才能确定doGetNextFrame到底调用的是哪个类的函数;fSource属于MediaSink类的成员变量:
FramedSource* fSource;
但是FrameSource子类很多;所以需要确定fSource到底指向的是哪个子类;
不好意思,内容太多,懒得再拷贝了;具体内容参考上面的链接!
后续还会继续更新关于doGetNextFrame函数获取的帧数据是怎么处理和发送的,H264VideoStreamFramer中的doGetNextFrame函数和MPEGVideoStreamFramer中的doGetNextFrame函数都是什么作用!期待的话关注我,了解最新动态!
该文章持续更新!如果有错误或者模糊的地方欢迎留言探讨!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)