LIVE555源码研究之RTPServer(转)

来源:互联网 发布:淘宝联盟电脑版的好处 编辑:程序博客网 时间:2024/05/16 04:41

本文转自http://blog.csdn.net/ithzhang/article/details/38902825

转载请注明出处

LIVE555研究之五:RTPServer(二)

 

     接上文,main函数的几行代码创建了RTSPServer类的子类DynamicRTSPServer对象。RTPServer类是服务器类的基类,DynamicRTSPServer代表具体的服务器子类。我们今天介绍的服务器程序就是基于该类实现的。

     在创建DynamicRTSPServer时传入了值为554的端口号。这是因为RTSP默认端口号为554,http默认使用80端口是一样的。

      DynamicRTSPServer

        继承关系:

                                              

                                                                                                                        

    Medium是很多类的基类。内部定义了指向环境类的引用和一个char类型媒体名称。并定义了按照媒体名称,查找对应媒体的成员函数lookupByName。由于MediaSinkMediaSouceMediaSessionRTSPClientRTPServer均继承自该类,因此在Medium中定义了很多判断该类是哪个媒体类型的函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. virtual Boolean isSource() const;  
  2.   
  3. virtual Boolean isSink() const;  
  4.   
  5. virtual Boolean isRTCPInstance() const;  
  6.   
  7. virtual Boolean isRTSPClient() const;  
  8.   
  9. virtual Boolean isRTSPServer() const;  
  10.   
  11. virtual Boolean isMediaSession() const;  
  12.   
  13. virtual Boolean isServerMediaSession() const;  
  14.   
  15. virtual Boolean isDarwinInjector() const;  

 

     Medium中的实现均是返回false。在对应的子类中均会重定义对应函数,并返回true

TaskToken fNextTask用来保存延迟任务的ID。保存的任务ID用于被重新调度,或者在该媒体对象被销毁时从延迟队列中取消调度。

       RTPServer类是服务器类的基类,代表了服务器对象。在整个服务器运行期间,该对象一直存在。

定义了以下成员变量:

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HashTable* fServerMediaSessions;   
  2.   
  3. HashTable* fClientConnections;   
  4.   
  5. HashTable* fClientConnectionsForHTTPTunneling;     
  6.   
  7. HashTable* fClientSessions;   
  8.   
  9. HashTable* fPendingRegisterRequests;  


       从其成员变量可以看到RTPServer中维护了ServerMediaSession对象、ClientConnectionClientSession对象的HashTable

       ServerMediaSessionSession对应服务器端一个媒体文件,当客户端请求多个媒体文件时,RTPServer内会维护对应的多个ServerMediaSession对象。ServerMediaSession对象通过媒体文件名进行标识,如客户端请求a.264文件,则服务器就会在保存ServerMediaSessionHashTable中搜索对应文件名为a.264ServerMediaSession。如未找到,则说明还未为该媒体文件创建对应的ServerMediaSession。并创建一个新的ServerMediaSession与媒体文件名关联后添加到HashTable

       lookupServerMediaSession用于在map中搜索对应媒体文件名对应的ServerMediaSession

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">void addServerMediaSession(ServerMediaSession* serverMediaSession);  
  2.   
  3.   virtual ServerMediaSession* lookupServerMediaSession(char const* streamName);  
  4.   
  5.   void removeServerMediaSession(ServerMediaSession* serverMediaSession);  
  6.   
  7.   void removeServerMediaSession(char const* streamName);  
  8. </span>  


 

       以上三个成员函数分别用来添加、查询和删除对应ServerMediaSession项。

       removeServerMediaSession被调用后,在RTPServer中维护的fServerMediaSessionHashTable中,该ServerMediaSession会被删除。但是对应的ServerMediaSession对象并不一定会被释放。因为此时其他客户端还有可能在使用该媒体文件。只有当其他客户端都释放了对该媒体文件的引用后,该对象才会被释放。

 

       closeAllClientSessionsForServerMediaSession用于删除所有客户端对某一个媒体文件的引用。

       deleteServerMediaSession在从fServerMediaSession中删除对应项目时同时也会删除所有客户端的引用,此后该对象的引用计数为0可以被安全释放。

       removeServerMediaSession时会检查引用计数,只有当引用计数为0时该对象才会被释放。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (serverMediaSession->referenceCount() == 0) //只有当引入计数为0时才会被释放  
  2. {  
  3.     Medium::close(serverMediaSession);  
  4. }   
  5. else   
  6. {  
  7.   serverMediaSession->deleteWhenUnreferenced() = True;  
  8. }  

