live555学习 --SETUP命令处理
SETUP命令概述
首先更正一个概念:
ServerMediaSession原先说代表一个流,其实是不准确的。它代表的是server端的一个媒体的名字,而说ServerMediaSubsession代表一个Track是准确的。以后流指的是那些有数据流动的组合。
SETUP命令,主要用于协商客户端与服务器的通信细节,如通信协议、地址等等,SETUP请求中最重要的是"Transport"头部。
客户端需要对文件中的每一个流发送一个SETUP命令。客户端还可以通过其中的"destination"属性来重定向RTP数据的接收地址,不过这是需要服务器支持的,在live555中需要定义宏RTSP_ALLOW_CLIENT_DESTINATION_SETTING。
1: SETUP rtsp://192.168.9.80/123.264/track1 RTSP/1.0
2: CSeq: 313: Transport: RTP/AVP/TCP;unicast;interleaved=0-14: User-Agent: LibVLC/1.1.0 (LIVE555 Streaming Media v2010.03.16)5:6: response: RTSP/1.0 200 OK7: CSeq: 318: Date: Wed, Nov 30 2011 06:40:49 GMT9: Transport: RTP/AVP/TCP;unicast;destination=192.168.9.80;source=192.168.9.80;interleaved=0-110: Session: A00F79DE11:
1: void RTSPServer::RTSPClientSession
2: ::handleCmd_SETUP(char const* cseq,3: char const* urlPreSuffix, char const* urlSuffix,4: char const* fullRequestStr) {5: // Normally, "urlPreSuffix" should be the session (stream) name, and "urlSuffix" should be the
// subsession (track) name.
6: // However (being "liberal in what we accept"), we also handle 'aggregate' SETUP requests
//(i.e., without a track name),
7: // in the special case where we have only a single track. I.e., in this case, we also handle:
8: // "urlPreSuffix" is empty and "urlSuffix" is the session (stream) name, or
9: // "urlPreSuffix" concatenated with "urlSuffix" (with "/" inbetween) is the session (stream) name.
10: char const* streamName = urlPreSuffix; // in the normal case11: char const* trackId = urlSuffix; // in the normal case12: char* concatenatedStreamName = NULL; // in the normal case13:14: do {
15: //根据媒体流名称(文件名)查找相应的session, session是在DSCRIBE命令处理过程中创建的
16: // First, make sure the specified stream name exists:
17: fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName);18:19: //下面处理URL中不带 track id 的情况,当文件中只有一个流时,充许这种情况的出现,
//这里流名称保存在urlSuffix变量中
20: if (fOurServerMediaSession == NULL) {
21: // Check for the special case (noted above), before we up:
22: if (urlPreSuffix[0] == '\0') {
23: streamName = urlSuffix;24: } else { // allow for the "/" and the trailing '\0'25: concatenatedStreamName = new char[strlen(urlPreSuffix) + strlen(urlSuffix) + 2];26: sprintf(concatenatedStreamName, "%s/%s", urlPreSuffix, urlSuffix);
27: streamName = concatenatedStreamName;28: }29: trackId = NULL;30:31:32: // Check again:
33: fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName);//重新查找session
34: }35: if (fOurServerMediaSession == NULL) {
36: handleCmd_notFound(cseq);37: break;
38: }39:40: fOurServerMediaSession->incrementReferenceCount(); //增加session的引用计数
41:42: //若这是这个session所处理的第一个"SETUP"命令,需要构建一个streamState型的数组,并初化
43: if (fStreamStates == NULL) {
44: // This is the first "SETUP" for this session. Set up our array of states for
//all of this session's subsessions (tracks):
45: ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
// begin by counting the number of subsessions (tracks)
46: for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}
47:48: fStreamStates = new struct streamState[fNumStreamStates];49:50: iter.reset();51: ServerMediaSubsession* subsession;52: for (unsigned i = 0; i < fNumStreamStates; ++i) {53: subsession = iter.next();54: fStreamStates[i].subsession = subsession;55: fStreamStates[i].streamToken = NULL; // for now; it may be changed by the
// "getStreamParameters()" call that comes later
56: }57: }58:59: //查找track id 对应的subsession是否存在,不存在则进行错误处理
60: // Look up information for the specified subsession (track):
61: ServerMediaSubsession* subsession = NULL;62: unsigned streamNum;
63: if (trackId != NULL && trackId[0] != '\0') { // normal case64: for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) {
65: subsession = fStreamStates[streamNum].subsession;66: if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;67: }68: if (streamNum >= fNumStreamStates) {
69: // The specified track id doesn't exist, so this request fails:
70: handleCmd_notFound(cseq);71: break;
72: }73: } else {
74: //例外情况:URL中不存在 track id,仅当只有一个subsession的情况下才充许出现
75: // Weird case: there was no track id in the URL.
76: // This works only if we have only one subsession:
77: if (fNumStreamStates != 1) {
78: handleCmd_bad(cseq);79: break;
80: }81: streamNum = 0;82: subsession = fStreamStates[streamNum].subsession;83: }84: // ASSERT: subsession != NULL
85:86: //处理Transport头部,获取传输相关信息(1.1)
87: // Look for a "Transport:" header in the request string, to extract client parameters:
88: StreamingMode streamingMode;89: char* streamingModeString = NULL; // set when RAW_UDP streaming is specified90: char* clientsDestinationAddressStr;
91: u_int8_t clientsDestinationTTL;92: portNumBits clientRTPPortNum, clientRTCPPortNum;93: unsigned char rtpChannelId, rtcpChannelId;94: parseTransportHeader(fullRequestStr, streamingMode, streamingModeString,95: clientsDestinationAddressStr, clientsDestinationTTL,96: clientRTPPortNum, clientRTCPPortNum,97: rtpChannelId, rtcpChannelId);98: if (streamingMode == RTP_TCP && rtpChannelId == 0xFF ||
99: streamingMode != RTP_TCP && fClientOutputSocket != fClientInputSocket) {100: // An anomolous situation, caused by a buggy client. Either:
101: // 1/ TCP streaming was requested, but with no "interleaving=" fields.
(QuickTime Player sometimes does this.), or
102: // 2/ TCP streaming was not requested, but we're doing RTSP-over-HTTP tunneling
(which implies TCP streaming).
103: // In either case, we assume TCP streaming, and set the RTP and RTCP channel ids to
proper values:
104: streamingMode = RTP_TCP;105: rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;106: }107: fTCPStreamIdCount += 2;108:109: Port clientRTPPort(clientRTPPortNum);110: Port clientRTCPPort(clientRTCPPortNum);111:112: //处理Range头部(可选)
113: // Next, check whether a "Range:" header is present in the request.
114: // This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
115: double rangeStart = 0.0, rangeEnd = 0.0;
116: fStreamAfterSETUP = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd) ||
parsePlayNowHeader(fullRequestStr);
117:118: // Then, get server parameters from the 'subsession':
119: int tcpSocketNum = streamingMode == RTP_TCP ? fClientOutputSocket : -1;
120: netAddressBits destinationAddress = 0;121: u_int8_t destinationTTL = 255;122: #ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING123: if (clientsDestinationAddressStr != NULL) {
124:125: //RTP数据发送到destination指定的地址,而不是正在通信的客户端。为了安全考虑,
//一般应禁止该功能(将上面的宏定义去掉)
126:127: // Use the client-provided "destination" address.
128: // Note: This potentially allows the server to be used in denial-of-service
129: // attacks, so don't enable this code unless you're sure that clients are
130: // trusted.
131: destinationAddress = our_inet_addr(clientsDestinationAddressStr);132: }133: // Also use the client-provided TTL.
134: destinationTTL = clientsDestinationTTL;135: #endif136: delete[] clientsDestinationAddressStr;
137: Port serverRTPPort(0);138: Port serverRTCPPort(0);139:140: // Make sure that we transmit on the same interface that's used by the client
//(in case we're a multi-homed server):
141: struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;142: getsockname(fClientInputSocket, (struct sockaddr*)&sourceAddr, &namelen);
143: netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;144: netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;145: // NOTE: The following might not work properly, so we ifdef it out for now:
146: #ifdef HACK_FOR_MULTIHOMED_SERVERS147: ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;148: #endif149:150: //从subsession中获取参数(1.2)
151: //获取rtp连接信息,在其中已建立起了server端的rtp和rtcp socket,返回
//fStreamStates[streamNum].streamToken表示数据流已经建立起来了
152: //fOurSessionId, 标识了一个客户端的session,是在RTSPServer::incomingConnectionHandler函数
//中生成的随机数
153: subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,154: clientRTPPort, clientRTCPPort,155: tcpSocketNum, rtpChannelId, rtcpChannelId,156: destinationAddress, destinationTTL, fIsMulticast,157: serverRTPPort, serverRTCPPort,158: fStreamStates[streamNum].streamToken);159: SendingInterfaceAddr = origSendingInterfaceAddr;160: ReceivingInterfaceAddr = origReceivingInterfaceAddr;161: //形成RTSP回应字符串
162: struct in_addr destinationAddr;
163: destinationAddr.s_addr = destinationAddress;164: char* destAddrStr = strDup(our_inet_ntoa(destinationAddr));
165: char* sourceAddrStr = strDup(our_inet_ntoa(sourceAddr.sin_addr));
166: if (fIsMulticast) {
167: switch (streamingMode) {
168: case RTP_UDP:
169: snprintf(170: (char*) fResponseBuffer,
171: sizeof fResponseBuffer,
172: "RTSP/1.0 200 OK\r\n"
173: "CSeq: %s\r\n"
174: "%s"
175: "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"
176: "Session: %08X\r\n\r\n", cseq, dateHeader(),
177: destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()),178: ntohs(serverRTCPPort.num()), destinationTTL,179: fOurSessionId);180: break;
181: case RTP_TCP:
182: // multicast streams can't be sent via TCP
183: handleCmd_unsupportedTransport(cseq);184: break;
185: case RAW_UDP:
186: snprintf(187: (char*) fResponseBuffer,
188: sizeof fResponseBuffer,
189: "RTSP/1.0 200 OK\r\n"
190: "CSeq: %s\r\n"
191: "%s"
192: "Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"
193: "Session: %08X\r\n\r\n", cseq, dateHeader(),
194: streamingModeString, destAddrStr, sourceAddrStr,195: ntohs(serverRTPPort.num()), destinationTTL,196: fOurSessionId);197: break;
198: }199: } else {
200: switch (streamingMode) {
201: case RTP_UDP: {
202: snprintf(203: (char*) fResponseBuffer,
204: sizeof fResponseBuffer,
205: "RTSP/1.0 200 OK\r\n"
206: "CSeq: %s\r\n"
207: "%s"
208: "Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
209: "Session: %08X\r\n\r\n", cseq, dateHeader(),
210: destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()),211: ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()),212: ntohs(serverRTCPPort.num()), fOurSessionId);213: break;
214: }215: case RTP_TCP: {
216: snprintf(217: (char*) fResponseBuffer,
218: sizeof fResponseBuffer,
219: "RTSP/1.0 200 OK\r\n"
220: "CSeq: %s\r\n"
221: "%s"
222: "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
223: "Session: %08X\r\n\r\n", cseq, dateHeader(),
224: destAddrStr, sourceAddrStr, rtpChannelId, rtcpChannelId,225: fOurSessionId);226: break;
227: }228: case RAW_UDP: {
229: snprintf(230: (char*) fResponseBuffer,
231: sizeof fResponseBuffer,
232: "RTSP/1.0 200 OK\r\n"
233: "CSeq: %s\r\n"
234: "%s"
235: "Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
236: "Session: %08X\r\n\r\n", cseq, dateHeader(),
237: streamingModeString, destAddrStr, sourceAddrStr,238: ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),239: fOurSessionId);240: break;
241: }242: }243: }244: delete[] destAddrStr;
245: delete[] sourceAddrStr;
246: delete[] streamingModeString;
247: } while (0);
248:249:250: delete[] concatenatedStreamName;
251: //返回后,回应字符串会被立即发送
252: }253:
live555 中有两个 streamstate,一个是类 StreamState ,一个是此处的结构 struct streamState。类 SteamState 就是 streamToken,而 struct streamState 中保存了 MediaSubsession (即track) 和类 StreamState 的对应。类 StreamState 代表一个真正流动起来的数据流。这个数据流是从源流到 Sink 。客户端与服务端的一个 rtp 会话中,有两个数据流,服务端是从 XXXFileSouce 流到 RTPSink,而客户端则是从 RTPSource 流到 XXXFileSink 。建立数据流的过程就是把 Source 与 Sink 连接起来。
为何不把 StreamToken 保存在 MediaSubsession 中呢?看起来在 struct streamState 中是一个MediaSubsession 对应一个 streamToken 呀?因为 MediaSubsession 代表一个track 的静态数据,它是可以被其它 rtp 会话重用的。比如不同的用户可能会连接到同一个媒体的同一个track 。所以streamToken与MediaSubsession独立存在,只是被RTSPClientSession给对应了起来。
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的分析函数:
1: static void parseTransportHeader(char const* buf,2: StreamingMode& streamingMode,3: char*& streamingModeString,
4: char*& destinationAddressStr,
5: u_int8_t& destinationTTL,6: portNumBits& clientRTPPortNum, // if UDP
7: portNumBits& clientRTCPPortNum, // if UDP
8: unsigned char& rtpChannelId, // if TCP9: unsigned char& rtcpChannelId // if TCP10: ) {11: // Initialize the result parameters to default values:
12: streamingMode = RTP_UDP; //默认使用UDP方式传输RTP
13: ...14: // Then, run through each of the fields, looking for ones we handle:
15: char const* fields = buf + 11;16: char* field = strDupSize(fields);
17: while (sscanf(fields, "%[^;]", field) == 1) {18: if (strcmp(field, "RTP/AVP/TCP") == 0) { //使用了RTP OVER TCP 方式传输19: streamingMode = RTP_TCP;20: } else if (strcmp(field, "RAW/RAW/UDP") == 0 || //裸的UDP数据,不使用RTP协议
//这种方式没见过,看名字应该是使用某种协议的UDP传输,但也被当成了裸的UDP数据
21: strcmp(field, "MP2T/H2221/UDP") == 0) {
22: streamingMode = RAW_UDP;23: streamingModeString = strDup(field); /
/destination属性, 客户端可以通过这个属性重新设置RTP的发送地址,注意,服务器端可能拒绝该属性
24: } else if (_strncasecmp(field, "destination=", 12) == 0) {25: delete[] destinationAddressStr;
26: destinationAddressStr = strDup(field+12);27: } else if (sscanf(field, "ttl%u", &ttl) == 1) {28: destinationTTL = (u_int8_t)ttl;
//client_port属性,客户端接收RTP、RTCP的端口号
29: } else if (sscanf(field, "client_port=%hu-%hu", &p1, &p2) == 2) {30: clientRTPPortNum = p1;31: clientRTCPPortNum = p2;32: } else if (sscanf(field, "client_port=%hu", &p1) == 1) { //客户端只提供了RTP的端口号的情况33: clientRTPPortNum = p1;34: clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1; //interleaved属性,仅在使用RTP OVER TCP方式传输时有用,两个数字分别标识了RTP和RTCP的TCP数据包
35: } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {36: rtpChannelId = (unsigned char)rtpCid; //RTP标识37: rtcpChannelId = (unsigned char)rtcpCid; //RTCP标识38: }39:40: fields += strlen(field);41: while (*fields == ';') ++fields; // skip over separating ';' chars42: if (*fields == '\0' || *fields == '\r' || *fields == '\n') break;43: }44: delete[] field;
45: }46:
3.从subsession中获取参数(1.2)
getStreamParameters是定义在ServerMediaSubsession类中的纯虚函数,其实现在子类OnDemandServerMediaSubsession中。这个函数中将完成source,RTPSink的创建工作,并将其与客户端的映射关系保存下来。
1: void OnDemandServerMediaSubsession
2: ::getStreamParameters(unsigned clientSessionId,
3: netAddressBits clientAddress,4: Port const& clientRTPPort,
5: Port const& clientRTCPPort,<LeftMouse>
6: int tcpSocketNum,
7: unsigned char rtpChannelId,8: unsigned char rtcpChannelId,9: netAddressBits& destinationAddress,10: u_int8_t& /*destinationTTL*/,
11: Boolean& isMulticast,12: Port& serverRTPPort,13: Port& serverRTCPPort,14: void*& streamToken) {
15: if (destinationAddress == 0) destinationAddress = clientAddress;
16: struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
17: isMulticast = False;18:19: //ServerMediaSubsession并没有保存所有基于自己的数据流,而是只记录了最后一次建立的数据流。
20: //利用这个变量和fReuseFirstSource可以实现多client连接到一个流的形式。
21: if (fLastStreamToken != NULL && fReuseFirstSource) {
22: //当fReuseFirstSource参数为True时,不需要再创建source,sink, groupsock等实例,
//只需要记录客户端的地址即可
23: //如果已经基于这个ServerMediaSubsession创建了一个连接,并且希望使用这个连接,则直接返回这个连接。
24:25: // Special case: Rather than creating a new 'StreamState',
26: // we reuse the one that we've already created:
27: serverRTPPort = ((StreamState*)fLastStreamToken)->serverRTPPort();28: serverRTCPPort = ((StreamState*)fLastStreamToken)->serverRTCPPort();29: ++((StreamState*)fLastStreamToken)->referenceCount(); //增加引用记数
30: streamToken = fLastStreamToken;31: } else {
32: //正常情况下,创建一个新的media source
33: // Normal case: Create a new media source:
34: unsigned streamBitrate;
35:36: //创建source,还记得在处理DESCRIBE命令时,也创建过吗? 是的,那是在
37: //OnDemandServerMediaSubsession::sdpLines()函数中, 但参数clientSessionId为0。
38: //createNewStreamSource函数的具体实现参见前前的文章中关于DESCRIBE命令的处理流程
39: FramedSource* mediaSource40: = createNewStreamSource(clientSessionId, streamBitrate);41:42: // Create 'groupsock' and 'sink' objects for the destination,
43: // using previously unused server port numbers:
44: RTPSink* rtpSink;45: BasicUDPSink* udpSink;46: Groupsock* rtpGroupsock;47: Groupsock* rtcpGroupsock;48: portNumBits serverPortNum;49: if (clientRTCPPort.num() == 0) {
50:51: //使用RAW UDP传输,当然就不用使用RTCP了
52:53: // We're streaming raw UDP (not RTP). Create a single groupsock:
54: NoReuse dummy; // ensures that we skip over ports that are already in use
55: for (serverPortNum = fInitialPortNum; ; ++serverPortNum) {
56: struct in_addr dummyAddr; dummyAddr.s_addr = 0;
57:58: serverRTPPort = serverPortNum;59: rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
60: if (rtpGroupsock->socketNum() >= 0) break; // success61: }62:63: rtcpGroupsock = NULL;64: rtpSink = NULL;65: udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);66: } else {
67:68: //创建一对groupsocks实例,分别用于传输RTP、RTCP
69:70: //RTP、RTCP的端口号是相邻的,并且RTP端口号为偶数。初始端口fInitialPortNum = 6970,
71: //这是OnDemandServerMediaSubsession构造函数的缺省参数
72:73: // Normal case: We're streaming RTP (over UDP or TCP). Create a pair of
74: // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even):
75: NoReuse dummy; // ensures that we skip over ports that are already in use
76: for (portNumBits serverPortNum = fInitialPortNum; ; serverPortNum += 2) {
77: struct in_addr dummyAddr; dummyAddr.s_addr = 0;
78:79: serverRTPPort = serverPortNum;80: rtpGroupsock = new Groupsock(envir(), dummyAddr, serverRTPPort, 255);
81: if (rtpGroupsock->socketNum() < 0) {
82: delete rtpGroupsock;
83: continue; // try again84: }85:86: serverRTCPPort = serverPortNum+1;87: rtcpGroupsock = new Groupsock(envir(), dummyAddr, serverRTCPPort, 255);
88: if (rtcpGroupsock->socketNum() < 0) {
89: delete rtpGroupsock;
90: delete rtcpGroupsock;
91: continue; // try again92: }93:94: break; // success95: }96:97: //创建RTPSink,与source类似,在处理DESCRIBE命令进行过,具体过程参见DESCRIBE命令的处理流程
98: unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic99: rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);100: udpSink = NULL;101: }102:103:104: // Turn off the destinations for each groupsock. They'll get set later
105: // (unless TCP is used instead):
106: if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations();
107: if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations();
108:109:110: //重新配置发送RTP 的socket缓冲区大小
111: if (rtpGroupsock != NULL) {
112: // Try to use a big send buffer for RTP - at least 0.1 second of
113: // specified bandwidth and at least 50 KB
114: unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes115: if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024;
//这个函数在groupsock中定义
116: increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize);117: }118:119:120: //建立流的状态对像(stream token),其它包括sink、source、groupsock等的对应关系
121: //注意,live555中定义了两个StreamState结构,这里的StreamState定义为一个类。在RTSPServer中,
122: //定义了一个内部结构体StreamState,其streamToken成员指向此处的StreamState实例
123:124:125: // Set up the state of the stream. The stream will get started later:
126: streamToken = fLastStreamToken127: = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,128: streamBitrate, mediaSource,129: rtpGroupsock, rtcpGroupsock);130: }131:132:133: //这里定义了类Destinations来保存目的地址、RTP端口、RTCP端口,并将其与对应的clientSessionId保存到哈希表
134: //fDestinationsHashTable中,这个哈希表是定义在OnDemandServerMediaSubsession类中
135:136: //记录下所有clientSessionID对应的目的rtp/rtcp地址,是因为现在不能把目的rtp,rtcp地址加入到server端rtp的
//groupSocket中。试想在ReuseFirstSource时,这样会引起client端立即收到rtp数据。 其次,也可以利用这个
//hash table找出client的rtp/rtcp端口等信息,好像其它地方还真没有可以保存的RTSPClientSession中的
//streamstates在ReuseFirstSource时也不能准确找出client端的端口等信息。
137: // Record these destinations as being for this client session id:
138: Destinations* destinations;139: if (tcpSocketNum < 0) { // UDP140: destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort);
141: } else { // TCP142: destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId);
143: }144: fDestinationsHashTable->Add((char const*)clientSessionId, destinations);145: }146:
流程不复杂:如果需要重用上一次建立的流,就利用之(这样就可以实现一rtp server对应多个rtp client的形式);如果不需要,则创建合适的source,然后创建rtp sink,然后利用它们创建streamSoken。
以上内容出自:http://blog.youkuaiyun.com/gavinr/article/details/7029830