LIVE555学习3:live555MediaServer讲解——Live555从启动到响应Client过程分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011003120/article/details/83415769

参考博文:
Live555学习之(三)------建立RTSP连接的过程(RTSP服务器端)
https://www.cnblogs.com/jqctop1/p/4386533.html

live555学习笔记5-RTSP服务运作
https://blog.csdn.net/niu_gao/article/details/6911130

1 概述

在前面文章《LIVE555学习1:Linux下live555的编译及测试》中使用了live555MediaServer测试程序,可以将本地的视频文件通过流发送给RTSP Client端。于是便好奇,对live555MediaServer.cpp中live555从启动到响应RTSP Client之间的流程进行了大致的分析,使之有个大致的印象,这样才能更好进行随后细致的学习。

下面分析的是live555MediaServer.cpp中的程序,只是对大致流程进行分析,不涉及到具体代码的讲解,刚刚开始live555的学习,对于各种虚函数,各种继承感到头疼。。。。

2 代码分析

live555MediaServer.cpp中的主函数如下:

int main(int argc, char** argv) {
  // Begin by setting up our usage environment:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

  UserAuthenticationDatabase* authDB = NULL;
#ifdef ACCESS_CONTROL
  // To implement client access control to the RTSP server, do the following:
  authDB = new UserAuthenticationDatabase;
  authDB->addUserRecord("username1", "password1"); // replace these with real strings
  // Repeat the above with each <username>, <password> that you wish to allow
  // access to the server.
#endif

  // Create the RTSP server.  Try first with the default port number (554),
  // and then with the alternative port number (8554):
  RTSPServer* rtspServer;
  portNumBits rtspServerPortNum = 554;
  rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB);
  if (rtspServer == NULL) {
    rtspServerPortNum = 8554;
    rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB);
  }
  if (rtspServer == NULL) {
    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
    exit(1);
  }

  *env << "LIVE555 Media Server\n";
  *env << "\tversion " << MEDIA_SERVER_VERSION_STRING
       << " (LIVE555 Streaming Media library version "
       << LIVEMEDIA_LIBRARY_VERSION_STRING << ").\n";

  char* urlPrefix = rtspServer->rtspURLPrefix();
  *env << "Play streams from this server using the URL\n\t"
       << urlPrefix << "<filename>\nwhere <filename> is a file present in the current directory.\n";
  *env << "Each file's type is inferred from its name suffix:\n";
  *env << "\t\".264\" => a H.264 Video Elementary Stream file\n";
  *env << "\t\".265\" => a H.265 Video Elementary Stream file\n";
  *env << "\t\".aac\" => an AAC Audio (ADTS format) file\n";
  *env << "\t\".ac3\" => an AC-3 Audio file\n";
  *env << "\t\".amr\" => an AMR Audio file\n";
  *env << "\t\".dv\" => a DV Video file\n";
  *env << "\t\".m4e\" => a MPEG-4 Video Elementary Stream file\n";
  *env << "\t\".mkv\" => a Matroska audio+video+(optional)subtitles file\n";
  *env << "\t\".mp3\" => a MPEG-1 or 2 Audio file\n";
  *env << "\t\".mpg\" => a MPEG-1 or 2 Program Stream (audio+video) file\n";
  *env << "\t\".ogg\" or \".ogv\" or \".opus\" => an Ogg audio and/or video file\n";
  *env << "\t\".ts\" => a MPEG Transport Stream file\n";
  *env << "\t\t(a \".tsx\" index file - if present - provides server 'trick play' support)\n";
  *env << "\t\".vob\" => a VOB (MPEG-2 video with AC-3 audio) file\n";
  *env << "\t\".wav\" => a WAV Audio file\n";
  *env << "\t\".webm\" => a WebM audio(Vorbis)+video(VP8) file\n";
  *env << "See http://www.live555.com/mediaServer/ for additional documentation.\n";

  // Also, attempt to create a HTTP server for RTSP-over-HTTP tunneling.
  // Try first with the default HTTP port (80), and then with the alternative HTTP
  // port numbers (8000 and 8080).

  if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) {
    *env << "(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)\n";
  } else {
    *env << "(RTSP-over-HTTP tunneling is not available.)\n";
  }

  env->taskScheduler().doEventLoop(); // does not return

  return 0; // only to prevent compiler warning
}

