这几天在弄h264的rtsp 的服务端,开始是参考了网络上一个人的代码效发现效果不是太好。还是决定好好捋一下这个过程,为了怕以后忘了还是在这里记录一下。有关live555的更多内容请详细参考:http://blog.csdn.net/nkmnkm/article/category/1066093,本文有部分参考该博客。
首先创建
TaskScheduler *scheduler = BasicTaskScheduler::createNew();
_env = BasicUsageEnvironment::createNew(*scheduler);
这里就不用讲了,创建计划任务等会会执行 _env->taskScheduler().doEventLoop();
主要的工作如下:
1为所有需要操作的socket执行select。
2找出第一个应执行的socket任务(handler)并执行之。
3找到第一个应响应的事件,并执行之。
4找到第一个应执行的延迟任务并执行之。
具体过程可以参考一下live555的源码。
然后是创建一个RTSPServer
RTSPServer *rtspServer = RTSPServer::createNew(*_env, 8554);
可以看一下
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds) {
int ourSocket = setUpOurSocket(env, ourPort); //建立TCP socket
if (ourSocket == -1) return NULL;
return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}
要监听客户端的连接,就需要利用任务调度机制,所以需要添加所以需添加一个socket handler,
祥见如下
RTSPServer::RTSPServer(UsageEnvironment& env,
int ourSocket, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds)
: Medium(env),
fRTSPServerSocket(ourSocket), fRTSPServerPort(ourPort),
fHTTPServerSocket(-1), fHTTPServerPort(0), fClientSessionsForHTTPTunneling(NULL),
fAuthDB(authDatabase), fReclamationTestSeconds(reclamationTestSeconds),
fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)) {
#ifdef USE_SIGNALS
// Ignore the SIGPIPE signal, so that clients on the same host that are killed
// don't also kill us:
signal(SIGPIPE, SIG_IGN);
#endif
// Arrange to handle connections from others:
env.taskScheduler().turnOnBackgroundReadHandling(fRTSPServerSocket,
(TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandlerRTSP, this);
}
这里的turnOnBackgroundReadHandling是UsageEnvironment中的一个方法
void turnOnBackgroundReadHandling(int socketNum, BackgroundHandlerProc* handlerProc, void* clientData) {
setBackgroundHandling(socketNum, SOCKET_READABLE, handlerProc, clientData);
}
setBackgroundHandling在TaskScheduler类中的一个纯虚函数,但是他放在UsageEnvironment.hh中
这是一个添加socket任务的函数
在BasicTaskScheduler中,他们的继承关系是:BasicTaskScheduler-》BasicTaskScheduler0-》TaskScheduler
void BasicTaskScheduler
::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
if (socketNum < 0) return;
FD_CLR((unsigned)socketNum, &fReadSet);
FD_CLR((unsigned)socketNum, &fWriteSet);
FD_CLR((unsigned)socketNum, &fExceptionSet);
if (conditionSet == 0) {
fHandlers->clearHandler(socketNum);
if (socketNum+1 == fMaxNumSockets) {
--fMaxNumSockets;
}
} else {
fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
if (socketNum+1 > fMaxNumSockets) {
fMaxNumSockets = socketNum+1;
}
if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);
if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);
if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);
}
}
这里可以看到主要是为socketNum添加到select中set中
当收到客户的连接时需保存下代表客户端的新socket,以后用这个socket与这个客户通讯。每个客户将来会对应一个rtp会话,而且各客户的RTSP请求只控制自己的rtp会话,那么最好建立一个会话类,代表各客户的rtsp会话。可以看到在TaskScheduler处理sockect的函数为BackgroundHandlerProc
从上面的RTSPServer的代码中可以看到
env.taskScheduler().turnOnBackgroundReadHandling(fRTSPServerSocket,
(TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandlerRTSP, this);
incomingConnectionHandlerRTSP为传入的他们的处理函数,这个函数如下:
void RTSPServer::incomingConnectionHandlerRTSP(void* instance, int /*mask*/) {
RTSPServer* server = (RTSPServer*)instance;
server->incomingConnectionHandlerRTSP1();
}
void RTSPServer::incomingConnectionHandlerRTSP1() {
incomingConnectionHandler(fRTSPServerSocket);
}
void RTSPServer::incomingConnectionHandler(int serverSocket) {
struct sockaddr_in clientAddr;
SOCKLEN_T clientAddrLen = sizeof clientAddr;
//接受连接
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket < 0) {
int err = envir().getErrno();
if (err != EWOULDBLOCK) {
envir().setResultErrMsg("accept() failed: ");
}
return;
}
//设置socket的参数
makeSocketNonBlocking(clientSocket);
increaseSendBufferTo(envir(), clientSocket, 50*1024);
#ifdef DEBUG
envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif
//产生一个sesson id
// Create a new object for this RTSP session.
// (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number). We don't bother checking for
// a collision; the probability of two concurrent sessions getting the same session id is very low.)
// (We do, however, avoid choosing session id 0, because that has a special use (by "OnDemandServerMediaSubsession").)
unsigned sessionId;
do { sessionId = (unsigned)our_random32(); } while (sessionId == 0);
//创建RTSPClientSession,注意传入的参数
(void)createNewClientSession(sessionId, clientSocket, clientAddr);
}
创建了一个RTSPClientSession
RTSPServer::createNewClientSession(unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr) {
return new RTSPClientSession(*this, sessionId, clientSocket, clientAddr);
}
RTSPClientSession要提供什么功能呢?可以想象:需要监听客户端的rtsp请求并回应它,需要在DESCRIBE请求中返回所请求的流的信息,需要在SETUP请求中建立起RTP会话,需要在TEARDOWN请求中关闭RTP会话,等等...
RTSPClientSession要侦听客户端的请求,就需把自己的socket handler加入计划任务。证据如下:
RTSPServer::RTSPClientSession
::RTSPClientSession(RTSPServer& ourServer, unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr)
: fOurServer(ourServer), fOurSessionId(sessionId),
fOurServerMediaSession(NULL),
fClientInputSocket(clientSocket), fClientOutputSocket(clientSocket), fClientAddr(clientAddr),
fSessionCookie(NULL), fLivenessCheckTask(NULL),
fIsMulticast(False), fSessionIsActive(True), fStreamAfterSETUP(False),
fTCPStreamIdCount(0), fNumStreamStates(0), fStreamStates(NULL), fRecursionCount(0) {
// Arrange to handle incoming requests:
resetRequestBuffer();
envir().taskScheduler().turnOnBackgroundReadHandling(fClientInputSocket,
(TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
noteLiveness();
}
执行函数是incomingRequestHandler,如下:
void RTSPServer::RTSPClientSession::incomingRequestHandler(void* instance, int /*mask*/) {
RTSPClientSession* session = (RTSPClientSession*)instance;
session->incomingRequestHandler1();
}
void RTSPServer::RTSPClientSession::incomingRequestHandler1() {
struct sockaddr_in dummy; // 'from' address, meaningless in this case
int bytesRead = readSocket(envir(), fClientInputSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
handleRequestBytes(bytesRead);
}
上面是read了一段数据,handleRequestBytes主要是一些数据的处理,由于代码太多,不一一列出,以下代码就是证据,主要是对一些rtsp的请求作出回应:
if (strcmp(cmdName, "OPTIONS") == 0) {
handleCmd_OPTIONS(cseq);
} else if (strcmp(cmdName, "DESCRIBE") == 0) {
handleCmd_DESCRIBE(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
} else if (strcmp(cmdName, "SETUP") == 0) {
handleCmd_SETUP(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
} else if (strcmp(cmdName, "TEARDOWN") == 0
|| strcmp(cmdName, "PLAY") == 0
|| strcmp(cmdName, "PAUSE") == 0
|| strcmp(cmdName, "GET_PARAMETER") == 0
|| strcmp(cmdName, "SET_PARAMETER") == 0) {
handleCmd_withinSession(cmdName, urlPreSuffix, urlSuffix, cseq, (char const*)fRequestBuffer);
} else {
handleCmd_notSupported(cseq);
}
下面重点讲一下下RTSPClientSession响应DESCRIBE请求的过程:
void RTSPServer::RTSPClientSession::handleCmd_DESCRIBE(
char const* cseq,
char const* urlPreSuffix,
char const* urlSuffix,
char const* fullRequestStr)
{
char* sdpDescription = NULL;
char* rtspURL = NULL;
do {
//整理一下下RTSP地址
char urlTotalSuffix[RTSP_PARAM_STRING_MAX];
if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2
> sizeof urlTotalSuffix) {
handleCmd_bad(cseq);
break;
}
urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
strcat(urlTotalSuffix, urlPreSuffix);
strcat(urlTotalSuffix, "/");
}
strcat(urlTotalSuffix, urlSuffix);
//验证帐户和密码
if (!authenticationOK("DESCRIBE", cseq, urlTotalSuffix, fullRequestStr))
break;
// We should really check that the request contains an "Accept:" #####
// for "application/sdp", because that's what we're sending back #####
// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
//跟据流的名字查找ServerMediaSession,如果找不到,会创建一个。每个ServerMediaSession中至少要包含一个
//ServerMediaSubsession。一个ServerMediaSession对应一个媒体,可以认为是Server上的一个文件,或一个实时获取设备。其包含的每个ServerMediaSubSession代表媒体中的一个Track。所以一个ServerMediaSession对应一个媒体,如果客户请求的媒体名相同,就使用已存在的ServerMediaSession,如果不同,就创建一个新的。一个流对应一个StreamState,StreamState与ServerMediaSubsession相关,但代表的是动态的,而ServerMediaSubsession代表静态的。
ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
if (session == NULL) {
handleCmd_notFound(cseq);
break;
}
// Then, assemble a SDP description for this session:
//获取SDP字符串,在函数内会依次获取每个ServerMediaSubSession的字符串然连接起来。
sdpDescription = session->generateSDPDescription();
if (sdpDescription == NULL) {
// This usually means that a file name that was specified for a
// "ServerMediaSubsession" does not exist.
snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 404 File Not Found, Or In Incorrect Format\r\n"
"CSeq: %s\r\n"
"%s\r\n", cseq, dateHeader());
break;
}
unsigned sdpDescriptionSize = strlen(sdpDescription);
// Also, generate our RTSP URL, for the "Content-Base:" header
// (which is necessary to ensure that the correct URL gets used in
// subsequent "SETUP" requests).
rtspURL = fOurServer.rtspURL(session, fClientInputSocket);
//形成响应DESCRIBE请求的RTSP字符串。
snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s", cseq, dateHeader(), rtspURL, sdpDescriptionSize,
sdpDescription);
} while (0);
delete[] sdpDescription;
delete[] rtspURL;
//返回后会被立即发送(没有把socket write操作放入计划任务中)。
}
这里我们稍微看下lookupServerMediaSession
ServerMediaSession* RTSPServer::lookupServerMediaSession(char const* streamName) {
return (ServerMediaSession*)(fServerMediaSessions->Lookup(streamName));
}
而fServerMediaSessions是HashTable类型,也就是一个HashTable的查找过程
一般RTSPServer建立后需要addServerMediaSession(ServerMediaSession*)
现在看一下这个addServerMediaSession
void RTSPServer::addServerMediaSession(ServerMediaSession* serverMediaSession) {
if (serverMediaSession == NULL) return;
char const* sessionName = serverMediaSession->streamName();
if (sessionName == NULL) sessionName = "";
ServerMediaSession* existingSession
= (ServerMediaSession*)(fServerMediaSessions->Add(sessionName, (void*)serverMediaSession));
//fServerMediaSessions就是上文提到的HashTable类型指针。可以看出是一个sessionName对应一个serverMediaSession
removeServerMediaSession(existingSession); // if any
}
这里需要理解serverMediaSession和ServerMediaSubsession,在上面的程序中已有解释,不过要好好理解。
这里的ServerMediaSession一般会新建一个,然后是addSubsession加入ServerMediaSubsession。
这个新建和添加的过程后面再来看,现在再回到请求响应的过程,rtsp的请求基本是
OPTION、DESCRIBE、SETUP、PLAY、TEARDOWN
讲过DESCRIBE,其他的就不一一列举,下面看看比较关键的PLAY。
在RTSPServer.cpp中
void RTSPServer::RTSPClientSession
::handleCmd_PLAY(ServerMediaSubsession* subsession, char const* cseq,
char const* fullRequestStr)
这里的内容太多,基本都是回应的信息最后执行的是
fStreamStates[i].subsession->startStream(fOurSessionId,
fStreamStates[i].streamToken,
(TaskFunc*)noteClientLiveness, this,
rtpSeqNum, rtpTimestamp,
handleAlternativeRequestByte, this);
那么我们来看一下这个startStream,但在ServerMediaSubsession中startStream为纯虚函数。
因此ServerMediaSession addSubsession的时候应该是一个继承自ServerMediaSubsession的类,OnDemandServerMediaSubsession是继承自ServerMediaSubsession。
我们先看看OnDemandServerMediaSubsession中的startStream。
void OnDemandServerMediaSubsession::statStream(unsigned clientSessionId,void* streamToken,TaskFunc* rtcpRRHandler,void* rtcpRRHandlerClientData,
unsigned short& rtpSeqNum,unsigned& rtpTimestamp,ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,void* serverRequestAlternativeByteHandlerClientData) {
StreamState* streamState = (StreamState*)streamToken;
Destinations* destinations
= (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId));
if (streamState != NULL) {
streamState->startPlaying(destinations,
rtcpRRHandler, rtcpRRHandlerClientData,
serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
if (streamState->rtpSink() != NULL) {
rtpSeqNum = streamState->rtpSink()->currentSeqNo();
rtpTimestamp = streamState->rtpSink()->presetNextTimestamp();
}
}
}
最后调用的是
streamState->startPlaying,但是这个
streamState确实头一次见到,也没看到在哪里建立。
再想想哪里还没有看呢?记得中间setup命令的响应过程没看,现在就去看看void RTSPServer::RTSPClientSession::handleCmd_SETUP
这里代码挺多的,主要是构建回复命令,我们重点看
fStreamStates = new struct streamState[fNumStreamStates];
iter.reset();
ServerMediaSubsession* subsession;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
subsession = iter.next();
fStreamStates[i].subsession = subsession;
fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
}
}
看到没有这里建立了streamState,但是 fStreamStates[i].streamToken赋的为空,源码的解释是// for now; it may be changed by the "getStreamParameters()" call that comes later
果然在下面我们看到了调用的代码。
subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[streamNum].streamToken);
在ServerMediaSubsession中getStreamParameters也是纯虚函数,我们来看看OnDemandServerMediaSubsession中的getStreamParameters。
if (fLastStreamToken != NULL && fReuseFirstSource) {
......
}else{
。。。。。。
FramedSource* mediaSource
= createNewStreamSource(clientSessionId, streamBitrate);
...........
RTPSink* rtpSink;
..........
rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
}
这两个比较重要创建rtspSink和FramedSource。
我们要实现自己的实时的h264 rtp传输,因此这个rtspSink和FramedSource要自己实现。因此我们addSubsession的时候要add一个继承自OnDemandServerMediaSubsession自己显示的类,主要目的是实现自己的rtspSink和FramedSource。从中我们还看到是建立了一个StreamState
证据如下:
streamToken = fLastStreamToken
= new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
streamBitrate, mediaSource,
rtpGroupsock, rtcpGroupsock);
这里的rtpSink即为我们在上面createNewRTPSink出来的,这里的mediaSource 是通过FramedSource* mediaSource = createNewStreamSource(clientSessionId, streamBitrate);出来的。
StreamState::StreamState(OnDemandServerMediaSubsession& master,
Port const& serverRTPPort, Port const& serverRTCPPort,
RTPSink* rtpSink, BasicUDPSink* udpSink,
unsigned totalBW, FramedSource* mediaSource,
Groupsock* rtpGS, Groupsock* rtcpGS)
: fMaster(master), fAreCurrentlyPlaying(False), fReferenceCount(1),
fServerRTPPort(serverRTPPort), fServerRTCPPort(serverRTCPPort),
fRTPSink(rtpSink), fUDPSink(udpSink), fStreamDuration(master.duration()),
fTotalBW(totalBW), fRTCPInstance(NULL) /* created later */,
fMediaSource(mediaSource), fRTPgs(rtpGS), fRTCPgs(rtcpGS) {
}
上面是StreamState的构造函数,fMediaSource即使我们自定义的reateNewStreamSource。
再回到streamState->startPlaying。
这部分的代码前面的是rtcp的部分,这里我们不做讨论,后面再做讨论。最后是执行了这里,这里的fMediaSource为new streamState时传入的mediaSource即为createNewStreamSource的返回我们这里为H264VideoStreamFramer1,其实是继承自
H264VideoStreamFramer的一个自己写的类。
f (fRTPSink != NULL) {
fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
fAreCurrentlyPlaying = True;
}
而这里的继承关系如下:H264VideoRTPSink->VideoRTPSink->MultiFramedRTPSink->RTPSink->MediaSink
这里startPlaying是在MediaSink中实现的
Boolean MediaSink::startPlaying(MediaSource& source,
afterPlayingFunc* afterFunc,
void* afterClientData) {
// Make sure we're not already being played:
if (fSource != NULL) {
envir().setResultMsg("This sink is already being played");
return False;
}
// Make sure our source is compatible:
if (!sourceIsCompatibleWithUs(source)) {
envir().setResultMsg("MediaSink::startPlaying(): source is not compatible!");
return False;
}
fSource = (FramedSource*)&source;
fAfterFunc = afterFunc;
fAfterClientData = afterClientData;
return continuePlaying();
}
可以看到这里fSource即我们自己实现的那个source。fAfterFunC为afterPlayingStreamState。fAfterClientData为StreamState指针。
continuePlaying()在MediaSink中是一个纯虚函数,在H264VideoRTPSink中有实现
Boolean H264VideoRTPSink::continuePlaying() {
// First, check whether we have a 'fragmenter' class set up yet.
// If not, create it now:
if (fOurFragmenter == NULL) {
fOurFragmenter = new H264FUAFragmenter(envir(), fSource, OutPacketBuffer::maxSize,
ourMaxPacketSize() - 12/*RTP hdr size*/);
} else {
fOurFragmenter->reassignInputSource(fSource);
}
fSource = fOurFragmenter;
// Then call the parent class's implementation:
return MultiFramedRTPSink::continuePlaying();
}
这里要注意的是对fSource进行了复制。 fSource被指向了H264FUAFragmenter类,这个类主要实现了H264按照RFC3984进行RTP分包,不过这里的实现每个RTP中最多只包含一个NALU,没有实现组合封包的情形。
H264FUAFragmenter->FramedFilter->FramedSource。很明显,这是一个filter,包装了MPEGVideoStreamFramer类的对像。
以后的部分详见学习笔记2.