前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONS
,GET_PARAMETER
,SET_PARAMETER
等。篇幅所限,没有分析最为重要的 DESCRIBE
,SETUP
和 PLAY
这些方法的处理。
本文继续分析 live555 对 RTSP 请求,分析 DESCRIBE
,SETUP
和 PLAY
这些最为重要的方法的处理。
在 RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead)
中,通过调用 handleCmd_DESCRIBE()
函数处理 DESCRIBE
请求,如下所示:
handleCmd_DESCRIBE()
函数的定义是这样的:
handleCmd_DESCRIBE()
函数通过一个 do-while(0) 结构实现对 DESCRIBE
方法的处理。do-while(0) 结构的好处大概是,在出错时,即无需直接返回,搞乱控制流,又可以在不用 goto 语句的情况下,跳到函数的结尾处吧。
这个函数中 DESCRIBE
操作整体的执行流程为:
1.首先对在 URL 上执行 DESCRIBE
操作的权限的认证,“LIVE555 Media Server” 不提供认证,认证将总是成功。
查找或创建一个
ServerMediaSession
结构,用于操作媒体流的元信息。查找根据 URL 中资源的路径进行,对于 “LIVE555 Media Server”,也是对应的文件相对于服务器运行的目录的相对路径。函数执行失败时,会直接返回 404 失败给客户端:123void RTSPServer::RTSPClientConnection::handleCmd_notFound() {setRTSPResponse("404 Stream Not Found");}生成 SDP 描述。失败时,也会返回 404 失败给客户端。
获得 RTSP URL。RTSP URL 由服务器的 IP 地址和 URL 路径拼接而成:
123456789101112131415161718192021222324252627282930313233343536char* RTSPServer::rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket) const {char* urlPrefix = rtspURLPrefix(clientSocket);char const* sessionName = serverMediaSession->streamName();char* resultURL = new char[strlen(urlPrefix) + strlen(sessionName) + 1];sprintf(resultURL, "%s%s", urlPrefix, sessionName);delete[] urlPrefix;return resultURL;}char* RTSPServer::rtspURLPrefix(int clientSocket) const {struct sockaddr_in ourAddress;if (clientSocket < 0) {// Use our default IP address in the URL:ourAddress.sin_addr.s_addr = ReceivingInterfaceAddr != 0? ReceivingInterfaceAddr: ourIPAddress(envir()); // hack} else {SOCKLEN_T namelen = sizeof ourAddress;getsockname(clientSocket, (struct sockaddr*)&ourAddress, &namelen);}char urlBuffer[100]; // more than big enough for "rtsp://<ip-address>:<port>/"portNumBits portNumHostOrder = ntohs(fServerPort.num());if (portNumHostOrder == 554 /* the default port number */) {sprintf(urlBuffer, "rtsp://%s/", AddressString(ourAddress).val());} else {sprintf(urlBuffer, "rtsp://%s:%hu/",AddressString(ourAddress).val(), portNumHostOrder);}return strDup(urlBuffer);}生成响应消息。
在 DESCRIBE
请求中,客户端可以通过 Accept
向服务器表明,支持哪种媒体流会话的描述方式,但在 live555 中,似乎是认定了客户端只会请求 SDP,因而整个处理过程都按照产生媒体流 SDP 的方式进行。SDP 消息是放在响应消息的消息体中的。
创建/查找 ServerMediaSession
handleCmd_DESCRIBE()
函数通过 lookupServerMediaSession()
查找或创建一个 ServerMediaSession
结构,这个函数在 GenericMediaServer
类中声明:
它是一个虚函数,调用的实际的函数实现位于继承层次中实现了该函数的最底层的类中。对于 DynamicRTSPServer
-> RTSPServerSupportingHTTPStreaming
-> RTSPServer
-> GenericMediaServer
这个继承层次,实现了这个方法的类有 DynamicRTSPServer
和 GenericMediaServer
。这里来看 DynamicRTSPServer
类中的实现:
DynamicRTSPServer::lookupServerMediaSession()
首先检查对应的文件是否存在,然后通过父类的方法查找文件对应的 ServerMediaSession
结构是否已存在。父类方法在 GenericMediaServer
类中的定义如下:
然后根据检查和查找的结果分为几种情况来处理:
- 文件不存在,对应的
ServerMediaSession
结构已存在 -> 移除ServerMediaSession
结构,返回NULL
给调用者。 - 文件不存在,对应的
ServerMediaSession
结构不存在 -> 返回NULL
给调用者。 - 文件存在,
ServerMediaSession
结构存在,且是流媒体会话中的第一次查找 -> 移除ServerMediaSession
结构,然后创建新的结构,保存起来,并把它返回给调用者。 - 文件存在,(
ServerMediaSession
结构存在,但不是流媒体会话中的第一次查找) 或者 (ServerMediaSession
结构不存在) -> 创建新的ServerMediaSession
结构,保存起来,并返回给调用者。
对于 DESCRIBE
方法而言,此时流媒体会话通常都还没有建立,因而总是会执行第一次查找的流程。
创建新的 ServerMediaSession
被交给 createNewSMS()
来完成:
createNewSMS()
根据文件的后缀名创建 ServerMediaSession
结构。我们只看 H.264 格式视频的 ServerMediaSession
结构的创建。
createNewSMS()
用宏创建 ServerMediaSession
结构,在宏中,通过 do-while(0) 中的 ServerMediaSession::createNew()
创建;将输出缓冲区设置为 100000 字节;创建类型为 H264VideoFileServerMediaSubsession
的 ServerMediaSubsession
并设置给 ServerMediaSession
结构;然后将 ServerMediaSession
结构返回给调用者。
注意,输出缓冲区对视频流中一帧的大小做了限制,对于一些分辨率比较高的视频流,这个大小可能无法满足要求,比如 1080P 的视频流,某些帧大小可能超过 100000,达到 150000,甚至更多。
ServerMediaSession::createNew()
定义如下:
ServerMediaSession
中 ServerMediaSubsession
被组织为一个单向链表。
新加的 ServerMediaSubsession
总是会被放在链表的尾部。
生成 SDP 消息
SDP 消息由 ServerMediaSession
生成:
SDP 消息中主要包括两块内容,一块是通用的 SDP 消息内容,这主要包括时间戳,服务器 IP 地址,持续时间等;另一块是流媒体会话中的子会话特有的信息。
对于如下的 SDP 消息:
其中通用的 SDP 消息内容为:
由 H.264 流媒体文件产生的内容为:
子会话的 SDP 消息来自于 ServerMediaSubsession::sdpLines()
,它被声明为纯虚函数:
对于我们由文件创建的 H.264 流媒体子会话而言,ServerMediaSubsession
为 H264VideoFileServerMediaSubsession
,它有着如下图所示的继承体系:
由图可知,H.264 流媒体子会话所特有的 SDP 内容将来自于 OnDemandServerMediaSubsession::sdpLines()
:
在这个函数中,为了构造 SDP 行以描述子会话,它首先创建一个临时的 FramedSource
和 RTPSink
,然后从这些结构中获得信息以构造 SDP 行,并在最后销毁临时的 FramedSource
和 RTPSink
。
构造 SDP 行过程如下:
创建临时的 FramedSource
和 RTPSink
的函数都是纯虚函数:
对于 H264VideoFileServerMediaSubsession
的类继承层次结构,这两个函数的实现都在 H264VideoFileServerMediaSubsession
类中:
创建的实际 FramedSource
和 RTPSink
类型分别为 H264VideoStreamFramer
和 H264VideoRTPSink
。
打赏
Done.
live555 源码分析系列文章
live555 源码分析:简介
live555 源码分析:基础设施
live555 源码分析:MediaSever
Wireshark 抓包分析 RTSP/RTP/RTCP 基本工作过程
live555 源码分析:RTSPServer
live555 源码分析:DESCRIBE 的处理
live555 源码分析:SETUP 的处理
live555 源码分析:PLAY 的处理
live555 源码分析:RTSPServer 组件结构
live555 源码分析:ServerMediaSession
live555 源码分析:子会话 SDP 行生成
live555 源码分析:子会话 SETUP
live555 源码分析:播放启动