SETUP命令概述
SETUP命令,主要用于协商客户端与服务器的通信细节,如通信协议、地址等等,SETUP请求中最重要的是"Transport"头部。
客户端需要对,文件中的每一个流发送一个SETUP命令。
客户端还可以通过其中的"destination"属性来重定向RTP数据的接收地址,不过这是需要服务器支持的,在live555中需要定义宏RTSP_ALLOW_CLIENT_DESTINATION_SETTING。
SETUP的响应中,包含一个"Session"头部,这是服务器产生的一个随机数,用于标识特定的客户端。
来看一个具体的SETUP消息实例- SETUP rtsp://192.168.9.80/123.264/track1 RTSP/1.0
- CSeq: 31
- Transport: RTP/AVP/TCP;unicast;interleaved=0-1
- User-Agent: LibVLC/1.1.0 (LIVE555 Streaming Media v2010.03.16)
- response: RTSP/1.0 200 OK
- CSeq: 31
- Date: Wed, Nov 30 2011 06:40:49 GMT
- Transport: RTP/AVP/TCP;unicast;destination=192.168.9.80;source=192.168.9.80;interleaved=0-1
- Session: A00F79DE
1.命令处理函数handleCmd_SETUP
- void RTSPServer::RTSPClientSession
- ::handleCmd_SETUP(char const* cseq,
- char const* urlPreSuffix, char const* urlSuffix,
- char const* fullRequestStr) {
- // Normally, "urlPreSuffix" should be the session (stream) name, and "urlSuffix" should be the subsession (track) name.
- // However (being "liberal in what we accept"), we also handle 'aggregate' SETUP requests (i.e., without a track name),
- // in the special case where we have only a single track. I.e., in this case, we also handle:
- // "urlPreSuffix" is empty and "urlSuffix" is the session (stream) name, or
- // "urlPreSuffix" concatenated with "urlSuffix" (with "/" inbetween) is the session (stream) name.
- char const* streamName = urlPreSuffix; // in the normal case
- char const* trackId = urlSuffix; // in the normal case
- char* concatenatedStreamName = NULL; // in the normal case
- do {
- //根据媒体流名称(文件名)查找相应的session, session是在DSCRIBE命令处理过程中创建的
- // First, make sure the specified stream name exists:
- fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName);
- //下面处理URL中不带 track id 的情况,当文件中只有一个流时,充许这种情况的出现,这里流名称保存在urlSuffix变量中
- if (fOurServerMediaSession == NULL) {
- // Check for the special case (noted above), before we up:
- if (urlPreSuffix[0] == '\0') {
- streamName = urlSuffix;
- } else {
- concatenatedStreamName = new char[strlen(urlPreSuffix) + strlen(urlSuffix) + 2]; // allow for the "/" and the trailing '\0'
- sprintf(concatenatedStreamName, "%s/%s", urlPreSuffix, urlSuffix);
- streamName = concatenatedStreamName;
- }
- trackId = NULL;
- // Check again:
- fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName); //重新查找session
- }
- if (fOurServerMediaSession == NULL) {
- handleCmd_notFound(cseq);
- break;
- }
- fOurServerMediaSession->incrementReferenceCount(); //增加session的引用计数
- //若这是这个session所处理的第一个"SETUP"命令,需要构建一个streamState型的数组,并初化
- if (fStreamStates == NULL) {
- // This is the first "SETUP" for this session. Set up our array of states for all of this session's subsessions (tracks):
- ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
- for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {} // begin by counting the number of subsessions (tracks)
- fStreamStates = new struct streamState[fNumStreamStates];
- iter.reset();
- ServerMediaSubsession* subsession;
- for (unsigned i = 0; i < fNumStreamStates; ++i) {
- subsession = iter.next();
- fStreamStates[i].subsession = subsession;
- fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
- }
- }
- //查找track id 对应的subsession是否存在,不存在则进行错误处理
- // Look up information for the specified subsession (track):
- ServerMediaSubsession* subsession = NULL;
- unsigned streamNum;
- if (trackId != NULL && trackId[0] != '\0') { // normal case
- for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) {
- subsession = fStreamStates[streamNum].subsession;
- if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
- }
- if (streamNum >= fNumStreamStates) {
- // The specified track id doesn't exist, so this request fails:
- handleCmd_notFound(cseq);
- break;
- }
- } else {
- //例外情况:URL中不存在 track id,仅当只有一个subsession的情况下才充许出现
- // Weird case: there was no track id in the URL.
- // This works only if we have only one subsession:
- if (fNumStreamStates != 1) {
- handleCmd_bad(cseq);
- break;
- }
- streamNum = 0;
- subsession = fStreamStates[streamNum].subsession;
- }
- // ASSERT: subsession != NULL
- //处理Transport头部,获取传输相关信息(1.1)
- // Look for a "Transport:" header in the request string, to extract client parameters:
- StreamingMode streamingMode;
- char* streamingModeString = NULL; // set when RAW_UDP streaming is specified
- char* clientsDestinationAddressStr;
- u_int8_t clientsDestinationTTL;
- portNumBits clientRTPPortNum, clientRTCPPortNum;
- unsigned char rtpChannelId, rtcpChannelId;
- parseTransportHeader(fullRequestStr, streamingMode, streamingModeString,
- clientsDestinationAddressStr, clientsDestinationTTL,
- clientRTPPortNum, clientRTCPPortNum,
- rtpChannelId, rtcpChannelId);
- if (streamingMode == RTP_TCP && rtpChannelId == 0xFF ||
- streamingMode != RTP_TCP && fClientOutputSocket != fClientInputSocket) {
- // An anomolous situation, caused by a buggy client. Either:
- // 1/ TCP streaming was requested, but with no "interleaving=" fields. (QuickTime Player sometimes does this.), or
- // 2/ TCP streaming was not requested, but we're doing RTSP-over-HTTP tunneling (which implies TCP streaming).
- // In either case, we assume TCP streaming, and set the RTP and RTCP channel ids to proper values:
- streamingMode = RTP_TCP;
- rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;
- }
- fTCPStreamIdCount += 2;
- Port clientRTPPort(clientRTPPortNum);
- Port clientRTCPPort(clientRTCPPortNum);
- //处理Range头部(可选)
- // Next, check whether a "Range:" header is present in the request.
- // This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
- double rangeStart = 0.0, rangeEnd = 0.0;
- fStreamAfterSETUP = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd) || parsePlayNowHeader(fullRequestStr);
- // Then, get server parameters from the 'subsession':
- int tcpSocketNum = streamingMode == RTP_TCP ? fClientOutputSocket : -1;
- netAddressBits destinationAddress = 0;
- u_int8_t destinationTTL = 255;
- #ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
- if (clientsDestinationAddressStr != NULL) {
- //RTP数据发送到destination指定的地址,而不是正在通信的客户端。为了安全考虑,一般应禁止该功能(将上面的宏定义去掉)
- // Use the client-provided "destination" address.
- // Note: This potentially allows the server to be used in denial-of-service
- // attacks, so don't enable this code unless you're sure that clients are
- // trusted.
- destinationAddress = our_inet_addr(clientsDestinationAddressStr);
- }
- // Also use the client-provided TTL.
- destinationTTL = clientsDestinationTTL;
- #endif
- delete[] clientsDestinationAddressStr;
- Port serverRTPPort(0);
- Port serverRTCPPort(0);
- // Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server):
- struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
- getsockname(fClientInputSocket, (struct sockaddr*)&sourceAddr, &namelen);
- netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;
- netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;
- // NOTE: The following might not work properly, so we ifdef it out for now:
- #ifdef HACK_FOR_MULTIHOMED_SERVERS
- ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
- #endif
- //从subsession中获取参数(1.2)
- //fOurSessionId, 标识了一个客户端的session,是在RTSPServer::incomingConnectionHandler函数中生成的随机数
- subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
- clientRTPPort, clientRTCPPort,
- tcpSocketNum, rtpChannelId, rtcpChannelId,
- destinationAddress, destinationTTL, fIsMulticast,
- serverRTPPort, serverRTCPPort,
- fStreamStates[streamNum].streamToken);
- SendingInterfaceAddr = origSendingInterfaceAddr;
- ReceivingInterfaceAddr = origReceivingInterfaceAddr;
- //下面是组装响应包
- ...
- }
2.Transport头部(1.1)
Transport头部包含了用于传输的重要信息。看一个SETUP请求的Transport头部Transport: RTP/AVP/TCP;unicast;interleaved=0-1
使用了 RTP OVER TCP 方式进行传输,使用单播方式,interleaved属性中的0和1将分别用于标识TCP包中的RTP与RTCP数据
下面看看Transport的分析函数
- static void parseTransportHeader(char const* buf,
- StreamingMode& streamingMode,
- char*& streamingModeString,
- char*& destinationAddressStr,
- u_int8_t& destinationTTL,
- portNumBits& clientRTPPortNum, // if UDP
- portNumBits& clientRTCPPortNum, // if UDP
- unsigned char& rtpChannelId, // if TCP
- unsigned char& rtcpChannelId // if TCP
- ) {
- // Initialize the result parameters to default values:
- streamingMode = RTP_UDP; //默认使用UDP方式传输RTP
- ...
- // Then, run through each of the fields, looking for ones we handle:
- char const* fields = buf + 11;
- char* field = strDupSize(fields);
- while (sscanf(fields, "%[^;]", field) == 1) {
- if (strcmp(field, "RTP/AVP/TCP") == 0) { //使用了RTP OVER TCP 方式传输
- streamingMode = RTP_TCP;
- } else if (strcmp(field, "RAW/RAW/UDP") == 0 || //裸的UDP数据,不使用RTP协议
- strcmp(field, "MP2T/H2221/UDP") == 0) { //这种方式没见过,看名字应该是使用某种协议的UDP传输,但也被当成了裸的UDP数据
- streamingMode = RAW_UDP;
- streamingModeString = strDup(field);
- } else if (_strncasecmp(field, "destination=", 12) == 0) { //destination属性, 客户端可以通过这个属性重新设置RTP的发送地址,注意,服务器端可能拒绝该属性
- delete[] destinationAddressStr;
- destinationAddressStr = strDup(field+12);
- } else if (sscanf(field, "ttl%u", &ttl) == 1) {
- destinationTTL = (u_int8_t)ttl;
- } else if (sscanf(field, "client_port=%hu-%hu", &p1, &p2) == 2) { //client_port属性,客户端接收RTP、RTCP的端口号
- clientRTPPortNum = p1;
- clientRTCPPortNum = p2;
- } else if (sscanf(field, "client_port=%hu", &p1) == 1) { //客户端只提供了RTP的端口号的情况
- clientRTPPortNum = p1;
- clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1;
- } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {//interleaved属性,仅在使用RTP OVER TCP方式传输时有用,两个数字分别标识了RTP和RTCP的TCP数据包
- rtpChannelId = (unsigned char)rtpCid; //RTP标识
- rtcpChannelId = (unsigned char)rtcpCid; //RTCP标识
- }
- fields += strlen(field);
- while (*fields == ';') ++fields; // skip over separating ';' chars
- if (*fields == '\0' || *fields == '\r' || *fields == '\n') break;
- }
- delete[] field;
- }
3.从subsession中获取参数(1.2)
getStreamParameters是定义在ServerMediaSubsession类中的纯虚函数,其实现在子类OnDemandServerMediaSubsession中。这个函数中将完成source,RTPSink的创建工作,并将其与客户端的映射关系保存下来。- void OnDemandServerMediaSubsession
- ::getStreamParameters(unsigned clientSessionId,
- netAddressBits clientAddress,
- Port const& clientRTPPort,
- Port const& clientRTCPPort,<LeftMouse>
- int tcpSocketNum,
- unsigned char rtpChannelId,
- unsigned char rtcpChannelId,
- netAddressBits& destinationAddress,
- u_int8_t& /*destinationTTL*/,
- Boolean& isMulticast,
- Port& serverRTPPort,
- Port& serverRTCPPort,
- void*& streamToken) {
- if (destinationAddress == 0) destinationAddress = clientAddress;
- struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
- isMulticast = False;
- if (fLastStreamToken != NULL && fReuseFirstSource) {
- //当fReuseFirstSource参数为True时,不需要再创建source,sink, groupsock等实例,只需要记录客户端的地址即可
- // 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 {
- //正常情况下,创建一个新的media source
- // Normal case: Create a new media source:
- unsigned streamBitrate;
- //创建source,还记得在处理DESCRIBE命令时,也创建过吗? 是的,那是在
- //OnDemandServerMediaSubsession::sdpLines()函数中, 但参数clientSessionId为0。
- //createNewStreamSource函数的具体实现参见前前的文章中关于DESCRIBE命令的处理流程
- FramedSource* mediaSource
- = createNewStreamSource(clientSessionId, streamBitrate);
- // Create 'groupsock' and 'sink' objects for the destination,
- // using previously unused server port numbers:
- RTPSink* rtpSink;
- BasicUDPSink* udpSink;
- Groupsock* rtpGroupsock;
- Groupsock* rtcpGroupsock;
- portNumBits serverPortNum;
- if (clientRTCPPort.num() == 0) {
- //使用RAW UDP传输,当然就不用使用RTCP了
- // We're streaming raw UDP (not RTP). Create a single groupsock:
- NoReuse dummy; // ensures that we skip over ports that are already in use
- for (serverPortNum = fInitialPortNum; ; ++serverPortNum) {
- struct in_addr dummyAddr; dummyAddr.s_addr = 0;
- serverRTPPort = serverPortNum;
- rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
- if (rtpGroupsock->socketNum() >= 0) break; // success
- }
- rtcpGroupsock = NULL;
- rtpSink = NULL;
- udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);
- } else {
- //创建一对groupsocks实例,分别用于传输RTP、RTCP
- //RTP、RTCP的端口号是相邻的,并且RTP端口号为偶数。初始端口fInitialPortNum = 6970,
- //这是OnDemandServerMediaSubsession构造函数的缺省参数
- // Normal case: We're streaming RTP (over UDP or TCP). Create a pair of
- // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even):
- NoReuse dummy; // ensures that we skip over ports that are already in use
- for (portNumBits serverPortNum = fInitialPortNum; ; serverPortNum += 2) {
- struct in_addr dummyAddr; dummyAddr.s_addr = 0;
- serverRTPPort = serverPortNum;
- rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
- if (rtpGroupsock->socketNum() < 0) {
- delete rtpGroupsock;
- continue; // try again
- }
- serverRTCPPort = serverPortNum+1;
- rtcpGroupsock = new Groupsock(envir(), dummyAddr, serverRTCPPort, 255);
- if (rtcpGroupsock->socketNum() < 0) {
- delete rtpGroupsock;
- delete rtcpGroupsock;
- continue; // try again
- }
- break; // success
- }
- //创建RTPSink,与source类似,在处理DESCRIBE命令进行过,具体过程参见DESCRIBE命令的处理流程
- unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
- rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
- udpSink = NULL;
- }
- // Turn off the destinations for each groupsock. They'll get set later
- // (unless TCP is used instead):
- if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations();
- if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations();
- //重新配置发送RTP 的socket缓冲区大小
- if (rtpGroupsock != NULL) {
- // Try to use a big send buffer for RTP - at least 0.1 second of
- // specified bandwidth and at least 50 KB
- unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
- if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024;
- increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize); //这个函数在groupsock中定义
- }
- //建立流的状态对像(stream token),其它包括sink、source、groupsock等的对应关系
- //注意,live555中定义了两个StreamState结构,这里的StreamState定义为一个类。在RTSPServer中,
- //定义了一个内部结构体StreamState,其streamToken成员指向此处的StreamState实例
- // Set up the state of the stream. The stream will get started later:
- streamToken = fLastStreamToken
- = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
- streamBitrate, mediaSource,
- rtpGroupsock, rtcpGroupsock);
- }
- //这里定义了类Destinations来保存目的地址、RTP端口、RTCP端口,并将其与对应的clientSessionId保存到哈希表
- //fDestinationsHashTable中,这个哈希表是定义在OnDemandServerMediaSubsession类中
- // Record these destinations as being for this client session id:
- Destinations* destinations;
- if (tcpSocketNum < 0) { // UDP
- destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort);
- } else { // TCP
- destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId);
- }
- fDestinationsHashTable->Add((char const*)clientSessionId, destinations);
- }