[live555 处理] 请求消息 二 "DESCRIBE"

根据log 分析 RTSPServer::RTSPClientConnection::handleRequestBytes 中对DESCRIBE 请求的处理

1. 解析 请求

和OPTIONS 请求一样

fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() %s %d new bytes:%s\n",
        this, numBytesRemaining > 0 ? "processing" : "read", newBytesRead, ptr);

log打印接收客户端消息如下

RTSPClientConnection[0xef5bfc40]::handleRequestBytes() read 127 new bytes:
DESCRIBE rtsp://192.168.0.10:8554/H264Video.mkv RTSP/1.0
CSeq: 2
Accept: application/sdp
Date: Sat, 01 Jan 2000 04:49:58 GMT

并且通过 parseRTSPRequestString解析数据获得消息如下

Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, 
                            fLastCRLF+2 - fRequestBuffer,
                            cmdName, sizeof cmdName,
                            urlPreSuffix, sizeof urlPreSuffix,
                            urlSuffix, sizeof urlSuffix,
                            cseq, sizeof cseq,
                            sessionIdStr, sizeof sessionIdStr,
                            contentLength);
parseRTSPRequestString() succeeded, returning cmdName "DESCRIBE", urlPreSuffix "", urlSuffix "H264Video.mkv", CSeq "2", Content-Length 0, with 0 bytes following the message.

成功解析了数据,就要处理数据,最后返回response 给客户端

2. 处理解析数据 获取session:


Boolean const requestIncludedSessionId = sessionIdStr[0] != '\0';
if (requestIncludedSessionId) {
    clientSession
      = (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));
    if (clientSession != NULL) clientSession->noteLiveness();
}

requestIncludedSessionId 的是通过判断 请求消息是否含有 session: 来判断,当前 DESCRIBE 请求消息中,在log中并没有,所以,这里直接忽略,不创建clientSession

3. 处理 DESCRIBE 请求消息

根据上面解析的结果

returning cmdName "DESCRIBE", urlPreSuffix "", urlSuffix "H264Video.mkv", CSeq "2", Content-Length 0, with 0 bytes following the message

urlPreSuffix = “”
urlSuffix = “H264Video.mkv”

handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);

在处理DESCRIBE 请求的时候,可以看到最后返回客户端的数据格式如下:

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);

这里主要分析 rtspURL,sdpDescriptionSize, sdpDescription如何获取的

urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
  strcat(urlTotalSuffix, urlPreSuffix);
  strcat(urlTotalSuffix, "/");
}
strcat(urlTotalSuffix, urlSuffix);

// 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);

// Then, assemble a SDP description for this session:
sdpDescription = session->generateSDPDescription();

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);

上面就是获取 rtspURL,sdpDescriptionSize, sdpDescription整个过程,首先确定urlTotalSuffix ,根据判断最后执行 strcat(urlTotalSuffix, urlSuffix); 拼接字符串 urlTotalSuffix = “H264Video.mkv “

然后根据 urlTotalSuffix 在ServerMediaSession table 表中查找ServerMediaSession 具体逻辑如下


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;
  }
}

上面代码比较重要,是判断那一种传输流媒体方式,是通过摄像头,还是本地文件流,就在这里区分
由于我进行的是本地测试

FILE* fid = fopen(streamName, "rb");

然后在 已有session表中查找

// Next, check whether we already have a "ServerMediaSession" for this file:
  ServerMediaSession* sms = RTSPServer::lookupServerMediaSession(streamName);

很明显第一次创建 会话肯定没有,所以新建streamName 的对应的session

      sms = createNewSMS(envir(), streamName, fid); 
      addServerMediaSession(sms);

创建新的 session 然后通过addServerMediaSession 添加到 session table表中
具体流程图
sms