ClientConnection对象

 

       ClientConnection对象定义在RTPServer内部,为其内部类。主要用于和客户端的通信。当有新的客户端连接到服务器时,会新建ClientConnection对象。其内部定义了发送、接收socket以及发送和接收缓冲区,并对客户端的命令进行处理和回应。  

 void handleRequestBytes(int newBytesRead);

       用于处理客户端命令,在对RTSP命令进行分析后,提取出各种信息,然后进行分流处理。

       对于OPTIONSDESCRIBE、命令不支持、命令有误等其他错误命令的响应会直接在ClientConnection中进行处理。     而对于SETUPPLAYPAUSETERARDOWN等命令会传递到ClientSession中进行处理。

以下为分流代码:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. else if (strcmp(cmdName, "TEARDOWN") == 0  
  2.               || strcmp(cmdName, "PLAY") == 0  
  3.               || strcmp(cmdName, "PAUSE") == 0  
  4.               || strcmp(cmdName, "GET_PARAMETER") == 0  
  5.               || strcmp(cmdName, "SET_PARAMETER") == 0)  
  6.         {  
  7.              if (clientSession != NULL) {  
  8.   
  9.                clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);  
  10.   
  11.              } else  
  12.              {  
  13.                handleCmd_sessionNotFound();  
  14.              }  

       ClientSession对象会在客户端请求SETUP命令时在ClientConnection中创建,并分配一个ClientSessionID。对于SETUP之前和对一些出错处理命令会在ClientConnection中进行响应。

       ClientConnection维护了RTPServer的指针,可以在新建ClientSession对象后将其加入到RTPServer维护的fClientSessions中。

ClientSession中定义的成员:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. RTSPServer& fOurServer;  
  2.   
  3.  u_int32_t fOurSessionId;  
  4.   
  5. ServerMediaSession* fOurServerMediaSession;  
  6.   
  7.    

       ClientSession也维护了对RTPServer的引用。同时也保存了指向ServerMediaSession的指针。在对SETUP的响应中,有这样一句话:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1.   if (fOurServerMediaSession == NULL)   
  2. {  
  3.        // We're accessing the "ServerMediaSession" for the first time.  
  4.        fOurServerMediaSession = sms;  
  5.        fOurServerMediaSession->incrementReferenceCount();  
  6.  }   
  7. else if (sms != fOurServerMediaSession)   
  8. {  
  9.        // The client asked for a stream that's different from the one originally requested for this stream id.  Bad request:  
  10.        ourClientConnection->handleCmd_bad();  
  11.        break;  
  12. }  

     由此我们知道按照目前的实现,每个clientSession只能对应一个ServerMediaSession。即每个客户端只能请求一个媒体文件,不能同时请求两个媒体文件。如果需要同时支持多个媒体文件,就需要在ClientSession中维护一个ServerMediaSession集合。

 

ClientSessionnoteLiveness用于客户端保活。其内部实现如下:
void RTSPServer::RTSPClientSession::noteLiveness()

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. {  
  2.    if (fOurServer.fReclamationTestSeconds > 0)   
  3.     {  
  4.      envir().taskScheduler()  
  5.        .rescheduleDelayedTask(fLivenessCheckTask,  
  6.                           fOurServer.fReclamationTestSeconds*1000000,  
  7.                           (TaskFunc*)livenessTimeoutTask, this);  
  8.     }  
  9. }  

       上述代码向调度器请求重新调度一个延迟任务,在fReclamationTestSeconds后会调用livenessTimeoutTask。其实现很简单仅仅删除自身。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void RTSPServer::RTSPClientSession  
  2.    ::livenessTimeoutTask(RTSPClientSession* clientSession)   
  3. {  
  4.    delete clientSession;  
  5. }  

       当服务器收到对应客户端的RR包时会调用noteLiveness,重新计时。

       fReclamationTestSecondsRTPServer构造时传入,默认为65s。表示如65s内未收到客户端RTCP包即认为客户端已断开。

       如果在fReclamationTestSeconds的时间内再次调用noteLiveness,则该延迟任务会被设置成新的时间,原来的调度不再起作用。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct streamState   
  2. {  
  3.   
  4.    ServerMediaSubsession* subsession;  
  5.     void* streamToken;  
  6. } * fStreamStates;  

      fStreamStates指向一个动态分配的数组。fNumStreamStates表示该数组包含的元素个数。

     ServerMediaSession代表一个track(媒体流)。streamTokenvoid*类型的指针,但它指向StreamState类的对象。StreamState对象代表一个真正流动起来的数据流。这个流从XXXXFileSouce流向RTPSink

     可以看到一个ServerMediaSubSession对应一个StreamState。但ServerMediaSubSession对应一个静态的流,可以被多个客户端重用。如:多个客户端可能会请求同一个媒体文件中的trackStreamState代表一个动态的流。

 

