RTSP的Client建立流程(testProgs中的testRTSPClient示例)
参考文档:
testRtspClient流程图请参考下面链接:
http://blog.csdn.net/smilestone_322/article/details/17297817
1)
Sink和source
Source是接收数据,Sink是消费数据;
int main(intargc,char**
argv) {
// Begin by setting up our usage environment:
TaskScheduler*
scheduler = BasicTaskScheduler::createNew();
UsageEnvironment*
env = BasicUsageEnvironment::createNew(*scheduler);
// We need at least one "rtsp://" URL argument:
if (argc < 2) {
usage(*env,argv[0]);
return 1;
}
// There are argc-1 URLs: argv[1] through argv[argc-1]. Open and start streaming each one:
for (inti = 1;i <=argc-1; ++i) {
openURL(*env,argv[0],argv[i]);
}
// All subsequent activity takes place within the event loop:
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
// This function call does not return, unless, at some point in time, "eventLoopWatchVariable" gets set to something non-zero.
return 0;
}
TestRtspClient的堆栈调用流程如下:
1)
首先OpenURL,函数如下:
void
openURL(UsageEnvironment&
env, char const*progName,charconst*
rtspURL) {
// Begin by creating a "RTSPClient" object. Note that there is a separate "RTSPClient" object for each stream that we wish
// to receive (even if more than stream uses the same "rtsp://" URL).
RTSPClient*
rtspClient = ourRTSPClient::createNew(env,rtspURL,RTSP_CLIENT_VERBOSITY_LEVEL,progName);
if (rtspClient ==NULL) {
env <<
"Failed to create a RTSP client for URL \"" <<
rtspURL << "\": " <<
env.getResultMsg() <<
"\n";
return;
}
++rtspClientCount;
// Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream.
// Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response.
// Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop:
rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
2) }
首先通过ourRTSPClient::createNew函数最终会调用ourRTSPClient的构造函数,基类RTSPClient的指针指向派生类ourRTSPClient对象,并且最终会调用RTSPClient的构造函数;
sendDescribeCommand函数往服务器端发送Describe请求;continueAfterDESCRIBE为回调函数;在DoEventLoop中的SingleStep中调用;RTP over tcp 还是udp 由宏#defineREQUEST_STREAMING_OVER_TCPFalse进行控制;
unsigned
RTSPClient::sendDescribeCommand(responseHandler*responseHandler,Authenticator*authenticator)
{
if (authenticator !=NULL)fCurrentAuthenticator = *authenticator;
return
sendRequest(new
RequestRecord(++fCSeq,
"DESCRIBE", responseHandler));
}
将continueAfterDESCRIBE函数传递到responseHandler,相当于continueAfterDESCRIBE为一个回调函数;注意RequestRecord这个类的作用;在SendRequest中调用RequestRecord的构造函数
RTSPClient::RequestRecord::RequestRecord(unsignedcseq,char
const* commandName,
responseHandler*handler,MediaSession*session,MediaSubsession*subsession,u_int32_tbooleanFlags,doublestart,double
end, float
scale,charconst*
contentStr)
:fNext(NULL),fCSeq(cseq),fCommandName(commandName),fSession(session),fSubsession(subsession),fBooleanFlags(booleanFlags),
fStart(start),fEnd(end),fAbsStartTime(NULL),fAbsEndTime(NULL),fScale(scale),fContentStr(strDup(contentStr)),fHandler(handler)
{
}
将回调函数保存在RequestRecord类的fHandler上; RequestRecord类定义如下:
// The state of a request-in-progress:
class
RequestRecord {
public:
RequestRecord(unsignedcseq,char
const* commandName,
responseHandler* handler,
MediaSession*
session = NULL,
MediaSubsession* subsession =
NULL, u_int32_t
booleanFlags = 0,
double
start = 0.0f, double
end = -1.0f, float
scale = 1.0f, char
const* contentStr =
NULL);
RequestRecord(unsignedcseq,responseHandler*handler,
char
const* absStartTime,
char const* absEndTime =NULL,float
scale = 1.0f,
MediaSession*
session = NULL,
MediaSubsession* subsession =
NULL);
// alternative constructor for creating "PLAY" requests that include 'absolute' time values
virtual ~RequestRecord();
RequestRecord*&
next() { return
fNext; }
unsigned&
cseq() { return
fCSeq; }
char
const* commandName()
const { return
fCommandName; }
MediaSession*
session() const {
return fSession; }
MediaSubsession*
subsession() const {
return fSubsession; }
u_int32_t
booleanFlags() const {
return fBooleanFlags; }
double
start() const { returnfStart; }
double
end() const { returnfEnd; }
char
const* absStartTime()
const { return
fAbsStartTime; }
char
const* absEndTime()
const { return
fAbsEndTime; }
float
scale() const { returnfScale; }
char*
contentStr() const {
return fContentStr; }
responseHandler*&
handler() { return
fHandler; }
private:
RequestRecord*
fNext;
unsigned
fCSeq;
char
const* fCommandName;
MediaSession*
fSession;
MediaSubsession*
fSubsession;
u_int32_t
fBooleanFlags;
double
fStart, fEnd;
char *fAbsStartTime, *fAbsEndTime;// used for optional 'absolute' (i.e., "time=") range specifications
float
fScale;
char*
fContentStr;
responseHandler* fHandler;
};
在其他地方就通过RequestRecord类的fHandler 调用到回调函数continueAfterDESCRIBE;注意跟踪在哪个地方调用了RequestRecord类的fHandler,我猜是在DoEvent();中调用的。在DoEvent的函数incomingDataHandler1中的handleResponseBytes中调用
(*foundRequest->handler())(this,resultCode,resultString);
typedef
void (responseHandler)(RTSPClient*rtspClient,
int
resultCode, char*
resultString);
// A function that is called in response to a RTSP command. The parameters are as follows:
// "rtspClient": The "RTSPClient" object on which the original command was issued.
// "resultCode": If zero, then the command completed successfully. If non-zero, then the command did not complete
// successfully, and "resultCode" indicates the error, as follows:
// A positive "resultCode" is a RTSP error code (for example, 404 means "not found")
// A negative "resultCode" indicates a socket/network error; 0-"resultCode" is the standard "errno" code.
// "resultString": A ('\0'-terminated) string returned along with the response, or else NULL.
// In particular:
// "resultString" for a successful "DESCRIBE" command will be the media session's SDP description.
// "resultString" for a successful "OPTIONS" command will be a list of allowed commands.
// Note that this string can be present (i.e., not NULL) even if "resultCode" is non-zero - i.e., an error message.
// Also, "resultString" can be NULL, even if "resultCode" is zero (e.g., if the RTSP command succeeded, but without
// including an appropriate result header).
// Note also that this string is dynamically allocated, and must be freed by the handler (or the caller)
// - using "delete[]".
sendRequest函数如下:
unsigned
RTSPClient::sendRequest(RequestRecord*request) {
char*
cmd = NULL;
do {
Boolean
connectionIsPending = False;
if (!fRequestsAwaitingConnection.isEmpty()) {
// A connection is currently pending (with at least one enqueued request). Enqueue this request also:
connectionIsPending =
True;
} else
if (fInputSocketNum < 0) {
// we need to open a connection
int
connectResult = openConnection();
if (connectResult < 0)break;// an error occurred
else
if (connectResult == 0) {
// A connection is pending
connectionIsPending =
True;
} // else the connection succeeded. Continue sending the command.
}
if (connectionIsPending) {
//将request入队列,估计在其它的地方,遍历队列,访问request 的 responseHandler* fHandler;
fRequestsAwaitingConnection.enqueue(request);
return
request->cseq();
}
// If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP:
if (fTunnelOverHTTPPortNum != 0 &&strcmp(request->commandName(),"GET")
!= 0 && fOutputSocketNum ==fInputSocketNum) {
if (!setupHTTPTunneling1())break;
fRequestsAwaitingHTTPTunneling.enqueue(request);
return
request->cseq();
}
// Construct and send the command:
// First, construct command-specific headers that we need:
char*
cmdURL = fBaseURL;
// by default
Boolean
cmdURLWasAllocated = False;
char
const* protocolStr =
"RTSP/1.0"; // by default
char*
extraHeaders = (char*)"";// by default
Boolean
extraHeadersWereAllocated = False;
char*
contentLengthHeader = (char*)"";// by default
Boolean
contentLengthHeaderWasAllocated = False;
char
const* contentStr =
request->contentStr();
// by default
if (contentStr ==NULL)contentStr ="";
unsigned
contentStrLen = strlen(contentStr);
if (contentStrLen > 0) {
char
const* contentLengthHeaderFmt =
"Content-Length: %d\r\n";
unsigned
contentLengthHeaderSize = strlen(contentLengthHeaderFmt)
+ 20 /* max int len */;
contentLengthHeader =
new char[contentLengthHeaderSize];
sprintf(contentLengthHeader,contentLengthHeaderFmt,contentStrLen);
contentLengthHeaderWasAllocated =True;
}
if (strcmp(request->commandName(),"DESCRIBE") == 0) {
extraHeaders = (char*)"Accept: application/sdp\r\n";
} else
if (strcmp(request->commandName(),"OPTIONS") == 0) {
} else
if (strcmp(request->commandName(),"ANNOUNCE") == 0) {
extraHeaders = (char*)"Content-Type: application/sdp\r\n";
} else
if (strcmp(request->commandName(),"SETUP") == 0) {
MediaSubsession&
subsession = *request->subsession();
Boolean
streamUsingTCP = (request->booleanFlags()&0x1) != 0;
Boolean
streamOutgoing = (request->booleanFlags()&0x2) != 0;
Boolean
forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0;
char
const *prefix, *separator, *suffix;
constructSubsessionURL(subsession,prefix,separator,suffix);
char
const* transportFmt;
if (strcmp(subsession.protocolName(),"UDP") == 0) {
suffix =
"";
transportFmt =
"Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
} else {
transportFmt =
"Transport: RTP/AVP%s%s%s=%d-%d\r\n";
}
cmdURL =
new char[strlen(prefix) +strlen(separator) +strlen(suffix)
+ 1];
cmdURLWasAllocated =
True;
sprintf(cmdURL,"%s%s%s",prefix,separator,suffix);
// Construct a "Transport:" header.
char
const* transportTypeStr;
char
const* modeStr =
streamOutgoing ? ";mode=receive" :
"";
// Note: I think the above is nonstandard, but DSS wants it this way
char
const* portTypeStr;
portNumBits
rtpNumber, rtcpNumber;
if (streamUsingTCP) {// streaming over the RTSP connection
transportTypeStr =
"/TCP;unicast";
portTypeStr =
";interleaved";
rtpNumber =
fTCPStreamIdCount++;
rtcpNumber =
fTCPStreamIdCount++;
} else {
// normal RTP streaming
unsigned
connectionAddress = subsession.connectionEndpointAddress();
Boolean
requestMulticastStreaming
= IsMulticastAddress(connectionAddress) || (connectionAddress == 0 &&forceMulticastOnUnspecified);
transportTypeStr =
requestMulticastStreaming ? ";multicast" :";unicast";
portTypeStr =
";client_port";
rtpNumber =
subsession.clientPortNum();
if (rtpNumber == 0) {
envir().setResultMsg("Client port number unknown\n");
delete[]
cmdURL;
break;
}
rtcpNumber =
rtpNumber + 1;
}
unsigned
transportSize = strlen(transportFmt)
+ strlen(transportTypeStr) +strlen(modeStr) +strlen(portTypeStr)
+ 2*5/* max port len */;
char*
transportStr = new
char[transportSize];
sprintf(transportStr,transportFmt,
transportTypeStr,
modeStr, portTypeStr,
rtpNumber, rtcpNumber);
// When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands:
char*
sessionStr = createSessionString(fLastSessionId);
// The "Transport:" and "Session:" (if present) headers make up the 'extra headers':
extraHeaders =
new char[transportSize +strlen(sessionStr)];
extraHeadersWereAllocated =True;
sprintf(extraHeaders,"%s%s",transportStr,sessionStr);
delete[]
transportStr; delete[]
sessionStr;
} else
if (strcmp(request->commandName(),"GET") == 0 ||strcmp(request->commandName(),"POST")
== 0) {
// We will be sending a HTTP (not a RTSP) request.
// Begin by re-parsing our RTSP URL, just to get the stream name, which we'll use as our 'cmdURL' in the subsequent request:
char*
username;
char*
password;
NetAddress
destAddress;
portNumBits
urlPortNum;
if (!parseRTSPURL(envir(),fBaseURL,username,password,destAddress,urlPortNum,
(charconst**)&cmdURL))break;
if (cmdURL[0] =='\0')cmdURL = (char*)"/";
delete[]
username;
delete[]
password;
protocolStr =
"HTTP/1.0";
if (strcmp(request->commandName(),"GET") == 0) {
// Create a 'session cookie' string, using MD5:
struct {
struct
timeval timestamp;
unsigned
counter;
} seedData;
gettimeofday(&seedData.timestamp,NULL);
seedData.counter = ++fSessionCookieCounter;
our_MD5Data((unsignedchar*)(&seedData),sizeofseedData,fSessionCookie);
// DSS seems to require that the 'session cookie' string be 22 bytes long:
fSessionCookie[23] =
'\0';
char
const* const extraHeadersFmt =
"x-sessioncookie: %s\r\n"
"Accept: application/x-rtsp-tunnelled\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n";
unsigned
extraHeadersSize = strlen(extraHeadersFmt)
+ strlen(fSessionCookie);
extraHeaders =
new char[extraHeadersSize];
extraHeadersWereAllocated =True;
sprintf(extraHeaders,extraHeadersFmt,
fSessionCookie);
} else {
// "POST"
char
const* const extraHeadersFmt =
"x-sessioncookie: %s\r\n"
"Content-Type: application/x-rtsp-tunnelled\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"Content-Length: 32767\r\n"
"Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";
unsigned
extraHeadersSize = strlen(extraHeadersFmt)
+ strlen(fSessionCookie);
extraHeaders =
new char[extraHeadersSize];
extraHeadersWereAllocated =True;
sprintf(extraHeaders,extraHeadersFmt,
fSessionCookie);
}
} else {
// "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER"
// First, make sure that we have a RTSP session in progress
if (fLastSessionId ==NULL) {
envir().setResultMsg("No RTSP session is currently in progress\n");
break;
}
char
const* sessionId;
float
originalScale;
if (request->session() !=NULL) {
// Session-level operation
cmdURL = (char*)sessionURL(*request->session());
sessionId =
fLastSessionId;
originalScale =
request->session()->scale();
} else {
// Media-level operation
char
const *prefix, *separator, *suffix;
constructSubsessionURL(*request->subsession(),prefix,separator,suffix);
cmdURL =
new char[strlen(prefix) +strlen(separator) +strlen(suffix)
+ 1];
cmdURLWasAllocated =
True;
sprintf(cmdURL,"%s%s%s",prefix,separator,suffix);
sessionId =
request->subsession()->sessionId();
originalScale =
request->subsession()->scale();
}
if (strcmp(request->commandName(),"PLAY") == 0) {
// Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':
char*
sessionStr = createSessionString(sessionId);
char*
scaleStr = createScaleString(request->scale(),originalScale);
char*
rangeStr = createRangeString(request->start(),request->end(),request->absStartTime(),request->absEndTime());
extraHeaders =
new char[strlen(sessionStr) +strlen(scaleStr) +strlen(rangeStr)
+ 1];
extraHeadersWereAllocated =True;
sprintf(extraHeaders,"%s%s%s",sessionStr,scaleStr,rangeStr);
delete[]
sessionStr; delete[]
scaleStr; delete[]
rangeStr;
} else {
// Create a "Session:" header; this makes up our 'extra headers':
extraHeaders =
createSessionString(sessionId);
extraHeadersWereAllocated =True;
}
}
char*
authenticatorStr = createAuthenticatorString(request->commandName(),fBaseURL);
char
const* const cmdFmt =
"%s %s %s\r\n"
"CSeq: %d\r\n"
"%s"
"%s"
"%s"
"%s"
"\r\n"
"%s";
unsigned
cmdSize = strlen(cmdFmt)
+ strlen(request->commandName()) +strlen(cmdURL) +strlen(protocolStr)
+ 20 /* max int len */
+ strlen(authenticatorStr)
+ fUserAgentHeaderStrLen
+ strlen(extraHeaders)
+ strlen(contentLengthHeader)
+ contentStrLen;
cmd =
new char[cmdSize];
sprintf(cmd,cmdFmt,
request->commandName(),cmdURL,protocolStr,
request->cseq(),
authenticatorStr,
fUserAgentHeaderStr,
extraHeaders,
contentLengthHeader,
contentStr);
delete[]
authenticatorStr;
if (cmdURLWasAllocated)delete[]cmdURL;
if (extraHeadersWereAllocated)delete[]extraHeaders;
if (contentLengthHeaderWasAllocated)delete[]contentLengthHeader;
if (fVerbosityLevel >= 1)envir() <<"Sending request: " <<cmd <<"\n";
if (fTunnelOverHTTPPortNum != 0 &&strcmp(request->commandName(),"GET")
!= 0 && strcmp(request->commandName(),"POST") != 0) {
// When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it.
// (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.)
char*
origCmd = cmd;
cmd =
base64Encode(origCmd,
strlen(cmd));
if (fVerbosityLevel >= 1)envir() <<"\tThe request was base-64 encoded to: " <<cmd
<<"\n\n";
delete[]
origCmd;
}
if (send(fOutputSocketNum,cmd,strlen(cmd),
0) < 0) {<