live555 源码分析: DESCRIBE 的处理

2024-04-20 21:08

本文主要是介绍live555 源码分析: DESCRIBE 的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONSGET_PARAMETERSET_PARAMETER 等。篇幅所限,没有分析最为重要的 DESCRIBESETUPPLAY 这些方法的处理。

本文继续分析 live555 对 RTSP 请求,分析 DESCRIBESETUPPLAY 这些最为重要的方法的处理。

RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) 中,通过调用 handleCmd_DESCRIBE() 函数处理 DESCRIBE 请求,如下所示:

      } else if (strcmp(cmdName, "DESCRIBE") == 0) {handleCmd_DESCRIBE(urlPreSuffix, urlSuffix,(char const*) fRequestBuffer);}

handleCmd_DESCRIBE() 函数的定义是这样的:

void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {ServerMediaSession* session = NULL;char* sdpDescription = NULL;char* rtspURL = NULL;do {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)) break;// 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":session = fOurServer.lookupServerMediaSession(urlTotalSuffix);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();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;
}

handleCmd_DESCRIBE() 函数通过一个 do-while(0) 结构实现对 DESCRIBE 方法的处理。do-while(0) 结构的好处大概是,在出错时,即无需直接返回,搞乱控制流,又可以在不用 goto 语句的情况下,跳到函数的结尾处吧。

这个函数中 DESCRIBE 操作整体的执行流程为:
1.首先对在 URL 上执行 DESCRIBE 操作的权限的认证,“LIVE555 Media Server” 不提供认证,认证将总是成功。
2. 查找或创建一个 ServerMediaSession 结构,用于操作媒体流的元信息。查找根据 URL 中资源的路径进行,对于 “LIVE555 Media Server”,也是对应的文件相对于服务器运行的目录的相对路径。函数执行失败时,会直接返回 404 失败给客户端:

void RTSPServer::RTSPClientConnection::handleCmd_notFound() {setRTSPResponse("404 Stream Not Found");
}
  1. 生成 SDP 描述。失败时,也会返回 404 失败给客户端。
  2. 获得 RTSP URL。RTSP URL 由服务器的 IP 地址和 URL 路径拼接而成:
char* RTSPServer
::rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket) const {char* urlPrefix = rtspURLPrefix(clientSocket);char const* sessionName = serverMediaSession->streamName();char* resultURL = new char[strlen(urlPrefix) + strlen(sessionName) + 1];sprintf(resultURL, "%s%s", urlPrefix, sessionName);delete[] urlPrefix;return resultURL;
}char* RTSPServer::rtspURLPrefix(int clientSocket) const {struct sockaddr_in ourAddress;if (clientSocket < 0) {// Use our default IP address in the URL:ourAddress.sin_addr.s_addr = ReceivingInterfaceAddr != 0? ReceivingInterfaceAddr: ourIPAddress(envir()); // hack} else {SOCKLEN_T namelen = sizeof ourAddress;getsockname(clientSocket, (struct sockaddr*)&ourAddress, &namelen);}char urlBuffer[100]; // more than big enough for "rtsp://<ip-address>:<port>/"portNumBits portNumHostOrder = ntohs(fServerPort.num());if (portNumHostOrder == 554 /* the default port number */) {sprintf(urlBuffer, "rtsp://%s/", AddressString(ourAddress).val());} else {sprintf(urlBuffer, "rtsp://%s:%hu/",AddressString(ourAddress).val(), portNumHostOrder);}return strDup(urlBuffer);
}
  1. 生成响应消息。

DESCRIBE 请求中,客户端可以通过 Accept 向服务器表明,支持哪种媒体流会话的描述方式,但在 live555 中,似乎是认定了客户端只会请求 SDP,因而整个处理过程都按照产生媒体流 SDP 的方式进行。SDP 消息是放在响应消息的消息体中的。

创建/查找 ServerMediaSession

handleCmd_DESCRIBE() 函数通过 lookupServerMediaSession() 查找或创建一个 ServerMediaSession 结构,这个函数在 GenericMediaServer 类中声明:

  virtual ServerMediaSession*lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession = True);

它是一个虚函数,调用的实际的函数实现位于继承层次中实现了该函数的最底层的类中。对于 DynamicRTSPServer -> RTSPServerSupportingHTTPStreaming -> RTSPServer -> GenericMediaServer 这个继承层次,实现了这个方法的类有 DynamicRTSPServerGenericMediaServer。这里来看 DynamicRTSPServer 类中的实现:

