客户端需要对,文件中的每一个流发送一个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
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; //下面是组装响应包 ... }
使用了 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; }
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); }