织梦CMS - 轻松建站从此开始!

罗索

浅析live555MediaServer初始化流程

落鹤生 发布于 2012-05-08 09:33 点击:次 
live555 MediaServer初始化流程分析。
TAG:

mediaServer/live555MediaServer.cpp
==> main
1. RTSPServer::RTSPServer这个是live555MediaServer监听客户端连接的默认处理函数,监听端口为554(root权限执行)或8554
incomingConnectionHandlerRTSP
2. RTSPServer::setUpTunnelingOverHTTP通过http为rtsp做一个tunneling隧道,端口80或8000或8080,即RTSP-over-HTTP tunneling
incomingConnectionHandlerHTTP
3. RTSPServer::RTSPClientSession::RTSPClientSession
incomingRequestHandler

上面的incomingConnectionHandlerRTSP和incomingConnectionHandlerHTTP最终都会调用
下面的incomingConnectionHandler统一处理函数[luther.gliethttp]
void RTSPServer::incomingConnectionHandler(int serverSocket) {
  struct sockaddr_in clientAddr;
  SOCKLEN_T clientAddrLen = sizeof clientAddr;
  int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen); // 一个client尝试连接本server
  if (clientSocket < 0) {
    int err = envir().getErrno();
    if (err != EWOULDBLOCK) {
        envir().setResultErrMsg("accept() failed: ");
    }
    return;
  }
  makeSocketNonBlocking(clientSocket);
  increaseSendBufferTo(envir(), clientSocket, 50*1024);

#ifdef DEBUG
  envir() << "accept()ed connection from " << our_inet_ntoa(clientAddr.sin_addr) << '\n';
#endif

  // Create a new object for this RTSP session.
  // (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number).  We don't bother checking for
  //  a collision; the probability of two concurrent sessions getting the same session id is very low.)
  unsigned sessionId = (unsigned)our_random();
  (void)createNewClientSession(sessionId, clientSocket, clientAddr); // 将这个client添加到select中,随后与该client进行的一切数据交互
// 都将由incomingRequestHandler函数处理.
}

4. env->taskScheduler().doEventLoop();将使系统进入select或poll,等待554或8554或80或8000或8080端口上client客户端程序的连接.
它将调用BasicTaskScheduler::SingleStep完成select操作,即:
select(fMaxNumSockets, &readSet, &writeSet, &exceptionSet, &tv_timeToDelay);

5. RTSPServer::RTSPClientSession::incomingRequestHandler这是连接到本live555MediaServer服务器的client随后数据处理函数[luther.gliethttp]
void RTSPServer::RTSPClientSession::incomingRequestHandler1() {
  struct sockaddr_in dummy; // 'from' address, meaningless in this case

  int bytesRead = readSocket(envir(), fClientInputSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy); // 读取是client发送过来的数据到fRequestBuffer[]数组
  handleRequestBytes(bytesRead); // 处理client发送过来的接收到fRequestBuffer[]数组中的bytesRead字节数据
}
// 处理client发送过来的接收到fRequestBuffer[]数组中的bytesRead字节数据[luther.gliethttp]
void RTSPServer::RTSPClientSession::handleRequestBytes(int newBytesRead) {
  noteLiveness();

  if (newBytesRead <= 0 || (unsigned)newBytesRead >= fRequestBufferBytesLeft) {
    // Either the client socket has died, or the request was too big for us.
    // Terminate this connection:
#ifdef DEBUG
    fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
#endif
    delete this;
    return;
  }

  Boolean endOfMsg = False;
  unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
#ifdef DEBUG
  ptr[newBytesRead] = '\0';
  fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes:%s\n", this, newBytesRead, ptr);
#endif

  if (fClientOutputSocket != fClientInputSocket) {
// 对于rtsp-over-http连接,将使用base64编码数据,下面将先解码[luther.gliethttp]
    // We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
    // We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes):
    unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
    unsigned newBase64RemainderCount = numBytesToDecode%4;
    numBytesToDecode -= newBase64RemainderCount;
    if (numBytesToDecode > 0) {
      ptr[newBytesRead] = '\0';
      unsigned decodedSize;
      unsigned char* decodedBytes = base64Decode((char*)(ptr-fBase64RemainderCount), decodedSize);
#ifdef DEBUG
      fprintf(stderr, "Base64-decided %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
      for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
      fprintf(stderr, "\n");
#endif

      // Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original):
      unsigned char* to = ptr-fBase64RemainderCount;
      for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i];
      
      // Then copy any remaining (undecoded) bytes to the end:
      for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];

      newBytesRead = decodedSize + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder)
      delete[] decodedBytes;
    }
    fBase64RemainderCount = newBase64RemainderCount;
    if (fBase64RemainderCount > 0) return; // because we know that we have more input bytes still to receive
  }