ServerMediaSession* DynamicRTSPServer
::lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession) {// First, check whether the specified "streamName" exists as a local file:FILE* fid = fopen(streamName, "rb");Boolean fileExists = fid != NULL;// Next, check whether we already have a "ServerMediaSession" for this file:ServerMediaSession* sms = RTSPServer::lookupServerMediaSession(streamName);Boolean 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;}return 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);return sms;}
}

DynamicRTSPServer::lookupServerMediaSession() 首先检查对应的文件是否存在,然后通过父类的方法查找文件对应的 ServerMediaSession 结构是否已存在。父类方法在 GenericMediaServer 类中的定义如下:

ServerMediaSession* GenericMediaServer
::lookupServerMediaSession(char const* streamName, Boolean /*isFirstLookupInSession*/) {// Default implementation:return (ServerMediaSession*)(fServerMediaSessions->Lookup(streamName));
}

然后根据检查和查找的结果分为几种情况来处理:
1. 文件不存在,对应的 ServerMediaSession 结构已存在 -> 移除 ServerMediaSession 结构,返回 NULL 给调用者。
2. 文件不存在,对应的 ServerMediaSession 结构不存在 -> 返回 NULL 给调用者。
3. 文件存在,ServerMediaSession 结构存在,且是流媒体会话中的第一次查找 -> 移除 ServerMediaSession 结构,然后创建新的结构,保存起来,并把它返回给调用者。
4. 文件存在,(ServerMediaSession 结构存在,但不是流媒体会话中的第一次查找) 或者 (ServerMediaSession 结构不存在) -> 创建新的
ServerMediaSession 结构,保存起来,并返回给调用者。

对于 DESCRIBE 方法而言,此时流媒体会话通常都还没有建立,因而总是会执行第一次查找的流程。

创建新的 ServerMediaSession 被交给 createNewSMS() 来完成:

#define NEW_SMS(description) do {\
char const* descStr = description\", streamed by the LIVE555 Media Server";\
sms = ServerMediaSession::createNew(env, fileName, fileName, descStr);\
} while(0)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;
. . . . . .} else if (strcmp(extension, ".264") == 0) {// Assumed to be a H.264 Video Elementary Stream file:NEW_SMS("H.264 Video");OutPacketBuffer::maxSize = 100000; // allow for some possibly large H.264 framessms->addSubsession(H264VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));} else if (strcmp(extension, ".265") == 0) {
. . . . . .return sms;
}

createNewSMS() 根据文件的后缀名创建 ServerMediaSession 结构。我们只看 H.264 格式视频的 ServerMediaSession 结构的创建。

createNewSMS() 用宏创建 ServerMediaSession 结构,在宏中,通过 do-while(0) 中的 ServerMediaSession::createNew() 创建;将输出缓冲区设置为 100000 字节;创建类型为 H264VideoFileServerMediaSubsessionServerMediaSubsession 并设置给 ServerMediaSession 结构;然后将 ServerMediaSession 结构返回给调用者。

注意,输出缓冲区对视频流中一帧的大小做了限制,对于一些分辨率比较高的视频流,这个大小可能无法满足要求,比如 1080P 的视频流,某些帧大小可能超过 100000,达到 150000,甚至更多。

ServerMediaSession::createNew() 定义如下:

ServerMediaSession* ServerMediaSession
::createNew(UsageEnvironment& env,char const* streamName, char const* info,char const* description, Boolean isSSM, char const* miscSDPLines) {return new ServerMediaSession(env, streamName, info, description,isSSM, miscSDPLines);
}
. . . . . .
ServerMediaSession::ServerMediaSession(UsageEnvironment& env,char const* streamName,char const* info,char const* description,Boolean isSSM, char const* miscSDPLines): Medium(env), fIsSSM(isSSM), fSubsessionsHead(NULL),fSubsessionsTail(NULL), fSubsessionCounter(0),fReferenceCount(0), fDeleteWhenUnreferenced(False) {fStreamName = strDup(streamName == NULL ? "" : streamName);char* libNamePlusVersionStr = NULL; // by defaultif (info == NULL || description == NULL) {libNamePlusVersionStr = new char[strlen(libNameStr) + strlen(libVersionStr) + 1];sprintf(libNamePlusVersionStr, "%s%s", libNameStr, libVersionStr);}fInfoSDPString = strDup(info == NULL ? libNamePlusVersionStr : info);fDescriptionSDPString = strDup(description == NULL ? libNamePlusVersionStr : description);delete[] libNamePlusVersionStr;fMiscSDPLines = strDup(miscSDPLines == NULL ? "" : miscSDPLines);gettimeofday(&fCreationTime, NULL);
}

ServerMediaSessionServerMediaSubsession 被组织为一个单向链表。

