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

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed

Redis如何使用zset处理排行榜和计数问题

《Redis如何使用zset处理排行榜和计数问题》Redis的ZSET数据结构非常适合处理排行榜和计数问题,它可以在高并发的点赞业务中高效地管理点赞的排名,并且由于ZSET的排序特性,可以轻松实现根据... 目录Redis使用zset处理排行榜和计数业务逻辑ZSET 数据结构优化高并发的点赞操作ZSET 结

微服务架构之使用RabbitMQ进行异步处理方式

《微服务架构之使用RabbitMQ进行异步处理方式》本文介绍了RabbitMQ的基本概念、异步调用处理逻辑、RabbitMQ的基本使用方法以及在SpringBoot项目中使用RabbitMQ解决高并发... 目录一.什么是RabbitMQ?二.异步调用处理逻辑:三.RabbitMQ的基本使用1.安装2.架构

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约