// 检查是否接收到了一个完整帧(通过结尾的2个\r\n判断)[luther.gliethttp]
  // Look for the end of the message: <CR><LF><CR><LF>
  unsigned char *tmpPtr = ptr;
  if (fRequestBytesAlreadySeen > 0) --tmpPtr;
      // in case the last read ended with a <CR>
  while (tmpPtr < &ptr[newBytesRead-1]) {
    if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
      if (tmpPtr - fLastCRLF == 2) { // This is it:
    endOfMsg = True;
    break;
      }
      fLastCRLF = tmpPtr;
    }
    ++tmpPtr;
  }

  fRequestBufferBytesLeft -= newBytesRead;
  fRequestBytesAlreadySeen += newBytesRead;

  if (!endOfMsg) return; // subsequent reads will be needed to complete the request

  // Parse the request string into command name and 'CSeq', then handle the command:
  fRequestBuffer[fRequestBytesAlreadySeen] = '\0';
  char cmdName[RTSP_PARAM_STRING_MAX];
  char urlPreSuffix[RTSP_PARAM_STRING_MAX];
  char urlSuffix[RTSP_PARAM_STRING_MAX];
  char cseq[RTSP_PARAM_STRING_MAX];
  if (parseRTSPRequestString((char*)fRequestBuffer, fRequestBytesAlreadySeen,
                 cmdName, sizeof cmdName,
                 urlPreSuffix, sizeof urlPreSuffix,
                 urlSuffix, sizeof urlSuffix,
                 cseq, sizeof cseq)) {
#ifdef DEBUG
    fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\"\n", cmdName, urlPreSuffix, urlSuffix);
#endif
    if (strcmp(cmdName, "OPTIONS") == 0) {
      handleCmd_OPTIONS(cseq);
    } else if (strcmp(cmdName, "DESCRIBE") == 0) {
      handleCmd_DESCRIBE(cseq, urlSuffix, (char const*)fRequestBuffer);
    } else if (strcmp(cmdName, "SETUP") == 0) {
      handleCmd_SETUP(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
    } else if (strcmp(cmdName, "TEARDOWN") == 0
           || strcmp(cmdName, "PLAY") == 0
           || strcmp(cmdName, "PAUSE") == 0
           || strcmp(cmdName, "GET_PARAMETER") == 0
           || strcmp(cmdName, "SET_PARAMETER") == 0) {
      handleCmd_withinSession(cmdName, urlPreSuffix, urlSuffix, cseq,
                  (char const*)fRequestBuffer);
    } else {
      handleCmd_notSupported(cseq);
    }
  } else {
#ifdef DEBUG
    fprintf(stderr, "parseRTSPRequestString() failed\n");
#endif
    // The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling):
    char sessionCookie[RTSP_PARAM_STRING_MAX];
    if (parseHTTPRequestString(cmdName, sizeof cmdName, sessionCookie, sizeof sessionCookie)) {
#ifdef DEBUG
      fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", sessionCookie \"%s\"\n", cmdName, sessionCookie);
#endif
      // Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'.
      Boolean isValidHTTPTunnelingCmd = True;
      if (sessionCookie[0] == '\0') {
    isValidHTTPTunnelingCmd = False;
      } else if (strcmp(cmdName, "GET") == 0) {
    handleHTTPCmd_GET(sessionCookie);
      } else if (strcmp(cmdName, "POST") == 0) {
    // We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command.
    // Check for this, and handle it if it exists:
    unsigned char const* extraData = fLastCRLF+4;
    unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData;
    if (handleHTTPCmd_POST(sessionCookie, extraData, extraDataSize)) {
      // We don't respond to the "POST" command, and we go away:
      delete this;
      return;
    }
      } else {
    isValidHTTPTunnelingCmd = False;
      }
      if (!isValidHTTPTunnelingCmd) {
    handleHTTPCmd_notSupported();
      }
    } else {
#ifdef DEBUG
    fprintf(stderr, "parseHTTPRequestString() failed!\n");
#endif
      handleCmd_bad(cseq);
    }
  }

#ifdef DEBUG
  fprintf(stderr, "sending response: %s", fResponseBuffer);
#endif
  send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);

  if (strcmp(cmdName, "SETUP") == 0 && fStreamAfterSETUP) {
    // The client has asked for streaming to commence now, rather than after a
    // subsequent "PLAY" command.  So, simulate the effect of a "PLAY" command:
    handleCmd_withinSession("PLAY", urlPreSuffix, urlSuffix, cseq,
                (char const*)fRequestBuffer);
  }

  resetRequestBuffer(); // to prepare for any subsequent request
  if (!fSessionIsActive) delete this;
}

