live555源码分析(五)DESCRIBE请求的处理

live555源码分析系列

live555源码分析(一)live555初体验

live555源码分析(二)基本组件上

live555源码分析(三)基本组件下

live555源码分析(四)RTSPServer分析

live555源码分析(五)DESCRIBE请求的处理

live555源码分析(六)SETUP和PLAY请求的处理

live555源码分析(七)播放过程

live555源码分析(八)多播

live555源码分析(五)DESCRIBE请求的处理

文章目录

  • live555源码分析(五)DESCRIBE请求的处理
    • 一、源码分析
    • 二、总结

一、源码分析

本文续接上文,将分析DESCRIBE请求的处理过程

DESCRIBE的处理主要是返回sdp文件,接下来将重点讲解

handleCmd_DESCRIBE处理函数的内容大致如下

void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
	ServerMediaSession* session = NULL;

	/* 找到会话 */
	session = fOurServer.lookupServerMediaSession(urlTotalSuffix);	
	
	/* 获取sdp信息 */
	sdpDescription = session->generateSDPDescription();

	/* 返回结果 */
	snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	     "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
	     "%s"
	     "Content-Base: %s/\r\n"
	     "Content-Type: application/sdp\r\n"
	     "Content-Length: %d\r\n\r\n"
	     "%s",
	     fCurrentCSeq,
	     dateHeader(),
	     rtspURL,
	     sdpDescriptionSize,
	     sdpDescription);
}

首先会根据url中指定的会话从服务器中找到会话(rtsp://ip:port/session中的session字段)

然后通过会话的generateSDPDescription函数获取sdp信息

接下来看generateSDPDescription函数的定义

char* ServerMediaSession::generateSDPDescription() {
    if (fIsSSM) { //多播?
		char const* const sourceFilterFmt =
     		 "a=source-filter: incl IN IP4 * %s\r\n"
      		"a=rtcp-unicast: reflection\r\n"; //反馈RTPC单播
    }
    
	char const* const sdpPrefixFmt =
      "v=0\r\n"
      "o=- %ld%06ld %d IN IP4 %s\r\n"
      "s=%s\r\n"
      "i=%s\r\n"
      "t=0 0\r\n"
      "a=tool:%s%s\r\n"
      "a=type:broadcast\r\n"
      "a=control:*\r\n"
      "%s"
      "%s"
      "a=x-qt-text-nam:%s\r\n"
      "a=x-qt-text-inf:%s\r\n"
      "%s";
    
    /* 遍历子会话 */
    for (subsession = fSubsessionsHead; subsession != NULL;
         subsession = subsession->fNext) {
        ...
         /* 获取媒体级的sdp信息 */
    	char const* sdpLines = subsession->sdpLines();
    	...
    }
}

从上面可以看到generateSDPDescription会先描述好sdp的会话级信息,然后再调用每个子会话获取sdp的媒体级信息

我们在live555源码分析(四)RTSPServer分析的示例中,添加的子会话只有H264VideoFileServerMediaSubsession,所以下面基于它来分析

首先看如何获取媒体级的sdp信息

char const*
OnDemandServerMediaSubsession::sdpLines() {
	if (fSDPLines == NULL) {
		FramedSource* inputSource = createNewStreamSource(0, estBitrate);
		RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
		setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);
		
		Medium::close(dummyRTPSink);
		closeStreamSource(inputSource);
	}
	
	return fSDPLines;
}

如果sdp信息为空,那么就创建一个临时的源和临时的消费者,然后再获取sdp信息,获取之后会将源和消费者删除

createNewStreamSourcecreateNewRTPSink都是虚函数,它们在H264VideoFileServerMediaSubsession中被重写,定义如下

FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
    estBitrate = 500; // kbps, estimate

    /* 文件字节流 */
    ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
    if (fileSource == NULL) return NULL;
    fFileSize = fileSource->fileSize();

    /* 获取NALU */
    return H264VideoStreamFramer::createNew(envir(), fileSource);
}

这里用到的是装饰者模式,ByteStreamFileSource是获取字节流,将其传递给H264VideoStreamFramerH264VideoStreamFramer可以解析然后获取一帧一帧的NALU

RTPSink* H264VideoFileServerMediaSubsession
::createNewRTPSink(Groupsock* rtpGroupsock,
		   unsigned char rtpPayloadTypeIfDynamic,
		   FramedSource* /*inputSource*/) {
  return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}

