live555 源码分析: SETUP 的处理

SETUP 请求在 RTSP 的整个工作流程中,用于建立流媒体会话。本文分析 live555 对 SETUP 请求的处理。

RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) 中,通过 RTSPServer::RTSPClientSessionhandleCmd_SETUP() 函数处理 SETUP 请求,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection,
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 {
// First, make sure the specified stream name exists:
ServerMediaSession* sms
= fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
if (sms == NULL) {
// Check for the special case (noted above), before we give 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:
sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
}
if (sms == NULL) {
if (fOurServerMediaSession == NULL) {
// The client asked for a stream that doesn't exist (and this session descriptor has not been used before):
ourClientConnection->handleCmd_notFound();
} else {
// The client asked for a stream that doesn't exist, but using a stream id for a stream that does exist. Bad request:
ourClientConnection->handleCmd_bad();
}
break;
} else {
if (fOurServerMediaSession == NULL) {
// We're accessing the "ServerMediaSession" for the first time.
fOurServerMediaSession = sms;
fOurServerMediaSession->incrementReferenceCount();
} else if (sms != fOurServerMediaSession) {
// The client asked for a stream that's different from the one originally requested for this stream id. Bad request:
ourClientConnection->handleCmd_bad();
break;
}
}
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):
fNumStreamStates = fOurServerMediaSession->numSubsessions();
fStreamStates = new struct streamState[fNumStreamStates];
ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
ServerMediaSubsession* subsession;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
subsession = iter.next();
fStreamStates[i].subsession = subsession;
fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming
fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
}
}
// Look up information for the specified subsession (track):
ServerMediaSubsession* subsession = NULL;
unsigned trackNum;
if (trackId != NULL && trackId[0] != '\0') { // normal case
for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) {
subsession = fStreamStates[trackNum].subsession;
if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
}
if (trackNum >= fNumStreamStates) {
// The specified track id doesn't exist, so this request fails:
ourClientConnection->handleCmd_notFound();
break;
}
} else {
// Weird case: there was no track id in the URL.
// This works only if we have only one subsession:
if (fNumStreamStates != 1 || fStreamStates[0].subsession == NULL) {
ourClientConnection->handleCmd_bad();
break;
}
trackNum = 0;
subsession = fStreamStates[trackNum].subsession;
}
// ASSERT: subsession != NULL
void*& token = fStreamStates[trackNum].streamToken; // alias
if (token != NULL) {
// We already handled a "SETUP" for this track (to the same client),
// so stop any existing streaming of it, before we set it up again:
subsession->pauseStream(fOurSessionId, token);
fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
subsession->deleteStream(fOurSessionId, token);
}
// 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 && ourClientConnection->fClientOutputSocket != ourClientConnection->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;
}
if (streamingMode == RTP_TCP) fTCPStreamIdCount += 2;
Port clientRTPPort(clientRTPPortNum);
Port clientRTCPPort(clientRTCPPortNum);
// Next, check whether a "Range:" or "x-playNow:" 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;
char* absStart = NULL; char* absEnd = NULL;
Boolean startTimeIsNow;
if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) {
delete[] absStart; delete[] absEnd;
fStreamAfterSETUP = True;
} else if (parsePlayNowHeader(fullRequestStr)) {
fStreamAfterSETUP = True;
} else {
fStreamAfterSETUP = False;
}
// Then, get server parameters from the 'subsession':
if (streamingMode == RTP_TCP) {
// Note that we'll be streaming over the RTSP TCP connection:
fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket;
fOurRTSPServer.noteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
}
netAddressBits destinationAddress = 0;
u_int8_t destinationTTL = 255;
#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
if (clientsDestinationAddressStr != NULL) {
// 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(ourClientConnection->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->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[trackNum].streamToken);
SendingInterfaceAddr = origSendingInterfaceAddr;
ReceivingInterfaceAddr = origReceivingInterfaceAddr;
AddressString destAddrStr(destinationAddress);
AddressString sourceAddrStr(sourceAddr);
char timeoutParameterString[100];
if (fOurRTSPServer.fReclamationSeconds > 0) {
sprintf(timeoutParameterString, ";timeout=%u",
fOurRTSPServer.fReclamationSeconds);
} else {
timeoutParameterString[0] = '\0';
}
if (fIsMulticast) {
switch (streamingMode) {
case RTP_UDP: {
snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(),
ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL,
fOurSessionId, timeoutParameterString);
break;
}
case RTP_TCP: {
// multicast streams can't be sent via TCP
ourClientConnection->handleCmd_unsupportedTransport();
break;
}
case RAW_UDP: {
snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), destinationTTL,
fOurSessionId, timeoutParameterString);
break;
}
}
} else {
switch (streamingMode) {
case RTP_UDP: {
snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
fOurSessionId, timeoutParameterString);
break;
}
case RTP_TCP: {
if (!fOurRTSPServer.fAllowStreamingRTPOverTCP) {
ourClientConnection->handleCmd_unsupportedTransport();
} else {
snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId,
fOurSessionId, timeoutParameterString);
}
break;
}
case RAW_UDP: {
snprintf((char*) ourClientConnection->fResponseBuffer,
sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),
fOurSessionId, timeoutParameterString);
break;
}
}
}
delete[] streamingModeString;
} while (0);
delete[] concatenatedStreamName;
}

