live555学习 转载 + 原创

LIVE555不支持实时流 需要自己实现

 

1. RTSP连接的建立过程
      RTSPServer类用于构建一个RTSP服务器,该类同时在其内部定义了一个RTSPClientSession类,用于处理单独的客户会话。
      首先创建RTSP服务器(具体实现类是DynamicRTSPServer),在创建过程中,先建立Socket(ourSocket)在TCP的554 端口进行监听,然后把连接处理函数句柄(RTSPServer::incomingConnectionHandler)和socket句柄传给任务调度器(taskScheduler)。
      任务调度器把socket句柄放入后面select调用中用到的socket句柄集(fReadSet)中,同时将socket句柄和 incomingConnectionHandler句柄关联起来。接着,主程序开始进入任务调度器的主循环(doEventLoop),在主循环中调用系统函数select阻塞,等待网络连接。
      当RTSP客户端输入(rtsp://192.168.0.1/1.mpg)连接服务器时,select返回对应的scoket,进而根据前面保存的对应关系,可找到对应处理函数句柄,这里就是前面提到的incomingConnectionHandler了。在 incomingConnectionHandler中创建了RTSPClientSession,开始对这个客户端的会话进行处理。

2. DESCRIBE请求消息处理过程
      RTSP服务器收到客户端的DESCRIBE请求后,根据请求URL(rtsp://192.168.0.1/1.mpg),找到对应的流媒体资源,返回响应消息。live555中的ServerMediaSession类用来处理会话中描述,它包含多个(音频或视频)的子会话描述 (ServerMediaSubsession)。
      RTSP服务器收到客户端的连接请求,建立了RTSPClientSession类,处理单独的客户会话。在建立RTSPClientSession的过程中,将新建立的socket句柄(clientSocket)和RTSP请求处理函数句柄 RTSPClientSession::incomingRequestHandler传给任务调度器,由任务调度器对两者进行一对一关联。
      当客户端发出RTSP请求后,服务器主循环中的select调用返回,根据socket句柄找到对应的incomingRequestHandler,开始消息处理。先进行消息的解析,如果发现请求是DESCRIBE则进入handleCmd_DESCRIBE函数。根据客户端请求URL的后缀(如 1.mpg),调用成员函数DynamicRTSPServer::lookupServerMediaSession查找对应的流媒体信息 ServerMediaSession。如果ServerMediaSession不存在,但是本地存在1.mpg文件,则创建一个新的 ServerMediaSession。在创建ServerMediaSession过程中,根据文件后缀.mpg,创建媒体MPEG-1or2的解复用器(MPEG1or2FileServerDemux)。再由MPEG1or2FileServerDemux创建一个子会话描述 MPEG1or2DemuxedServerMediaSubsession。最后由ServerMediaSession完成组装响应消息中的SDP信息(SDP组装过程见下面的描述),然后将响应消息发给客户端,完成一次消息交互。
SDP消息组装过程:
      ServerMediaSession负责产生会话公共描述信息,子会话描述由 MPEG1or2DemuxedServerMediaSubsession产生。 MPEG1or2DemuxedServerMediaSubsession在其父类成员函数 OnDemandServerMediaSubsession::sdpLines()中生成会话描述信息。在sdpLines()实现里面,创建一个虚构(dummy)的FramedSource(具体实现类为MPEG1or2AudioStreamFramer和 MPEG1or2VideoStreamFramer)和RTPSink(具体实现类为MPEG1or2AudioRTPSink和 MPEG1or2VideoRTPSink),最后调用setSDPLinesFromRTPSink(...)成员函数生成子会话描述。

3. SETUP请求消息处理过程
        RTSPClientSession类用于处理单独的客户会话。其类成员函数handleCmd_SETUP()处理客户端的SETUP请求。调用 parseTransportHeader()对SETUP请求的传输头解析,调用子会话(这里具体实现类为 OnDemandServerMediaSubsession)的getStreamParameters()函数获取流媒体发送传输参数。将这些参数组装成响应消息,返回给客户端。
        获取发送传输参数的过程:调用子会话(具体实现类MPEG1or2DemuxedServerMediaSubsession)的 createNewStreamSource(...)创建MPEG1or2VideoStreamFramer,选择发送传输参数,并调用子会话的 createNewRTPSink(...)创建MPEG1or2VideoRTPSink。同时将这些信息保存在StreamState类对象中,用于记录流的状态。
        客户端发送两个SETUP请求,分别用于建立音频和视频的RTP接收。
4. PLAY请求消息处理过程
      RTSPClientSession类成员函数handleCmd_PLAY()处理客户端的播放请求。首先调用子会话的startStream(),内部调用MediaSink::startPlaying(...),然后是 MultiFramedRTPSink::continuePlaying(),接着调用 MultiFramedRTPSink::buildAndSendPacket(...)。buildAndSendPacke内部先设置RTP包头,内部再调用MultiFramedRTPSink::packFrame()填充编码帧数据。
      packFrame内部通过FramedSource::getNextFrame(), 接着MPEGVideoStreamFramer::doGetNextFrame(),再接着经过 MPEGVideoStreamFramer::continueReadProcessing(), FramedSource::afterGetting(...), MultiFramedRTPSink::afterGettingFrame(...), MultiFramedRTPSink::afterGettingFrame1(...)等一系列繁琐调用,最后到了 MultiFramedRTPSink::sendPacketIfNecessary(), 这里才真正发送RTP数据包。然后是计算下一个数据包发送时间,把MultiFramedRTPSink::sendNext(...)函数句柄传给任务调度器,作为一个延时事件调度。在主循环中,当MultiFramedRTPSink::sendNext()被调度时,又开始调用 MultiFramedRTPSink::buildAndSendPacket(...)开始新的发送数据过程,这样客户端可以源源不断的收到服务器传来的RTP包了。
发送RTP数据包的间隔计算方法:
        Update the time at which the next packet should be sent, based on the duration of the frame that we just packed into it.

DESCRIBE:

DECRIBE命令的处理过程比较复杂,这里简单的概括一下
1)创建了session及subsession,一个媒体文件将对应一个session,媒体文件中的每一个流对应一个subssion。session中,记录了一个subsession的链表。
2)为了获取SDP信息,做了大量的工作,不但创建了sink、source等实例, 还需要从媒体文件中获取信息。需要注意的是,这里创建的sink、source只是临时的,只是为了获取SDP信息而存在

  1.         //跟据流的名字查找ServerMediaSession,如果找不到,会创建一个。每个ServerMediaSession中至少要包含一个  
  2.         //ServerMediaSubsession。一个ServerMediaSession对应一个媒体,可以认为是Server上的一个文件,或一个实时获取设备。其包含的每个ServerMediaSubSession代表媒体中的一个Track。所以一个ServerMediaSession对应一个媒体,如果客户请求的媒体名相同,就使用已存在的ServerMediaSession,如果不同,就创建一个新的。一个流对应一个StreamState,StreamState与ServerMediaSubsession相关,但代表的是动态的,而ServerMediaSubsession代表静态的。  
  3.         ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix); 

 