Boolean
ServerMediaSession::addSubsession(ServerMediaSubsession* subsession) {if (subsession->fParentSession != NULL) return False; // it's already usedif (fSubsessionsTail == NULL) {fSubsessionsHead = subsession;} else {fSubsessionsTail->fNext = subsession;}fSubsessionsTail = subsession;subsession->fParentSession = this;subsession->fTrackNumber = ++fSubsessionCounter;return True;
}

新加的 ServerMediaSubsession 总是会被放在链表的尾部。

生成 SDP 消息

SDP 消息由 ServerMediaSession 生成:

float ServerMediaSession::duration() const {float minSubsessionDuration = 0.0;float maxSubsessionDuration = 0.0;for (ServerMediaSubsession* subsession = fSubsessionsHead; subsession != NULL;subsession = subsession->fNext) {// Hack: If any subsession supports seeking by 'absolute' time, then return a negative value, to indicate that only subsessions// will have a "a=range:" attribute:char* absStartTime = NULL; char* absEndTime = NULL;subsession->getAbsoluteTimeRange(absStartTime, absEndTime);if (absStartTime != NULL) return -1.0f;float ssduration = subsession->duration();if (subsession == fSubsessionsHead) { // this is the first subsessionminSubsessionDuration = maxSubsessionDuration = ssduration;} else if (ssduration < minSubsessionDuration) {minSubsessionDuration = ssduration;} else if (ssduration > maxSubsessionDuration) {maxSubsessionDuration = ssduration;}}if (maxSubsessionDuration != minSubsessionDuration) {return -maxSubsessionDuration; // because subsession durations differ} else {return maxSubsessionDuration; // all subsession durations are the same}
}
. . . . . .
char* ServerMediaSession::generateSDPDescription() {AddressString ipAddressStr(ourIPAddress(envir()));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 IP4 * %s\r\n""a=rtcp-unicast: reflection\r\n";unsigned const sourceFilterFmtSize = strlen(sourceFilterFmt) + ipAddressStrSize + 1;sourceFilterLine = new char[sourceFilterFmtSize];sprintf(sourceFilterLine, sourceFilterFmt, ipAddressStr.val());} else {sourceFilterLine = strDup("");}char* rangeLine = NULL; // for nowchar* sdp = NULL; // for nowdo {// 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();if (sdpLines == NULL) continue; // the media's not availablesdpLength += 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=0-\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 thererangeLine = strDup("");}char const* const sdpPrefixFmt ="v=0\r\n""o=- %ld%06ld %d IN IP4 %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 + 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 changesdp = 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)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: linefDescriptionSDPString, // a=x-qt-text-nam: linefInfoSDPString, // a=x-qt-text-inf: linefMiscSDPLines); // 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 longchar const* sdpLines = subsession->sdpLines();if (sdpLines != NULL)snprintf(mediaSDP, sdpLength, "%s", sdpLines);}} while (0);delete[] rangeLine; delete[] sourceFilterLine;return sdp;
}

SDP 消息中主要包括两块内容,一块是通用的 SDP 消息内容,这主要包括时间戳,服务器 IP 地址,持续时间等;另一块是流媒体会话中的子会话特有的信息。

对于如下的 SDP 消息:

v=0
o=- 1504342443358944 1 IN IP4 10.240.248.20
s=H.264 Video, streamed by the LIVE555 Media Server
i=video/raw_h264_stream.264
t=0 0
a=tool:LIVE555 Streaming Media v2017.07.18
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:H.264 Video, streamed by the LIVE555 Media Server
a=x-qt-text-inf:video/raw_h264_stream.264
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:500
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42802A;sprop-parameter-sets=Z0KAKtoBEA8eXlIKDAoNoUJq,aM4G4g==
a=control:track1

其中通用的 SDP 消息内容为:

v=0
o=- 1504342443358944 1 IN IP4 10.240.248.20
s=H.264 Video, streamed by the LIVE555 Media Server
i=video/raw_h264_stream.264
t=0 0
a=tool:LIVE555 Streaming Media v2017.07.18
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:H.264 Video, streamed by the LIVE555 Media Server
a=x-qt-text-inf:video/raw_h264_stream.264

由 H.264 流媒体文件产生的内容为:

m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:500
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42802A;sprop-parameter-sets=Z0KAKtoBEA8eXlIKDAoNoUJq,aM4G4g==
a=control:track1

子会话的 SDP 消息来自于 ServerMediaSubsession::sdpLines(),它被声明为纯虚函数:

