http://blog.sina.com.cn/s/blog_77e76c040100pt1l.html
最近因项目开发需要,开始学习开源项目live555,特别将个人的一些学习心得做一下记录,如有理解不正确之处,欢迎各位朋友指出。
首先是源码的下载,这可以从http://www.live555.com上下载,同时上面亦提供了相关的文档,文档虽较为粗糙,但总比找不到任何文档说明强。当然,我在学习的过程中,也从网上查找了一段时间,收获还是有一点的,其中就有诸如:RTSP服务器实例live555源代码分析、live555源代码简介、live555代码解读系列、基于live555的rtp-rtcp研究等文章。
我的学习是在VS2008环境下进行的,要能够在VS环境下顺利进行,还得做相关工作。live开源虽说是用C++写的,代码风格也非常优秀,但是其是用makefile文件来做的,而对未接触过makefile文件的我是一个问题。幸运的是在网上偶然发现了一篇介绍在VC6环境下编译live的文章,然后照着上面所说的做,磕磕碰碰,编译成功了四个库并转移到了VS2008环境中。更幸运的是又是偶然在网上发现了一篇介绍用VS2008编译live的文章并提供了编译后的工程下载,download,然后就开始了相对漫长的学习过程。
从程序的结构来看,live项目包括了四个基本库、程序入口类(在mediaServer中)和一些测试代码(在testProgs中)。四个基本库是UsageEnvironment、BasicUsageEnvironment、groupsock和liveMedia。
UsageEnvironment包括抽象类UsageEnvironment和抽象类TaskScheduler,这两个类用于事件调度,其中包括实现了对事件的异步读取、对事件句柄的设置及对错误信息的输出等;该库中还有一个HashTable,这是一个通用的HashTable,在整个项目中都可以使用它,当然该HashTable也是一个抽象类。
BasicUsageEnvironment中的类主要是对UsageEnvironment中对应类的实现。
groupsock,顾名思义,用于数据包的接收和发送,其同时支持多播和单播。groupsock库中包括了GroupEId、Groupsock、GroupsockHelper、NetAddress、NetInterface等类,其中Groupsock类有两个构造函数,一个是“for a source-independent multicast group”,另一个是“for a source-specific multicast group”;而GroupsockHelper类主要用于读写Socket。
liveMedia是很重要的一个库,其不仅包含了实现RTSP Server的类,还包含了针对不同流媒体类型(如TS流、PS流等)编码的类。在该库中,基类是Medium,层次关系非常清晰。在该库中,有几个很重要的类,如RTSPServer、ServerMediaSession、RTPSink、RTPInterface、FramedSource等。
在http://www.live555.com上的相关文档中提到穿透防火墙的问题,方法是开启一个HTTP的tunnel,然后我们可以在liveMedia库中找到一个RTSPOverHTTPServer的类,该类解决了这样的问题。
mediaServer下的live555MediaServer提供了main函数,DynamicRTSPServer继承了RTSPServer并重写了虚函数lookupServerMediaSession。
前面已经讲到,通过不断地尝试(其实要在XP SP3环境下使用VS2008编译成功还是挺费神的),总算把源代码编译成功,同时又参考了新下载的一个用VS2008编译通过的live555源代码。结合这些,开始对主要类结构进行初步分析。
鉴于UsageEnvironment库、BasicUsageEnvironment库和groupsock库中的类较少,就暂且不作分析了。这里主要针对liveMedia库中的主要类结构进行分析。通过查看类关系图,可以从整体把握,但是苦于类太多,用类关系图看起来也不方便,于是就自己重新整理了一下,下面是 liveMedia库的主要类结构。(注:其他单类及结构体等不在此列出)
l Medium
n RTSPServer
n RTSPOverHTTPServer
n MediaSession
n ServerMediaSession
n ServerMediaSubsession
u OnDemandServerMediaSubsession
l FileServerMediaSubsession
n ADTSAudioFileServerMediaSubsession
n AMRAudioFileServerMediaSubsession
n H263plusVideoFileServerMediaSubsession
n MP3AudioFileServerMediaSubsession
n MPEG1or2VideoFileServerMediaSubsession
n MPEG2TransportFileServerMediaSubsession
n MPEG4VideoFileServerMediaSubsession
n WAVAudioFileServerMediaSubsession
l MPEG1or2DemuxedServerMediaSubsession
u PassiveServerMediaSubsession
n MediaSource
u FramedSource
l FramedFileSource
n ByteStreamFileSource
n ADTSAudioFileSource
n MP3FileSource
u MP3HTTPSource
l BasicUDPSource
l RTPSource
n MultiFramedRTPSource
u RawQCELPRTPSource
u AC3AudioRTPSource
u MPEG4GenericRTPSource
u RawAMRRTPSource
u H261VideoRTPSource
u H263plusVideoRTPSource
u H264VideoRTPSource
u JPEGVideoRTPSource
u MP3ADURTPSource
u MPEG1or2AudioRTPSource
u MPEG1or2VideoRTPSource
u MPEG4ESVideoRTPSource
u MPEG4GenericRTPSource
u MPEG4LATMAudioRTPSource
u DVVideoRTPSource
u QuickTimeGenericRTPSource
u SimpleRTPSource
l AMRAudioSource
n AMRDeinterleaver
n AMRAudioFileSource
l ByteStreamMultiFileSource
l DeviceSource
l JPEGVideoSource
l MPEG1or2DemuxedElementaryStream
l MPEG2TransportStreamMultiplexor
n MPEG2TransportStreamFromESSource
n MPEG2TransportStreamFromPESSource
l AudioInputDevice
n WAVAudioFileSource
l FramedFilter
n H264FUAFragmenter
n QCELPDeinterleaver
n AC3AudioStreamFramer
n ADUFromMP3Source
n uLawFromPCMAudioSource
n H264VideoStreamFramer
n MP3FromADUSource
u MP3Transcoder
n PCMFromuLawAudioSource
n MPEG2IFrameIndexFromTransportStream
n NetworkFromHostOrder16
n HostFromNetworkOrder16
n MP3ADUinterleaverBase
u MP3ADUinterleaver
u MP3ADUdeinterleaver
n MPEG2TransportStreamFramer
n EndianSwap16
n H263plusVideoStreamFramer
n MPEGVideoStreamFramer
u MPEG1or2VideoStreamFramer
l MPEG1or2VideoStreamDiscreteFramer
u MPEG4VideoStreamFramer
l MPEG4VideoStreamDiscreteFramer
n MPEG1or2AudioStreamFramer
n DVVideoStreamFramer
n MP3ADUTranscoder
n MPEG2TransportStreamTrickModeFilter
n MediaSink
u DummySink
u BasicUDPSink
u RTPSink
l MultiFramedRTPSink
n MPEG4GenericRTPSink
n VideoRTPSink
u H264VideoRTPSink
u MPEG1or2VideoRTPSink
u H263plusVideoRTPSink
u JPEGVideoRTPSink
u DVVideoRTPSink
u MPEG4ESVideoRTPSink
n AudioRTPSink
u AC3AudioRTPSink
u MPEG4LATMAudioRTPSink
u GSMAudioRTPSink
u MPEG1or2AudioRTPSink
u AMRAudioRTPSink
u MP3ADURTPSink
n SimpleRTPSink
u HTTPSink
l MPEG1or2VideoHTTPSink
u FileSink
l AMRAudioFileSink
l H264VideoFileSink
n RTCPInstance
n RTSPClient
n SIPClient
n DarwinInjector
n QuickTimeFileSink
n MPEG1or2Demux
n MPEG2TransportStreamIndexFile
n MPEG1or2FileServerDemux
n AVIFileSink
l BufferedPacketFactory
n QCELPBufferedPacketFactory
n AMRBufferedPacketFactory
n MPEG4GenericBufferedPacketFactory
n ADUBufferedPacketFactory
n QTGenericBufferedPacketFactory
n LATMBufferedPacketFactory
n H264BufferedPacketFactory
n JPEGBufferedPacketFactory
l BufferedPacket
n QCELPBufferedPacket
n AMRBufferedPacket
n MPEG4GenericBufferedPacket
n ADUBufferedPacket
n QTGenericBufferedPacket
n LATMBufferedPacket
n H264BufferedPacket
n JPEGBufferedPacket
l StreamParser
n AC3AudioStreamParser
n MPEGVideoStreamParser
u MPEG1or2VideoStreamParser
u MPEG4VideoStreamParser
n MPEG1or2AudioStreamParser
n H263plusVideoStreamParser
n MPEGProgramStreamParser
从上面这个主要的类结构可以看出,liveMedia库中的基类为Medium,其下又有几个非常重要的部分,一个是×××Subsession,除Medium父类外,所有的×××Subsession类都继承于ServerMediaSubsession;一个是×××Source,MediaSource的frameSource下主要包含FramedFileSource、RTPSource、FramedFilter等几个主要的部分;一个是MediaSink,以继承于RTPSink的类居多。
此外,还包含了用于处理packet的BufferedPacketFactory和BufferedPacket及其相关子类,用于处理流分析的StreamParser及其子类。
基本上,整个liveMedia库的主要类结构就是这样。不过,类太多了,分析起来还是有较大的困难。于是乎,采取去掉枝叶保留主干的做法,将整个服务器精简成支持一种格式的服务器,如MP3流服务器。经过分离,一个基于MP3的测试服务器的主要类结构如下所示。(注:其他单类及结构体等不在此列出)
l Medium
n RTSPServer
n MediaSession
n ServerMediaSession
n ServerMediaSubsession
u OnDemandServerMediaSubsession
l FileServerMediaSubsession
n MP3AudioFileServerMediaSubsession
n MediaSource
u FramedSource
l FramedFileSource
n MP3FileSource
u MP3HTTPSource
l BasicUDPSource
l RTPSource
n MultiFramedRTPSource
u MP3ADURTPSource
u MPEG1or2AudioRTPSource
u SimpleRTPSource
l FramedFilter
n ADUFromMP3Source
n MP3FromADUSource
u MP3Transcoder
n MP3ADUinterleaverBase
u MP3ADUinterleaver
u MP3ADUdeinterleaver
n MP3ADUTranscoder
n MediaSink
u BasicUDPSink
u RTPSink
l MultiFramedRTPSink
n AudioRTPSink
u MPEG1or2AudioRTPSink
u MP3ADURTPSink
n RTCPInstance
l BufferedPacketFactory
n ADUBufferedPacketFactory
l BufferedPacket
n ADUBufferedPacket
根据上面这种相对简单的类结构,分析起来就方便多了。于是,开始进入具体的分析细节了,这是一个相对漫长的过程,再加上本人的C++水平有限,这又将是一个相对艰苦的过程,一切慢慢来吧……
末了,讲讲启动服务器的过程:
LIVE555是一个纯粹的RTSP服务器,其服务器主类为liveMedia库下的RTSPServer;mediaServer下的live555MediaServer为主程序的入口类,DynamicRTSPServer是RTSPServer的实现类。
从live555MediaServer类的入口函数main中可以非常清晰地分析出服务器的启动过程。
首先是createNew一个TaskSchedulers对象和一个UsageEnvironment对象,这是初始工作。
之后是一段访问控制的代码。然后开始进入创建RTSP服务器的代码段,服务器指定了一个默认端口554和一个可供替代的端口8554。
接下来,createNew一个DynamicRTSPServer,这里建立了Socket(ourSocket)在TCP的554端口(默认端口)进行监听,然后把连接处理函数句柄和socket句柄传给任务调度器(即taskScheduler),既是RTSPServer类中的这句代码:env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, (TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandler,this)。紧接着就是对socket句柄和incomingConnectionHandler句柄的处理,主要是进行关联等。
最后,进入主循环(即env->taskScheduler().doEventLoop();),等待客户端连接。服务器启动完毕。
文章的最后,需要说明的是,在编译运行的过程中,我是使用VLC播放器来进行测试的,同时通过使用Ethereal的网络分析工具抓包分析其建立到传输的过程,虽然在live555源代码中关于RTSP建立及收发数据包的过程已经用代码写得非常清楚(这个好好分析一下源码就可以了),但我想,学习使用一下一些网络分析工具对自身也是颇为有益的。
处理连接请求的基本流程:
l Step 1:与客户端建立RTSP连接(调用incomingConnectionHandler方法),创建ClientSession并关联fClientSocket与incomingRequestHandler(调用incomingConnectionHandler1)。
l Step 2:接收客户端请求(调用incomingRequestHandler方法)。
l Step 3:从客户端Socket读取数据,并对请求数据(即the request string)进行转换(调用parseRTSPRequestString方法,该方法在RTSPCommon类中)。
l Step 4:根据分离出来的指令进行分别处理:
n OPTIONS→handleCmd_OPTIONS
n DESCRIBE→handleCmd_DESCRIBE
handleCmd_DESCRIBE这一个方法比较重要,首先根据urlSuffix查找ServerMediaSession是否存在(调用lookupServerMediaSession方法,该方法中通过HashTable来查找)。
在testOnDemandRTSPServer项目工程中,仅仅是通过streamName来确认session是否为NULL。而在完整的live555MediaServer项目工程中,则是通过DynamicRTSPServer类来处理,其首先是查找文件是否存在,若文件不存在,则判断ServerMediaSession(即smsExists)是否存在,如果存在则将其remove(调用removeServerMediaSession方法);若文件存在,则根据文件名创建一个ServerMediaSession(调用createNewSMS方法,若在该方法中找不到对应的文件扩展名,则将返回NULL)。
如果通过lookupServerMediaSession返回的是NULL,则向客户端发送响应消息并将fSessionIsActive置为FALSE;否则,为该session组装一个SDP描述信息(调用generateSDPDescription方法,该方法在ServerMediaSession类中),组装完成后,生成一个RTSP URL(调用rtspURL方法,该方法在RTSPServer类中)。
n SETUP→handleCmd_SETUP
handleCmd_SETUP方法中,有两个关键的名词,一个是urlPreSuffix,代表了session name(即stream name);一个是urlSuffix,代表了subsession name(即track name),后面经常用到的streamName和trackId分别与这两个名词有关。
接下来会创建session's state,包括incrementReferenceCount等。紧接着,会针对确定的subsession(track)查找相应的信息。接着,在request string查找一个"Transport:" header,目的是为了从中提取客户端请求的一些参数(调用parseTransportHeader方法,该方法在RTSPServer类中),如clientsDestinationAddressStr、ClientRTPPortNum等。
再接着是getStreamParameters(该方法在ServerMediaSession类中被定义为纯虚函数并在OnDemandServerMediaSubsession类中被重定义),然后通过fIsMulticast和streamingMode来组装不同的响应消息。
n PLAY→handleCmd_PLAY:处理播放请求,具体的实现流程请参见后面的步骤。
n PAUSE→handleCmd_PAUSE:处理暂停请求,在执行了该请求后,最终会调用StopPlaying方法,并将fAreCurrentlyPlaying置为FALSE。
n TEARDOWN→handleCmd_TEARDOWN:处理停止请求,将fSessionIsActive置为FALSE。
n GET_PARAMETER→handleCmd_GET_PARAMETER:该方法主要是维持客户端与服务器通信的生存状态,just for keep alive。
n SET_PARAMETER→handleCmd_SET_PARAMETER:该方法未针对SET_PARAMETER作实现,使用该方法会调用handleCmd_notSupported方法,并将最终引发与客户端断开连接。
l Step 5:根据Step 4的不同指令进行消息响应(调用send方法),该消息响应是即时的。
l Step 6:处理客户端发送“SETUP”指令后即开始播放的特殊情况。
l Step 7:将RequestBuffer进行重置,以便于为之后到来的请求做好准备。
l Step 8:检查fSessionIsActive是否为FALSE,如果是则删除当前的ClientSession。
处理PLAY的基本流程:
l Step 1:对rtspURL及相关header的处理,涉及较多的细节。
l Step 2:根据不同的header对流进行缩放比例或定位的处理。
如果为sawScaleHeader,则进行缩放比例的处理(调用setStreamScale方法,该方法在OnDemandServerMediaSubsession类中实现)。
如果为sawRangeHeader,则进行寻找流的处理(即是对流进行定位,调用seekStream方法,该方法在OnDemandServerMediaSubsession类中实现;同时,该方法的调用是在初始播放前及播放过程中由于用户拖动播放进度条而产生的系列请求)。
在OnDemandServerMediaSubsession类中,seekStream方法中调用了seekStreamSource方法,该方法在对应的媒体格式文件的FileServerMediaSubsession类中实现(如针对WAV格式,则在WAVAudioFileServerMediaSubsession类中实现;针对MP3格式,则在MP3AudioFileServerMediaSubsession类中实现)。
同理,OnDemandServerMediaSubsession类中的setStreamScale方法中所调用的setStreamSourceScale方法亦是类似的实现机制。
l Step 3:开始进行流式播放(调用startStream方法,该方法在OnDemandServerMediaSubsession类中实现)。
n Step 3.1:根据clientSessionId从fDestinationsHashTable中查找到destinations(包括了客户端的IP地址、RTP端口号、RTCP端口号等信息)。
n Step 3.2:调用startPlaying方法,在该方法中根据RTPSink或UDPSink分别调用startPlaying方法。
如果是调用RTPSink的startPlaying方法,则接着会调用MediaSink类中的startPlaying方法,并在该方法中调用MultiFramedRTPSink类中的continuePlaying方法,之后便是buildAndSendPacket了。这里已经来到重点了,即是关于不断读取Frame并Send的要点。在MultiFramedRTPSink类中,通过buildAndSendPacket、packFrame、afterGettingFrame、afterGettingFrame1、sendPacketIfNecessary和sendNext构成了一个循环圈,数据包的读取和发送在这里循环进行着。特别注意的是sendPacketIfNecessary方法中的后面代码(nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);),通过Delay amount of time后,继续下一个Task,并回过来继续调用buildAndSendPacket方法。
在packFrame方法中,正常情况下,需要调用getNextFrame方法(该方法在FramedSource类中,并且对不同媒体格式的Frame的获取出现在FramedSource类的getNextFrame方法中,通过调用doGetNextFrame方法来实现)来获取新的Frame。
如果是调用UDPSink的startPlaying方法,则接着会调用MediaSink类中的startPlaying方法,并在该方法中调用BasicUDPSink类中的continuePlaying方法。在这之后由若干个方法构成了一个循环圈:continuePlaying1、afterGettingFrame、afterGettingFrame1、sendNext。并在afterGettingFrame1方法中实现了packet的发送(fGS->output(envir(), fGS->ttl(), fOutputBuffer, frameSize);)。
Step 3.3:针对RTPSink创建RTCP instance(RTP与RTCP的配套使用决定了其必须这么做,否则可能就跟直接使用UDP发送数据包没什么两样了^_^),创建RTCP instance时,将incomingReportHandler句柄作为BackgroundHandlerProc,以便于处理RTCP的报告,并开始startNetworkReading。这里RTP/RTCP的使用方式有两种,一种建立在TCP之上,一种建立在UDP之上。
http://blog.csdn.net/huangxinfeng/archive/2010/03/11/5369391.aspx
http://blog.csdn.net/huangxinfeng/archive/2010/03/12/5374721.aspx
http://blog.csdn.net/huangxinfeng/archive/2010/03/22/5404233.aspx