SETUP命令概述

SETUP命令,主要用于协商客户端与服务器的通信细节,如通信协议、地址等等,SETUP请求中最重要的是"Transport"头部。

客户端需要对,文件中的每一个流发送一个SETUP命令。

客户端还可以通过其中的"destination"属性来重定向RTP数据的接收地址,不过这是需要服务器支持的,在live555中需要定义宏RTSP_ALLOW_CLIENT_DESTINATION_SETTING。

SETUP的响应中,包含一个"Session"头部,这是服务器产生的一个随机数,用于标识特定的客户端。

PLAY命令概述

PLAY命令要求在SETUP命令之后进行,此命令处理过程中就开始发送数据了,在处理PLAY命令过程中还创建了RTCPInstance实例。

客户端可以通过PLAY命令的Scale头部域,指定播放速率,不过这个功能要看服务器对特定媒体的具体实现,当sacale=1时正常播放,sacale>1时快进,sacale<0时快退。

客户端可以通过PLAY命令的Range头部域,指定播放的时间范围,同样此功能也依赖于服务器中特定媒体的具体实现。

对于PLAY命令请求中的URL有以下几种情况(与PAUSE、TEARDOWN、GET_PARAMETER、SET_PARAMETER处理是一样的):
1)非聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix,urlPreSuffix作为stream name, urlSuffix作为subsession的trackId
2)非聚合的情况下,才能根据trackId找到subsession
3)聚合,如
    rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlSuffix作为stream name,而urlPreSuffix忽略
    rtsp://192.168.1.1/urlPreSuffix, 只存在urlPreSuffix,并将其作为stream name, 这应该是最常见的情况
4)聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlPreSuffix/urlSuffix整个作为stream name 
我们可以对session中的subsession进行单独控制(这需要提供subsession的trackId), 也可以对整个session进行控制(这种情况应该是最常见的吧)。

打开文件:

createNewStreamSource

->FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
->ByteStreamFileSource::createNew(envir(), fFileName);

->FILE* fid = OpenInputFile(env, fileName);

->fid = fopen(fileName, "rb");):

char const*
OnDemandServerMediaSubsession::sdpLines() {
  if (fSDPLines == NULL) {
    // We need to construct a set of SDP lines that describe this
    // subsession (as a unicast stream).  To do so, we first create
    // dummy (unused) source and "RTPSink" objects,
    // whose parameters we use for the SDP lines:
    unsigned estBitrate;
    FramedSource* inputSource = createNewStreamSource(0, estBitrate);
    if (inputSource == NULL) return NULL; // file not found

    struct in_addr dummyAddr;
    dummyAddr.s_addr = 0;
    Groupsock dummyGroupsock(envir(), dummyAddr, 0, 0);
    unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
    RTPSink* dummyRTPSink
      = createNewRTPSink(&dummyGroupsock, rtpPayloadType, inputSource);

    setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);
    Medium::close(dummyRTPSink);
    closeStreamSource(inputSource);
  }

  return fSDPLines;
}


实时传输:

重载FrameSource,写一个服务类,可以从FrameSource的派生类读取帧数据,转发给live555.就可以了

可以参照http://www.cnblogs.com/mlj318/archive/2013/01/23/2872932.html

http://blog.csdn.net/ghostyu/article/details/7396854


 

 

你可能感兴趣的:(live555学习 转载 + 原创)