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信息,获取之后会将源和消费者删除
createNewStreamSource
和createNewRTPSink
都是虚函数,它们在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
是获取字节流,将其传递给H264VideoStreamFramer
,H264VideoStreamFramer
可以解析然后获取一帧一帧的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?audiochar* rtpmapLine = rtpSink->rtpmapLine(); //m=video 0 RTP/AVP 96char 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
可能需要数据源来获取媒体信息