live555源码分析系列
live555源码分析(一)live555初体验
live555源码分析(二)基本组件上
live555源码分析(三)基本组件下
live555源码分析(四)RTSPServer分析
live555源码分析(五)DESCRIBE请求的处理
live555源码分析(六)SETUP和PLAY请求的处理
live555源码分析(七)播放过程
live555源码分析(八)多播
本文续接上文,将分析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?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
可能需要数据源来获取媒体信息