ServerMediaSession

 

         ServerMediaSession代表服务器端一个媒体文件。

其成员如下:

  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ServerMediaSubsession* fSubsessionsHead;  
  2.   
  3.   ServerMediaSubsession* fSubsessionsTail;  
  4.   
  5.   unsigned fSubsessionCounter;   
  6.   char* fStreamName;  
  7.   char* fInfoSDPString;  
  8.   char* fDescriptionSDPString;  
  9.   char* fMiscSDPLines;  
  10.   struct timeval fCreationTime;  
  11.   unsigned fReferenceCount;  
  12.   Boolean fDeleteWhenUnreferenced;  

      可以看到其主要成员为fSubsessionsHeadfSubsessionsTail。代表该媒体文件中的多个媒体流trackfStreamName为该媒体文件名。fDescritionSDPString代表SDP字符串。用于在客户端发送DESCRIBE命令时返回给客户端。

       fReferenceCount为引用计数。当将fDeleteWhenUnreferenced设置为true,且引用计数为0时,ServerMediaSession会被释放。该值在构造函数中默认赋值为false。即所有ServerMediaSession即使不存在被客户端引用时,也不会被释放。对于长时间运行的服务器程序将会出现内存消耗耗尽的情况。解决方案就是在构造时将fDeleteWhenUnreferenced的默认值赋值为true

       其他成员函数是用来操纵MediaSubSession

 

MediaSubSession

 

       如果一个媒体文件中既包含音频流又包含视频流,我们称这个媒体文件中包含两个track。每个track对应一个ServerMediaSubsession

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ServerMediaSession* fParentSession;  
  2.  netAddressBits fServerAddressForSDP;  
  3.  portNumBits fPortNumForSDP;  
  4. private:  
  5.   ServerMediaSubsession* fNext;  
  6.   unsigned fTrackNumber; // within an enclosing ServerMediaSession  
  7.   char const* fTrackId;   

    fParentSession指向该MediaSubSession所属的ServerMediaSession。

fNext指向下一个同属于一个ServerMediaSession的ServerMediaSubsession。如果只包含一个媒体流,则fNext指针为NULL。

    fTrackNumber为track号。在客户端发送DESCRIBE命令时,服务器端会为每个媒体流分配一个TrackID。

    fTrackId 为字符串指针,该字符串由track和fTrackNumber拼接而成。如track1、track2。

    ServerMediaSubsession中仅仅定义了空的接口,具体实现均放在其子类。

 

OnDemandServerMediaSubsession

 

    HashTablefDestinationsHashTable; 存储sessionID和Destinations的映射。

    Destinations为目的地址。每个ClientSession在HashTable中都有与自己对应的项。

    Destinations可以维护一对RTP和RTCP的端口和地址信息。

 

 