如下是vlc播放时,live555MediaServer打印出来的通信数据log
luther@gliethttp:~/live/mediaServer$ ./live555MediaServer
LIVE555 Media Server
    version 0.62 (LIVE555 Streaming Media library version 2010.11.17).
Play streams from this server using the URL
    rtsp://192.168.1.103:8554/<filename>
where <filename> is a file present in the current directory.
Each file's type is inferred from its name suffix:
    ".aac" => an AAC Audio (ADTS format) file
    ".amr" => an AMR Audio file
    ".m4e" => a MPEG-4 Video Elementary Stream file
    ".dv" => a DV Video file
    ".mp3" => a MPEG-1 or 2 Audio file
    ".mpg" => a MPEG-1 or 2 Program Stream (audio+video) file
    ".ts" => a MPEG Transport Stream file
        (a ".tsx" index file - if present - provides server 'trick play' support)
    ".wav" => a WAV Audio file
See http://www.live555.com/mediaServer/ for additional documentation.
(We use port 8000 for optional RTSP-over-HTTP tunneling.)
OPTIONS rtsp://192.168.1.103:8554/1.mp3 RTSP/1.0
CSeq: 1
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


sending response: RTSP/1.0 200 OK
CSeq: 1
Date: Sun, Nov 21 2010 09:32:41 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

DESCRIBE rtsp://192.168.1.103:8554/1.mp3 RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


glx:->handleCmd_DESCRIBE urlSuffix=1.mp3
sending response: RTSP/1.0 200 OK
CSeq: 2
Date: Sun, Nov 21 2010 09:32:41 GMT
Content-Base: rtsp://192.168.1.103:8554/1.mp3/
Content-Type: application/sdp // 返回SDP description for this session [luther.gliethttp]
Content-Length: 387

v=0
o=- 1290331961956174 1 IN IP4 192.168.1.103
s=MPEG-1 or 2 Audio, streamed by the LIVE555 Media Server
i=1.mp3
t=0 0
a=tool:LIVE555 Streaming Media v2010.11.17
a=type:broadcast
a=control:*
a=range:npt=0-274.281
a=x-qt-text-nam:MPEG-1 or 2 Audio, streamed by the LIVE555 Media Server
a=x-qt-text-inf:1.mp3
m=audio 0 RTP/AVP 14
c=IN IP4 0.0.0.0
b=AS:128
a=control:track1
SETUP rtsp://192.168.1.103:8554/1.mp3/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=46744-46745
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


sending response: RTSP/1.0 200 OK
CSeq: 3
Date: Sun, Nov 21 2010 09:32:41 GMT
Transport: RTP/AVP;unicast;destination=192.168.1.103;source=192.168.1.103;client_port=46744-46745;server_port=6970-6971
Session: 41F28E31

PLAY rtsp://192.168.1.103:8554/1.mp3/ RTSP/1.0
CSeq: 4
Session: 41F28E31
Range: npt=0.000-
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


sending response: RTSP/1.0 200 OK
CSeq: 4
Date: Sun, Nov 21 2010 09:32:41 GMT
Range: npt=0.000-274.281
Session: 41F28E31
RTP-Info: url=rtsp://192.168.1.103:8554/1.mp3/track1;seq=38568;rtptime=3627847412

GET_PARAMETER rtsp://192.168.1.103:8554/1.mp3/ RTSP/1.0
CSeq: 5
Session: 41F28E31
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


sending response: RTSP/1.0 200 OK
CSeq: 5
Date: Sun, Nov 21 2010 09:32:41 GMT
Session: 41F28E31

TEARDOWN rtsp://192.168.1.103:8554/1.mp3/ RTSP/1.0
CSeq: 6
Session: 41F28E31
User-Agent: VLC media player (LIVE555 Streaming Media v2010.02.10)


sending response: RTSP/1.0 200 OK
CSeq: 6
Date: Sun, Nov 21 2010 09:32:46 GMT



如下是live555MediaServer打开client希望打开的媒体文件
server会打开该文件,同时根据文件后缀名,创建解析该类型文件的结构体,
然后边解析数据边回传给client,所以live555MediaServer只支持它自己支持的audio/video文件[luther.gliethttp]

