美文网首页
Live555 源代码分析(五)

Live555 源代码分析(五)

作者: RonZheng2010 | 来源:发表于2020-09-23 08:25 被阅读0次

    1 主程序

    1.1 基本概念与实体

    下图展示主程序中涉及的主要概念与实体。

    MediaServer是服务器的抽象。

    • 它创建用于接受客户端连接的socket。它还是其他实体的容器。

    ClientConnection是与客户端的数据连接的抽象。

    • 当客户端连接服务器时,MediaServer创建ClientConnection的实例,保存在成员fClientConnections[]中。

    MediaSession是媒体的抽象。

    • 当用户请求建立媒体连接时,ClientConnection创建MediaSession实例,保存在MediaServer的成员fServerMediaSessions[]中。这是一个Hash表,key值是媒体的名字。

    媒体中可以有多个通道,MediaSubsession是媒体通道的抽象。

    • 创建MediaSession时,同时为其中的通道创建MediaSubsession实例,保存在成员fSubsessionHead指向的链表中。

    ClientSession是与客户端的对话的抽象,承载在ClientConnection上。

    • 当客户端请求开始播放时,创建ClientSession实例,保存在成员fClientSessions[]中。这是一个Hash表,key值是全局唯一的session id。

    StreamState是ClientSession用于挂接到MediaSubsession的中介。

    • ClientSession要挂接到MediaSession上,获得媒体源。它的成员fStreamStates[]引用MediaSession中的MediaSubsession实例。
    • fStreamStates[]是一个数组,这里的trackNum指它的索引。

    1.2 Main()

    Main()创建任务调度器,创建RTSPServer实例,将它的socket置于调度器的监听下,最后运行调度器,处理socket事件。

    • 调用BasicTaskScheduler::createNew()创建任务调度器,这是一个BasicTaskScheduler实例。

    • 引用调度器实例,创建BasicUsageEnvironment实例。

    • 调用DynamicRTSPServer::createNew(),创建RTSPServer实例。

      • 调用GenericMediaServer::setUpOurSocket()创建TCP socket。createNew()的参数指定本地端口号,这里先指定端口号554。但绑定可能失败,如果失败了,main()会再次调用createNew(),用端口号8554再重试一次。
      • 创建的socket保存在GenericMediaServer的成员fServerSocket中。
      • 调用TaskSheduler::turnOnBackgroundReadHandling()将socket置于监听状态,回调函数为GenericMediaServer::incomingConnectionHandler()。
    • 调用RTSPServer::rtspURLPrefix()得到URL信息,其中调用ourIPAddress()得到本地地址。这个信息用于输出给用户看,没其他用处。

    • 调用BasicTaskScheduler0::doEventLoop()调度任务。

    GenericMediaServer::setUpOurSocket()创建TCP socket。

    • 调用setupStreamSocket()创建TCP socket。
    • 调用increaseSendBufferTo()重新设置增加缓存大小。
    • 调用listen()开始监听。

    1.3 GenericMediaServer::incomingConnectionHandler()

    当有新的数据连接请求时,GenericMediaServer::incomingConnectionHandler()被调用。其中调用incomingConnectionHandlerOnSocket(),参数是成员fServerSocket。

    • 调用accept(),返回新数据连接的socket,同时得到客户端的地址。
    • 调用makeSocketNonBlocking()设置socket为非阻塞模式。
    • 调用increaseSendBufferTo()增加发送缓存大小。
    • 用新连接的socket和客户端地址,调用RTSPServerSupportingHTTPStreaming::createNewClientConnnection()。

    在createNewClientConnection()中,

    • 创建RTSPClientConnectionSupportingHTTPStreaming实例。
      • ClientConnection的成员fOurSocket保存新连接的socket值,成员fClientAddr保存客户端地址。
      • 调用TaskScheduler::turnOnBackgroundReadHanding(),将新连接的socket置于监听状态,回调函数为GenericMediaServer::incomingRequestHandler()。
    • 调用RTSPClientConnection::resetRequestBuffer()与接收缓存有关的位置指示变量。

    1.4 ClientConnection::incomingRequestHandler()

    当数据连接有数据到达时,ClientConnection::incomingRequestHandler()被调用。

    • 调用readSocket()读取数据,这是RTCP请求字符串。这里使用的接收缓存是成员fRequestBuffer。

    • 调用RTSPClientConnection::handleRequestBytes()解析RTCP请求并处理。

      • 调用parseRTSPRequestString()解析请求字符串,得到RTCP命令字及参数,包括:
      • cmdName是请求名称。
      • urlSuffix是文件名
      • cseq是请求序列号。回复的字符串应该包括这个序列号,这样请求者能将回应和请求一一对应。
      • SessionIdStr是client session的编号。
    • 后面根据请求名称,调用不同的函数处理。如handleCmd_OPTIONS()处理OPTIONS请求等。

    • 对于SETUP请求,这里会创建ClientSession实例。

    • 对于SETUP请求之后的请求,parseRTSPRequestString()应该得到有效的sessionIdStr,调用GenericMediaServer::lookupClientSession()应该得到对应的ClientSession实例。然后调用RTSPClientSession::handleCmd_withinSession(),其中再调用相应的处理函数。

    1.4.1.请求OPTIONS

    OPTIONS的请求字符串如下:

    OPTIONS rtsp://192.168.2.133:8554/jzl.mp3 
    RTSP/1.0\r\n
    CSeq: 2\r\n
    User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)\r\n\r\n
    

    调用handleCmd_OPTIONS()处理。

    • 它的工作只是填充回复字符串,告诉客户端支持那些请求。
    RTSP/1.0 200 OK\r\n
    CSeq: 2\r\n
    Date: Tue, Apr 28 2020 02:37:34 GMT\r\n
    Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER\r\n\r\n
    

    1.4.2 请求DESCRIBE

    DESCRIBE的请求字符串如下:

    DESCRIBE rtsp://192.168.2.133:8554/jzl.mp3 
    RTSP/1.0\r\n
    CSeq: 3\r\n
    User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)\r\n
    Accept: application/sdp\r\n\r\n
    

    调用handleCmd_DESCRIBE()处理。

    • 调用DynamicRTSPServer::lookupServerMediaSession()查找ServerMediaSession实例,因为这时实例还不存在,所以这里会先创建一个。
    • 用这个ServerMediaSeesin实例调用generateSDPDescription()。
      • 调用ourIPAddress()得到本地地址。
      • 构建合适的SDP前缀字符串,本地地址是它的一个组成部分。
      • 遍历ServerMediaSession的成员fSubsessionHead,对其中的子session,调用OnDemandServerMediaSubsession::sdpLines()得到它的sdp字符串。
      • 连接sdp前缀字符串和sdp字符串。
    • 调用RTSPServer::rtspURL()得到URL信息。这是头部字符串的一部分。
    • 组合所有上述信息成为回复字符串。

    如下是DESCRIBE请求的回复字符串。

    • 第一部分是头部字符串,第二部分是SDP前缀字符串,第三部分是SDP字符串。
    • 这里只有一个子session,”a=control:track1”中的track1是它的通道名字。
    RTSP/1.0 200 OK\r\n
    CSeq: 3\r\n
    Date: Tue, Apr 28 2020 06:31:55 GMT\r\n
    Content-Base: rtsp://192.168.2.133:8554/jzl.mp3/\r\n
    Content-Type: application/sdp\r\n
    Content-Length: 391\r\n\r\n
    v=0\r\n
    o=- 1588055515931667 1 IN IP4 192.168.2.133\r\n
    s=MPEG-1 or 2 Audio, streamed by the LIVE555 Media Server\r\n
    i=jzl.mp3\r\n
    t=0 0\r\n
    a=tool:LIVE555 Streaming Media v2019.12.30\r\n
    a=type:broadcast\r\n
    a=control:*\r\n
    a=range:npt=0-232.590\r\n
    a=x-qt-text-nam:MPEG-1 or 2 Audio, streamed by the LIVE555 Media Server\r\n
    a=x-qt-text-inf:jzl.mp3\r\n
    m=audio 0 RTP/AVP 14\r\n
    c=IN IP4 0.0.0.0\r\n
    b=AS:128\r\n
    a=control:track1\r\n
    

    DynamicRTSPServer::lookupServerMediaSessin()根据key值,在GenericMediaServer的成员fSerMediaSessions中查找对应的实例。fServerMediaSessesions是一个Hash表。这里的key值是文件名“jzl.mp3”。

    • 调用fopen()打开文件,得到文件指针。
    • 调用全局函数createNewSMS()创建新的MediaSession实例。并调用GenericMediaServer::addServerMediaSession()将它加入成员fServerMediaSesion。

    在全局函数createSMS()中,

    • 从文件名jzl.mp3中,解析出扩展名“.mp3”。根据扩展名做不同处理。 对于扩展名“.mp3”,++ 调用NEW_SMS(),创建ServerMediaSession实例。
      • 调用MP3AudioFileServerMediaSubsession::createNew()。它创建ServerMediaSubsession实例,同时把文件名jzl.mp3保存在成员fFileName中。后面创建MP3FileSource实例时将用到它。
      • 把这个ServerMediaSubsession加入ServerMediaSession的子session表。ServerMediaSession的成员fSubsesisionHead和fSubsessionTail指向这个表的首部和尾部。

    OnDemandServerMediaSubsession::sdpLines()得到子session的sdp字符串。

    • 调用createNewStreamSource()创建FramedSource实例,
    • 调用createGroupsock()创建Groupsock实例
    • 基于FramedSource和Groupsock实例,调用createNewRTPSink()创建RTPSink实例。
    • 调用setSDPLinesFromRTPSink()。其中调用RTPSink的接口构建sdp字符串。
    • 清理掉以上创建的所有FramedSource、Groupsock、和RTPSink实例。这时只有ServerMediaSession和ServerMediaSubsession的实例保留下来。

    1.4.3 请求SETUP

    SETUP的请求字符串如下。它请求jzl.mp3文件的通道track1。

    SETUP rtsp://192.168.2.133:8554/jzl.mp3/track1 RTSP/1.0\r\n
    CSeq: 4\r\n
    User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)\r\n
    Transport: RTP/AVP;unicast;client_port=65512-65513\r\n\r\n
    

    这里先调用createNewClientSessionWithId()创建ClientSession实例,再调用handleCmd_SETUP()处理。注意这里的handleCmd_SETUP()从属于RTSPClientSession(是它的成员函数),而前面的handleCmd_DESCRIBE()从属于RTSPClientConnection。

    createNewClientSessionWithId()实现如下:

    • 调用our_random32()得到一个随机数。
    • 用这个随机数调用RTSPServer::createNewClientSession()。实际上其中创建了一个RTSPClientSession实例。
      • ClientConnection的构造函数调用TaskScheduler::resheduleDelayedTask()设置延时任务,回调函数是ClientSession::LivenessTimeoutTask()。这个函数的作用是在这个Client session实例长时间不工作时,删除它。
    • 将这个实例加入GenericMediaServer的成员fClientSessions,这是一个ClientSession实例的Hash表。
    • our_random32()实际上在一个循环中调用的,用lookupClientSession()查找这个随机数,看是不是已经使用。这样一直找到不重复的随机数为止。

    RTSPClientSession::handleCmd_SETUP()的实现如下。

    • 请求字符串中的“jzl.mp3/track1”被分解为它的两个参数:参数urlPreSuffix是“jzl.mp3”,参数urlSuffix是“track1”。
    • 用“jzl.mp2”调用lookupServerMediaSession(),找到对应的ServerMediaSession实例。这个实例在handleCmd_DESCRIBE()中已经创建好了。
    • 调用numSubsessions()得到ServerMediaSession中的子session数量。
    • 新建streamState数组fStreamStates[],长度与子Session数量相同。
    • 遍历子session链表,依次设置streamStates[]的成员subsession引用ServerMediaSession的成员fSubsessionsHead链表中的元素。
      • 同时比较subsession的trackId成员与“track1”是否相同。如果是,则记住它的trackNum。
    • 调用parseTransportHeader()解析得到传输头中包括的数据流配置信息。传递给它的是trackNum引用的streamState实例。
      • 传输头包括在子字符串“RTP/AVP;unicast;client_port=65512-65513”中。这里指定数据流采用RTP/AVP协议,单播UDP,RTP连接的客户端端口为65512,RTCP连接的客户端端口为65513。
      • 流的目标组播地址可以与发起请求的客户端地址不同。如果不同,则传输头中需要包含“destination=xxx”指定这一点。
    • 调用parseRangeHeader()解析得到点播范围。如果失败,则再调用parsePlayNowHeader()看能不能找到。这里没有设置点播范围,两个函数都返回失败结果,所以从头开始播到尾。
    • 用前面找到的子session,调用OnDemandSererMediaSubsession::getStreamParameters()创建数据流通道。
      • 与子sesssion对应的streamState实例,它的成员streamToken,是这个函数的一个参数。OnDemandServerMediaSubsession实例将它当做辅助数据结构,它被定义为类StreamState,与streamState只是首字母不同。
    • 构造回复字符串。

    以下是handleCmd_SETUP()的回复字符串。它的传输头部指定了收发两端的地址和端口,同时还指定了Session编号0x4F1FB7E7。

    RTSP/1.0 200 OK\r\n
    CSeq: 4\r\n
    Date: Wed, Apr 29 2020 08:30:41 GMT\r\n
    Transport: RTP/AVP;unicast;destination=192.168.2.128;source=192.168.2.133;client_port=63032-63033;server_port=6970-6971\r\n
    Session: 4F1FB7E7;timeout=65\r\n\r\n
    

    getStreamParameters()创建数据通道。

    • 它的参数clientAddress指定了目标地址。这里因为请求中没有包括“destination=xxx”,所以这个地址是0。这时将目标地址指定ClientConnection的客户端地址。
    • 调用createNewStreamSource()创建FramedSource实例。这是包括MP3FileSource在内的链,使用的MP3文件名为MP3AudioServerMediaSubsession的成员fFileName,在handleCmd_DESCRIBE()中已经指定为jzl.mp3。
    • 创建用于RTP连接和RTCP连接的Groupsock实例。
      • 从成员fInitialPortNum指定的起始端口开始,尝试可用的本地端口,作为RTP连接的本地端口。fInitialPortNum在OnDemandServerMediaSubsession的构造函数中,指定缺省值为6970。
      • 调用createGroupsock()创建RTP连接的Groupsock实例。
      • 如果不复用RTCP连接和RTP连接,则将RTCP连接的端口加1,创建RTCP连接的Groupsock实例。
    • 为RTP连接,调用MP3AudioFileServerMediaSession::createNewRTPSink()创建RTPSink实例。
    • 基于上述Groupsock和RTPSink实例,创建StreamState实例。
    • 创建Destinations实例,保存RTP连接和RTCP连接的目标端口地址,这里分别是63032和63033。将Destination实例加入成员fDestinationHashTable中。

    1.4.4 请求PLAY

    PLAY的请求字符串如下。它请求jzl.mp3文件,ClientSession的编号是0xC47271EC。

    这个session在之前处理SETUP请求时已经创建了。这里先用它查询先前创建的ClientSession实例,再调用RTSPClientSession::handleCmd_PLAY()。

    PLAY rtsp://192.168.2.133:8554/jzl.mp3/ RTSP/1.0\r\n
    CSeq: 5\r\n
    User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)\r\n
    Session: C47271EC\r\n
    Range: npt=0.000-\r\n\r\n\
    000port=53834-53835\r\n\r\n
    

    RTSPClientSession::handleCmd_PLAY()的实现如下。

    • 调用parseScaleHeader()和parseRangeHeader(),得到播放的起点和终点。
    • 遍历成员fNumStreamStates,对其中streamState实例的成员subsession,调用OnDemandServerMediaSubsession::seekStream(),调整播放位置。
    • 遍历成员fNumStreamStates,对其中streamState实例的成员subsession,调用OnDemandServerMediaSubsession::startStream(),开始播放。

    OnDemandServerMediaSubsession::seekStream()的工作如下:

    • 调用MP3AudioFileServerMediaSubsession::seekStreamSource()在媒体源中重新定位。

    OnDemandServrMediaSubsession::startStream()的工作如下:

    • 根据ClientSession的Id在成员fDestinationHashTable中查找对应的Destinations实例,其中保存了目标组播地址。
    • 调用StreamState::startPlaying()开始播放。
      • 这个StreamState实例是在处理SETUP请求时创建的, streamState结构的成员streamToken保存了这个实例。
      • 调用OnDemandServerMediaSubsession::createRTCP()创建RTCPInstance实例。这里指定RTPSink实例,和RTCP连接的Groupsock实例作为参数。并调用setAppHandler()和setSpecificRRHandler()设置回调函数。
      • 调用MediaSink::startPlaying(),开始解析媒体帧并通过RTP连接发送,同时RTCPInstance也通过RTCP连接发送RTCP信息。

    以下是handleCmd_PLAY()的回复字符串。

    RTSP/1.0 200 OK\r\n
    CSeq: 5\r\n
    Date: Wed, May 06 2020 07:59:07 GMT\r\n
    Range: npt=0.000-\r\n
    Session: C47271EC\r\n
    RTP-Info: url=rtsp://192.168.2.151:8554/jzl.mp3/track1;seq=15637;rtptime=133202039\r\n\r\n
    

    1.4.5 连接图

    如下是最后得到的连接图。

    • Live555服务器端IP地址为192.168.2.133,而客户端(这里使用VLC Media Player)IP地址为192.168.2.139。
    • Live555创建TCP socket,绑定端口554或8554监听。
    • VLC客户端本地端口xxx,连接Live555的TCP socket。后者接收请求,与客户创建TCP连接(用绿色标注),本地端口yyy。xxx和yyy由协议层自动确定。
    • 客户端通过TCP连接发起请求,指定自己的RTP和RTCP连接的端口分别是65512和65513。
    • 服务器端为RTP流和RTCP流分别创建UDP socket,本地端口分别为6970和6971。
    • 服务器通过RTP流发送数据帧,通过RTCP流发送RTP流的信息。

    相关文章

      网友评论

          本文标题:Live555 源代码分析(五)

          本文链接:https://www.haomeiwen.com/subject/fhopyktx.html