您的位置:首页 > 其它

Live555源码彻底解密(根据testRTSPClient讲解)

2017-11-07 16:02 507 查看
RTSP的Client建立流程(testProgs中的testRTSPClient示例)

参考文档:

http://www.live555.com/liveMedia/doxygen/html/testRTSPClient_8cpp.html#db610df7edad8ceaf6e28e6de0367a13

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) {<
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: