欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LIVE555-媒体流建立(3)

程序员文章站 2022-07-07 11:54:30
...

前一篇文章讲解了live555的RTSP的创建流程,接下来我将对流媒体的创建过程进行讲解。

目录

1.基本概念了解

2.OPTIONS请求

3.DESCRIBE请求

4.SETUP请求


1.基本概念了解

在讲解这些之前,咱们先熟悉一下live555中的一些基本类的概念和相互之间的关系:

LIVE555-媒体流建立(3)

MediaServer:媒体服务器。一般每个程序中只有一个媒体服务器,该对象是我们运行程序的最高长官,把控全局,其内部记录了所有的clientsession、CllientConnection和ServerMediaSession等对象,其派生了很多子类,例如上一章的rtspServer、以及在范例中自定义的DynamicRTSPServer等等。

ClientConnection:客户端连接对象。通过上一篇文章,可以知道每当客户端连接上服务器后就会创建一个连接对象,负责与客户端之间的数据交互,此处不在赘述,详细可以参见上篇文字(live555- RTSP创建)。

ServerMediaSession(简称:SMS):流媒体会话。当客户端请求播放某个流媒体(test.ts)时,live555就会查找此媒体会话,若不存在则创建新的流媒体会话。一个媒体(文件或直播码流)对应这一个SMS,SMS在MediaServer中是通过流媒体名称进行区分的,所以每个流媒体文件只有一个SMS,负责处理多个客户端的流媒体发送,每个会话中有一个或多个ServerMediaSubSession(通道),实际负责处理媒体流数据传输的是subSession,SMS相当于一个外壳容器。

ServerMediaSubSession:流媒体子会话(通道)。流媒体子会话可以理解为媒体传输通道,例如视频通道、音频通道等,一个SMS中可能包含多个媒体通道。每个媒体通道负责数据的传输控制。

StreamState:媒体流。媒体流包含媒体发送和包装的RTPsink,以及读取媒体文件或码流的source,媒体流保存在clientsession中,一个subsession的通道下可能存在多个媒体流(一个客户端会话对应一个媒体流),也可以设置多个客户端使用一个媒体流。

ClientSession:客户端会话。每当客户端与媒体服务器建立连接(SETUP)后,就会创建一个会话对象,当媒体流传输完成后就会销毁此对象。与网络中的会话机制类似,用户登录后就创建会话,直到登出会话结束。

客户端会话有一个独一无二的sessionId,以确保此会话独一无二,后续与客户端交互都会带着Sessionid。

客户端会话保存着此会话中的媒体流(StramState),通过媒体流就行流媒体产生和推送。

 


ok 熟悉了上面的概念后,咱们就可以继续进行媒体流创建。后面以VLC作为客户端请求test.264文件,通过wireshark抓包分析媒体流的创建过程。

2.OPTIONS请求

首先客户端发起options请求,询问服务器支持那些请求命令,

客户端请求命令抓包截图如下:

LIVE555-媒体流建立(3)

服务器收到信息后进入TSPServer::RTSPClientConnection::handleRequestBytes()函数中,主要进行以下几步:

1)解析请求字符串内容。

2)获得请求命令字符串OPTIONS.

3)进入OPTIONS命令处理函数RTSPClientConnection::handleCmd_OPTIONS(),获得应答字符串。

4)发送应答命令。

服务器应答命令抓包如下:

LIVE555-媒体流建立(3)

3.DESCRIBE请求

describe请求从服务器获取流媒体文件格式信息和传输信息,

客户端请求命令抓包如下:

LIVE555-媒体流建立(3)

服务器收到信息后进入TSPServer::RTSPClientConnection::handleRequestBytes()函数中,主要进行以下几步:

1)解析请求命令DESCRIBE,进入函数RTSPClientConnection::handleCmd_DESCRIBE(),代码简化如下:

RTSPClientConnection:handleCmd_DESCRIBE(char const* urlPreSuffix, 
char const* urlSuffix, char const* fullRequestStr) 
{
	ServerMediaSession* session = NULL;
    //! 1.查找对应的SMS,不存在则终止
	session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
	
    //! 2.生成对应的SDP信息,不存在则种植
       sdpDescription = session->generateSDPDescription();
  
    //! 3.拼装应答语句
	snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,...)
}

