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

相关文章

Java堆转储文件之1.6G大文件处理完整指南

《Java堆转储文件之1.6G大文件处理完整指南》堆转储文件是优化、分析内存消耗的重要工具,:本文主要介绍Java堆转储文件之1.6G大文件处理的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言文件为什么这么大?如何处理这个文件?分析文件内容(推荐)删除文件(如果不需要)查看错误来源如何避

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

SpringBoot中六种批量更新Mysql的方式效率对比分析

《SpringBoot中六种批量更新Mysql的方式效率对比分析》文章比较了MySQL大数据量批量更新的多种方法,指出REPLACEINTO和ONDUPLICATEKEY效率最高但存在数据风险,MyB... 目录效率比较测试结构数据库初始化测试数据批量修改方案第一种 for第二种 case when第三种

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

Java docx4j高效处理Word文档的实战指南

《Javadocx4j高效处理Word文档的实战指南》对于需要在Java应用程序中生成、修改或处理Word文档的开发者来说,docx4j是一个强大而专业的选择,下面我们就来看看docx4j的具体使用... 目录引言一、环境准备与基础配置1.1 Maven依赖配置1.2 初始化测试类二、增强版文档操作示例2.

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核