第一步,在这个函数中,首先查找资源对应的 ServerMediaSession,先尝试以资源路径不包含 track id 的部分查找,如果失败则会以资源全路径查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ServerMediaSession* sms
= fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
if (sms == NULL) {
// Check for the special case (noted above), before we give 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:
sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
}

在这里 lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession)isFirstLookupInSession 参数不再是默认值了,而是根据 fOurServerMediaSession 的值来确定。

可见在处理 DESCRIBE 请求时创建的 ServerMediaSession 将总是会被销毁,并重建。

第二步,根据前一步的查找结果做容错处理或更新状态。

如果查找或创建 ServerMediaSession 失败,且 fOurServerMediaSession 为空,向客户端返回 404 错误,这种情况比较容易理解,即在 DESCRIBE 请求之后,资源被移除了;如果查找或创建 ServerMediaSession 失败,且 fOurServerMediaSession 为非空,此时则向客户端返回 400 错误,这种场景似乎是,执行了多次 SETUP 操作,第一次执行的时候资源在,但后面执行的时候,资源不存在了。

查找或创建 ServerMediaSession 失败总是会使函数提前返回。

1
2
3
4
5
6
7
8
9
10
if (sms == NULL) {
if (fOurServerMediaSession == NULL) {
// The client asked for a stream that doesn't exist (and this session descriptor has not been used before):
ourClientConnection->handleCmd_notFound();
} else {
// The client asked for a stream that doesn't exist, but using a stream id for a stream that does exist. Bad request:
ourClientConnection->handleCmd_bad();
}
break;
} else {

对于查找或创建 ServerMediaSession 成功的情况,如果此时 fOurServerMediaSession 为空,表明这是这个流媒体会话第一次执行 SETUP 操作,此时需要更新 fOurServerMediaSession 并增加其引用计数;如果 fOurServerMediaSession 非空,且与找到的不同,则向客户端返回 400 错误响应消息,不是很明白这究竟是什么样的场景

第三部,根据需要创建流状态。为流媒体的每个子会话创建一个流状态结构 streamState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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):
fNumStreamStates = fOurServerMediaSession->numSubsessions();
fStreamStates = new struct streamState[fNumStreamStates];
ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
ServerMediaSubsession* subsession;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
subsession = iter.next();
fStreamStates[i].subsession = subsession;
fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming
fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
}
}

第四步,查找特定子会话(track)的信息。对于 URL 中携带了 track id 的请求,就根据 track id 查找,否则对于只有一个子会话的情况,就以该会话作为 track 会话。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Look up information for the specified subsession (track):
ServerMediaSubsession* subsession = NULL;
unsigned trackNum;
if (trackId != NULL && trackId[0] != '\0') { // normal case
for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) {
subsession = fStreamStates[trackNum].subsession;
if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
}
if (trackNum >= fNumStreamStates) {
// The specified track id doesn't exist, so this request fails:
ourClientConnection->handleCmd_notFound();
break;
}
} else {
// Weird case: there was no track id in the URL.
// This works only if we have only one subsession:
if (fNumStreamStates != 1 || fStreamStates[0].subsession == NULL) {
ourClientConnection->handleCmd_bad();
break;
}
trackNum = 0;
subsession = fStreamStates[trackNum].subsession;
}

第五步,如果 track 子会话的 token 非空,说明已经为该 track 处理过 SETUP 请求,则在重新设置它之前,先停止其已有的流。

1
2
3
4
5
6
7
8
void*& token = fStreamStates[trackNum].streamToken; // alias
if (token != NULL) {
// We already handled a "SETUP" for this track (to the same client),
// so stop any existing streaming of it, before we set it up again:
subsession->pauseStream(fOurSessionId, token);
fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
subsession->deleteStream(fOurSessionId, token);
}