H264VideoRTPSink的作用是将H264VideoStreamFramer提供的NALU进行RTP打包

再回到OnDemandServerMediaSubsession::sdpLines()函数,看看setSDPLinesFromRTPSink函数是如何设置sdp信息的

void OnDemandServerMediaSubsession
::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) {
	
	char const* mediaType = rtpSink->sdpMediaType(); //video?audio
	char* rtpmapLine = rtpSink->rtpmapLine(); //m=video 0 RTP/AVP 96
	char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource); //a=rtpmap:96 ...
	
	char const* const sdpFmt =
    "m=%s %u RTP/AVP %d\r\n"
    "c=IN IP4 %s\r\n"
    "b=AS:%u\r\n"
    "%s"
    "%s"
    "%s"
    "%s"
    "a=control:%s\r\n";
    
    sprintf(...);
}

可以看到媒体信息都是从rtpSink中获取的,这里的rtpSInk对应上面生成的消费者H264VideoRTPSink

看一看rtpSink->sdpMediaType()的定义

char const* VideoRTPSink::sdpMediaType() const {
	return "video";
}

再看看rtpSink->rtpmapLine()的定义

char* RTPSink::rtpmapLine() const {
	char const* const rtpmapFmt = "a=rtpmap:%d %s/%d%s\r\n";
	sprintf(rtpmapLine, rtpmapFmt,
            rtpPayloadType(), rtpPayloadFormatName(),
            rtpTimestampFrequency(), encodingParamsPart);
}

接下来具体分析getAuxSDPLine,其定义如下

char const* H264VideoFileServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource) {
	fDummyRTPSink->startPlaying(*inputSource, afterPlayingDummy, this);
	checkForAuxSDPLine(this);
    
    envir().taskScheduler().doEventLoop(&fDoneFlag);
}

首先让消费者播放,然后设置退出检查,接着就进入事件循环

这里有个疑问,为什么要在这里播放?

因为H.264的Sink中的媒体信息需要SPS和PPS来提供信息,而在没有播放前,Sink里面是没有缓存SPS和PPS的,只有播放一段时间后,才会有缓存SPS和PPS,这是才能获取到sdp信息

看一看checkForAuxSDPLine中如何检查退出条件

void H264VideoFileServerMediaSubsession::checkForAuxSDPLine1() {
	if (fDummyRTPSink != NULL && (dasl = fDummyRTPSink->auxSDPLine()) != NULL) {
        fAuxSDPLine = strDup(dasl);
        setDoneFlag();
    } else if (!fDoneFlag) {
    	int uSecsToDelay = 100000;
        envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
                                                    (TaskFunc*)checkForAuxSDPLine, this);
    }
}

如果可以通过fDummyRTPSink->auxSDPLine()获取sdp信息,那么就会将sdp信息拷贝下来,通过setDoneFlag来退出事件循环

否则,将再10ms后再次检查

看一下fDummyRTPSink->auxSDPLine()的实现

char const* H264VideoRTPSink::auxSDPLine() {
    /* 获取sps和pps */
    framerSource->getVPSandSPSandPPS(vpsDummy, vpsDummySize, sps, spsSize, pps, ppsSize);
    
	u_int32_t profileLevelId = (spsWEB[1]<<16) | (spsWEB[2]<<8) | spsWEB[3];

    char const* fmtpFmt =
    "a=fmtp:%d packetization-mode=1"
    ";profile-level-id=%06X"
    ";sprop-parameter-sets=%s,%s\r\n";
    sprintf(fmtp, fmtpFmt, ...);
    
    return fFmtpSDPLine;
}

可以看到H264VideoRTPSink::auxSDPLine()会获取sps和pps,然后生成sdp信息,再返回

整一个sdp文件的生成过程就是这样,下面来总结一下

二、总结

服务器对于DESCRIBE请求的处理主要是获取sdp文件,RTSPClientConnection会向ServerMediaSession获取sdp信息,ServerMediaSession会生成会话级的sdp信息,然后遍历所以子会话获取媒体级的sdp信息,子会话再向RTPSink(消费者)获取媒体信息来组成媒体级的sdp信息,RTPSink可能需要数据源来获取媒体信息

你可能感兴趣的:(live555源码分析与应用,live555)