浅析live555MediaServer初始化流程
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:
unsigned char *tmpPtr = ptr;
if (fRequestBytesAlreadySeen > 0) --tmpPtr;
// in case the last read ended with a
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/
where 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 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 <)
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;
}
阅读(6447) | 评论(1) | 转发(0) |