会话中流媒体的控制的具体操作过程,暂时先不详细分析。

第六步,从请求字符串中查找 Transport: 头部,并从中提取客户端参数。SETUP 请求的 Transport: 头部看起来可能像下面这样:

1
Transport: RTP/AVP/UDP;unicast;client_port=19586-19587

在这个头部中,包含有通信的方式,对于 UDP,是单播还是多播,客户端收发 RTP/RTCP 包所用的端口号等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
typedef enum StreamingMode {
RTP_UDP,
RTP_TCP,
RAW_UDP
} StreamingMode;
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;
streamingModeString = NULL;
destinationAddressStr = NULL;
destinationTTL = 255;
clientRTPPortNum = 0;
clientRTCPPortNum = 1;
rtpChannelId = rtcpChannelId = 0xFF;
portNumBits p1, p2;
unsigned ttl, rtpCid, rtcpCid;
// First, find "Transport:"
while (1) {
if (*buf == '\0') return; // not found
if (*buf == '\r' && *(buf+1) == '\n' && *(buf+2) == '\r') return; // end of the headers => not found
if (_strncasecmp(buf, "Transport:", 10) == 0) break;
++buf;
}
// Then, run through each of the fields, looking for ones we handle:
char const* fields = buf + 10;
while (*fields == ' ') ++fields;
char* field = strDupSize(fields);
while (sscanf(fields, "%[^;\r\n]", field) == 1) {
if (strcmp(field, "RTP/AVP/TCP") == 0) {
streamingMode = RTP_TCP;
} else if (strcmp(field, "RAW/RAW/UDP") == 0
|| strcmp(field, "MP2T/H2221/UDP") == 0) {
streamingMode = RAW_UDP;
streamingModeString = strDup(field);
} else if (_strncasecmp(field, "destination=", 12) == 0) {
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) {
clientRTPPortNum = p1;
clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p2; // ignore the second port number if the client asked for raw UDP
} else if (sscanf(field, "client_port=%hu", &p1) == 1) {
clientRTPPortNum = p1;
clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1;
} else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
rtpChannelId = (unsigned char)rtpCid;
rtcpChannelId = (unsigned char)rtcpCid;
}
fields += strlen(field);
while (*fields == ';' || *fields == ' ' || *fields == '\t') ++fields; // skip over separating ';' chars or whitespace
if (*fields == '\0' || *fields == '\r' || *fields == '\n') break;
}
delete[] field;
}
. . . . . .
// 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 && ourClientConnection->fClientOutputSocket != ourClientConnection->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;
}
if (streamingMode == RTP_TCP) fTCPStreamIdCount += 2;
Port clientRTPPort(clientRTPPortNum);
Port clientRTCPPort(clientRTCPPortNum);

我们前面在示例 Transport: 头部中,可以看到 “unicast”,但在 live555 中,不是根据 Transport: 头部的内容来确定流用单播还是多播的。

