zoukankan      html  css  js  c++  java
  • Darwin Streaming Server中RTSP请求的处理过程(Darwin流媒体服务器解析2)

    在Darwin流媒体服务器解析1中,我们分析了建立一路RTSP Session的过程,本文将具体深入到RTSPSession内部,分析RTSPSession对每一个RTSP请求的处理过程:

          继续在Darwin流媒体服务器解析1中最后说到的,进入到RTSPSession::Run()函数中进行请求报文的处理。处理RTSP请求的流程中,DSS主要采用了状态机(state machine)的处理方式。

          这里流程不太一样的就是第一次对请求报文进行处理,后续的所有请求流程大体一致(非第一次请求则直接从kReadingRequest状态转到@3状态):

          @1:kReadingFirstRequest:进入初始化状态,实际在此状态中读取请求报文的方式与后续在kReadingRequest的读取及处理的方式是一致的,只不过在第一次进行Request处理的时候需要检测该请求的方式是以普通RTSP的方式,还是以RTSP-over-HTTP tunneling方式进行报文交互,检测步骤为第二步@2。

    if (err == QTSS_RequestArrived)//此时收到完整请求的报文
                        fState = kHTTPFilteringRequest;
    

          @2:kHTTPFilteringRequest:对报文内容进行过滤,检查是否有HTTP报文中所出现的一些关键字(POST/GET等),是则直接进入RTSP-over-HTTP状态处理的下一步:kSocketHasBeenBoundIntoHTTPTunnel(此处因为并没有这种需求,故不进行深入),否的话进入@3kHaveNonTunnelMessage状态。

    //检测是否为RTSP-over-HTTP tunneling
    QTSS_Error  preFilterErr = this->PreFilterForHTTPProxyTunnel();
     

          @3:kHaveNonTunnelMessage:进入此状态,说明请求报文格式是正确的,请求已进入受理状态,故在此状态中对fReadMutex,fSessionMutex进行加锁,禁止在处理报文的过程中接收以RTP Interleaved接收RTP数据或者发出RTSP响应报文

    // We have an RTSP request and are about to begin processing. We need to
    // make sure that anyone sending interleaved data on this session won't
    // be allowed to do so until we are done sending our response
    // We also make sure that a POST session can't snarf in while we're
    // processing the request.
    fReadMutex.Lock();
    fSessionMutex.Lock();

    重置响应缓冲区并进入下一个状态(中间有一些对错误报文[E2BIG/QTSS_BadArgument/etc..]的响应此处就忽略)@4kFilteringRequest

          @4:kFilteringRequest:在处理RTSP请求的时候,服务器调用RTSP Filter(过滤器)角色。它会调用所有注册了RTSP Filter角色的模块,并将RTSP请求对象作为参数传递给它。每个RTSP Filter角色都可以改变qtssRTSPReqFullRequest属性的值。比如说,一个RTSP Filter角色可以把/foo/foo.mov改为/bar/bar.mov,从而改变用于满足请求的文件夹。

    重要提示

    任何处理RTSP Filter角色的模块对客户端进行响应,都会导致服务器跳过其它已经注册了RTSP Filter角色的模块,以及跳过已经注册其它RTSP角色的模块,然后立即调用该响应模块的RTSP Postprocessor(后处理)角色。这里所说的对客户进行响应是指模块向客户端发送任何形式的数据。

    // Invoke filter modules
    numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPFilterRole);
    for (; (fCurrentModule < numModules) && <span style="color:#ff0000;">((!fRequest->HasResponseBeenSent()) || fModuleState.eventRequested)</span>; fCurrentModule++){}

     

    在调用了所有RTSP Filter(过滤器)角色之后,服务器就会对请求进行解析。解析请求包括填充RTSP对象剩余的属性值,以及创建下面两个会话:

    • 一个RTSP会话(RTSPSession),和当前这个特定的请求相关联,在客户端关闭与服务器之间的RTSP连接时,这个会话也会被关闭。
    • 一个客户会话(RTPSession),和产生当前请求的客户端连接相关联,这个会话会一直保持,直到客户端的流播放结束。
    // Otherwise, this is a normal request, so parse it and get the RTPSession.
       this->SetupRequest();

          @5:kRoutingRequest:RTSP请求解析完成之后,服务器会以RTSP Route(路由)角色调用所有注册了该角色的模块,并传入一个RTSP对象。每个RTSP Route角色都可以使用RTSP对象中的属性值来确定是否要改变qtssRTSPReqRootDir属性的值,进而改变用于处理当前请求的目录。举例来说,如果语言的类型是French(法语),则模块可以把qtssRTSPReqRootDir属性修改到包含法语版本的被请求文件的目录下。

    重要提示

    任何处理RTSP Route角色的模块对客户端进行响应,会导致服务器跳过其它注册了RTSP Route角色的模块,以及跳过注册了其它RTSP角色的模块,并且立即调用响应模块的RTSP Postprocessor(后处理)角色。

    在调用了所有RTSP Route角色之后,服务器就会以RTSP Preprocessor角色调用每个注册了该角色的模块。通常情况下,RTSP Preprocessor角色会通过qtssRTSPReqAbsoluteURL属性值来确定当前请求是否和模块处理的请求的类型相匹配。

          @6:kPreprocessingRequest:如果请求的类型互相匹配,则RTSP Preprocessor角色就调用QTSS_Write或者QTSS_WriteV函数来向客户发送数据,对客户端进行响应。如果只需要发送标准响应,则模块可以调用QTSS_SendStandardRTSPResponse,或者QTSS_AppendRTSPHeaderQTSS_SendRTSPHeaders函数。

    重要提示

    任何处理RTSP Preprocessor角色的模块对客户端进行响应,都会导致服务器跳过注册了RTSP Preprocessor角色的所有其它模块,以及跳过注册了其它RTSP角色的所有模块,并且立即调用响应模块的RTSP Postprocessor角色。

    如果没有RTSP Preprocessor角色对RTSP的请求进行响应,则服务器就以RTSP Request(请求)角色调用成功注册了该角色的模块(第一个注册RTSP Request角色的模块,是唯一一个可以注册RTSP Request角色的模块)。RTSP Request角色负责响应所有没有被RTSP Preprocessor角色(的模块)处理过的RTSP请求。

          @7:kProcessingRequest:RTSP Request角色对请求进行处理之后,服务器就调用注册了RTSP Postprocessor角色的模块。RTSP Postprocessor角色通常执行一些统计任务,比如记录各种统计信息。

    处理RTSP Preprocessor或者RTSP Request角色的模块可能需要为特定的客户会话生成一些媒体数据。如果这样的话,模块可以通过调用QTSS_Play函数来实现,这个函数会使模块的RTP Send Packets(RTP发送数据包)角色被调用。

          @8:RTP数据包发送:RTP Send Packets角色调用QTSS_Write或者QTSS_WriteV函数,在RTP会话的基础上向客户发送数据。当RTP Send Packets角色发送完成一些数据包之后,就会把控制权返回给服务器,并指定服务器下次调用模块的RTP Send Packets角色的间隔时间。这个周期会一直重复,直到所有的媒体数据包被发送完成,或者由于客户请求的原因需要暂停或中止客户会话为止。

    QTSS_Error  QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock)
    {
        switch (inRole)
        {
            case QTSS_Register_Role:
                return Register(&inParamBlock->regParams);
            case QTSS_Initialize_Role:
                return Initialize(&inParamBlock->initParams);
            case QTSS_RereadPrefs_Role:
                return RereadPrefs();
            case QTSS_RTSPRequest_Role:
                return ProcessRTSPRequest(&inParamBlock->rtspRequestParams);
    <span style="color:#ff0000;">        case QTSS_RTPSendPackets_Role:
                return SendPackets(&inParamBlock->rtpSendPacketsParams);
    </span>        case QTSS_ClientSessionClosing_Role:
                return DestroySession(&inParamBlock->clientSessionClosingParams);
        }
        return QTSS_NoErr;
    }
           theErr = QTSS_Write(theStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, theFlags);
           //每一次发送返回在发送后是否继续的消息
            isBeginningOfWriteBurst = false;
            
            if ( theErr == QTSS_WouldBlock )//阻塞,等待下一次服务器唤醒
            { 
    
                if (currentTimeStamp != pauseTimeStamp) // reset the packet time stamp so we adjust it again when we really do send it
                   SetPacketTimeStamp(currentTimeStamp, packetDataPtr);
               
                // 如果在返回参数中设置了下一次唤醒的时间间隔则
                // In the case of a QTSS_WouldBlock error, the packetTransmitTime field of the packet struct will be set to
                // the time to wakeup, or -1 if not known.
                // If the time to wakeup is not given by the server, just give a fixed guess interval
                if ((*theFile)->fPacketStruct.suggestedWakeupTime == -1)
                    <span style="color:#ff0000;">inParams->outNextPacketTime = sFlowControlProbeInterval;    // for buffering, try me again in # MSec不确定下次唤醒时间
    </span>            else
                {
                    Assert((*theFile)->fPacketStruct.suggestedWakeupTime > inParams->inCurrentTime);
                    <span style="color:#ff0000;">inParams->outNextPacketTime = (*theFile)->fPacketStruct.suggestedWakeupTime - inParams->inCurrentTime;//确定下次唤醒时间
    </span>            }
                
                //qtss_printf("Call again: %qd\n", inParams->outNextPacketTime);
                    
                return QTSS_NoErr;
            }
            else//继续发送
            {
    
              (void) QTSS_SetValue(theStream, sRTPStreamLastSentPacketSeqNumAttrID, 0, &curSeqNum, sizeof(curSeqNum));
              (*theFile)->fPacketStruct.packetData = NULL;
            }


    至此,一路RTSP Request的框架处理过程已经打通,其中能够包括:OPTIONS/DESCRIBE/SETUP/PLAY/TEARDOWN/PAUSE/SET_PARAMETER等RTSP请求,下一节我们将深入进行到RTSPSession所请求的Role Module中,看他们是如何RTSP请求进行解析和处理的!

    ------------------------------------------------------------
    本文转自www.easydarwin.org,更多开源流媒体解决方案,请关注我们的微信:EasyDarwin 

  • 相关阅读:
    asp.net超强日历
    闲话多线程的创建
    一些应该熟记于心的jQuery函数和技巧
    jQuery选择符总结
    使用OQL+SQLMAP解决ORM多表复杂的查询问题
    同样的SQL语句在查询分析器执行很快,但是网站上执行超时的诡异问题
    "开门待客"还是“送货上门”?
    使用ASP.NET MVC2+PDF.NET 构建一个简单的新闻管理程序
    使用PDF.NET数据开发框架的实体操作语言OQL构造复杂查询条件
    “批量少次”还是“少量多次”邮件通信系统效率浅谈
  • 原文地址:https://www.cnblogs.com/babosa/p/9218017.html
Copyright © 2011-2022 走看看