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; } |