第七步,检查请求中是否有 Range:x-playNow: 头部。这不是标准 RTSP 支持的做法,但一些客户端可能会用这种方法把 SETUPPLAY 结合起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Boolean parseRangeParam(char const* paramStr,
double& rangeStart, double& rangeEnd,
char*& absStartTime, char*& absEndTime,
Boolean& startTimeIsNow) {
delete[] absStartTime; delete[] absEndTime;
absStartTime = absEndTime = NULL; // by default, unless "paramStr" is a "clock=..." string
startTimeIsNow = False; // by default
double start, end;
int numCharsMatched1 = 0, numCharsMatched2 = 0, numCharsMatched3 = 0, numCharsMatched4 = 0;
Locale l("C", Numeric);
if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
rangeStart = start;
rangeEnd = end;
} else if (sscanf(paramStr, "npt = %n%lf -", &numCharsMatched1, &start) == 1) {
if (paramStr[numCharsMatched1] == '-') {
// special case for "npt = -<endtime>", which matches here:
rangeStart = 0.0; startTimeIsNow = True;
rangeEnd = -start;
} else {
rangeStart = start;
rangeEnd = 0.0;
}
} else if (sscanf(paramStr, "npt = now - %lf", &end) == 1) {
rangeStart = 0.0; startTimeIsNow = True;
rangeEnd = end;
} else if (sscanf(paramStr, "npt = now -%n", &numCharsMatched2) == 0 && numCharsMatched2 > 0) {
rangeStart = 0.0; startTimeIsNow = True;
rangeEnd = 0.0;
} else if (sscanf(paramStr, "clock = %n", &numCharsMatched3) == 0 && numCharsMatched3 > 0) {
rangeStart = rangeEnd = 0.0;
char const* utcTimes = &paramStr[numCharsMatched3];
size_t len = strlen(utcTimes) + 1;
char* as = new char[len];
char* ae = new char[len];
int sscanfResult = sscanf(utcTimes, "%[^-]-%[^\r\n]", as, ae);
if (sscanfResult == 2) {
absStartTime = as;
absEndTime = ae;
} else if (sscanfResult == 1) {
absStartTime = as;
delete[] ae;
} else {
delete[] as; delete[] ae;
return False;
}
} else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched4) == 0 && numCharsMatched4 > 0) {
// We accept "smtpe=" parameters, but currently do not interpret them.
} else {
return False; // The header is malformed
}
return True;
}
Boolean parseRangeHeader(char const* buf,
double& rangeStart, double& rangeEnd,
char*& absStartTime, char*& absEndTime,
Boolean& startTimeIsNow) {
// First, find "Range:"
while (1) {
if (*buf == '\0') return False; // not found
if (_strncasecmp(buf, "Range: ", 7) == 0) break;
++buf;
}
char const* fields = buf + 7;
while (*fields == ' ') ++fields;
return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime, startTimeIsNow);
}
. . . . . .
static Boolean parsePlayNowHeader(char const* buf) {
// Find "x-playNow:" header, if present
while (1) {
if (*buf == '\0') return False; // not found
if (_strncasecmp(buf, "x-playNow:", 10) == 0) break;
++buf;
}
return True;
}
. . . . . .
double rangeStart = 0.0, rangeEnd = 0.0;
char* absStart = NULL; char* absEnd = NULL;
Boolean startTimeIsNow;
if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) {
delete[] absStart; delete[] absEnd;
fStreamAfterSETUP = True;
} else if (parsePlayNowHeader(fullRequestStr)) {
fStreamAfterSETUP = True;
} else {
fStreamAfterSETUP = False;
}

尽管 RTP/RTCP 也支持 TCP 模式,但这种做法不是很主流,后面我们主要来看基于 UDP 单播的模式。

第八步,为会话分配网络资源,如服务器端 RTP 和 RTCP 的端口等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
netAddressBits destinationAddress = 0;
u_int8_t destinationTTL = 255;
#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
if (clientsDestinationAddressStr != NULL) {
// 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(ourClientConnection->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->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[trackNum].streamToken);
SendingInterfaceAddr = origSendingInterfaceAddr;
ReceivingInterfaceAddr = origReceivingInterfaceAddr;
AddressString destAddrStr(destinationAddress);
AddressString sourceAddrStr(sourceAddr);

后面我们再来分析 H264VideoFileServerMediaSubsession 更详细地处理过程。

第九步,生成超时参数字符串。

1
2
3
4
5
6
if (fOurRTSPServer.fReclamationSeconds > 0) {
sprintf(timeoutParameterString, ";timeout=%u",
fOurRTSPServer.fReclamationSeconds);
} else {
timeoutParameterString[0] = '\0';
}

第十步,生成响应消息。我们仅来看,RTP 的 UDP 单播模式响应消息的生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
} else {
switch (streamingMode) {
case RTP_UDP: {
snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
fOurSessionId, timeoutParameterString);
break;
}

生成的消息看起来大概就像下面这样:

1
2
3
4
5
RTSP/1.0 200 OK
CSeq: 3
Date: Sat, Sep 02 2017 08:54:03 GMT
Transport: RTP/AVP;unicast;destination=10.240.248.20;source=10.240.248.20;client_port=19586-19587;server_port=6970-6971
Session: D10C8C71;timeout=65

通过 Transport: 将协商的通信参数,如服务器端为会话分配的用于收发 RTP/RTCP 包的 UDP 端口。

抛开容错,总结一下 SETUP 请求的常规处理流程:

  1. 为会话创建 ServerMediaSession
  2. 解析请求头 Transport: 中包含的关于客户端请求的传输方式的内容,如使用 UDP 传输还是用 TCP 传输,客户端为 RTP/RTCP 传输开辟的端口等。
  3. 解析请求头中的 Range:x-playNow: 以支持 SETUPPLAY 的合并。
  4. 为会话分配网络资源,如服务器端的 RTP/RTCP 端口等。
  5. 产生响应消息。

ServerMediaSubsession 中,一些更详细地处理过程,留待后面分析。

打赏

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 源码分析:播放启动

坚持原创技术分享,您的支持将鼓励我继续创作!