live555 rtsp服务器实战之createNewStreamSource
概述
live555用于实际项目开发时,createNewStreamSource和doGetNextFrame是必须要实现的两个虚函数,一般会创建两个类来实现这两个函数:假如这两个类为H264LiveVideoServerMediaSubssion和H264FramedLiveSource;
H264LiveVideoServerMediaSubssion为实时视频会话类,用于实现createNewStreamSource虚函数;
H264FramedLiveSource为实时视频帧资源类,用户实现doGetNextFrame函数;
那么这两个函数是什么时候被调用以及他们的作用是什么呢?本节将详细介绍;
声明:该文章基于H264视频源为基础分析,其他源类似;
由于这两个类主要是自定义虚函数,所以存在一定的继承关系,首先需要明确这两个类的继承关系(本章只介绍H264LiveVideoServerMediaSubssion):
H264LiveVideoServerMediaSubssion:OnDemandServerMediaSubsession:ServerMediaSubsession:Medium
createNewStreamSource
本章介绍createNewStreamSource函数;
虚函数createNewStreamSource声明于OnDemandServerMediaSubsession类:
virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) = 0;
可以看出声明时为纯虚函数(live555提供的对外接口,用户自定义实现),必须有子类对该函数进行实现;该函数用于获取媒体流源的资源对象,简单点说就是指明服务器调用哪个doGetNextFrame函数获取视频流;
因此H264LiveVideoServerMediaSubssion类需要继承OnDemandServerMediaSubsession类;
createNewStreamSource函数实现解析:
FramedSource* H264LiveVideoServerMediaSubssion::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) { /* Remain to do : assign estBitrate */ estBitrate = 1000; // kbps, estimate //创建视频源 H264FramedLiveSource* liveSource = H264FramedLiveSource::createNew(envir(), Server_datasize, Server_databuf, Server_dosent); if (liveSource == NULL) { return NULL; } // Create a framer for the Video Elementary Stream: return H264VideoStreamFramer::createNew(envir(), liveSource); }
上述代码就是最基础的虚函数createNewStreamSource的实现,其中H264FramedLiveSource就是视频源资源的类;也就是获取视频帧的函数doGetNextFrame所在的类;该函数主要做了两件事:
1. 创建h264帧资源类对象liveSource;目的是获取调用doGetNextFrame的方法;
2. 返回H264VideoStreamFramer类对象,并将h264帧资源类对象传递进去,最终liveSource赋值给StreamParser类中成员变量fInputSource和FramedFilter类的成员变量fInputSource;
该函数在SETUP信令交互时期被调用,首先看一下信令处理函数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(); } . . . }
handleRequestBytes函数是在doEventLoop主循环中监测的RTSP客户端有数据发送时调用的函数(关于rtsp的tcp udp协议交互,参考上面的文章),作用就是处理各种信令(OPTION DESCRIBE SETUP等)及数据;其中就调用了handleCmd_SETUP处理SETUP信令的函数;handleCmd_SETUP函数又调用了getStreamParameters函数:
void OnDemandServerMediaSubsession ::getStreamParameters(unsigned clientSessionId, struct sockaddr_storage const &clientAddress, Port const &clientRTPPort, Port const &clientRTCPPort, int tcpSocketNum, unsigned char rtpChannelId, unsigned char rtcpChannelId, TLSState *tlsState, struct sockaddr_storage &destinationAddress, u_int8_t & /*destinationTTL*/, Boolean &isMulticast, Port &serverRTPPort, Port &serverRTCPPort, void *&streamToken) { if (addressIsNull(destinationAddress)) { // normal case - use the client address as the destination address: destinationAddress = clientAddress; } isMulticast = False; if (fLastStreamToken != NULL && fReuseFirstSource) { // Special case: Rather than creating a new 'StreamState', // we reuse the one that we've already created: serverRTPPort = ((StreamState *)fLastStreamToken)->serverRTPPort(); serverRTCPPort = ((StreamState *)fLastStreamToken)->serverRTCPPort(); ++((StreamState *)fLastStreamToken)->referenceCount(); streamToken = fLastStreamToken; } else { // Normal case: Create a new media source: unsigned streamBitrate; FramedSource *mediaSource = createNewStreamSource(clientSessionId, streamBitrate); . . . } . . . }
该函数就在OnDemandServerMediaSubsession类,也就是createNewStreamSource实现类H264LiveVideoServerMediaSubssion的父类,这时调用的createNewStreamSource就是我们自己实现的逻辑啦!返回值mediaSource后续被用来调用doGetNextFrame函数获取视频帧使用!
可以看出这两件事的目的都是为了告诉rtsp服务器怎么获取视频帧数据;关于fInputSource变量什么时候被使用的,请参考我的下篇文章:未知地址!哈哈哈!
下节的doGetNextFrame分析才是重点,敬请期待!