转载至:http://xingyunbaijunwei.blog.163.com/blog/static/76538067201221621636396/
PLAY命令概述
PLAY命令要求在SETUP命令之后进行,此命令处理过程中就开始发送数据了,在处理PLAY命令过程中还创建了RTCPInstance实例。
客户端可以通过PLAY命令的Scale头部域,指定播放速率,不过这个功能要看服务器对特定媒体的具体实现,当sacale=1时正常播放,sacale>1时快进,sacale<0时快退。
客户端可以通过PLAY命令的Range头部域,指定播放的时间范围,同样此功能也依赖于服务器中特定媒体的具体实现。
对于PLAY命令请求中的URL有以下几种情况(与PAUSE、TEARDOWN、GET_PARAMETER、SET_PARAMETER处理是一样的):
1) 非聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix,urlPreSuffix作为stream name, urlSuffix作为subsession的trackId
2) 非聚合的情况下,才能根据trackId找到subsession
3) 聚合,如
rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlSuffix作为stream name,而urlPreSuffix忽略
rtsp://192.168.1.1/urlPreSuffix, 只存在urlPreSuffix,并将其作为stream name, 这应该是最常见的情况
4) 聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlPreSuffix/urlSuffix整个作为stream name
我们可以对session中的subsession进行单独控制(这需要提供subsession的trackId), 也可以对整个session进行控制(这种情况应该是最常见的吧)。
贴一个SETUP消息实例:
1: PLAY rtsp://192.168.9.80/123.mpg/ RTSP/1.0
2: CSeq: 53: Session: 263BD44B4: Range: npt=0.000-5: User-Agent: LibVLC/1.1.0 (LIVE555 Streaming Media v2010.03.16)6:7: response: RTSP/1.0 200 OK8: CSeq: 59: Date: Wed, Nov 30 2011 06:55:07 GMT10: Range: npt=0.000-11: Session: 263BD44B12: RTP-Info: url=rtsp://192.168.9.80/123.mpg/track1;seq=38851;rtptime=1434098600,ur
13: l=rtsp://192.168.9.80/123.mpg/track2;seq=27752;rtptime=3595585826
14:
代码分析的过程比较烦琐,就先把总结性的东西放到最前面了
1.关于SETUP命令请求包中的ULR处理
1: void RTSPServer::RTSPClientSession ::handleCmd_withinSession(char const* cmdName,2: char const* urlPreSuffix, char const* urlSuffix,3: char const* cseq, char const* fullRequestStr) {4:5: //非聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix,urlPreSuffix作为stream name, urlSuffix作为subsession的trackId
6: //非聚合的情况下,才能根据trackId找到subsession
7: //聚合,如
8: //1)rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlSuffix作为stream name,而urlPreSuffix忽略
9: //2)rtsp://192.168.1.1/urlPreSuffix, 只存在urlPreSuffix,并将其作为stream name, 这应该是最常见的情况
10: //聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlPreSuffix/urlSuffix整个作为stream name
11:12: // This will either be:
13: // - an operation on the entire server, if "urlPreSuffix" is "", and "urlSuffix" is "*" (i.e., the special "*" URL), or
14: // - a non-aggregated operation, if "urlPreSuffix" is the session (stream)
15: // name and "urlSuffix" is the subsession (track) name, or
16: // - an aggregated operation, if "urlSuffix" is the session (stream) name,
17: // or "urlPreSuffix" is the session (stream) name, and "urlSuffix" is empty,
18: // or "urlPreSuffix" and "urlSuffix" are both nonempty, but when concatenated, (with "/") form the session (stream) name.
19: // Begin by figuring out which of these it is:
20: ServerMediaSubsession* subsession;21: if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') {
22: // An operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER:
23: if (strcmp(cmdName, "GET_PARAMETER") == 0) {24: handleCmd_GET_PARAMETER(NULL, cseq, fullRequestStr);25: } else if (strcmp(cmdName, "SET_PARAMETER") == 0) {26: handleCmd_SET_PARAMETER(NULL, cseq, fullRequestStr);27: } else {
28: handleCmd_notSupported(cseq);29: }30: return;
31: } else if (fOurServerMediaSession == NULL) { // There wasn't a previous SETUP!32: handleCmd_notSupported(cseq);33: return;
34: }else if (urlSuffix[0] != '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0) {35:36: //非聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix,urlPreSuffix作为stream name,
urlSuffix作为subsession的trackId
37: //非聚合的情况下,才能根据trackId找到subsession
38:39: // Non-aggregated operation.
40: // Look up the media subsession whose track id is "urlSuffix":
41: ServerMediaSubsessionIterator iter(*fOurServerMediaSession);42: while ((subsession = iter.next()) != NULL) {
43: if (strcmp(subsession->trackId(), urlSuffix) == 0) break; // success44: }45: if (subsession == NULL) { // no such track!46: handleCmd_notFound(cseq);47: return;
48: }49: } else if (strcmp(fOurServerMediaSession->streamName(), urlSuffix) == 0 ||50: (urlSuffix[0] == '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0)) {51:52: //聚合,如
53: //1)rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlSuffix作为stream name,而urlPreSuffix忽略
54: //2)rtsp://192.168.1.1/urlPreSuffix, 只存在urlPreSuffix,并将其作为stream name
55:56:57: // Aggregated operation
58: subsession = NULL;59: } else if (urlPreSuffix[0] != '\0' && urlSuffix[0] != '\0') {60:61: //聚合,如rtsp://192.168.1.1/urlPreSuffix/urlSuffix, 将urlPreSuffix/urlSuffix整个作为stream name
62:63: //Aggregated operation,
64: // Aggregated operation, if <urlPreSuffix>/<urlSuffix> is the session (stream) name:
65: unsigned const urlPreSuffixLen = strlen(urlPreSuffix);66: if (strncmp(fOurServerMediaSession->streamName(), urlPreSuffix, urlPreSuffixLen) == 0 &&
67: fOurServerMediaSession->streamName()[urlPreSuffixLen] == '/' &&68: strcmp(&(fOurServerMediaSession->streamName())[urlPreSuffixLen+1], urlSuffix) == 0) {69: subsession = NULL;70: } else {
71: handleCmd_notFound(cseq);72: return;
73: }74: } else { // the request doesn't match a known stream and/or track at all!75: handleCmd_notFound(cseq);76: return;
77: }78:79: if (strcmp(cmdName, "TEARDOWN") == 0) {80: handleCmd_TEARDOWN(subsession, cseq);81: } else if (strcmp(cmdName, "PLAY") == 0) {82: handleCmd_PLAY(subsession, cseq, fullRequestStr);83: } else if (strcmp(cmdName, "PAUSE") == 0) {84: handleCmd_PAUSE(subsession, cseq);85: } else if (strcmp(cmdName, "GET_PARAMETER") == 0) {86: handleCmd_GET_PARAMETER(subsession, cseq, fullRequestStr);87: } else if (strcmp(cmdName, "SET_PARAMETER") == 0) {88: handleCmd_SET_PARAMETER(subsession, cseq, fullRequestStr);89: }90: }91:
2.PLAY命令处理函数handleCmd_PLAY(1.1)
当RTSPClientSession收到PLAY请求时,就开始传输RTP数据。下面看一下流启动的代码:
1: void RTSPServer::RTSPClientSession
2: ::handleCmd_PLAY(ServerMediaSubsession* subsession, char const* cseq,3: char const* fullRequestStr) {4: char* rtspURL = fOurServer.rtspURL(fOurServerMediaSession, fClientInputSocket);
5: unsigned rtspURLSize = strlen(rtspURL);
6:7: //分析"Scale:"头部
8: //Scale头,指示了播放的速率,scale = 1为正常播放,大于1快进,小于0则表示快退
9:10:11: // Parse the client's "Scale:" header, if any:
12: float scale;
13: Boolean sawScaleHeader = parseScaleHeader(fullRequestStr, scale);14:15:16: //测试scale的值是否能满足,这期间可能会改变scale的值
17: // Try to set the stream's scale factor to this value:
18: if (subsession == NULL /*aggregate op*/) { //聚合的情况下,subsession还不确定19: fOurServerMediaSession->testScaleFactor(scale); //测试scale的值(见2.1)
20: } else {
21: subsession->testScaleFactor(scale);22: }23:24: char buf[100];
25: char* scaleHeader;
26: if (!sawScaleHeader) {
27: buf[0] = '\0'; // Because we didn't see a Scale: header, don't send one back
28: } else {
29: sprintf(buf, "Scale: %f\r\n", scale);
30: }31: scaleHeader = strDup(buf);32:33: //分析"Range:"头部
34: //"Range:"头部,表示要播放的时间范围。如Range: npt=0.000-,从0时刻开始播放看到结束
35: //不含Range 首部域的PLAY 请求也是合法的。它从媒体流开头开始播放,直到媒体流被暂停
36:37: // Parse the client's "Range:" header, if any:
38: double rangeStart = 0.0, rangeEnd = 0.0;
39: Boolean sawRangeHeader = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd);40:41: //关于"Range:"头部的其它操作
42: ...43:44: //下面创建响应中的"RTP-Info:"行
45:46:47: // Create a "RTP-Info:" line. It will get filled in from each subsession's state:
48: char const* rtpInfoFmt =49: "%s" // "RTP-Info:", plus any preceding rtpInfo items50: "%s" // comma separator, if needed51: "url=%s/%s"
52: ";seq=%d"
53: ";rtptime=%u"
54: ;55: unsigned rtpInfoFmtSize = strlen(rtpInfoFmt);
56: char* rtpInfo = strDup("RTP-Info: ");57: unsigned i, numRTPInfoItems = 0;
58:59: //根据要求,在每个subsession上进行seeking/scaling操作
60: // Do any required seeking/scaling on each subsession, before starting streaming:
61: for (i = 0; i < fNumStreamStates; ++i) {
62: if (subsession == NULL /* means: aggregated operation */63: || subsession == fStreamStates[i].subsession) {64: if (sawScaleHeader) {
65: fStreamStates[i].subsession->setStreamScale(fOurSessionId, //设置subsession的scale值(见2.1)
66: fStreamStates[i].streamToken,67: scale);68: }69: if (sawRangeHeader) {
70:71: //计算流的播放时间streamDuration
72: double streamDuration = 0.0; // by default; means: stream until the end of the media
// the 0.001 is because we limited the values to 3 decimal places
73: if (rangeEnd > 0.0 && (rangeEnd+0.001) < duration) {
74: // We want the stream to end early. Set the duration we want:
75: streamDuration = rangeEnd - rangeStart;
// should happen only if scale < 0.0 这里情况下进行快退操作
76: if (streamDuration < 0.0) streamDuration = -streamDuration;
77: }78: u_int64_t numBytes;79: fStreamStates[i].subsession->seekStream(fOurSessionId, //设置每个subsession上的播放时间范围(见2.2)
80: fStreamStates[i].streamToken,81: rangeStart, streamDuration, numBytes);82: }83: }84: }85:86:87: // Create the "Range:" header that we'll send back in our response.
88: //(Note that we do this after seeking, in case the seeking operation changed the range start time)
89: char* rangeHeader;
90: if (!sawRangeHeader) {
91: buf[0] = '\0'; // Because we didn't see a Range: header, don't send one back
92: } else if (rangeEnd == 0.0 && scale >= 0.0) {93: sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart);
94: } else {
95: sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd);
96: }97: rangeHeader = strDup(buf);98:99: //现在终于开始媒体数据传输了
100:101: // Now, start streaming:
102: for (i = 0; i < fNumStreamStates; ++i) {
103: if (subsession == NULL /* means: aggregated operation */104: || subsession == fStreamStates[i].subsession) {105: unsigned short rtpSeqNum = 0;106: unsigned rtpTimestamp = 0;
107:108:109: //开始各个subsession上的数据传输, 即开始播放了(见2.3)
110:111:112: fStreamStates[i].subsession->startStream(fOurSessionId,113: fStreamStates[i].streamToken,114: (TaskFunc*)noteClientLiveness, this,
115: rtpSeqNum, rtpTimestamp,116: handleAlternativeRequestByte, this);
117: const char *urlSuffix = fStreamStates[i].subsession->trackId();118: char* prevRTPInfo = rtpInfo;
119: unsigned rtpInfoSize = rtpInfoFmtSize
120: + strlen(prevRTPInfo)121: + 1122: + rtspURLSize + strlen(urlSuffix)123: + 5 /*max unsigned short len*/
124: + 10 /*max unsigned (32-bit) len*/
125: + 2 /*allows for trailing \r\n at final end of string*/;
126: rtpInfo = new char[rtpInfoSize];127:128: //subsession中的信息添加到"RTP-Info:"行中
129:130: sprintf(rtpInfo, rtpInfoFmt,131: prevRTPInfo,132: numRTPInfoItems++ == 0 ? "" : ",",
133: rtspURL, urlSuffix,134: rtpSeqNum,135: rtpTimestamp136: );137: delete[] prevRTPInfo;
138: }139: }140:141: //下面是组装响应包的操作
142: if (numRTPInfoItems == 0) {
143: rtpInfo[0] = '\0';144: } else {
145: unsigned rtpInfoLen = strlen(rtpInfo);
146: rtpInfo[rtpInfoLen] = '\r';147: rtpInfo[rtpInfoLen + 1] = '\n';148: rtpInfo[rtpInfoLen + 2] = '\0';149: }150:151:152: // Fill in the response:
153: snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,154: "RTSP/1.0 200 OK\r\n"
155: "CSeq: %s\r\n"
156: "%s"
157: "%s"
158: "%s"
159: "Session: %08X\r\n"
160: "%s\r\n", cseq, dateHeader(), scaleHeader, rangeHeader,
161: fOurSessionId, rtpInfo);162: delete[] rtpInfo;
163: delete[] rangeHeader;
164: delete[] scaleHeader;
165: delete[] rtspURL;
166: }167:
有个问题,如果这个streamToken使用的是已存在的(还记得ReuseFirstSource吗),为它再次调用startStream()时,究竟会做什么呢?应该是把这个客户端的地址和rtp/rtcp端口传给rtp server的groupSocket,rtp server自然就开始向这个客户端发送数据了。具体可以看后面void StreamState::startPlaying(…)的代码。
3.关于播放速度参数scale(2.1)
scale参数指定了播放的速率,scale = 1为正常播放,大于1快进,小于0则表示快退。是否能满足scale的要求,要看服务器是否支持。看ServerMediaSession中的测试函数。
1: void ServerMediaSession::testScaleFactor(float& scale) {2: // First, try setting all subsessions to the desired scale.
3: // If the subsessions' actual scales differ from each other, choose the
4: // value that's closest to 1, and then try re-setting all subsessions to that
5: // value. If the subsessions' actual scales still differ, re-set them all to 1.
6: float minSSScale = 1.0;
7: float maxSSScale = 1.0;
8: float bestSSScale = 1.0;
9: float bestDistanceTo1 = 0.0;
10: ServerMediaSubsession* subsession;11: for (subsession = fSubsessionsHead; subsession != NULL;
12: subsession = subsession->fNext) {13: float ssscale = scale;
14: subsession->testScaleFactor(ssscale);15: if (subsession == fSubsessionsHead) { // this is the first subsession16: minSSScale = maxSSScale = bestSSScale = ssscale;17: bestDistanceTo1 = (float)fabs(ssscale - 1.0f);
18: } else {
19: if (ssscale < minSSScale) {
20: minSSScale = ssscale;21: } else if (ssscale > maxSSScale) {22: maxSSScale = ssscale;23: }24:25: float distanceTo1 = (float)fabs(ssscale - 1.0f);26: if (distanceTo1 < bestDistanceTo1) {
27: bestSSScale = ssscale;28: bestDistanceTo1 = distanceTo1;29: }30: }31: }32: if (minSSScale == maxSSScale) {
33: // All subsessions are at the same scale: minSSScale == bestSSScale == maxSSScale
34: scale = minSSScale;35: return;
36: }37:38:39: // The scales for each subsession differ. Try to set each one to the value
40: // that's closest to 1:
41: for (subsession = fSubsessionsHead; subsession != NULL;
42: subsession = subsession->fNext) {43: float ssscale = bestSSScale;
44: subsession->testScaleFactor(ssscale);45: if (ssscale != bestSSScale) break; // no luck46: }47: if (subsession == NULL) {
48: // All subsessions are at the same scale: bestSSScale
49: scale = bestSSScale;50: return;
51: }52:53: // Still no luck. Set each subsession's scale to 1:
54: for (subsession = fSubsessionsHead; subsession != NULL;
55: subsession = subsession->fNext) {56: float ssscale = 1;
57: subsession->testScaleFactor(ssscale);58: }59: scale = 1;60: }61:
上面的函数处理过程有些繁锁,主要是处理各subsession支持不同的scale的情况, 这种情况下将选取scale值最靠近1的值(1为正常速率)。上面的函数中实际上调用了subsession上的testScaleFactor函数。其在ServerMediaSubsession类中有默认实现,如下:
1: void ServerMediaSubsession::testScaleFactor(float& scale) {2: // default implementation: Support scale = 1 only
3: scale = 1;4: }5:
默认只能正常播放,再来看subsession上的setStreamScale函数
1: void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,2: void* /*streamToken*/, float /*scale*/) {3: // default implementation: do nothing
4: }5:
do nothing!不过这是一个虚函数,在nDemandServerMediaSubsession中有重新实现
1: void OnDemandServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,2: void* streamToken, float scale) {3:4: //当多个客户端从同一个source接收数据时,sacale值是不能改变的
5:6: // Changing the scale factor isn't allowed if multiple clients are receiving data
7: // from the same source:
8: if (fReuseFirstSource) return;9:10: StreamState* streamState = (StreamState*)streamToken;11: if (streamState != NULL && streamState->mediaSource() != NULL) {
12: setStreamSourceScale(streamState->mediaSource(), scale);13: }14: }15:
继续看setStreamSourceScale函数
1: void OnDemandServerMediaSubsession
2: ::setStreamSourceScale(FramedSource* /*inputSource*/, float /*scale*/) {3: // Default implementation: Do nothing
4: }5:
什么都做,这样如果要实现快进/快退操作, 只需要在自己的subsession中重新实现这个函数即可。
4.设置播放的时间范围(2.2)
1: void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,2: void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes) {3: numBytes = 0; // by default: unknown
4:5: //同样的多个客户端对应同一个source时,不充许此操作
6: // Seeking isn't allowed if multiple clients are receiving data from
7: // the same source:
8: if (fReuseFirstSource) return;9:10: StreamState* streamState = (StreamState*)streamToken;11: if (streamState != NULL && streamState->mediaSource() != NULL) {
12: seekStreamSource(streamState->mediaSource(), seekNPT, streamDuration, numBytes);13: }14: }15:
seekStreamSource也是定义在OnDemandServerMediaSubsession上的虚函数,默认实现也是do nothing, 需要在自己的子类中重新实现。看了下H264VideoFileServerMediaSubsession类,并没有去实现seekStreamSource,所以*.264的文件每次打开只能从头看起了。
5.开始播放(2.3)
进行了如此多的工作,终于要开始播放了,期待。。。
startStream函数是定义在ServerMediaSubsession类中的纯虚函数,首先来看其子类OnDemandServerMediaSubsession中的实现
1: void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,2: void* streamToken,
3: TaskFunc* rtcpRRHandler,4: void* rtcpRRHandlerClientData,
5: unsigned short& rtpSeqNum,6: unsigned& rtpTimestamp,
7: ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,8: void* serverRequestAlternativeByteHandlerClientData) {
9: StreamState* streamState = (StreamState*)streamToken;10: Destinations* destinations11: = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId));12: if (streamState != NULL) {
13: streamState->startPlaying(destinations,14: rtcpRRHandler, rtcpRRHandlerClientData,15: serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);16: if (streamState->rtpSink() != NULL) {
17: rtpSeqNum = streamState->rtpSink()->currentSeqNo(); //这个rtpSeqNum有什么用呢?
18: rtpTimestamp = streamState->rtpSink()->presetNextTimestamp();19: }20: }21: }22:
这里主要调用函数StreamState::startPlaying
1: void StreamState::startPlaying(Destinations* dests,
2: TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
3: ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,4: void* serverRequestAlternativeByteHandlerClientData) {
5: if (dests == NULL) return;6:7: //创建RTCPInstance实例
8:9: if (fRTCPInstance == NULL && fRTPSink != NULL) {
10: // Create (and start) a 'RTCP instance' for this RTP sink:
11: fRTCPInstance12: = RTCPInstance::createNew(fRTPSink->envir(), fRTCPgs,13: fTotalBW, (unsigned char*)fMaster.fCNAME,14: fRTPSink, NULL /* we're a server */);
15: // Note: This starts RTCP running automatically
16: }17:18: if (dests->isTCP) {
19: //使用TCP传输RTP 和 RTCP
20:21: // Change RTP and RTCP to use the TCP socket instead of UDP:
22: if (fRTPSink != NULL) {
23: fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);24: fRTPSink->setServerRequestAlternativeByteHandler(dests->tcpSocketNum, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);25: }26: if (fRTCPInstance != NULL) {
27: fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);28: fRTCPInstance->setSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId,29: rtcpRRHandler, rtcpRRHandlerClientData);30: }31: } else {
32: //使用UDP传输RTP、RTCP
33:34: // Tell the RTP and RTCP 'groupsocks' about this destination
35: // (in case they don't already have it):
36: if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort);
37: if (fRTCPgs != NULL) fRTCPgs->addDestination(dests->addr, dests->rtcpPort);
38: if (fRTCPInstance != NULL) {
39: fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort,40: rtcpRRHandler, rtcpRRHandlerClientData);41: }42: }43:44: //下面调用sink上的sdtartPlaying函数开始传输数据
45: if (!fAreCurrentlyPlaying && fMediaSource != NULL) {
46: if (fRTPSink != NULL) { //通过RTP协议传输47: fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
48: fAreCurrentlyPlaying = True;49: } else if (fUDPSink != NULL) { //裸的UDP数据包,不使用RTP协议50: fUDPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
51: fAreCurrentlyPlaying = True;52: }53: }54: }55:
上面的函数最终调用了MediaSink::startPlaying函数,开始传输数据,这个过程的函数调用比较复杂,将在下一篇文章中单独分析。大致过程是,先取一个帧发送出去,然后此函数就返回了,再处理response包的发送等剩余操作。