《Rtsp流媒体服务器小结.docx》由会员分享,可在线阅读,更多相关《Rtsp流媒体服务器小结.docx(14页珍藏版)》请在三一办公上搜索。
1、Darwin Streaming Server流媒体服务器小结Darwin Streaming Server (简称 DSS)是 QuickTime Streaming Server 开放式源代 码的版本,同时支持 FreeBSD、Linux、Solaris Windows NT 和 Windows 2000 等多个操作系统,是当前所有同类产品中支持平台最多的一个。DSS源代码完全采用标准C+语言写成,每个C+类都对应着一对和类同名 的.h/.cpp文件。整个服务器包括多个子系统,分别存放在独立的工程内,其中,最为重要的是基 础功能类库(CommonUtilitiesLib)和流化服务器(St
2、reamingServer)两个工程, 前者是整个系统的通用代码工具箱,包括了线程管理、数据结构、网络和文本分 析等多个功能模块。后者包含了 DSS对多个国际标准的实现,是整个服务器的 主工程。DSS实现了四种IETF制定的国际标准,分别是:实时流传输协议RTSP(Real-time Streaming Protocol, RFC 2326)、实时传输协议(RTP Real-time Transfer Protocol, RFC 1889)、实时传输控制协议 RTCP (Real-time Transport Control Protocol,RFC 1889)、会话描述协议 SDP(Sess
3、ion Description Protocol, RFC 2327)。1. 主线程(main thread):这个线程负责检查服务器是否需要关闭,记录状态信息, 或者打印统计信息。2. 空闲任务线程(Idle Task thread):空闲任务线程管理一个周期性的任务队列,该任务队列有两种类型:超时任务和套接口任务。3. 事件线程(Event thread):事件线程负责侦听套接口事件,比如收到RTSP请求 和RTP数据包,然后把事件传递给任务线程。4. 一个或多个任务线程(Task threads):任务线程从事件线程中接收RTSP和RTP 请求,然后把请求传递到恰当的服务器模块进行处理,
4、把数据包发送给客户端。基础功能类库(Common Utilities)1. Darwin Streaming Server 支持包括 Windows, Linux 以及 Solaris 在内的多种操 作系统平台。我们知道,Windows和Unix (或Unix-like)操作系统之间无论从 内核还是编程接口上都有着本质的区别,即使是Linux和Solaris,在编程接口上 也大为不同。为此,DSS开发了多个用于处理时间、临界区、信号量、事件、互 斥量和线程等操作系统相关的类,这些类为上层提供了统一的使用接口,但在内 部却需要针对不同的操作系统采用不同的方法实现。OSCond状态变量的基本功能和
5、操作,OSMutex互斥量的基本功能和操作,OSThread线程类,OSFileSource简单文件类,OSQueue队列类,OSHashTable哈希表类,OSHeap 堆类,OSRef 参考引用类。2. Task类,用来处理事件通知机制。在Task.h/cpp文件中,定义了三个主要的类,分别是:任务线程池类(TaskThreadPool Class ) 任务线程类(TaskThread Class)以及任务类(Task Class )o 每个Task对象有两个主要的方法:Signal和Run。当服务器希望发送一个事件 给某个Task对象时,就会调用Signal()方法;而Run()方法是在
6、Task对象获得处 理该事件的时间片后运行的,服务器中的大部分工作都是在不同Task对象的 Run()函数中进行的。每个Task对象的目标就是利用很小的且不会阻塞的时间片 完成服务器指定某个工作。任务线程类(TaskThread)是OSThread类的一个子类,代表专门用于运行任务类 的一个线程。在每个任务线程对象内部都有一个OSQueue_Blocking类型的任务 队列,存储该线程需要执行的任务。服务器调用一个任务的 Signal函数,实际上 就是将该任务加入到某个任务线程类的任务队列中去。另外,为了统一管理这些任务线程,DSS还开发了任务线程池类,该类负责 生成、删除以及维护内部的任务线
7、程列表。这种由事件去触发任务的概念已经被集成到了DSS的各个子系统中。例如, 在DSS中经常将一个Task对象和一个Socket对象关联在一起,当Socket对象 收到事件(通过select()函数),相对应的Task对象就会被传信(通过Signal() 函数);而包含着处理代码的Run()函数就将在某个任务线程中运行。3.Socket 类DSS中的Socket类一般都采用异步模式的(即非阻塞的),而且能够向对 应的Task对象传信(Signal), Socket类中具有代表性的类是:EventContext、 EventThread、Socket、UDPSocket、TCPSocket 以及
8、 TCPListenerSocket 等等。在 eventcontext.h/.cpp 文件中,定义了两个类:EventContext 类和 EventThread 类。Event Context提供了检测Unix式的文件描述符(Socket就是一种文件描述 符)产生的事件(通常是EV_RE或EV_WR)的能力,同时还可以传信指定的 任务。EventThread类是OSThread类的子类,它本身很简单,只是重载了 OSThread 的纯虚函数Entry(),用以监控所有的Socket端口是否有数据到来。EventContext对象负责维护指定的描述符,其主要函数包括InitNonBlocki
9、ng、 CleanUp 和 RequestEvent 等。其中 InitNonBlocking 函数调用 Socket API ioctlsocket 将用户指定的描述符设置为异步,CleanUp函数用于关闭该描述符;另外,用户 通过RequestEvent函数申请对该描述符中某些事件的监听Socket Class、UDPSocket Class 和 TCPSocketClass 三个类都是 EventContext 的子 类,它们封装了 TCP和UDP的部分实现,同时扩展了 EventContext中的事件, 但都没有改变其运行机制。TCPListenerSocket用于监听TCP端口,当一
10、个新连 接请求到达后,该类将赋予这个新连接一个Socket对象和一个Task对象的配对。二服务器模块:处理网络和协议。主要有3个子系统:RTSP子系统,RTP子系统以及公共服务子 系统。服务器内核:这个子系统中的类都有一个QTSS前缀。QTSSServer负责处理服务器的启动和关闭。QTSSServerInterface负责保存服务器全局变量,以及收集服务器的各种统计信 息。QTSSPrefs是存储服务器 偏好设定的地方。QTSSModule,QTSSModuleInterface,和 QTSSCallbacks 类的唯一目的就是支持 QTSS的模块API。(1) RTSP子系统负责解析和处理
11、RTSP请求,以及实现QTSS模块API的RTSP部分。其中 的几个类直接对应QTSS API的一些元素(例如,RTSPRequestInterface 类就是对应QTSS_RTSPRequestObject对象)。每一个RTSP/TCP连接都对 应一个 RTSP 的 session. 主 要 的 类有 RTSPSession,RTSPRequest,RTSPResponseStream 和 RTSPRequestStream.(2) RTP子系统负责媒体数据包的发送,根据RTCP的反馈进行服务质量控制。主要的类有 RTPSession,RTPStream 和 RTCPTask.(3) 公共服
12、务子系统负责服务器的启动/关闭,初始化参数设置以及为Module机制,跨平台的多线程 和事件机制等提供支持。DSS提供了一种称为Module的二次开发接口。使用这个开发接口,我们可以自 由扩张服务器的功能。DSS 定义了一个 TCPListenerSocket 类的子类 RTSPListenerSocket,用于监听 RTSP 连接请求。RTSPListenerSocket类做的唯一一件事就是重载了 GetSessionTask函 数,当客户的连接请求到达后,它创建了一个Socket对象和RTSPSession对象 的配对。RTSPSession对象是Task类的子类,是专门用于处理RTSP请
13、求的任务 类。当client端发出Play请求时,server端的RTSPListenerSocket监听到这个请 求,Run创建一个RTSPSession,这个RTSPSession被加入到任务队列中,当时 间片到达时,TaskThread线程就会调用RTSPSession对象的函数,在Run函数 中,维护一个RTSPSession状态机,对客户的RTSP请求做出不同的处理。请求分析完成后,RTSPSession进入请求处理状态(kProcessingRequest),DSS 会调用注册了 “请求处理任务”(QTSS_RTSTRequest_Role )的module,而QTSSFileMo
14、dule 就是这样一个 Moduleo QTSSFileModule 定义了一个分发函数 QTSSFileModuleDispatch,它根据传入的任务类别和任务参数调用相应的函数。 此时传入的任务是QTSS_RTSTRequest_Role,相应的处理函数是 ProcessRTSPRequest,该函数根据传入的RTSP Method调用相应的处理函数,此 时传入的method是play,所以调用函数DoPlay。IdleTaskThread,它的Entry中不停的超时等待,查看它队列中的task是否到期, 是则调用该task-signal.IdleTask 的 SetIdleTimer()
15、里面调用 IdleTaskThread-SetIdleTimer()把该 task 加入 它的 fIdleHeap.程序的数据流程为四部分,就是:1, task。每个Task对象有两个主要的方法:Signal和Run。当服务器希望发送一个事 件给某个Task对象时,就会调用Signal()方法;而Run()方法是在Task对 象获得处理该事件的时间片后运行的,应用可以通过继承Task并重写Run() 方法实现自己的任务。2,EventContext事件的触发者,当事件发生时,调用Task:signal().3, TaskThread,任务的驱动线程,对一个或者多个Task进行调度,通过调用 T
16、ask:run()处理事件.4, EventThread,EventContext 的驱动线程,可以处理多个 EventContext,发 生事件时调用 EventContext:process_event(),后者将调用 Task:Signal()流程:1, Client或者Task的子类向Event Context注册事件。2, EventContext 将事件放入 EventThread 的 Pool 内。3, EventThread调用select等待多个事件中任一个触发。4, 事件触发以后,EventThread 调用 EventContext:ProcessEvent()。5, 调
17、用 Task:signal()。6, Task:signal()将 task 放入 TaskThread 的队列。7, TaskThread调度相应的Task,执行其Run()方法。它的具体实现在各个Module 中定义。DSS的基本功能单元称为模块(module),每一个模块都是处理某一类事件的功能 集合。例如QTSSAcessModule就是鉴权授权事件的主要处理模块,类似于桌面窗 口的事件处理中的事件,DSS服务器核心处理RTSP请求,定义了若干角色(role), 一个角色就是一项任务,一个模块可以注册若干个角色,表示这个模块可以处理 这些注册的任务。比如一个模块注册了 QTSS_RST
18、PPreProcess_Role,则可以调用 这个Module来预处理RSTP请求。根据不同的任务,服务器会传递不同的对象给 模块,这些对象中包含有为完成这个任务所包含的一系列数据以及为向服务器传 递信息而存在的数据结构。Darwin streaming server的媒体存储格式hint track格式以及被ISO,ISMA等 标准组织采纳并成为正式标准。Hint track是Darwin streaming server的扩 展,通过在媒体文件中增加hint track轨道,存放了一些预先生成好的媒体描 述信息(SDP格式)和媒体数据打包的索引信息。通过ISMA传输协议进行传输时, 可以直
19、接根据数据打包索引信息来打包发送,不需要对信息进行重复的分析格式 组装数据包的处理过程。普通文件可以通过一些工具添加hint track属性。开源的工具有 mpeg4ip,mp4info.RTSP数据处理首先经由“RTSP Filter Role”对封包资料做某些改变等前置处理。当RTSP Filter Role完成,开始进行对该请求封包的分析以取得各种参数并建立一个 RTSP session及一个Client session.RTSP session主要负责处理此一请求所建立的 RTSP连线阶段的请求/回应。Client session则负责串流传送阶段的维护工作。对该请求分析完毕后,ser
20、ver呼叫各模块中的” RTSP Route Role”对RTSP对 象进行参数修改和设定。他可以改变文件的处理目录。接着呼叫” Preprocessor Role”判断该请求属于RTSP封包中的哪一种类型,以 便把该请求传送到对应的函数处理此一请求,若无法判别属于哪一类型,则将其 传送到” RTSP Request Role”,此一角色处理所有在” Preprocessor Role”中 未定义的类型。最后,来到“Postprocessor Role”,此角色负责统计的工作,例如存取记录等。在处理” Preporcessor Role”或是“RTSP Request Role”中的各模块时,
21、可能 会产生串流媒体资料。需要产生媒体资料时,程式会呼叫Client session中的 “QTSS_Play”物件,此物件会触发在“RTP Send Packets Role”中的各个模块,同时进入RTP subsystem中。RTP subsystem:RTP subsystem主要包括RTP封包传送及RTCP串流控制两部份。在封包传送 方面,“RTP Send Packets Role”在 RTP session 中经由呼叫“QTSS_Write” 或“QTSS_WriteV”把媒体资料传送到客户端。在传送过程中,每送一次封包后, Server等待一个时间值再继续呼叫” RTP Send
22、Packets Role”传送封包。在串 流控制方面,当Server接收到RTCP封包时会先判定此RTCP封包属于哪一种 封包,RFC1889 中定义了五种 RTCP 封包,分另U是:SR(Source description), RR(Receiver description),SDES(Source Description Items,include items), APP(Application specific functions)及 BYE。根据封包的种类,Server 会呼 叫“ RTCP Process Role ”中相应的模块进行处理。一个 client Session 对应多
23、个 rtp stream.passing Authentication means you are a valid user of the system. It does not mean y ou have access to the requested file. Authorization is the other half, which is the process of allowing the user access to the requested content.rtspSession 的流程:当client端发出Play请求时,server端的RTSPListenerSock
24、et监听到这个请 求,Run 在 GetSessionTask()函数中创建一个 RTSPSession,这个 RTSPSession 被加入到任务队列中,当signal通知数据到达时,TaskThread线程就会调用 RTSPSession对象的函数,在Run函数中,维护一个RTSPSession状态机,对客 户的RTSP请求做出不同的处理。rtspSession 的 run 函数中,casekReadingFirstRequest: initialized valuedfInputStream.ReadRequest()读取数据if (err = QTSS_RequestArrived)
25、fState = kHTTPFilteringRequest;casekHTTPFilteringRequest:fState = kHaveNonTunnelMessage;casekReadingRequest:if (err = fInputStream.ReadRequest() = QTSS_NoErr) fState = kHaveNonTunnelMessage;casekHaveNonTunnelMessage:fRequest = NEW RTSPRequest(this);fState = kFilteringRequest;casekFilteringRequest:th
26、is-SetupRequest();/setup rtpSession RTSPRequest:Parse()if (fRTPSession = NULL)theErr = this-CreateNewRTPSession(theMap);fState = kRoutingRequest;casekRoutingRequest:if(fRequest-SkipAuthorization() fState = kPreprocessingRequest;casekPreprocessingRequest:theModule= QTSServerInterface:GetModule(QTSSMo
27、dule:kRTSPPreProcessorRole, fCurrentModule);(void)theModule-CallDispatch(QTSS_RTSPPreProcessor_Role, &fRoleParams);fModuleState.isGlobalLocked = false;在 reflectorModule 模块中 ProcessRTSPRequest()处理if (*theMethod = qtssAnnounceMethod) returnDoAnnounce(inParams);if (*theMethod = qtssDescribeMethod)retur
28、nDoDescribe(inParams);if (*theMethod = qtssSetupMethod)returnDoSetup(inParams);caseqtssPlayMethod:returnDoPlay(inParams, (*theOutput)-GetReflectorSession();fState = kProcessingRequest;if (fRequest-HasResponseBeenSent()(fState = kPostProcessingRequest;break;casekPostProcessingRequest:fState = kSendin
29、gResponse;casekSendingResponse:fState = kCleaningUp;casekCleaningUp:this-CleanupRequest();fState = kReadingRequest;在client启动播放时,先建立rtspSession,然后进入run(),读了一个请求后,调用 setupRequest,然后,clearupRequest.循环直到 return 出 run()的 while(true)函数。在 setupRequest 的 FindRTPSession 中,会去给 fRTPSession 赋值。所以在cleanupRequest
30、中把它置为NULL。三.关键模块分析1.直播模块结构分析:ReflectorSocket:public IdleTask, public UDPSocketGetIncomingData()负责接收数据ProcessPacket(),负责把数据包填入 ReflectorSender 的 OSQueue。ReflectorSocketPool:UDPSocketPair* ReflectorSocketPool:ConstructUDPSocketPair()(return NEW UDPSocketPair(NEW ReflectorSocket(), NEW ReflectorSocket(
31、);ReflectorSender : public UDPDemuxerTaskReflectPackets()从它的 ReflectorStream 中找到 ReflectorOutput, 调用 SendPacketsToOutput(),里面调用 WritePacket()ReflectorOutput* theOutput =fStream-fOutputArraybucketIndexbucketMemberIndex;classRTPSessionOutput : public ReflectorOutputRTPSessionOutput:WritePacket()(由它的 R
32、TPSessionfClientSession 找出对应的 RTPStream,RTPStream 的 fCookieAttrID二sStreamCookieAttr 属性记录的是它对应 的 reflectorStream.判断该rtpStream对应的reflectorStream是否就是调用方的这个fstream.QTSS_Write(*theStreamPtr, &thePacket,.)theStreamPtr 为 RTPStreamReflectorStream:This object supports reflecting an RTP multicast stream to NR
33、TPStreams.BindSockets()用来把 reflectorSocket 和 reflectorSender 联系起来(ReflectorSocket*)fSockets-GetSocketA()-AddSender(&fRTPSender);(ReflectorSocket*)fSockets-GetSocketB()-AddSender(&fRTCPSender);UDPSocketPair* fSockets;ReflectorSenderfRTPSender;ReflectorSenderfRTCPSender;fOutputArray,记录 RTPSessionOutpu
34、t 信息,typedefReflectorOutput* Bucket;Bucket* fOutputArray;在它的构造函数中指定了fRTPSender.fStream = this;fRTCPSender.fStream = this;在同时开启多个窗口播放同一个sdp文件时,都是使用同一个ReflectorSession对象, FindOrCreateSession也不会调用SetupReflectorSession函数,即ReflectorStream ReflectorSocket等对 象也不会被再次创建。注意对于一个新的播放链接来说RTSPSession、RTPSession都是
35、新创建 的对象,所以需要重新创建TPSessionOutput对象。在 QTSSReflectorModule.cpp 的 DoSetup()函数中,由 DoSessionSetup 进入 FindOrCtreateSession(), 建 立 出 ReflectorSession, 里面 创 建 ReflectorStream,绑定它对于的 ReflectorSocket.ReflectorSession* FindOrCreateSession(ReflectorSession* theSessiontheSession = NEW ReflectorSession(inPath);Sou
36、rceInfo通过读取sdp文件,对应ffmpeg输出的rtp流。theSession-SetupReflectorSession(theInfo,inParams,theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs);该函数里面:fStreamArray = NEW ReflectorStream*fSourceInfo-GetNumStreams();for (UInt32 x = 0; x GetNumStreams(); x+) fStreamArrayx=NEW ReflectorStream(fSourceInfo-GetStreamI
37、nfo(x);fStreamArrayx-BindSockets(inParams,inFlags, filterState, filterTimeout);doSetup()中建立 outPut 和 rtpSession,reflectorSession 的关系。RTPSessionOutput* theNewOutput =NEW RTPSessionOutput(inParams-inClientSession, theSession, sServerPrefs, sStreamCookieAttr );theSession 类型为 ReflectorSession.theSession
38、-AddOutput(theNewOutput,true);(void)QTSS_SetValue(inParams-inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput);theErr=QTSS_AddRTPStream(inParams-inClientSession,inParams-inRTSPRequest, &newStream, 0);该函数中*outStream = NEW RTPStream(theSSRC, this);QTSS_ErrortheErr = (*outStream)-Setup
39、(request, inFlags);建立该 rtpStream 和 client 的 socket 联系。/ Place the stream cookie in this stream for future reference void* theStreamCookie =theSession-GetStreamCookie(theTrackID);theStreamCookie 为 reflectorStream.theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, theStreamCookie, sizeof(theStrea
40、mCookie);RTPStream类有的属性有:UDPSocketPair*fSockets;RTPSessionInterface* fSession;在 ReflectorStream:PushPacket 中,把 packet 拷贝到 reflectorSocket,调用 ProcessPacket/ Find the appropriate ReflectorSender for this packet.ReflectorSender* theSender = (ReflectorSender*)this- GetDemuxer()-GetTask(theRemoteAddr, 0)
41、;theSender-fPacketQueue.EnQueue(&thePacket-fQueueElem);在 ReflectorStream.cpp 的 ReflectorSocket:Run()中调用 GetIncomingData()和 ReflectPackets()收发包。ReflectorSender- reflectStream 找到 ReflectorOutput-从他对应的 rtpSession 找到 rtpStream,判断该 rtpStream 对应的 reflectorStream是否就是调用方的这个fstream.是则调用该rtpStream的 write函数发送数
42、据包。播放一下,马上出错原因是,在请求sdp文件失败返回后,调用了 fileModule 的 doDescribe,返回出错,再调用 QTSS_Teardown,销毁 rtpSession.Signal(killEvent),在它 的RTPSessionInterface()中调用 RTSPSession 的 DecrementObjectHolderCount()的 signal(killEvent),删除 rtspSession.Udpsocket句柄释放在eventSocket的虚构函数中close.2.点播模块分析1,RTPSession:Run()有(void)fModule-Cal
43、lDispatch(QTSS_RTPSendPackets_Role, &theParams);它调用 QTSSFileModuleDispatch 的 SendPackets(),2,RTSPSession:Run()有Case kProcessingRequest:(void)theModule-CallDispatch(QTSS_RTSPRequest_Role, &fRoleParams);调 用 ProcessRTSPRequest().QTSServer.cpp 中定义好了 sCallbacks.addr = (QTSS_Callback)QTSS_APIFileSession:里
44、面有QTRTPFilefFile;SDPSourceInfofSDPSource;FileSession 中 fFile 类型为 QTRTPFile,在 DoDescribe 函数中,调用 CreateQTRTPFile()函数,根据 inPath,*outFile二 New FileSession(),并(*outFile)-fFile.Initialize(inPath)FileSession 类中,主要结构有 QTRTPFilefFile; fAdjustedPlayTime; QTSS_PacketStructfPacketStruct;fStartTime,fStopTime;fLa
45、stRTPTime;QTRTPFile.h 中定义了QTFile * fFile,theSDPData.Ptr = theFile-fFile.GetSDPFile(&sdpLen);QTRTPFile:ErrorCodeqtfileErr = theFile-fFile.AddTrack(theTrackID, true);/都是调用了 qtRTPFile里面解析数据的函数。在 doDescribe()的最后,theFile-fSDPSource.Parse(theSDPData.Ptr, theSDPData.Len);SourceInfo:StreamInfo*theStreamInfo
46、=theFile-fSDPSource.GetStreamInfo(x);rtpSession 的功能:一 1 对应于 rtspSession,里面有 2 个 rtpStream,在 killEvent 中对所有注册了 QTSS_ClientSessionClosing_Role 角色的模块 调 用 (void)theModule-CallDispatch(QTSS_ClientSessionClosing_Role, &theParams);里面最重要的 fModule在rtspSession的run中给它赋值,让他对应上 fRTPSession.艮口 fRTPSession-SetPack
47、etSendingModule(theModule);IdleoutTask,IdleTask:Initialize()在 StartServer()函数中调用,它创建出 sIdleThread.ReflectSocket 继承自 IdleoutTaskTimeoutTask,初始化时传入task对象指针,它会加入到TimeoutTaskThread对 象的队列,该静态对象task在StartServer()函数中初始化, TimeoutTask:Initialize().TimeoutTask的构造函数会把它加入到TimeoutTaskThread的队列中,该task 的run函数会调用它队列中的所有元素,判断是否超时,调用theTimeoutTask-fTask-Signal(Task:kTimeoutEvent);RTPSessionInterface 类中有一个成员变量 TimeoutTaskfTimeoutTask;RTSPSessionInterface 类中也有 TimeoutTaskfTimeoutTask;四.实时转码和直播模块的关系在 trunk/APIModules/QTSSReflectorModule/模块下添加了ReflecotrFfmpeg.cpp,ReflectorF