除去一些无用的信息,这个函数里面主要做了两件事,第一件是创建一个RTSP Serve服务,第二件是进入doEventLoop进行循环监听。

下面了解一下这两件事的逻辑。

2.1 doEventLoop

在创建完全部的服务之后,函数会进入到doEventLoop函数中去,这个函数的定义如下:

  virtual void doEventLoop(char volatile* watchVariable = NULL) = 0;

可以看到,这函数其实是有参数的,如果我们指定了参数,就可以控制这个函数返回或者做一些其他的事情,如果我们不不指定参数,则这个函数一直运行,不再返回,这个函数是一个事件循环函数,用于调度事件。接下来,我们进入到这个函数的内部来看一下:

扫描二维码关注公众号,回复: 4153349 查看本文章
doEventLoop(BasicTaskScheduler0.cpp)
void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
  // Repeatedly loop, handling readble sockets and timed events:
  while (1) {
    if (watchVariable != NULL && *watchVariable != 0) break;
    SingleStep();
  }
}

在这个函数中,是一个循环函数,每一次循环首先对传递进来的变量进行了判断,用于确定是否要退出,然后就会进入SingleStep函数,接下来进入SingleStep函数一探真容:
SingleStep(BasicTaskScheduler.cpp)

这个函数代码有点多,就不再贴出全部的代码了,这个函数的主要作用是处理套接字和定时事件,即:

  • ①select各个socket
  • ②找出第一个应执行的socket任务(handler)并执行之
  • ③找到第一个应响应的事件,并执行之
  • ④找到第一个应执行的延迟任务并执行之

下面是函数的调用过程,记录一下:

main (live555MediaServer.cpp) --->  doEventLoop(BaskTaskSchduler0.cpp) ---> SingleStep(BaskTaskSchduler.cpp) 

2.2 计划任务

在上面的SingleStep函数中,我们看到函数会单线程执行这些任务,但是这些任务是如何来的?是如何添加的?

在上面,我们看到SingleStep中,函数循环执行三种任务做:Socket handler、event handler、定时任务(delay task),这三种任务又是什么?

在上面主函数中,我们看到是这样调用doEventLoop的:
env->taskScheduler().doEventLoop(); // does not return
上面的taskScheduler又是什么鬼?

关于taskScheduler这一块,这里就不再献丑了,分享一篇大神博文,写的很详细:
live555学习笔记4-计划任务(TaskScheduler)深入探讨
https://blog.csdn.net/niu_gao/article/details/6910549

在上面这篇博文中,我们可以看到三种任务的添加方式,如下:

delay task为:
void setBackgroundHandling(int socketNum, int conditionSet ,BackgroundHandlerProc* handlerProc, void* clientData)
event handler为:
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc)
delay task为:
TaskToken scheduleDelayedTask(int64_t microseconds, TaskFunc* proc,void* clientData)

关于任务是如何添加的,可以参考下面的博文:
live555 中的socket的任务调度分析
https://www.cnblogs.com/superPerfect/p/3611625.html

本文只是想介绍一些大致流程,所以对代码不会细致讲解,上面两篇博文已经很详细地说明了计划任务相关的信息。

而在接下来的分析中,我们也会看到计划任务相关的,例如:

env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);

envir().taskScheduler().setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);

在主函数中,我们介绍了doEventLoop函数,接下来,则要介绍一下另外一个重要的部分—RTSP服务。

2.3 RTSP服务

2.3.1 调用关系

首先记录一下部分函数之间的调用关系,接下来的分析就是按照这个调用关系依次进行分析的。

下面加粗的部分为重点部分。

main (live555MediaServer.cpp) --->  createNew(DynamicRTSPServer.cpp) ---> DynamicRTSPServer (DynamicRTSPServer.cpp) --->RTSPServerSupportingHTTPStreaming(RTSPServerSupportingHTTPStreaming.cpp) ---> RTSPServer(RTSPServer.cpp) --->GenericMediaServer(GenericMediaServer.cpp) ---> incomingConnectionHandler(GenericMediaServer.cpp) ---> incomingConnectionHandlerOnSocket(GenericMediaServer.cpp)   ---> createNewClientConnection(RTSPServer.cpp)--->RTSPClientConnection(RTSPServer.cpp) ---> ClientConnection(GenericMediaServer.cpp)--->incomingRequestHandler(GenericMediaServer.cpp) ---> handleRequestBytes(RTSPServer.cpp) 