RTSPServer::RTSPClientSession::handleCmd_DESCRIBE
==> fOurServer.lookupServerMediaSession(urlSuffix) // 这里的urlSuffix内容对应上面的"1.mp3"字符串
==> DynamicRTSPServer::lookupServerMediaSession
==> RTSPServer::lookupServerMediaSession(streamName)
==> sms = createNewSMS(envir(), streamName, fid); // Create a new "ServerMediaSession" object for streaming from the named file.
// 该函数为live555MediaServer所支持的所有audio/video文件格式,如果需要扩展,可以将其加入进来[luther.gliethttp]
// 至少没有看到对.mp4和.3gp的支持
static ServerMediaSession* createNewSMS(UsageEnvironment& env,
                    char const* fileName, FILE* /*fid*/) {
  // Use the file name extension to determine the type of "ServerMediaSession":
  char const* extension = strrchr(fileName, '.');
  if (extension == NULL) return NULL;

  ServerMediaSession* sms = NULL;
  Boolean const reuseSource = False;
  if (strcmp(extension, ".aac") == 0) {
    // Assumed to be an AAC Audio (ADTS format) file:
    NEW_SMS("AAC Audio");
    sms->addSubsession(ADTSAudioFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  } else if (strcmp(extension, ".amr") == 0) {
    // Assumed to be an AMR Audio file:
    NEW_SMS("AMR Audio");
    sms->addSubsession(AMRAudioFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  } else if (strcmp(extension, ".m4e") == 0) {
    // Assumed to be a MPEG-4 Video Elementary Stream file:
    NEW_SMS("MPEG-4 Video");
    sms->addSubsession(MPEG4VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  } else if (strcmp(extension, ".mp3") == 0) {
    // Assumed to be a MPEG-1 or 2 Audio file:
    NEW_SMS("MPEG-1 or 2 Audio");
    // To stream using 'ADUs' rather than raw MP3 frames, uncomment the following:
//#define STREAM_USING_ADUS 1
    // To also reorder ADUs before streaming, uncomment the following:
//#define INTERLEAVE_ADUS 1
    // (For more information about ADUs and interleaving,
    //  see <http://www.live555.com/rtp-mp3/>)
    Boolean useADUs = False;
    Interleaving* interleaving = NULL;
#ifdef STREAM_USING_ADUS
    useADUs = True;
#ifdef INTERLEAVE_ADUS
    unsigned char interleaveCycle[] = {0,2,1,3}; // or choose your own...
    unsigned const interleaveCycleSize
      = (sizeof interleaveCycle)/(sizeof (unsigned char));
    interleaving = new Interleaving(interleaveCycleSize, interleaveCycle);
#endif
#endif
    sms->addSubsession(MP3AudioFileServerMediaSubsession::createNew(env, fileName, reuseSource, useADUs, interleaving));
  } else if (strcmp(extension, ".mpg") == 0) {
    // Assumed to be a MPEG-1 or 2 Program Stream (audio+video) file:
    NEW_SMS("MPEG-1 or 2 Program Stream");
    MPEG1or2FileServerDemux* demux
      = MPEG1or2FileServerDemux::createNew(env, fileName, reuseSource);
    sms->addSubsession(demux->newVideoServerMediaSubsession());
    sms->addSubsession(demux->newAudioServerMediaSubsession());
  } else if (strcmp(extension, ".ts") == 0) {
    // Assumed to be a MPEG Transport Stream file:
    // Use an index file name that's the same as the TS file name, except with ".tsx":
    unsigned indexFileNameLen = strlen(fileName) + 2; // allow for trailing "x\0"
    char* indexFileName = new char[indexFileNameLen];
    sprintf(indexFileName, "%sx", fileName);
    NEW_SMS("MPEG Transport Stream");
    sms->addSubsession(MPEG2TransportFileServerMediaSubsession::createNew(env, fileName, indexFileName, reuseSource));
    delete[] indexFileName;
  } else if (strcmp(extension, ".wav") == 0) {
    // Assumed to be a WAV Audio file:
    NEW_SMS("WAV Audio Stream");
    // To convert 16-bit PCM data to 8-bit u-law, prior to streaming,
    // change the following to True:
    Boolean convertToULaw = False;
    sms->addSubsession(WAVAudioFileServerMediaSubsession::createNew(env, fileName, reuseSource, convertToULaw));
  } else if (strcmp(extension, ".dv") == 0) {
    // Assumed to be a DV Video file
    // First, make sure that the RTPSinks' buffers will be large enough to handle the huge size of DV frames (as big as 288000).
    OutPacketBuffer::maxSize = 300000;

    NEW_SMS("DV Video");
    sms->addSubsession(DVVideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  }

  return sms;
}

(ilovejoy)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201205/15990.html]
本文出处:百度博客 作者:ilovejoy 原文
顶一下
(2)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容