1.创建 ServerMediaSession 类
根据上篇:llive 555 信令类及消息,可知处理live555 处理消息DESCRIBE
void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
// enough space for urlPreSuffix/urlSuffix'\0'
urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
strcat(urlTotalSuffix, urlPreSuffix);
strcat(urlTotalSuffix, "/");
}
strcat(urlTotalSuffix, urlSuffix);
if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) return;
// We should really check that the request contains an "Accept:" #####
// for "application/sdp", because that's what we're sending back #####
// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
fOurServer.lookupServerMediaSession(urlTotalSuffix, DESCRIBELookupCompletionFunction, this);
}
信令时候调用fOurServer.lookupServerMediaSession。fOurServer 是DynamicRTSPServer 类对象
void DynamicRTSPServer
::lookupServerMediaSession(char const* streamName,
lookupServerMediaSessionCompletionFunc* completionFunc,
void* completionClientData,
Boolean isFirstLookupInSession) {
// First, check whether the specified "streamName" exists as a local file:
FILE* fid = fopen(streamName, "rb");
Boolean const fileExists = fid != NULL;
// Next, check whether we already have a "ServerMediaSession" for this file:
ServerMediaSession* sms = getServerMediaSession(streamName);
Boolean const smsExists = sms != NULL;
// Handle the four possibilities for "fileExists" and "smsExists":
if (!fileExists) {
if (smsExists) {
// "sms" was created for a file that no longer exists. Remove it:
removeServerMediaSession(sms);
}
sms = NULL;
} else {
if (smsExists && isFirstLookupInSession) {
// Remove the existing "ServerMediaSession" and create a new one, in case the underlying
// file has changed in some way:
removeServerMediaSession(sms);
sms = NULL;
}
if (sms == NULL) {
sms = createNewSMS(envir(), streamName, fid);
addServerMediaSession(sms);
}
fclose(fid);
}
if (completionFunc != NULL) {
(*completionFunc)(completionClientData, sms);
}
}
// Special code for handling Matroska files:
struct MatroskaDemuxCreationState {
MatroskaFileServerDemux* demux;
char watchVariable;
};
lookupServerMediaSession? 函数根据请求文件名:
1)先打开文件fid
2)getServerMediaSession(streamName);该函数根据查找是不是有对应ServerMediaSession
没有就创建 createNewSMS
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, ".ac3") == 0) {
// Assumed to be an AC-3 Audio file:
NEW_SMS("AC-3 Audio");
sms->addSubsession(AC3AudioFileServerMediaSubsession::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, ".264") == 0) {
// Assumed to be a H.264 Video Elementary Stream file:
NEW_SMS("H.264 Video");
OutPacketBuffer::maxSize = 600000; // allow for some possibly large H.264 frames
sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
} else if (strcmp(extension, ".265") == 0) {
// Assumed to be a H.265 Video Elementary Stream file:
NEW_SMS("H.265 Video");
OutPacketBuffer::maxSize = 600000; // allow for some possibly large H.265 frames
sms->addSubsession(H265VideoFileServerMediaSubsession::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, ".vob") == 0) {
// Assumed to be a VOB (MPEG-2 Program Stream, with AC-3 audio) file:
NEW_SMS("VOB (MPEG-2 video with AC-3 audio)");
MPEG1or2FileServerDemux* demux
= MPEG1or2FileServerDemux::createNew(env, fileName, reuseSource);
sms->addSubsession(demux->newVideoServerMediaSubsession());
sms->addSubsession(demux->newAC3AudioServerMediaSubsession());
} 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 = 600000;
NEW_SMS("DV Video");
sms->addSubsession(DVVideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
} else if (strcmp(extension, ".mkv") == 0 || strcmp(extension, ".webm") == 0) {
// Assumed to be a Matroska file (note that WebM ('.webm') files are also Matroska files)
OutPacketBuffer::maxSize = 600000; // allow for some possibly large VP8 or VP9 frames
NEW_SMS("Matroska video+audio+(optional)subtitles");
// Create a Matroska file server demultiplexor for the specified file.
// (We enter the event loop to wait for this to complete.)
MatroskaDemuxCreationState creationState;
creationState.watchVariable = 0;
MatroskaFileServerDemux::createNew(env, fileName, onMatroskaDemuxCreation, &creationState);
env.taskScheduler().doEventLoop(&creationState.watchVariable);
ServerMediaSubsession* smss;
while ((smss = creationState.demux->newServerMediaSubsession()) != NULL) {
sms->addSubsession(smss);
}
} else if (strcmp(extension, ".ogg") == 0 || strcmp(extension, ".ogv") == 0 || strcmp(extension, ".opus") == 0) {
// Assumed to be an Ogg file
NEW_SMS("Ogg video and/or audio");
// Create a Ogg file server demultiplexor for the specified file.
// (We enter the event loop to wait for this to complete.)
OggDemuxCreationState creationState;
creationState.watchVariable = 0;
OggFileServerDemux::createNew(env, fileName, onOggDemuxCreation, &creationState);
env.taskScheduler().doEventLoop(&creationState.watchVariable);
ServerMediaSubsession* smss;
while ((smss = creationState.demux->newServerMediaSubsession()) != NULL) {
sms->addSubsession(smss);
}
}
return sms;
}
createNewSMS 函数生成ServerMediaSubsession 类实
我们已h264为分析:
else if (strcmp(extension, ".264") == 0) {
// Assumed to be a H.264 Video Elementary Stream file:
NEW_SMS("H.264 Video");
OutPacketBuffer::maxSize = 600000; // allow for some possibly large H.264 frames
sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
先看一下:H264VideoFileServerMediaSubsession 继承关系
H264VideoFileServerMediaSubsession
? ? ? ?FileServerMediaSubsession
????????????????OnDemandServerMediaSubsession
????????????????????????ServerMediaSubsession
????????????????????????????????Medium
2. 生成rtpsink?lookupServerMediaSession 查找完调用DESCRIBELookupCompletionFunction
void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE_afterLookup(ServerMediaSession* session) {
char* sdpDescription = NULL;
char* rtspURL = NULL;
do {
if (session == NULL) {
handleCmd_notFound();
break;
}
// Increment the "ServerMediaSession" object's reference count, in case someone removes it
// while we're using it:
session->incrementReferenceCount();
// Then, assemble a SDP description for this session:
sdpDescription = session->generateSDPDescription(fAddressFamily);
if (sdpDescription == NULL) {
// This usually means that a file name that was specified for a
// "ServerMediaSubsession" does not exist.
setRTSPResponse("404 File Not Found, Or In Incorrect Format");
break;
}
unsigned sdpDescriptionSize = strlen(sdpDescription);
// Also, generate our RTSP URL, for the "Content-Base:" header
// (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket);
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
fCurrentCSeq,
dateHeader(),
rtspURL,
sdpDescriptionSize,
sdpDescription);
} while (0);
if (session != NULL) {
// Decrement its reference count, now that we're done using it:
session->decrementReferenceCount();
if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
fOurServer.removeServerMediaSession(session);
}
}
delete[] sdpDescription;
delete[] rtspURL;
}
session->generateSDPDescription(fAddressFamily);?生成sdp 信息
char* ServerMediaSession::generateSDPDescription(int addressFamily) {
struct sockaddr_storage ourAddress;
if (addressFamily == AF_INET) {
ourAddress.ss_family = AF_INET;
((sockaddr_in&)ourAddress).sin_addr.s_addr = ourIPv4Address(envir());
} else { // IPv6
ourAddress.ss_family = AF_INET6;
for (unsigned i = 0; i < 16; ++i) {
((sockaddr_in6&)ourAddress).sin6_addr.s6_addr[i] = ourIPv6Address(envir())[i];
}
}
AddressString ipAddressStr(ourAddress);
unsigned ipAddressStrSize = strlen(ipAddressStr.val());
// For a SSM sessions, we need a "a=source-filter: incl ..." line also:
char* sourceFilterLine;
if (fIsSSM) {
char const* const sourceFilterFmt =
"a=source-filter: incl IN %s * %s\r\n"
"a=rtcp-unicast: reflection\r\n";
unsigned const sourceFilterFmtSize
= strlen(sourceFilterFmt) + 3/*IP4 or IP6*/ + ipAddressStrSize + 1;
sourceFilterLine = new char[sourceFilterFmtSize];
sprintf(sourceFilterLine, sourceFilterFmt,
addressFamily == AF_INET ? "IP4" : "IP6",
ipAddressStr.val());
} else {
sourceFilterLine = strDup("");
}
char* rangeLine = NULL; // for now
char* sdp = NULL; // for now
do {
// Count the lengths of each subsession's media-level SDP lines.
// (We do this first, because the call to "subsession->sdpLines()"
// causes correct subsession 'duration()'s to be calculated later.)
unsigned sdpLength = 0;
ServerMediaSubsession* subsession;
for (subsession = fSubsessionsHead; subsession != NULL;
subsession = subsession->fNext) {
char const* sdpLines = subsession->sdpLines(addressFamily);
if (sdpLines == NULL) continue; // the media's not available
sdpLength += strlen(sdpLines);
}
if (sdpLength == 0) break; // the session has no usable subsessions
// Unless subsessions have differing durations, we also have a "a=range:" line:
float dur = duration();
if (dur == 0.0) {
rangeLine = strDup("a=range:npt=now-\r\n");
} else if (dur > 0.0) {
char buf[100];
sprintf(buf, "a=range:npt=0-%.3f\r\n", dur);
rangeLine = strDup(buf);
} else { // subsessions have differing durations, so "a=range:" lines go there
rangeLine = strDup("");
}
char const* const sdpPrefixFmt =
"v=0\r\n"
"o=- %ld%06ld %d IN %s %s\r\n"
"s=%s\r\n"
"i=%s\r\n"
"t=0 0\r\n"
"a=tool:%s%s\r\n"
"a=type:broadcast\r\n"
"a=control:*\r\n"
"%s"
"%s"
"a=x-qt-text-nam:%s\r\n"
"a=x-qt-text-inf:%s\r\n"
"%s";
sdpLength += strlen(sdpPrefixFmt)
+ 20 + 6 + 20 + 3/*IP4 or IP6*/ + ipAddressStrSize
+ strlen(fDescriptionSDPString)
+ strlen(fInfoSDPString)
+ strlen(libNameStr) + strlen(libVersionStr)
+ strlen(sourceFilterLine)
+ strlen(rangeLine)
+ strlen(fDescriptionSDPString)
+ strlen(fInfoSDPString)
+ strlen(fMiscSDPLines);
sdpLength += 1000; // in case the length of the "subsession->sdpLines()" calls below change
sdp = new char[sdpLength];
if (sdp == NULL) break;
// Generate the SDP prefix (session-level lines):
snprintf(sdp, sdpLength, sdpPrefixFmt,
fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
1, // o= <version> // (needs to change if params are modified)
addressFamily == AF_INET ? "IP4" : "IP6", // o= <address family>
ipAddressStr.val(), // o= <address>
fDescriptionSDPString, // s= <description>
fInfoSDPString, // i= <info>
libNameStr, libVersionStr, // a=tool:
sourceFilterLine, // a=source-filter: incl (if a SSM session)
rangeLine, // a=range: line
fDescriptionSDPString, // a=x-qt-text-nam: line
fInfoSDPString, // a=x-qt-text-inf: line
fMiscSDPLines); // miscellaneous session SDP lines (if any)
// Then, add the (media-level) lines for each subsession:
char* mediaSDP = sdp;
for (subsession = fSubsessionsHead; subsession != NULL;
subsession = subsession->fNext) {
unsigned mediaSDPLength = strlen(mediaSDP);
mediaSDP += mediaSDPLength;
sdpLength -= mediaSDPLength;
if (sdpLength <= 1) break; // the SDP has somehow become too long
char const* sdpLines = subsession->sdpLines(addressFamily);
if (sdpLines != NULL) snprintf(mediaSDP, sdpLength, "%s", sdpLines);
}
} while (0);
delete[] rangeLine; delete[] sourceFilterLine;
return sdp;
}
函数调用char const* sdpLines = subsession->sdpLines(addressFamily);?根据subsession类继承关系?OnDemandServerMediaSubsession::sdpLines(int addressFamily)
char const*
OnDemandServerMediaSubsession::sdpLines(int addressFamily) {
if (fSDPLines == NULL) {
// We need to construct a set of SDP lines that describe this
// subsession (as a unicast stream). To do so, we first create
// dummy (unused) source and "RTPSink" objects,
// whose parameters we use for the SDP lines:
unsigned estBitrate;
FramedSource* inputSource = createNewStreamSource(0, estBitrate);
if (inputSource == NULL) return NULL; // file not found
Groupsock* dummyGroupsock = createGroupsock(nullAddress(addressFamily), 0);
unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic
RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
if (dummyRTPSink != NULL) {
if (fParentSession->streamingUsesSRTP) {
fMIKEYStateMessage = dummyRTPSink->setupForSRTP(fParentSession->streamingIsEncrypted,
fMIKEYStateMessageSize);
}
if (dummyRTPSink->estimatedBitrate() > 0) estBitrate = dummyRTPSink->estimatedBitrate();
setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);
Medium::close(dummyRTPSink);
}
delete dummyGroupsock;
closeStreamSource(inputSource);
}
return fSDPLines;
}
1.)调用createNewStreamSource?
FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
estBitrate = 500; // kbps, estimate
// Create the video source:
ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
if (fileSource == NULL) return NULL;
fFileSize = fileSource->fileSize();
// Create a framer for the Video Elementary Stream:
return H264VideoStreamFramer::createNew(envir(), fileSource);
}
2. )调用 createNewRTPSink
调用H264VideoFileServerMediaSubsession::createNewRTPSink
RTPSink* H264VideoFileServerMediaSubsession
::createNewRTPSink(Groupsock* rtpGroupsock,
? ? ? ?unsigned char rtpPayloadTypeIfDynamic,
? ? ? ?FramedSource* /*inputSource*/) {
? return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}
|