在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对象剩余的属性值,以及创建下面两个会话:
// 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_AppendRTSPHeader
和QTSS_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