createNewSMS 函数很重要 根据streamName 来判断创建什么类型 session

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) {...
  if (strcmp(extension, ".amr") == 0) {...
  if (strcmp(extension, ".ac3") == 0) {...
  if (strcmp(extension, ".m4e") == 0) {...
  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 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 = 100000; // allow for some possibly large H.265 frames
    sms->addSubsession(H265VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  } else if (strcmp(extension, ".mp3") == 0) {...
  if (strcmp(extension, ".mpg") == 0) {...
  if (strcmp(extension, ".vob") == 0) {...
  if (strcmp(extension, ".ts") == 0) {...
  if (strcmp(extension, ".wav") == 0) {...
  if (strcmp(extension, ".dv") == 0) {...
  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 = 100000; // 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) {}

  return sms;
}

因为我用的是本地mkv 视频 ,创建sms

#define NEW_SMS(description) do {\
char const* descStr = description\
    ", streamed by the LIVE555 Media Server";\
sms = ServerMediaSession::createNew(env, fileName, fileName, descStr);\
} while(0)

创建好 sms 创建专门针对 MKV 格式的MatroskaFileServerDemux 多路复用对象 Matroska是一种新的多媒体封装格式,支持MKV

    MatroskaFileServerDemux::createNew(env, fileName, onMatroskaDemuxCreation, &creationState);

    ServerMediaSubsession* smss;
    while ((smss = creationState.demux->newServerMediaSubsession()) != NULL) {
      sms->addSubsession(smss);
    }

上面代码的详细流程画出来了,有点复杂,需要慢慢看,主要的目的,创建ServerMediaSession 和一个subServerMediaSession,保存sdp 需要的数据,过程中读取了文件,多媒体文件的文件信息
Matroska
这样就获得了sms 对象

先看DESCRIBE RESPONSE 返回消息log

sending response: RTSP/1.0 200 OK
CSeq: 2
Date: Sat, Jan 01 2000 00:05:00 GMT
Content-Base: rtsp://192.168.100.10:554/H264Video.mkv
Content-Type: application/sdp
Content-Length: 491

v=0
o=- 946685097694861 1 IN IP4 192.168.100.10
s=Session streamed by "RTSPServer"
i=H264Video.mkv
t=0 0
a=tool:LIVE555 Streaming Media v2018.02.28
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:Session streamed by "RTSPServer"
a=x-qt-text-inf:H264Video.mkv
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:1
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42E01F;sprop-parameter-sets=Z0LgH0VoBQBboQAAgAAAHgAAhAAAAAFoSOPI,aEjjyA==
a=control:track1

上面的代码获取了 在handleCmd_DESCRIBE 中的session 对象,然后通过generateSDPDescription 获取sdp信息

sdpDescription = session->generateSDPDescription();

sdp格式

    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";

通过 snprintf 格式化 reponse 消息

    // 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: line
         fDescriptionSDPString, // a=x-qt-text-nam: line
         fInfoSDPString, // a=x-qt-text-inf: line
         fMiscSDPLines); // miscellaneous session SDP lines (if any)

其他的具体参数,在代码中细看,这里列出来

通过session获取rtspURL

    rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket);

处理方式 fClientInputSocket 获取ip地址和端口 session获取 传给客户端的文件名称(带地址)

socket 获取ip 和端口的过程如下:

char* RTSPServer::rtspURLPrefix(int clientSocket) const {
  struct sockaddr_in ourAddress;
    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);
}

以上基本上处理完毕

最后 send 发送给客户端

send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);

说明: 代码逻辑结构比较复杂,所以流程图需要亲自跟一下,不然都不知道在说什么,省略了很多细节,细节梳理起来没没了

总结:

在 处理description的时候, rtsp服务器 工作
1. 创建connection 的 media session ,包含发送数据流的名称,或者说是源,是根据文件的格式创建不同的session
2. 获取 服务器ip地址 端口 流,过程中,读取文件的基本信息

主要是session 中数据转化为 sdp ,比较复杂,估计也需要好好学习一番,每一行 都是有用的,我遇到了再去分析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Live555是一款开源的多媒体流服务器,支持实时流媒体的传输。下面是部分Live555源代码的分析: 1. MediaSession类 MediaSession类是Live555中最核心的类之一,它表示了一个媒体会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSession类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fSubsessions:代表了一个或多个媒体流的传输会话,可以是RTP/RTCP、HTTP/RTSP等协议。 - fSdpLines:代表了SDP协议中的信息,可以是媒体流的编码格式、传输方式等信息。 MediaSession类中的核心方法包括: - createNew:用于创建一个新的媒体会话。 - addSubsession:用于添加一个媒体流的传输会话。 - generateSdpDescription:用于生成SDP协议描述信息。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 2. MediaSubsession类 MediaSubsession类表示了一个媒体流的传输会话,包括了媒体流的传输协议、媒体编码格式、传输方式等信息。MediaSubsession类中的成员变量包括: - fParentSession:代表了父级MediaSession类的实例。 - fRTPSink:代表了RTP数据的发送器。 - fRTCPInstance:代表了RTCP数据的发送器。 - fTrackNumber:代表了媒体流的轨道编号。 - fCodecName:代表了媒体流的编码格式。 - fMediumName:代表了媒体流的传输方式。 MediaSubsession类中的核心方法包括: - initiate:用于初始化媒体流的传输。 - startStream:用于开始媒体流的传输。 - pauseStream:用于暂停媒体流的传输。 - seekStream:用于跳转媒体流的传输。 3. RTSPServer类 RTSPServer类是Live555中实现RTSP协议的服务器类。RTSPServer类中的成员变量包括: - fServerMediaSession:代表了一个媒体服务器会话,负责提供媒体流的传输。 - fHTTPServerPort:代表了HTTP服务器的端口号。 - fRTSPServerPort:代表了RTSP服务器的端口号。 RTSPServer类中的核心方法包括: - createNew:用于创建一个新的RTSP服务器。 - start:用于启动RTSP服务器。 - stop:用于停止RTSP服务器。 - incomingConnectionHandler:用于处理RTSP客户端的连接请求。 - handleCmd_DESCRIBE:用于处理DESCRIBE命令。 - handleCmd_SETUP:用于处理SETUP命令。 - handleCmd_PLAY:用于处理PLAY命令。 - handleCmd_PAUSE:用于处理PAUSE命令。 - handleCmd_TEARDOWN:用于处理TEARDOWN命令。 以上是Live555源代码的部分分析,希望对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值