live555源码分析系列
live555源码分析(一)live555初体验
live555源码分析(二)基本组件上
live555源码分析(三)基本组件下
live555源码分析(四)RTSPServer分析
live555源码分析(五)DESCRIBE请求的处理
live555源码分析(六)SETUP和PLAY请求的处理
live555源码分析(七)播放过程
live555源码分析(八)多播
这是一个基于live555的RTSP服务器,此RTSP服务器实现了H.264文件的单播
#include
#include
#include
static char const* inputFileName = "test.h264";
int main(int argc, char* argv[])
{
/* 创建调度器 */
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
/* 创建rtsp服务器 */
RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554);
if (rtspServer == NULL)
{
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
return -1;
}
/* 创建会话 */
ServerMediaSession* sms = ServerMediaSession::createNew(*env, "live", "test", "test");
/* 添加子会话 */
sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName, True));
/* 向服务器添加会话 */
rtspServer->addServerMediaSession(sms);
char* url = rtspServer->rtspURL(sms);
*env << "Play this stream using the URL \"" << url << "\"\n";
delete[] url;
/* 循环处理事件 */
env->taskScheduler().doEventLoop();
return 0;
}
将此程序保存为h264_rtsp_server.cpp
执行下面的Makefile,可以得到rtspServer
,运行rtspServer
,会打印一个url,在vlc输入url,即可看到视频
下面将基于这个程序来讲解
我们从创建RTSPServer
开始分析
RTSPServer::createNew
会创建一个TCP套接字,绑定好指定的端口,作为RTSP服务器的监听套接字,然后会生成RTSPServer
RTSPServer*
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationSeconds) {
int ourSocket = setUpOurSocket(env, ourPort);
if (ourSocket == -1) return NULL;
return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds);
}
RTSPServer
继承于GenericMediaServer
,我们先看GenericMediaServer
的构造函数
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
unsigned reclamationSeconds) {
env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}
该构造函数会把服务器的套接字加入事件循中,接收客户端请求
看一看incomingConnectionHandler
是如何处理客户端连接的
void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
createNewClientConnection(clientSocket, clientAddr);
}
该函数会accept接收一个客户端,然后调用createNewClientConnection
创建一个客户端连接,createNewClientConnection
是一个虚函数,它在RTSPServer
中被重写
由上面可知,当从vlc输入url时,会向服务器发起连接,服务器会调用createNewClientConnection
创建一个客户端连接
createNewClientConnection
在RTSPServer
中被重写,其定义如下
GenericMediaServer::ClientConnection*
RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
return new RTSPClientConnection(*this, clientSocket, clientAddr);
}
可知它创建了一个RTSPClientConnection
,查看其定义
class RTSPClientConnection: public GenericMediaServer::ClientConnection {
protected:
virtual void handleRequestBytes(int newBytesRead);
virtual void handleCmd_OPTIONS();
virtual void handleCmd_DESCRIBE();
};
RTSPClientConnection
继承于ClientConnection
,它定义了许多命令请求处理
接下来看RTSPClientConnection
如何构造
首先看ClientConnection
的构造
GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
: fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
/* 添加到事件循环中 */
envir().taskScheduler()
.setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}
它将客户端连接套接字加入事件循环中,监听其可读或者异常
其处理函数为incomingRequestHandler
,查看其定义
void GenericMediaServer::ClientConnection::incomingRequestHandler() {
int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
handleRequestBytes(bytesRead);
}
incomingRequestHandler
先读取客户发送过来的信息,然后调用handleRequestBytes
处理,handleRequestBytes
是一个虚函数,在RTSPServer
中被重写
RTSPClientConnection
的构造函数没有太多内容这里就不分析了
接下来分析客户端的请求处理过程,首先先来看一个一般的交互过程
OPTIONS
C–>S
OPTIONS rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
CSeq: 2\r\n
\r\n
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 2\r\n
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n
\r\n
DESCRIBE
C–>S
DESCRIBE rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
CSeq: 3\r\n
Accept: application/sdp\r\n
\r\n
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 3\r\n
Content-length: 146\r\n
Content-type: application/sdp\r\n
\r\n
v=0\r\n
o=- 91565340853 1 in IP4 192.168.31.115\r\n
t=0 0\r\n
a=contol:*\r\n
m=video 0 RTP/AVP 96\r\n
a=rtpmap:96 H264/90000\r\n
a=framerate:25\r\n
a=control:track0\r\n
SETUP
C–>S
SETUP rtsp://192.168.31.115:8554/live/track0 RTSP/1.0\r\n
CSeq: 4\r\n
Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
\r\n
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 4\r\n
Transport: RTP/AVP;unicast;client_port=54492-54493;server_port=56400-56401\r\n
Session: 66334873\r\n
\r\n
PLAY
C–>S
PLAY rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
CSeq: 5\r\n
Session: 66334873\r\n
Range: npt=0.000-\r\n
\r\n
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 5\r\n
Range: npt=0.000-\r\n
Session: 66334873; timeout=60\r\n
\r\n
根据这个过程,来分析live555的处理过程
首先live555是解析请求,在RTSPClientConnection::handleRequestBytes
中定义,代码如下
void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF+2 - fRequestBuffer,
cmdName, sizeof cmdName,
urlPreSuffix, sizeof urlPreSuffix,
urlSuffix, sizeof urlSuffix,
cseq, sizeof cseq,
sessionIdStr, sizeof sessionIdStr,
contentLength);
if (strcmp(cmdName, "OPTIONS") == 0) {
handleCmd_OPTIONS();
} else if (strcmp(cmdName, "DESCRIBE") == 0) {
handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
} else if (strcmp(cmdName, "SETUP") == 0) {
clientSession
= (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();
clientSession->handleCmd_SETUP(...);
} else if (strcmp(cmdName, "PLAY") {
clientSession->handleCmd_withinSession(...);
}
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
}
首先会解析请求中的命令,序列号,url,是否已经有会话ID
然后根据不同的命令做相应的处理,注意的是在SETUP阶段,会创建一个客户端会话,然后利用它来处理命令,处理命令后会将结果放回给客户端
关于命令如何解析的,这里不做讲解,这就是一个字符串处理
先看一看handleCmd_OPTIONS
void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",
fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
}
handleCmd_OPTIONS
的处理非常简单,就是返回允许请求的方法
其他请求的处理过程较为复杂,将在后面的文章单独讲解,本文就到这里了