在DynamicRTSPServer就是重载了lookupServerMediaSession函数,根据不同的媒体流文件创建对应的媒体流会话(SMS),从而实现文件rtsp服务器,找到SMS后就是通过SMS获取SDP描述。其中generateSDPDescription函数主要用于拼装SDP信息,至于SDP大家可以自行了解,需要关注的是此函数调用了 subsession->sdpLines()产生部分SDP信息。下面列出此函数简化代码:

char const* OnDemandServerMediaSubsession::sdpLines() {
    //1.创建临时的源对象
    unsigned estBitrate;
    FramedSource* inputSource = createNewStreamSource(0, estBitrate);
    
    //2.创建临时的sink对象
    RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
  
    //!3.获取SDP信息
    setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);

    //!4.删除临时的source和sink对象
    Medium::close(dummyRTPSink);
    delete dummyGroupsock;
    closeStreamSource(inputSource);
}

此处主要注意的是此处创建的source和sink只是临时的,用完后会立刻删除,主要用于生成SDP信息。

 OK,生成SDP信息后,返回describe应答。

  • 服务器应答命令如下:

LIVE555-媒体流建立(3)

从上图可以看到contorl为通道track1, 其余sdp信息自行学习。 

4.SETUP请求

setup请求,用于与服务器建立会话,确定数据流传输方式。

客户端请求令如下:

LIVE555-媒体流建立(3)

可以看到此处与前面几个命令不同之处是uri增加了通道信息track1。Transport参数设置了传输模式,包的结构:UDP传输,单播,以及RTP和RTCP客户端所有端口。请求命令讲解到此,接下来是服务器的响应操作:

服务器收到信息后进入TSPServer::RTSPClientConnection::handleRequestBytes()函数中,主要进行以下几步:

1)在rtspServrer中查找clientsession,若未找到则创建CLientSession,其中每个ClientSession会创建一个对应的SessionId,其在rtspServrer中是唯一的,然后跳转到ClientSession的函数handleCmd_SETUP中。

2)接下来我们分析ClientSession的函数handleCmd_SETUP的函数

void RTSPClientSession::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) 
{
	//1.根据流名 查找SMS
       fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName, 
                           fOurServerMediaSession == NULL);
	
	//!2.将SMS中所有通道记录到结构体streamState数组中
	fNumStreamStates = fOurServerMediaSession->numSubsessions();
	fStreamStates = new struct streamState[fNumStreamStates];

	ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
	ServerMediaSubsession* subsession;
	for (unsigned i = 0; i < fNumStreamStates; ++i)
       {
		subsession = iter.next();
		fStreamStates[i].subsession = subsession;
		fStreamStates[i].tcpSocketNum = -1; 
		fStreamStates[i].streamToken = NULL; //在getStreamParameters赋值
	}
	

	//3.根据通道信息查找对应的SUbsussesion和trackNum
	ServerMediaSubsession* subsession = ....
	unsigned trackNum ....

         //4.继续解析报文,clientPort,udp或tcp,是否创建后就播放等信息
	.........此处主要描述UDP创建的RTP,
		Port clientRTPPort(clientRTPPortNum);
		Port clientRTCPPort(clientRTCPPortNum);

        //5.通过subsession创建对应的媒体流
	subsession->getStreamParameters(fOurSessionId, ourClientConnection->
            fClientAddr.sin_addr.s_addr,
			clientRTPPort, clientRTCPPort,
			fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
			destinationAddress, destinationTTL, fIsMulticast,
			serverRTPPort, serverRTCPPort,
			fStreamStates[trackNum].streamToken);
	
        //6.拼装应答文本
}

通过上述代码及本文开头类图,可以看出其中存在一个streamState结构体和StreamState类,注意区分,在streamState结构体中的一个成员streamToken指向StreamState对象指针,结构体声明如下:

  struct streamState {
      ServerMediaSubsession* subsession;
      int tcpSocketNum;
      void* streamToken;
    } * fStreamStates;

