zoukankan      html  css  js  c++  java
  • LIVE555研究之五:RTPServer(二)

                                         LIVE555研究之五:RTPServer(二)

     

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

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

          DynamicRTSPServer

            继承关系:

                                                 

                                                                                                                            

        Medium是非常多类的基类。内部定义了指向环境类的引用和一个char类型媒体名称。并定义了依照媒体名称,查找相应媒体的成员函数lookupByName

    由于MediaSinkMediaSouceMediaSessionRTSPClientRTPServer均继承自该类。因此在Medium中定义了非常多推断该类是哪个媒体类型的函数:

      virtual Boolean isSource() const;
    
      virtual Boolean isSink() const;
    
      virtual Boolean isRTCPInstance() const;
    
      virtual Boolean isRTSPClient() const;
    
      virtual Boolean isRTSPServer() const;
    
      virtual Boolean isMediaSession() const;
    
      virtual Boolean isServerMediaSession() const;
    
      virtual Boolean isDarwinInjector() const;
    

     

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

    TaskToken fNextTask用来保存延迟任务的ID

    保存的任务ID用于被又一次调度。或者在该媒体对象被销毁时从延迟队列中取消调度。

           RTPServer类是server类的基类。代表了server对象。在整个server执行期间,该对象一直存在。

    定义了下面成员变量:

     

      HashTable* fServerMediaSessions; 
    
      HashTable* fClientConnections; 
    
      HashTable* fClientConnectionsForHTTPTunneling;   
    
      HashTable* fClientSessions; 
    
      HashTable* fPendingRegisterRequests;
    
     
    


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

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

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

    void addServerMediaSession(ServerMediaSession* serverMediaSession);
    
      virtual ServerMediaSession* lookupServerMediaSession(char const* streamName);
    
      void removeServerMediaSession(ServerMediaSession* serverMediaSession);
    
      void removeServerMediaSession(char const* streamName);
    


     

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

           removeServerMediaSession被调用后。在RTPServer中维护的fServerMediaSessionHashTable中。该ServerMediaSession会被删除。可是相应的ServerMediaSession对象并不一定会被释放。

    由于此时其它client还有可能在使用该媒体文件。仅仅有当其它client都释放了对该媒体文件的引用后,该对象才会被释放。

     

           closeAllClientSessionsForServerMediaSession用于删除全部client对某一个媒体文件的引用。

           deleteServerMediaSession在从fServerMediaSession中删除相应项目时同一时候也会删除全部client的引用,此后该对象的引用计数为0能够被安全释放。

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

    if (serverMediaSession->referenceCount() == 0) //仅仅有当引入计数为0时才会被释放
    {
        Medium::close(serverMediaSession);
    } 
    else 
    {
      serverMediaSession->deleteWhenUnreferenced() = True;
    }
    

    ClientConnection对象

     

           ClientConnection对象定义在RTPServer内部,为其内部类。

    主要用于和client的通信。当有新的client连接到server时,会新建ClientConnection对象。其内部定义了发送、接收socket以及发送和接收缓冲区,并对client的命令进行处理和回应。  

     void handleRequestBytes(int newBytesRead);

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

           对于OPTIONSDESCRIBE、命令不支持、命令有误等其它错误命令的响应会直接在ClientConnection中进行处理。

         而对于SETUPPLAYPAUSETERARDOWN等命令会传递到ClientSession中进行处理。

    下面为分流代码:

     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
                  {
                    handleCmd_sessionNotFound();
                  }
    
    

           ClientSession对象会在client请求SETUP命令时在ClientConnection中创建。并分配一个ClientSessionID

    对于SETUP之前和对一些出错处理命令会在ClientConnection中进行响应。

           ClientConnection维护了RTPServer的指针,能够在新建ClientSession对象后将其增加到RTPServer维护的fClientSessions中。

    ClientSession中定义的成员:

    RTSPServer& fOurServer;
    
     u_int32_t fOurSessionId;
    
    ServerMediaSession* fOurServerMediaSession;
    
     

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

      if (fOurServerMediaSession == NULL) 
    {
           // We're accessing the "ServerMediaSession" for the first time.
           fOurServerMediaSession = sms;
           fOurServerMediaSession->incrementReferenceCount();
     } 
    else if (sms != fOurServerMediaSession) 
    {
           // The client asked for a stream that's different from the one originally requested for this stream id.  Bad request:
           ourClientConnection->handleCmd_bad();
           break;
    }
    

         由此我们知道依照眼下的实现。每一个clientSession仅仅能相应一个ServerMediaSession。即每一个client仅仅能请求一个媒体文件,不能同一时候请求两个媒体文件。假设须要同一时候支持多个媒体文件,就须要在ClientSession中维护一个ServerMediaSession集合。

     

    ClientSessionnoteLiveness用于client保活。

    其内部实现例如以下:
    void RTSPServer::RTSPClientSession::noteLiveness()

    {
       if (fOurServer.fReclamationTestSeconds > 0) 
        {
         envir().taskScheduler()
           .rescheduleDelayedTask(fLivenessCheckTask,
                              fOurServer.fReclamationTestSeconds*1000000,
                              (TaskFunc*)livenessTimeoutTask, this);
        }
    }
    

           上述代码向调度器请求又一次调度一个延迟任务,在fReclamationTestSeconds后会调用livenessTimeoutTask

    事实上现非常easy只删除自身。

    void RTSPServer::RTSPClientSession
       ::livenessTimeoutTask(RTSPClientSession* clientSession) 
    {
       delete clientSession;
    }
    

           当server收到相应client的RR包时会调用noteLiveness,又一次计时。

           fReclamationTestSecondsRTPServer构造时传入,默觉得65s。表示如65s内未收到clientRTCP包即觉得client已断开。

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

      struct streamState 
      {
    
         ServerMediaSubsession* subsession;
          void* streamToken;
      } * fStreamStates;
    

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

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

         能够看到一个ServerMediaSubSession相应一个StreamState

    ServerMediaSubSession相应一个静态的流。能够被多个client重用。

    如:多个client可能会请求同一个媒体文件里的trackStreamState代表一个动态的流。

     

    ServerMediaSession

     

             ServerMediaSession代表server端一个媒体文件。

    其成员例如以下:

      

    ServerMediaSubsession* fSubsessionsHead;
    
      ServerMediaSubsession* fSubsessionsTail;
    
      unsigned fSubsessionCounter; 
      char* fStreamName;
      char* fInfoSDPString;
      char* fDescriptionSDPString;
      char* fMiscSDPLines;
      struct timeval fCreationTime;
      unsigned fReferenceCount;
      Boolean fDeleteWhenUnreferenced;
    

          能够看到其主要成员为fSubsessionsHeadfSubsessionsTail。代表该媒体文件里的多个媒体流track

    fStreamName为该媒体文件名称。

    fDescritionSDPString代表SDP字符串。用于在client发送DESCRIBE命令时返回给client。

           fReferenceCount为引用计数。

    当将fDeleteWhenUnreferenced设置为true。且引用计数为0时。ServerMediaSession会被释放。

    该值在构造函数中默认赋值为false。即全部ServerMediaSession即使不存在被client引用时,也不会被释放。对于长时间执行的server程序将会出现内存消耗耗尽的情况。

    解决方式就是在构造时将fDeleteWhenUnreferenced的默认值赋值为true

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

     

    MediaSubSession

     

           假设一个媒体文件里既包括音频流又包括视频流。我们称这个媒体文件里包括两个track。每一个track相应一个ServerMediaSubsession

    ServerMediaSession* fParentSession;
     netAddressBits fServerAddressForSDP;
     portNumBits fPortNumForSDP;
    private:
      ServerMediaSubsession* fNext;
      unsigned fTrackNumber; // within an enclosing ServerMediaSession
      char const* fTrackId; 
    
    

        fParentSession指向该MediaSubSession所属的ServerMediaSession。

    fNext指向下一个同属于一个ServerMediaSession的ServerMediaSubsession。假设只包括一个媒体流。则fNext指针为NULL。

        fTrackNumber为track号。

    在client发送DESCRIBE命令时,server端会为每一个媒体流分配一个TrackID。

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

        ServerMediaSubsession中只定义了空的接口,详细实现均放在其子类。

     

    OnDemandServerMediaSubsession

     

        HashTable* fDestinationsHashTable; 存储sessionID和Destinations的映射。

        Destinations为目的地址。

    每一个ClientSession在HashTable中都有与自己相应的项。

        Destinations能够维护一对RTP和RTCP的port和地址信息。

     

     

    StreamState

        前面说过StreamState代表一个真正流动的流。如今让我们看下StreamState的到底实现了什么功能。

     

      OnDemandServerMediaSubsession& fMaster;
      Boolean fAreCurrentlyPlaying;
      unsigned fReferenceCount;
      Port fServerRTPPort, fServerRTCPPort;
      RTPSink* fRTPSink;
      BasicUDPSink* fUDPSink;
      float fStreamDuration;
      unsigned fTotalBW;
      RTCPInstance* fRTCPInstance;
      FramedSource* fMediaSource;
      float fStartNPT;
      Groupsock* fRTPgs;
      Groupsock* fRTCPgs;
    

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

          fReferenceCount为引用计数。

         fServerRTPPort为RTPport

         fServerRTCPPort为RTCPport

         fRTPSink抽象Sink类。

         fMediaSource为Souce基类。

        能够看到StreamState既维护了Sink。又维护了Souce。事实上在StreamState

         GroupSock主要用于处理组播。但也能够处理单播。

         Groupsock* fRTPgs和   Groupsock* fRTCPgs为RTP和RTCP的地址。用于向RTP和RTCPport发送数据。

     

    RTCPInstance

     

        RTCPInsance是对RTCP通信的封装。

    RTCP的功能是统计包的收发,为流量统计提供根据。因为其封装的比較完整。因此RTCPInstance与其它类间的关系不是那么紧密。

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

      void setByeHandler(TaskFunc* handlerTask, void* clientData,
                Boolean handleActiveParticipantsOnly = True);
      void setSRHandler(TaskFunc* handlerTask, void* clientData);
      void setRRHandler(TaskFunc* handlerTask, void* clientData);
      void setSpecificRRHandler(netAddressBits fromAddress, Port fromPort,
                   TaskFunc* handlerTask, void* clientData);
    

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

          setByeHandler用于设置在client结束与server的RTCP通信时的回调。

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

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

          setSpecificRRHandler该成员函数与SetRRHandler的差别在于。它能够设置针对某一client的RR包的回调。

    RTPClientSession就是调用此回调。为指定client注冊noteClientLiveness。用于检測client保活。如在一定时间内收不到RR包时即觉得client已经断开了连接。

    此时将会删除相应的clientSession对象。这里提供了一种监视client执行状态的好方法。

           每一个MediaSubSession相应一个StreamState对象。它们被保存在在ServerMediaClient中被StreamState数组中。

    在收到client的PLAYM命令后。ServerMediaClient的响应函数内会为每一个StreamState调用play

    // Now, start streaming:
    
      for (i = 0; i < fNumStreamStates; ++i)
    
     {
        if (subsession == NULL /* means: aggregated operation */
           || subsession == fStreamStates[i].subsession) 
         {
          unsigned short rtpSeqNum = 0;
          unsigned rtpTimestamp = 0;
          if (fStreamStates[i].subsession == NULL) continue;
          fStreamStates[i].subsession->startStream(fOurSessionId,
                             fStreamStates[i].streamToken,
                             (TaskFunc*)noteClientLiveness, this,
                             rtpSeqNum, rtpTimestamp,                         
    
          //略去部分代码
    
         }
     }
    
    

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

    if (fStreamStates == NULL) 
    {
          // 计算ServerMediaSubSession个数
           ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
          for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}      
          fStreamStates = new struct streamState[fNumStreamStates];      
          iter.reset();
          ServerMediaSubsession* subsession; 
          //将ServerMediaSubSession与streamStates通过fStreamStates数组进行关联
           for (unsigned i = 0; i < fNumStreamStates; ++i)
         {
            subsession = iter.next();
            fStreamStates[i].subsession = subsession;
            fStreamStates[i].streamToken = NULL;      
         }
    }
    
    

        上述代码中与ServerMediaSubSession 关联的streamToken被赋值为NULL

    并会在后面的getStreamParameters中被赋值,最后一个參数为指针的引用。用于在getStreamParameters中改动该指针。

     

    subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
                      clientRTPPort, clientRTCPPort,
                      tcpSocketNum, rtpChannelId, rtcpChannelId,
                      destinationAddress, destinationTTL, fIsMulticast,
                      serverRTPPort, serverRTCPPort,
                      fStreamStates[streamNum].streamToken); 
    

        getStreamParameters在OnDemandServerMediaSubsession又一次定义,能够看到创建StreamStates对象的代码:

     

    // Set up the state of the stream.  The stream will get started later:
        streamToken = fLastStreamToken
          = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
               streamBitrate, mediaSource,
               rtpGroupsock, rtcpGroupsock);
    


        能够看到StreamStates关联了Sink和Souce。

    之所以要在OnDemandServerMediaSubsession又一次定义的getStreamParameters中分配StreamStates对象,是由于它定义了新的创建详细MediaSouce和MediaSink的虚函数。

     

      

    virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
                           unsigned& estBitrate) = 0;
         // "estBitrate" is the stream's estimated bitrate, in kbps
       virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
                      unsigned char rtpPayloadTypeIfDynamic,
                      FramedSource* inputSource) = 0;
     

         StreamStates关联的MediaSouce和MediaSink均是详细的子类。

    若媒体文件为H264码流,则相应的Souce为H264VideoStreamFramer。相应的Sink为H264VideoRTPSink。

     

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

    for (i = 0; i < fNumStreamStates; ++i) 
    {
        if (subsession == NULL /* means: aggregated operation */
          || subsession == fStreamStates[i].subsession) 
       {
          unsigned short rtpSeqNum = 0;
          unsigned rtpTimestamp = 0;
          if (fStreamStates[i].subsession == NULL) continue;
          fStreamStates[i].subsession->startStream(fOurSessionId,
                             fStreamStates[i].streamToken,
                            (TaskFunc*)noteClientLiveness, this,
                             rtpSeqNum, rtpTimestamp,
                             RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection);
         }
    
    }
    
    

     

    startStram内部调用了StreamStates的startPlaying:

    void OnDemandServerMediaSubsession::startStream(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);
        RTPSink* rtpSink = streamState->rtpSink(); // alias
        if (rtpSink != NULL)
        {
          rtpSeqNum = rtpSink->currentSeqNo();
    
          rtpTimestamp = rtpSink->presetNextTimestamp();
        }
      }
    }
    
    

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

    fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);

            第一个參数即为详细的MediaSouce子类。

    StartPlaying之后,Sink会调用Souce的getNextFrame获得一帧数据。

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

     

                                                   2014.8.28于浙江杭州
  • 相关阅读:
    第十一节 CSS引入的三种方式
    第十节 表单
    第九节 页面布局(简历)
    第八节 HTML表格
    第七节 列表标签
    第六节 链接标签
    第五节 插入图的img标签
    WordPress 全方位优化指南(下)
    Cloud Insight!StatsD 系监控产品新宠!
    WordPress 全方位优化指南(上)
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/6918249.html
Copyright © 2011-2022 走看看