class ServerMediaSubsession: public Medium {
public:unsigned trackNumber() const { return fTrackNumber; }char const* trackId();virtual char const* sdpLines() = 0;

对于我们由文件创建的 H.264 流媒体子会话而言,ServerMediaSubsessionH264VideoFileServerMediaSubsession,它有着如下图所示的继承体系:

由图可知,H.264 流媒体子会话所特有的 SDP 内容将来自于 OnDemandServerMediaSubsession::sdpLines()

char const*
OnDemandServerMediaSubsession::sdpLines() {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 foundstruct in_addr dummyAddr;dummyAddr.s_addr = 0;Groupsock* dummyGroupsock = createGroupsock(dummyAddr, 0);unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamicRTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);if (dummyRTPSink != NULL && dummyRTPSink->estimatedBitrate() > 0) estBitrate = dummyRTPSink->estimatedBitrate();setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);Medium::close(dummyRTPSink);delete dummyGroupsock;closeStreamSource(inputSource);}return fSDPLines;
}

在这个函数中,为了构造 SDP 行以描述子会话,它首先创建一个临时的 FramedSourceRTPSink,然后从这些结构中获得信息以构造 SDP 行,并在最后销毁临时的 FramedSourceRTPSink

构造 SDP 行过程如下:

float ServerMediaSubsession::duration() const {// default implementation: assume an unbounded session:return 0.0;
}void ServerMediaSubsession::getAbsoluteTimeRange(char*& absStartTime, char*& absEndTime) const {// default implementation: We don't support seeking by 'absolute' time, so indicate this by setting both parameters to NULL:absStartTime = absEndTime = NULL;
}void ServerMediaSubsession::setServerAddressAndPortForSDP(netAddressBits addressBits,portNumBits portBits) {fServerAddressForSDP = addressBits;fPortNumForSDP = portBits;
}void OnDemandServerMediaSubsession
::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) {if (rtpSink == NULL) return;char const* mediaType = rtpSink->sdpMediaType();unsigned char rtpPayloadType = rtpSink->rtpPayloadType();AddressString ipAddressStr(fServerAddressForSDP);char* rtpmapLine = rtpSink->rtpmapLine();char const* rtcpmuxLine = fMultiplexRTCPWithRTP ? "a=rtcp-mux\r\n" : "";char const* rangeLine = rangeSDPLine();char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource);if (auxSDPLine == NULL) auxSDPLine = "";char const* const sdpFmt ="m=%s %u RTP/AVP %d\r\n""c=IN IP4 %s\r\n""b=AS:%u\r\n""%s""%s""%s""%s""a=control:%s\r\n";unsigned sdpFmtSize = strlen(sdpFmt)+ strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */+ strlen(ipAddressStr.val())+ 20 /* max int len */+ strlen(rtpmapLine)+ strlen(rtcpmuxLine)+ strlen(rangeLine)+ strlen(auxSDPLine)+ strlen(trackId());char* sdpLines = new char[sdpFmtSize];sprintf(sdpLines, sdpFmt, mediaType, // m= <media>fPortNumForSDP, // m= <port>rtpPayloadType, // m= <fmt list>ipAddressStr.val(), // c= addressestBitrate, // b=AS:<bandwidth>rtpmapLine, // a=rtpmap:... (if present)rtcpmuxLine, // a=rtcp-mux:... (if present)rangeLine, // a=range:... (if present)auxSDPLine, // optional extra SDP linetrackId()); // a=control:<track-id>delete[] (char*) rangeLine;delete[] rtpmapLine;fSDPLines = strDup(sdpLines);delete[] sdpLines;
}

创建临时的 FramedSourceRTPSink 的函数都是纯虚函数:

  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,unsigned& estBitrate) = 0;// "estBitrate" is the stream's estimated bitrate, in kbpsvirtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,unsigned char rtpPayloadTypeIfDynamic,FramedSource* inputSource) = 0;

对于 H264VideoFileServerMediaSubsession 的类继承层次结构,这两个函数的实现都在 H264VideoFileServerMediaSubsession 类中:

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);
}RTPSink* H264VideoFileServerMediaSubsession::createNewRTPSink(Groupsock* rtpGroupsock,unsigned char rtpPayloadTypeIfDynamic,FramedSource* /*inputSource*/) {return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}

创建的实际 FramedSourceRTPSink 类型分别为 H264VideoStreamFramerH264VideoRTPSink

live555 源码分析系列文章

live555 源码分析:简介
live555 源码分析:基础设施
live555 源码分析:MediaSever
Wireshark 抓包分析 RTSP/RTP/RTCP 基本工作过程
live555 源码分析:RTSPServer
live555 源码分析: DESCRIBE 的处理

这篇关于live555 源码分析: DESCRIBE 的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921327

相关文章

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超