言归正传,clientSession在解析handleCmd_SETUP函数中,首先找到对应的sms和subsession,然后通过subsession的函数getStreamParameters()对媒体流信息进行赋值(创建),可以看到该函数在基类中是纯虚函数,真正的实现实在子类中实现

LIVE555-媒体流建立(3)

接下来进入OnDemandServerMediaSubsession的重载函数,其简化代码如下所示:

void OnDemandServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
		      netAddressBits clientAddress,
		      Port const& clientRTPPort,
		      Port const& clientRTCPPort,
		      int tcpSocketNum,
		      unsigned char rtpChannelId,
		      unsigned char rtcpChannelId,
		      netAddressBits& destinationAddress,
		      u_int8_t& /*destinationTTL*/,
		      Boolean& isMulticast,
		      Port& serverRTPPort,
		      Port& serverRTCPPort,
		      void*& streamToken) {

//!1.如果重用流则直接返回上次的流对象的信息
  if (fLastStreamToken != NULL && fReuseFirstSource) {
    // Special case: Rather than creating a new 'StreamState',
    // we reuse the one that we've already created:
    serverRTPPort = ((StreamState*)fLastStreamToken)->serverRTPPort();
    serverRTCPPort = ((StreamState*)fLastStreamToken)->serverRTCPPort();
    ++((StreamState*)fLastStreamToken)->referenceCount();
    streamToken = fLastStreamToken;
  } 
  else {
  //!2.创建此ClientSession的Source
    FramedSource* mediaSource
      = createNewStreamSource(clientSessionId, streamBitrate);

    //!3.创建SINk
	rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
	if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0) streamBitrate = rtpSink->estimatedBitrate();
      }


    // 4.创建媒体流StreamState
    streamToken = fLastStreamToken
      = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
			streamBitrate, mediaSource,
			rtpGroupsock, rtcpGroupsock);
}
}

 通过上述代码可以看出,ClientSession中保存着对应流媒体的stramState数组,其中Clientsession使用的通道streamState.streamToken对象是隶属于此Clientsession会话的,如果未设置公用则,没个会话都会创建一个Source,一个SINK,一个StreamState(其中记录source和SINK)信息,至于sink和source我会在后续文章中讲解。

创建媒体流后,就基本完成setup信息了,回退到上一个函数进行应答信息拼装。

服务器应答命令如下:

LIVE555-媒体流建立(3)

Transport:说明UDP方式,非多播,目标地址,源地址以及RTP、RTCP的客户端端口和服务的端口

Session:ClientSessionID和超时信息。

5.PLAY请求

play请求,用于请求服务器播放视频,开始媒体流传输。

客户端请求命令如下:

LIVE555-媒体流建立(3)

可以看到客户端发送PLAY命令中带着sessionid信息,Range是播放时间段。

服务器收到信息后进入TSPServer::RTSPClientConnection::handleRequestBytes()函数中,主要进行以下几步:

1)在rtspServrer中查找clientsession,然后跳转到ClientSession的函数handleCmd_SETUP中。

2)在handleCmd_SETUP函数中,首先查找对应的媒体流,进行播放范围的偏移,然后调用fStreamStates[i].subsession->startStream开始播放,最终跳转道void StreamState::startPlaying函数中,其代码如下:

void StreamState
::startPlaying(Destinations* dests, unsigned clientSessionId,
	       TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
	       ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
	       void* serverRequestAlternativeByteHandlerClientData) {
  if (dests == NULL) return;

  //!1.创建RTCP实例
    fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char*)fMaster.fCNAME, fRTPSink);
        // Note: This starts RTCP running automatically
    fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData);
  }

   //!2.TCP处理
 
  
   //!3.设置目标地址
   
   //!4.发送RTCP信息
  if (fRTCPInstance != NULL) {
    // get RTCP-synchronized presentation times immediately:
    fRTCPInstance->sendReport();


    //!5.开始播放媒体流
      fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
      fAreCurrentlyPlaying = True;

}

PRTsink播放视频不再本文讲述之内,后续文章会继续讲解,至此服务器就开始推流了,后面快进、停止等命令就不在详解了基本一致,最后列出play的应答命令。

服务器应答命令如下:

LIVE555-媒体流建立(3)

相关标签: 流媒体