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

RTSPClient建立流程(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
constcommandName,
responseHandler*handler,MediaSession*session,MediaSubsession*subsession,u_int32_tbooleanFlags,doublestart,double
endfloat 
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
constcommandName,
responseHandlerhandler,

           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
 constabsEndTime =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) {<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值