StreamState

    前面说过StreamState代表一个真正流动的流,现在让我们看下StreamState的究竟实现了什么功能。

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. OnDemandServerMediaSubsession& fMaster;  
  2. Boolean fAreCurrentlyPlaying;  
  3. unsigned fReferenceCount;  
  4. Port fServerRTPPort, fServerRTCPPort;  
  5. RTPSink* fRTPSink;  
  6. BasicUDPSink* fUDPSink;  
  7. float fStreamDuration;  
  8. unsigned fTotalBW;  
  9. RTCPInstance* fRTCPInstance;  
  10. FramedSource* fMediaSource;  
  11. float fStartNPT;  
  12. Groupsock* fRTPgs;  
  13. Groupsock* fRTCPgs;  

      fMaster为对OnDemandServerMediaSubsession或其子类的引用。

      fReferenceCount为引用计数。

     fServerRTPPort为RTP端口

     fServerRTCPPort为RTCP端口

     fRTPSink抽象Sink类。

     fMediaSource为Souce基类。

    可以看到StreamState既维护了Sink,又维护了Souce。其实在StreamState

     GroupSock主要用于处理组播,但也可以处理单播。

     GroupsockfRTPgs和   GroupsockfRTCPgs为RTP和RTCP的地址,用于向RTP和RTCP端口发送数据。

 

RTCPInstance

 

    RTCPInsance是对RTCP通信的封装。RTCP的功能是统计包的收发,为流量统计提供依据。由于其封装的比较完整,因此RTCPInstance与其他类间的关系不是那么紧密。

RTCPInstanceRTPInterface提供支持,所以它既支持RTP over UDP,又支持RTP over TCP

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void setByeHandler(TaskFunc* handlerTask, void* clientData,  
  2.           Boolean handleActiveParticipantsOnly = True);  
  3. void setSRHandler(TaskFunc* handlerTask, void* clientData);  
  4. void setRRHandler(TaskFunc* handlerTask, void* clientData);  
  5. void setSpecificRRHandler(netAddressBits fromAddress, Port fromPort,  
  6.              TaskFunc* handlerTask, void* clientData);  

     以上四个成员函数均是用来设置回调函数。在满足一定条件时该回调被调用。

      setByeHandler用于设置在客户端结束与服务器的RTCP通信时的回调。

      setSRHandler用于设置在收到客户端的SR包时的回调。在收到SR包时该回调被调用。

      setRRHandler用于设置在收到客户端的RR包时的回调。在收到RR包时该回调被调用。

      setSpecificRRHandler该成员函数与SetRRHandler的区别在于,它可以设置针对某一客户端的RR包的回调。RTPClientSession就是调用此回调,为指定客户端注册noteClientLiveness。用于检测客户端保活。如在一定时间内收不到RR包时即认为客户端已经断开了连接。此时将会删除对应的clientSession对象。这里提供了一种监视客户端运行状态的好方法。

       每个MediaSubSession对应一个StreamState对象。它们被保存在在ServerMediaClient中被StreamState数组中。在收到客户端的PLAYM命令后,ServerMediaClient的响应函数内会为每个StreamState调用play

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // Now, start streaming:  
  2.   
  3.   for (i = 0; i < fNumStreamStates; ++i)  
  4.   
  5.  {  
  6.     if (subsession == NULL /* means: aggregated operation */  
  7.        || subsession == fStreamStates[i].subsession)   
  8.      {  
  9.       unsigned short rtpSeqNum = 0;  
  10.       unsigned rtpTimestamp = 0;  
  11.       if (fStreamStates[i].subsession == NULL) continue;  
  12.       fStreamStates[i].subsession->startStream(fOurSessionId,  
  13.                          fStreamStates[i].streamToken,  
  14.                          (TaskFunc*)noteClientLiveness, this,  
  15.                          rtpSeqNum, rtpTimestamp,                           
  16.   
  17.       //略去部分代码  
  18.   
  19.      }  
  20.  }  

    RTSPClientSession的handleCmd_SETUP中会根据ServerMediaSubSession的个数创建streamStates数组。 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (fStreamStates == NULL)   
  2. {  
  3.       // 计算ServerMediaSubSession个数  
  4.        ServerMediaSubsessionIterator iter(*fOurServerMediaSession);  
  5.       for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}        
  6.       fStreamStates = new struct streamState[fNumStreamStates];        
  7.       iter.reset();  
  8.       ServerMediaSubsession* subsession;   
  9.       //将ServerMediaSubSession与streamStates通过fStreamStates数组进行关联  
  10.        for (unsigned i = 0; i < fNumStreamStates; ++i)  
  11.      {  
  12.         subsession = iter.next();  
  13.         fStreamStates[i].subsession = subsession;  
  14.         fStreamStates[i].streamToken = NULL;        
  15.      }  
  16. }  

 

    上述代码中与ServerMediaSubSession 关联的streamToken被赋值为NULL。并会在后面的getStreamParameters中被赋值,最后一个参数为指针的引用,用于在getStreamParameters中修改该指针。

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,  
  2.                   clientRTPPort, clientRTCPPort,  
  3.                   tcpSocketNum, rtpChannelId, rtcpChannelId,  
  4.                   destinationAddress, destinationTTL, fIsMulticast,  
  5.                   serverRTPPort, serverRTCPPort,  
  6.                   fStreamStates[streamNum].streamToken);   

    getStreamParameters在OnDemandServerMediaSubsession重新定义,可以看到创建StreamStates对象的代码:

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // Set up the state of the stream.  The stream will get started later:  
  2.     streamToken = fLastStreamToken  
  3.       = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,  
  4.            streamBitrate, mediaSource,  
  5.            rtpGroupsock, rtcpGroupsock);  


    可以看到StreamStates关联了Sink和Souce。之所以要在OnDemandServerMediaSubsession重新定义的getStreamParameters中分配StreamStates对象,是因为它定义了新的创建具体MediaSouce和MediaSink的虚函数。

 

  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. virtual FramedSource* createNewStreamSource(unsigned clientSessionId,  
  2.                        unsigned& estBitrate) = 0;  
  3.      // "estBitrate" is the stream's estimated bitrate, in kbps  
  4.    virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,  
  5.                   unsigned char rtpPayloadTypeIfDynamic,  
  6.                   FramedSource* inputSource) = 0;  
  7. <span style="font-size:18px;"> </span>  

     StreamStates关联的MediaSouce和MediaSink均是具体的子类。若媒体文件为H264码流,则对应的Souce为H264VideoStreamFramer,对应的Sink为H264VideoRTPSink。

 

    在RTSPClientSession的handleCmd_PLAY中为每个MediaSubSession循环调用startStream,并传入与MediaSubSession关联的StramStates对象指针:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for (i = 0; i < fNumStreamStates; ++i)   
  2. {  
  3.     if (subsession == NULL /* means: aggregated operation */  
  4.       || subsession == fStreamStates[i].subsession)   
  5.    {  
  6.       unsigned short rtpSeqNum = 0;  
  7.       unsigned rtpTimestamp = 0;  
  8.       if (fStreamStates[i].subsession == NULL) continue;  
  9.       fStreamStates[i].subsession->startStream(fOurSessionId,  
  10.                          fStreamStates[i].streamToken,  
  11.                         (TaskFunc*)noteClientLiveness, this,  
  12.                          rtpSeqNum, rtpTimestamp,  
  13.                          RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection);  
  14.      }  
  15.   
  16. }  

 

startStram内部调用了StreamStates的startPlaying:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,  
  2.                      void* streamToken,  
  3.                      TaskFunc* rtcpRRHandler,  
  4.                      void* rtcpRRHandlerClientData,  
  5.                      unsigned short& rtpSeqNum,  
  6.                      unsigned& rtpTimestamp,  
  7.                      ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,  
  8.                       void* serverRequestAlternativeByteHandlerClientData) {  
  9.   StreamState* streamState = (StreamState*)streamToken;  
  10.   Destinations* destinations  
  11.     = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId));  
  12.   if (streamState != NULL)  
  13.   {  
  14.     streamState->startPlaying(destinations,  
  15.                  rtcpRRHandler, rtcpRRHandlerClientData,  
  16.                     serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);  
  17.     RTPSink* rtpSink = streamState->rtpSink(); // alias  
  18.     if (rtpSink != NULL)  
  19.     {  
  20.       rtpSeqNum = rtpSink->currentSeqNo();  
  21.   
  22.       rtpTimestamp = rtpSink->presetNextTimestamp();  
  23.     }  
  24.   }  
  25. }  

    streamStates的startPlaying内部则创建了RTCPInstance对象并调用了RTPSink的startPlaying函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);  

        第一个参数即为具体的MediaSouce子类。StartPlaying之后,Sink会调用Souce的getNextFrame获得一帧数据。

       上面介绍的各种类是支撑LIVE555的各种基础设施,对于各种码流都是通用的。


本文转自http://blog.csdn.net/ithzhang/article/details/38902825

0 0