在handleRequestBytes中,有对如下方法的对应函数解析
handleCmd_OPTIONS/handleCmd_DESCRIBE/handleCmd_SETUP/handleCmd_PLAY/handleCmd_PAUSE/handleCmd_TEARDOWN

2.3.2 Server监听端口的建立

如下,在createNew函数中,建立了一个554端口的socket,

DynamicRTSPServer*
DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
			     UserAuthenticationDatabase* authDatabase,
			     unsigned reclamationTestSeconds) {
  int ourSocket = setUpOurSocket(env, ourPort);
  if (ourSocket == -1) return NULL;

  return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

2.3.3 计划任务的添加

在创建完554端口的socket后,需要将这个socket加入计划任务,然后轮训进行监听。

函数如下:

GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
		     unsigned reclamationSeconds)
  : Medium(env),
    fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
    fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
  ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us
  
  // Arrange to handle connections from others:
  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}

注意到函数的最后部分是将这个socket加入了计划任务,当监听到这个socket的时候就会调用incomingConnectionHandler函数。

2.3.4 incomingConnectionHandler

当select到server socket的时候,就会调用incomingConnectionHandler这个函数,然后处理一些信息。如下:

void GenericMediaServer::incomingConnectionHandler(void* instance, int /*mask*/) {
  GenericMediaServer* server = (GenericMediaServer*)instance;
  server->incomingConnectionHandler();
}
void GenericMediaServer::incomingConnectionHandler() {
  incomingConnectionHandlerOnSocket(fServerSocket);
}

可以看到,最终是调用了incomingConnectionHandlerOnSocket函数,我们接下来分析这个函数。

2.3.5 Client 监听端口的建立

incomingConnectionHandlerOnSocket函数原型如下:

void GenericMediaServer::incomingConnectionHandlerOnSocket(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;
  }
  ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us
  makeSocketNonBlocking(clientSocket);
  increaseSendBufferTo(envir(), clientSocket, 50*1024);
  
#ifdef DEBUG
  envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif
  
  // Create a new object for handling this connection:
  (void)createNewClientConnection(clientSocket, clientAddr);
}

通过以上函数发现,当收到代表客户端的连接时,获取了一个Client Socket,然后会使用这个新的Socket继续与RTSP Client进行通信。

接下来继续看一下createNewClientConnection:

GenericMediaServer::ClientConnection*
RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
  return new RTSPClientConnection(*this, clientSocket, clientAddr);
}

在这个createNewClientConnection中,我们看到创建了一个RTSPClientConnection对象,所以每来一个RTSP Client的连接,这时候就会创建一个RTSP Client对象,用于和RTSP Client进行RTSP会话交互。

继续往下,看看这个RTSPClientConnection对象做了什么工作?

2.3.6 计划任务的添加

经过一系列的调用,我们可以看到如下函数:

GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
  // Add ourself to our 'client connections' table:
  fOurServer.fClientConnections->Add((char const*)this, this);
  
  // Arrange to handle incoming requests:
  resetRequestBuffer();
  envir().taskScheduler()
    .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}

在函数的最后,我们看到又把自己的socket handler加入到了计划任务,并且当监听到数据的时候,就会调用incomingRequestHandler函数。

2.3.7 incomingRequestHandler

在incomingRequestHandler函数中,可以看到,最终调用的是handleRequestBytes。

2.3.8 handleRequestBytes

这个函数便是最终函数,在这个函数里面,会分析接收到的RTSP Client端的会话(OPTIONS/DESCRIBE/SETUP/TEARDOWN/PLAY/PAUSE/GET_PARAMETER/SET_PARAMETER)
然后调用相应的函数进行回应,如:handleCmd_DESCRIBE、handleCmd_OPTIONS、handleCmd_GET_PARAMETER、handleCmd_SET_PARAMETER、handleCmd_SETUP、handleCmd_TEARDOWN、handleCmd_PLAY、handleCmd_PAUSE等。

这里只是介绍一下整个流程,所以RTSP会话的交互过程这里就不再详细介绍了。

3 小结

通过以上的分析,可以看到当RTSP Server Socket建立之后,就将其加入计划任务进行监听,当有RTSP Client连接时候,创建一个属于RTSP Client的对象,并将新的Socket加入计划任务,随后使用这个Socket与Client进行RTSP会话。

猜你喜欢

转载自blog.csdn.net/u011003120/article/details/83415769